diff mbox

[RFC,2/2] Initial version of RDS Control utility Signed-off-by: Konke Radlow <kradlow@cisco.com>

Message ID 89e7f656fc45f12f2cb5369738b3afd1f712674f.1343237398.git.kradlow@cisco.com (mailing list archive)
State New, archived
Headers show

Commit Message

Konke Radlow July 25, 2012, 5:44 p.m. UTC
---
 Makefile.am               |    3 +-
 configure.ac              |    1 +
 utils/rds-ctl/Makefile.am |    5 +
 utils/rds-ctl/rds-ctl.cpp |  978 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 986 insertions(+), 1 deletion(-)
 create mode 100644 utils/rds-ctl/Makefile.am
 create mode 100644 utils/rds-ctl/rds-ctl.cpp

Comments

Gregor Jasny July 26, 2012, 7:02 p.m. UTC | #1
On 7/25/12 7:44 PM, Konke Radlow wrote:
> --- /dev/null
> +++ b/utils/rds-ctl/rds-ctl.cpp
> @@ -0,0 +1,978 @@
> +/*
> + * rds-ctl.cpp is based on v4l2-ctl.cpp
> + * 
> + * the following applies for all RDS related parts:
> + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
> + * Author: Konke Radlow <koradlow@gmail.com>
> + * 
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU Lesser General Public License as published by
> + * the Free Software Foundation; either version 2.1 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA
> + */
> +
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <wchar.h>
> +#include <locale.h>
> +#include <inttypes.h>
> +#include <getopt.h>
> +#include <sys/types.h>
> +#include <fcntl.h>
> +#include <errno.h>
> +#include <sys/ioctl.h>
> +#include <sys/time.h>
> +#include <dirent.h>
> +#include <config.h>
> +#include <signal.h>
> +
> +#ifdef HAVE_SYS_KLOG_H
> +#include <sys/klog.h>
> +#endif

You don't call klog, so you can drop these three lines

> +static int parse_cl(int argc, char **argv)
> +{
> +	int i = 0;
> +	int idx = 0;
> +	int opt = 0;
> +	char short_options[26 * 2 * 2 + 1];
> +
> +	if (argc == 1) {
> +		usage_hint();
> +		exit(1);
> +	}
> +	for (i = 0; long_options[i].name; i++) {
> +		if (!isalpha(long_options[i].val))
> +			continue;
> +		short_options[idx++] = long_options[i].val;
> +		if (long_options[i].has_arg == required_argument)
> +			short_options[idx++] = ':';
> +	}
> +	while (1) {
> +		// TODO: remove option_index ?
> +		int option_index = 0;
> +
> +		short_options[idx] = 0;
> +		opt = getopt_long(argc, argv, short_options,
> +				 long_options, &option_index);
> +		if (opt == -1)
> +			break;
> +
> +		params.options[(int)opt] = 1;
> +		switch (opt) {
> +		case OptSetDevice:
> +			strncpy(params.fd_name, optarg, 80);
> +			if (optarg[0] >= '0' && optarg[0] <= '9' && optarg[1] == 0) {

see isdigit from <types.h> (or std::isdigit from <ctypes>)


Thanks,
Gregor
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Gregor Jasny July 26, 2012, 7:13 p.m. UTC | #2
On 7/25/12 7:44 PM, Konke Radlow wrote:

> +static void print_rds_af(struct v4l2_rds_af_set *af_set)
> +{
> +	int counter = 0;
> +
> +	printf("\nAnnounced AFs: %u", af_set->announced_af);
> +	for (int i = 0; i < af_set->size && i < af_set->announced_af; i++, counter++) {
> +		if (af_set->af[i] >= 87500000 ) {
> +			printf("\nAF%02d: %.1fMHz", counter, af_set->af[i] / 1000000.0);
> +			continue;
> +		}
> +		printf("\nAF%02d: %.1fkHz", counter, af_set->af[i] / 1000.0);
> +	}
> +}
> +
> +static void print_rds_pi(const struct v4l2_rds *handle)
> +{
> +	printf("\nArea Coverage: %s", v4l2_rds_get_coverage_str(handle));
> +}
> +
> +static void print_rds_data(struct v4l2_rds *handle, uint32_t updated_fields)
> +{
> +	if (params.options[OptPrintBlock])
> +		updated_fields = 0xFFFFFFFF;

You could use UINT32_MAX here

> +
> +	if (updated_fields & V4L2_RDS_PI && 
> +			handle->valid_fields & V4L2_RDS_PI) {
> +		printf("\nPI: %04x", handle->pi);
> +		print_rds_pi(handle);
> +	}

> +static int parse_cl(int argc, char **argv)
> +{
> +	int i = 0;
> +	int idx = 0;
> +	int opt = 0;
> +	char short_options[26 * 2 * 2 + 1];

Where comes the 26 and 2 from?
Could this be (ARRAY_SIZE(long_options) + 1 ) * 2?

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Konke Radlow July 26, 2012, 7:46 p.m. UTC | #3
1.
> +#ifdef HAVE_SYS_KLOG_H
> +#include <sys/klog.h>
> +#endif

I'll drop those lines

2.
> +             case OptSetDevice:
> +                     strncpy(params.fd_name, optarg, 80);
> +                     if (optarg[0] >= '0' && optarg[0] <= '9' && optarg[1] == 0) {

I didn't know about the isalpha function, thanks for the hint

3.
> +     if (params.options[OptPrintBlock])
> +             updated_fields = 0xFFFFFFFF;

will use that handy definition (UINT32_MAX )

4.
> +     int opt = 0;
> +     char short_options[26 * 2 * 2 + 1];

the number 26 was taken over from the code of the v4l2-ctl tool. I don't know
where that "magic" number is coming from. I just checked the v4l2-ctl code again
and there seem to be 26 short options defined in the "enum Option" type.


Thank you for your comments so far. I'll incorporate them tomorrow
morning when I'm
back on my working machine,

regards,
Konke



On Thu, Jul 26, 2012 at 9:13 PM, Gregor Jasny <gjasny@googlemail.com> wrote:
>
> On 7/25/12 7:44 PM, Konke Radlow wrote:
>
> > +static void print_rds_af(struct v4l2_rds_af_set *af_set)
> > +{
> > +     int counter = 0;
> > +
> > +     printf("\nAnnounced AFs: %u", af_set->announced_af);
> > +     for (int i = 0; i < af_set->size && i < af_set->announced_af; i++, counter++) {
> > +             if (af_set->af[i] >= 87500000 ) {
> > +                     printf("\nAF%02d: %.1fMHz", counter, af_set->af[i] / 1000000.0);
> > +                     continue;
> > +             }
> > +             printf("\nAF%02d: %.1fkHz", counter, af_set->af[i] / 1000.0);
> > +     }
> > +}
> > +
> > +static void print_rds_pi(const struct v4l2_rds *handle)
> > +{
> > +     printf("\nArea Coverage: %s", v4l2_rds_get_coverage_str(handle));
> > +}
> > +
> > +static void print_rds_data(struct v4l2_rds *handle, uint32_t updated_fields)
> > +{
> > +     if (params.options[OptPrintBlock])
> > +             updated_fields = 0xFFFFFFFF;
>
> You could use UINT32_MAX here
>
> > +
> > +     if (updated_fields & V4L2_RDS_PI &&
> > +                     handle->valid_fields & V4L2_RDS_PI) {
> > +             printf("\nPI: %04x", handle->pi);
> > +             print_rds_pi(handle);
> > +     }
>
> > +static int parse_cl(int argc, char **argv)
> > +{
> > +     int i = 0;
> > +     int idx = 0;
> > +     int opt = 0;
> > +     char short_options[26 * 2 * 2 + 1];
>
> Where comes the 26 and 2 from?
> Could this be (ARRAY_SIZE(long_options) + 1 ) * 2?
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Hans Verkuil July 27, 2012, 6:50 a.m. UTC | #4
On Thu July 26 2012 21:13:08 Gregor Jasny wrote:
> On 7/25/12 7:44 PM, Konke Radlow wrote:
> 
> > +static void print_rds_af(struct v4l2_rds_af_set *af_set)
> > +{
> > +	int counter = 0;
> > +
> > +	printf("\nAnnounced AFs: %u", af_set->announced_af);
> > +	for (int i = 0; i < af_set->size && i < af_set->announced_af; i++, counter++) {
> > +		if (af_set->af[i] >= 87500000 ) {
> > +			printf("\nAF%02d: %.1fMHz", counter, af_set->af[i] / 1000000.0);
> > +			continue;
> > +		}
> > +		printf("\nAF%02d: %.1fkHz", counter, af_set->af[i] / 1000.0);
> > +	}
> > +}
> > +
> > +static void print_rds_pi(const struct v4l2_rds *handle)
> > +{
> > +	printf("\nArea Coverage: %s", v4l2_rds_get_coverage_str(handle));
> > +}
> > +
> > +static void print_rds_data(struct v4l2_rds *handle, uint32_t updated_fields)
> > +{
> > +	if (params.options[OptPrintBlock])
> > +		updated_fields = 0xFFFFFFFF;
> 
> You could use UINT32_MAX here

I wouldn't. It's a bitmask, not a 'normal' integer and with UINT32_MAX you
lose that connection. The only change I'd make here is to use lower-case for the
hex characters instead of upper case, or perhaps changing it to ~0.

> 
> > +
> > +	if (updated_fields & V4L2_RDS_PI && 
> > +			handle->valid_fields & V4L2_RDS_PI) {
> > +		printf("\nPI: %04x", handle->pi);
> > +		print_rds_pi(handle);
> > +	}
> 
> > +static int parse_cl(int argc, char **argv)
> > +{
> > +	int i = 0;
> > +	int idx = 0;
> > +	int opt = 0;
> > +	char short_options[26 * 2 * 2 + 1];
> 
> Where comes the 26 and 2 from?

Really? Short options are a-z and A-Z. 26? Alphabet? For each option you need
at most two chars (option + an optional argument specifier).

Anyway, I guess a short comment wouldn't hurt.

Note that the option parsing code is all copied from v4l2-ctl and it's used in
a whole bunch of utilities in v4l-utils.

Regards,

	Hans

> Could this be (ARRAY_SIZE(long_options) + 1 ) * 2?
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Hans de Goede July 28, 2012, 11:11 a.m. UTC | #5
Hi,

Overall this looks good, but some of the code, for example the set/get freq and
get/set tuner stuff seems to be a 1 on 1 copy of code from v4l2-ctrl, please
factor this out into a common file which can be shared between both
utilities (I think Hans V's recent work on splitting v4l2-ctl into multiple
files comes a long way towards this).

Regards,

Hans

On 07/25/2012 07:44 PM, Konke Radlow wrote:
> ---
>   Makefile.am               |    3 +-
>   configure.ac              |    1 +
>   utils/rds-ctl/Makefile.am |    5 +
>   utils/rds-ctl/rds-ctl.cpp |  978 +++++++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 986 insertions(+), 1 deletion(-)
>   create mode 100644 utils/rds-ctl/Makefile.am
>   create mode 100644 utils/rds-ctl/rds-ctl.cpp
>
> diff --git a/Makefile.am b/Makefile.am
> index 6707f5f..47103a1 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -18,7 +18,8 @@ SUBDIRS += \
>   	utils/v4l2-compliance \
>   	utils/v4l2-ctl \
>   	utils/v4l2-dbg \
> -	utils/v4l2-sysfs-path
> +	utils/v4l2-sysfs-path \
> +	utils/rds-ctl
>
>   if LINUX_OS
>   SUBDIRS += \
> diff --git a/configure.ac b/configure.ac
> index 1d7eb29..1ad99e6 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -28,6 +28,7 @@ AC_CONFIG_FILES([Makefile
>   	utils/v4l2-sysfs-path/Makefile
>   	utils/xc3028-firmware/Makefile
>   	utils/qv4l2/Makefile
> +	utils/rds-ctl/Makefile
>
>   	contrib/freebsd/Makefile
>   	contrib/test/Makefile
> diff --git a/utils/rds-ctl/Makefile.am b/utils/rds-ctl/Makefile.am
> new file mode 100644
> index 0000000..9a84257
> --- /dev/null
> +++ b/utils/rds-ctl/Makefile.am
> @@ -0,0 +1,5 @@
> +bin_PROGRAMS = rds-ctl
> +
> +rds_ctl_SOURCES = rds-ctl.cpp
> +rds_ctl_LDADD = ../../lib/libv4l2/libv4l2.la ../../lib/libv4l2rds/libv4l2rds.la
> +
> diff --git a/utils/rds-ctl/rds-ctl.cpp b/utils/rds-ctl/rds-ctl.cpp
> new file mode 100644
> index 0000000..8ddb969
> --- /dev/null
> +++ b/utils/rds-ctl/rds-ctl.cpp
> @@ -0,0 +1,978 @@
> +/*
> + * rds-ctl.cpp is based on v4l2-ctl.cpp
> + *
> + * the following applies for all RDS related parts:
> + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
> + * Author: Konke Radlow <koradlow@gmail.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU Lesser General Public License as published by
> + * the Free Software Foundation; either version 2.1 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA
> + */
> +
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <wchar.h>
> +#include <locale.h>
> +#include <inttypes.h>
> +#include <getopt.h>
> +#include <sys/types.h>
> +#include <fcntl.h>
> +#include <errno.h>
> +#include <sys/ioctl.h>
> +#include <sys/time.h>
> +#include <dirent.h>
> +#include <config.h>
> +#include <signal.h>
> +
> +#ifdef HAVE_SYS_KLOG_H
> +#include <sys/klog.h>
> +#endif
> +
> +#include <linux/videodev2.h>
> +#include <libv4l2.h>
> +#include <libv4l2rds.h>
> +
> +#include <list>
> +#include <vector>
> +#include <map>
> +#include <string>
> +#include <algorithm>
> +
> +#define ARRAY_SIZE(arr) ((int)(sizeof(arr) / sizeof((arr)[0])))
> +
> +typedef std::vector<std::string> dev_vec;
> +typedef std::map<std::string, std::string> dev_map;
> +
> +/* Short option list
> +
> +   Please keep in alphabetical order.
> +   That makes it easier to see which short options are still free.
> +
> +   In general the lower case is used to set something and the upper
> +   case is used to retrieve a setting. */
> +enum Option {
> +	OptSetDevice = 'd',
> +	OptGetDriverInfo = 'D',
> +	OptGetFreq = 'F',
> +	OptSetFreq = 'f',
> +	OptHelp = 'h',
> +	OptReadRds = 'R',
> +	OptGetTuner = 'T',
> +	OptSetTuner = 't',
> +	OptUseWrapper = 'w',
> +	OptAll = 128,
> +	OptFreqSeek,
> +	OptListDevices,
> +	OptOpenFile,
> +	OptPrintBlock,
> +	OptSilent,
> +	OptTunerIndex,
> +	OptVerbose,
> +	OptWaitLimit,
> +	OptLast = 256
> +};
> +
> +struct ctl_parameters {
> +	bool terminate_decoding;
> +	char options[OptLast];
> +	char fd_name[80];
> +	bool filemode_active;
> +	double freq;
> +	uint32_t wait_limit;
> +	uint8_t tuner_index;
> +	struct v4l2_hw_freq_seek freq_seek;
> +};
> +
> +static struct ctl_parameters params;
> +static int app_result;
> +
> +static struct option long_options[] = {
> +	{"all", no_argument, 0, OptAll},
> +	{"device", required_argument, 0, OptSetDevice},
> +	{"file", required_argument, 0, OptOpenFile},
> +	{"freq-seek", required_argument, 0, OptFreqSeek},
> +	{"get-freq", no_argument, 0, OptGetFreq},
> +	{"get-tuner", no_argument, 0, OptGetTuner},
> +	{"help", no_argument, 0, OptHelp},
> +	{"info", no_argument, 0, OptGetDriverInfo},
> +	{"list-devices", no_argument, 0, OptListDevices},
> +	{"print-block", no_argument, 0, OptPrintBlock},
> +	{"read-rds", no_argument, 0, OptReadRds},
> +	{"set-freq", required_argument, 0, OptSetFreq},
> +	{"tuner-index", required_argument, 0, OptTunerIndex},
> +	{"verbose", no_argument, 0, OptVerbose},
> +	{"wait-limit", required_argument, 0, OptWaitLimit},
> +	{"wrapper", no_argument, 0, OptUseWrapper},
> +	{0, 0, 0, 0}
> +};
> +
> +static void usage_hint(void)
> +{
> +	fprintf(stderr, "Try 'rds-ctl --help' for more information.\n");
> +}
> +
> +static void usage_common(void)
> +{
> +	printf("\nGeneral/Common options:\n"
> +	       "  --all              display all information available\n"
> +	       "  -D, --info         show driver info [VIDIOC_QUERYCAP]\n"
> +	       "  -d, --device=<dev> use device <dev>\n"
> +	       "                     if <dev> is a single digit, then /dev/video<dev> is used\n"
> +	       "                     default: checks for RDS-capable devices,\n"
> +	       "                     uses device with lowest ID\n"
> +	       "  -h, --help         display this help message\n"
> +	       "  -w, --wrapper      use the libv4l2 wrapper library.\n"
> +	       "  --list-devices     list all v4l radio devices with RDS capabilities\n"
> +	       );
> +}
> +
> +static void usage_tuner(void)
> +{
> +	printf("\nTuner/Modulator options:\n"
> +	       "  -F, --get-freq     query the frequency [VIDIOC_G_FREQUENCY]\n"
> +	       "  -f, --set-freq=<freq>\n"
> +	       "                     set the frequency to <freq> MHz [VIDIOC_S_FREQUENCY]\n"
> +	       "  -T, --get-tuner    query the tuner settings [VIDIOC_G_TUNER]\n"
> +	       "  --tuner-index=<idx> Use idx as tuner idx for tuner/modulator commands\n"
> +	       "  --freq-seek=dir=<0/1>,wrap=<0/1>,spacing=<hz>\n"
> +	       "                     perform a hardware frequency seek [VIDIOC_S_HW_FREQ_SEEK]\n"
> +	       "                     dir is 0 (seek downward) or 1 (seek upward)\n"
> +	       "                     wrap is 0 (do not wrap around) or 1 (wrap around)\n"
> +	       "                     spacing sets the seek resolution (use 0 for default)\n"
> +	       );
> +}
> +
> +static void usage_rds(void)
> +{
> +	printf("\nRDS options: \n"
> +	       "  -R, --read-rds\n"
> +	       "                     enable reading of RDS data from device\n"
> +	       "  --file=<path>\n"
> +	       "                     open a RDS stream file dump instead of a device\n"
> +	       "                     all General and Tuner Options are disabled in this mode\n"
> +	       "  --wait-limit=<ms>\n"
> +	       "                     defines the maximum wait duration for avaibility of new\n"
> +	       "                     RDS data\n"
> +	       "                     <default>: 5000ms\n"
> +	       "  --print-block\n"
> +	       "                     prints all valid RDS fields, whenever a value is updated\n"
> +	       "                     instead of printing only updated values\n"
> +	       "  --verbose\n"
> +	       "                     turn on verbose mode - every received RDS group\n"
> +	       "                     will be printed\n"
> +	       );
> +}
> +
> +static void usage(void)
> +{
> +	printf("Usage:\n");
> +	usage_common();
> +	usage_tuner();
> +	usage_rds();
> +}
> +
> +static void signal_handler_interrupt(int signum)
> +{
> +	fprintf(stderr, "Interrupt received: Terminating program\n");
> +	params.terminate_decoding = true;
> +}
> +
> +static int test_open(const char *file, int oflag)
> +{
> + 	return params.options[OptUseWrapper] ? v4l2_open(file, oflag) : open(file, oflag);
> +}
> +
> +static int test_close(int fd)
> +{
> +	return params.options[OptUseWrapper] ? v4l2_close(fd) : close(fd);
> +}
> +
> +static int test_ioctl(int fd, int cmd, void *arg)
> +{
> +	return params.options[OptUseWrapper] ? v4l2_ioctl(fd, cmd, arg) : ioctl(fd, cmd, arg);
> +}
> +
> +static int doioctl_name(int fd, unsigned long int request, void *parm, const char *name)
> +{
> +	int retval = test_ioctl(fd, request, parm);
> +
> +	if (retval < 0) {
> +		app_result = -1;
> +	}
> +	if (params.options[OptSilent]) return retval;
> +	if (retval < 0)
> +		printf("%s: failed: %s\n", name, strerror(errno));
> +	else if (params.options[OptVerbose])
> +		printf("%s: ok\n", name);
> +
> +	return retval;
> +}
> +
> +#define doioctl(n, r, p) doioctl_name(n, r, p, #r)
> +
> +static const char *audmode2s(int audmode)
> +{
> +	switch (audmode) {
> +		case V4L2_TUNER_MODE_STEREO: return "stereo";
> +		case V4L2_TUNER_MODE_LANG1: return "lang1";
> +		case V4L2_TUNER_MODE_LANG2: return "lang2";
> +		case V4L2_TUNER_MODE_LANG1_LANG2: return "bilingual";
> +		case V4L2_TUNER_MODE_MONO: return "mono";
> +		default: return "unknown";
> +	}
> +}
> +
> +static std::string rxsubchans2s(int rxsubchans)
> +{
> +	std::string s;
> +
> +	if (rxsubchans & V4L2_TUNER_SUB_MONO)
> +		s += "mono ";
> +	if (rxsubchans & V4L2_TUNER_SUB_STEREO)
> +		s += "stereo ";
> +	if (rxsubchans & V4L2_TUNER_SUB_LANG1)
> +		s += "lang1 ";
> +	if (rxsubchans & V4L2_TUNER_SUB_LANG2)
> +		s += "lang2 ";
> +	if (rxsubchans & V4L2_TUNER_SUB_RDS)
> +		s += "rds ";
> +	return s;
> +}
> +
> +static std::string tcap2s(unsigned cap)
> +{
> +	std::string s;
> +
> +	if (cap & V4L2_TUNER_CAP_LOW)
> +		s += "62.5 Hz ";
> +	else
> +		s += "62.5 kHz ";
> +	if (cap & V4L2_TUNER_CAP_NORM)
> +		s += "multi-standard ";
> +	if (cap & V4L2_TUNER_CAP_HWSEEK_BOUNDED)
> +		s += "hwseek-bounded ";
> +	if (cap & V4L2_TUNER_CAP_HWSEEK_WRAP)
> +		s += "hwseek-wrap ";
> +	if (cap & V4L2_TUNER_CAP_STEREO)
> +		s += "stereo ";
> +	if (cap & V4L2_TUNER_CAP_LANG1)
> +		s += "lang1 ";
> +	if (cap & V4L2_TUNER_CAP_LANG2)
> +		s += "lang2 ";
> +	if (cap & V4L2_TUNER_CAP_RDS)
> +		s += "rds ";
> +	if (cap & V4L2_TUNER_CAP_RDS_BLOCK_IO)
> +		s += "rds block I/O ";
> +	if (cap & V4L2_TUNER_CAP_RDS_CONTROLS)
> +		s += "rds control ";
> +	return s;
> +}
> +
> +static std::string cap2s(unsigned cap)
> +{
> +	std::string s;
> +
> +	if (cap & V4L2_CAP_RDS_CAPTURE)
> +		s += "\t\tRDS Capture\n";
> +	if (cap & V4L2_CAP_RDS_OUTPUT)
> +		s += "\t\tRDS Output\n";
> +	if (cap & V4L2_CAP_TUNER)
> +		s += "\t\tTuner\n";
> +	if (cap & V4L2_CAP_MODULATOR)
> +		s += "\t\tModulator\n";
> +	if (cap & V4L2_CAP_AUDIO)
> +		s += "\t\tAudio\n";
> +	if (cap & V4L2_CAP_RADIO)
> +		s += "\t\tRadio\n";
> +	if (cap & V4L2_CAP_READWRITE)
> +		s += "\t\tRead/Write\n";
> +	if (cap & V4L2_CAP_ASYNCIO)
> +		s += "\t\tAsync I/O\n";
> +	if (cap & V4L2_CAP_STREAMING)
> +		s += "\t\tStreaming\n";
> +	if (cap & V4L2_CAP_DEVICE_CAPS)
> +		s += "\t\tDevice Capabilities\n";
> +	return s;
> +}
> +
> +static bool is_radio_dev(const char *name)
> +{
> +	return !memcmp(name, "radio", 5);
> +}
> +
> +static int calc_node_val(const char *s)
> +{
> +	int n = 0;
> +
> +	s = strrchr(s, '/') + 1;
> +	if (!memcmp(s, "video", 5)) n = 0;
> +	else if (!memcmp(s, "radio", 5)) n = 0x100;
> +	else if (!memcmp(s, "vbi", 3)) n = 0x200;
> +	else if (!memcmp(s, "vtx", 3)) n = 0x300;
> +	n += atol(s + (n >= 0x200 ? 3 : 5));
> +	return n;
> +}
> +
> +static bool sort_on_device_name(const std::string &s1, const std::string &s2)
> +{
> +	int n1 = calc_node_val(s1.c_str());
> +	int n2 = calc_node_val(s2.c_str());
> +
> +	return n1 < n2;
> +}
> +
> +static void print_devices(dev_vec files)
> +{
> +	dev_map cards;
> +	int fd = -1;
> +	std::string bus_info;
> +	struct v4l2_capability vcap;
> +	
> +	for (dev_vec::iterator iter = files.begin();
> +		iter != files.end(); ++iter) {
> +		fd = open(iter->c_str(), O_RDWR);
> +		memset(&vcap, 0, sizeof(vcap));
> +		if (fd < 0)
> +			continue;
> +		doioctl(fd, VIDIOC_QUERYCAP, &vcap);
> +		close(fd);
> +		bus_info = (const char *)vcap.bus_info;
> +	if (cards[bus_info].empty())
> +			cards[bus_info] += std::string((char *)vcap.card)\
> +				+ " (" + bus_info + "):\n";
> +		cards[bus_info] += "\t" + (*iter);
> +		cards[bus_info] += "\n";
> +	}
> +	for (dev_map::iterator iter = cards.begin();
> +			iter != cards.end(); ++iter) {
> +		printf("%s\n", iter->second.c_str());
> +	}
> +}
> +static dev_vec list_devices(void)
> +{
> +	DIR *dp;
> +	struct dirent *ep;
> +	dev_vec files;
> +	dev_map links;
> +	
> +	struct v4l2_tuner vt;
> +
> +	dp = opendir("/dev");
> +	if (dp == NULL) {
> +		perror ("Couldn't open the directory");
> +		exit(1);
> +	}
> +	while ((ep = readdir(dp)))
> +		if (is_radio_dev(ep->d_name))
> +			files.push_back(std::string("/dev/") + ep->d_name);
> +	closedir(dp);
> +
> +	/* Find device nodes which are links to other device nodes */
> +	for (dev_vec::iterator iter = files.begin(); \
> +			iter != files.end(); ) {
> +		char link[64+1];
> +		int link_len;
> +		std::string target;
> +
> +		link_len = readlink(iter->c_str(), link, 64);
> +		if (link_len < 0) {	/* Not a link or error */
> +			iter++;
> +			continue;
> +		}
> +		link[link_len] = '\0';
> +
> +		/* Only remove from files list if target itself is in list */
> +		if (link[0] != '/')	/* Relative link */
> +			target = std::string("/dev/");
> +		target += link;
> +		if (find(files.begin(), files.end(), target) == files.end()) {
> +			iter++;
> +			continue;
> +		}
> +		files.erase(iter);
> +	}
> +	/* Iterate through all devices, and remove all non-accessible devices
> +	 * and all devices that don't offer the RDS_BLOCK_IO capability */
> +	std::sort(files.begin(), files.end(), sort_on_device_name);
> +	for (dev_vec::iterator iter = files.begin(); \
> +			iter != files.end(); ++iter) {
> +		int fd = open(iter->c_str(), O_RDONLY | O_NONBLOCK);
> +		std::string bus_info;
> +
> +		if (fd < 0) {
> +			iter = files.erase(iter);
> +			continue;
> +		}
> +		memset(&vt, 0, sizeof(vt));
> +		if (doioctl(fd, VIDIOC_G_TUNER, &vt) != 0) {
> +			close(fd);
> +			iter = files.erase(iter);
> +			continue;
> +		}
> +		/* remove device if it doesn't support rds block I/O */
> +		if (!vt.capability & V4L2_TUNER_CAP_RDS_BLOCK_IO)
> +			iter = files.erase(iter);
> +	}
> +	return files;
> +}
> +
> +static int parse_subopt(char **subs, const char * const *subopts, char **value)
> +{
> +	int opt = getsubopt(subs, (char * const *)subopts, value);
> +
> +	if (opt == -1) {
> +		fprintf(stderr, "Invalid suboptions specified\n");
> +		return -1;
> +	}
> +	if (value == NULL) {
> +		fprintf(stderr, "No value given to suboption <%s>\n",
> +				subopts[opt]);
> +		return -1;
> +	}
> +	return opt;
> +}
> +
> +static void parse_freq_seek(char *optarg, struct v4l2_hw_freq_seek &seek)
> +{
> +	char *value;
> +	char *subs = optarg;
> +
> +	while (*subs != '\0') {
> +		static const char *const subopts[] = {
> +			"dir",
> +			"wrap",
> +			"spacing",
> +			NULL
> +		};
> +
> +		switch (parse_subopt(&subs, subopts, &value)) {
> +		case 0:
> +			seek.seek_upward = strtol(value, 0L, 0);
> +			break;
> +		case 1:
> +			seek.wrap_around = strtol(value, 0L, 0);
> +			break;
> +		case 2:
> +			seek.spacing = strtol(value, 0L, 0);
> +			break;
> +		default:
> +			usage_tuner();
> +			exit(1);
> +		}
> +	}
> +}
> +
> +static void print_byte(char byte, bool linebreak)
> +{
> +	int count=8;
> +	printf(" ");
> +	while(count--)
> +	{
> +		printf("%d", ( byte & 128 ) ? 1 : 0 );
> +		byte <<= 1;
> +	}
> +	if(linebreak)  printf("\n");
> +}
> +
> +static void print_rds_group(const struct v4l2_rds_group *rds_group)
> +{
> +	printf("\nComplete RDS data group received \n");
> +	printf("PI: %04x\n", rds_group->pi);
> +	printf("Group: %u%c \n", rds_group->group_id, rds_group->group_version);
> +	printf("Traffic Program: %s \n", (rds_group->traffic_prog)? "yes" : "no");
> +	printf("Program Type: %u \n", rds_group->pty);
> +	printf("Data B:");
> +	print_byte(rds_group->data_b_lsb, true);
> +	printf("Data C:");
> +	print_byte(rds_group->data_c_msb, false);
> +	print_byte(rds_group->data_c_lsb, true);
> +	printf("Data D:");
> +	print_byte(rds_group->data_d_msb, false);
> +	print_byte(rds_group->data_d_lsb, true);
> +	printf("\n");
> +}
> +
> +static void print_decoder_info(uint8_t di)
> +{
> +	printf("\nDI: ");
> +	if (di & V4L2_RDS_FLAG_STEREO)
> +		printf("Stereo, ");
> +	else
> +		printf("Mono, ");
> +	if (di & V4L2_RDS_FLAG_ARTIFICIAL_HEAD)
> +		printf("Artificial Head, ");
> +	else
> +		printf("No Artificial Head, ");
> +	if (di & V4L2_RDS_FLAG_COMPRESSED)
> +		printf("Compressed");
> +	else
> +		printf("Not Compressed");
> +}
> +
> +static void print_rds_statistics(struct v4l2_rds_statistics *statistics)
> +{
> +	printf("\n\nRDS Statistics: \n");
> +	printf("received blocks / received groups: %u / %u\n",\
> +		statistics->block_cnt, statistics->group_cnt);
> +
> +	float block_error_percentage = \
> +	((float)statistics->block_error_cnt / statistics->block_cnt) * 100.0;
> +	printf("block errors / group errors: %u (%3.2f%%) / %u \n",\
> +		statistics->block_error_cnt,
> +		block_error_percentage, statistics->group_error_cnt);
> +	float block_corrected_percentage = \
> +	((float)statistics->block_corrected_cnt / statistics->block_cnt) * 100.0;
> +	printf("corrected blocks: %u (%3.2f%%)\n",\
> +		statistics->block_corrected_cnt, block_corrected_percentage);
> +	for(int i=0; i<16; i++)
> +		printf("Group %02d: %u\n", i, statistics->group_type_cnt[i]);
> +}
> +
> +static void print_rds_af(struct v4l2_rds_af_set *af_set)
> +{
> +	int counter = 0;
> +
> +	printf("\nAnnounced AFs: %u", af_set->announced_af);
> +	for (int i = 0; i < af_set->size && i < af_set->announced_af; i++, counter++) {
> +		if (af_set->af[i] >= 87500000 ) {
> +			printf("\nAF%02d: %.1fMHz", counter, af_set->af[i] / 1000000.0);
> +			continue;
> +		}
> +		printf("\nAF%02d: %.1fkHz", counter, af_set->af[i] / 1000.0);
> +	}
> +}
> +
> +static void print_rds_pi(const struct v4l2_rds *handle)
> +{
> +	printf("\nArea Coverage: %s", v4l2_rds_get_coverage_str(handle));
> +}
> +
> +static void print_rds_data(struct v4l2_rds *handle, uint32_t updated_fields)
> +{
> +	if (params.options[OptPrintBlock])
> +		updated_fields = 0xFFFFFFFF;
> +
> +	if (updated_fields & V4L2_RDS_PI &&
> +			handle->valid_fields & V4L2_RDS_PI) {
> +		printf("\nPI: %04x", handle->pi);
> +		print_rds_pi(handle);
> +	}
> +
> +	if (updated_fields & V4L2_RDS_PS &&
> +			handle->valid_fields & V4L2_RDS_PS) {
> +		printf("\nPS: ");
> +		for (int i = 0; i < 8; ++i) {
> +			/* filter out special characters */
> +			if (handle->ps[i] >= 0x20 && handle->ps[i] <= 0x7E)
> +				printf("%lc",handle->ps[i]);
> +			else
> +				printf(" ");
> +		}
> +	}
> +
> +	if (updated_fields & V4L2_RDS_PTY && handle->valid_fields & V4L2_RDS_PTY)
> +		printf("\nPTY: %0u -> %s",handle->pty,\
> +			v4l2_rds_get_pty_str(handle));
> +
> +	if (updated_fields & V4L2_RDS_PTYN && handle->valid_fields & V4L2_RDS_PTYN) {
> +		printf("\nPTYN: ");
> +		for (int i = 0; i < 8; ++i) {
> +			/* filter out special characters */
> +			if (handle->ptyn[i] >= 0x20 && handle->ptyn[i] <= 0x7E)
> +				printf("%lc",handle->ptyn[i]);
> +			else
> +				printf(" ");
> +		}
> +	}
> +
> +	if(updated_fields & V4L2_RDS_RT && handle->valid_fields & V4L2_RDS_RT){
> +		printf("\nRT: ");
> +		for (int i = 0; i < handle->rt_length; ++i) {
> +			/* filter out special characters */
> +			if (handle->rt[i] >= 0x20 && handle->rt[i] <= 0x7E)
> +				printf("%lc",handle->rt[i]);
> +			else
> +				printf(" ");
> +		}
> +	}
> +
> +	if (updated_fields & V4L2_RDS_TP && handle->valid_fields & V4L2_RDS_TP)
> +		printf("\nTP: %s  TA: %s", (handle->tp)? "yes":"no",\
> +			handle->ta? "yes":"no");
> +	if (updated_fields & V4L2_RDS_MS && handle->valid_fields & V4L2_RDS_MS)
> +		printf("\nMS Flag: %s", (handle->ms)? "Music" : "Speech");
> +	if (updated_fields & V4L2_RDS_ECC && handle->valid_fields & V4L2_RDS_ECC)
> +		printf("\nECC: %X%x, Country: %u -> %s",\
> +		handle->ecc >> 4, handle->ecc & 0x0f, handle->pi >> 12, \
> +		v4l2_rds_get_country_str(handle));
> +	if (updated_fields & V4L2_RDS_LC && handle->valid_fields & V4L2_RDS_LC)
> +		printf("\nLanguage: %u -> %s ", handle->lc,\
> +		v4l2_rds_get_language_str(handle));
> +	if (updated_fields & V4L2_RDS_DI && handle->valid_fields & V4L2_RDS_DI)
> +		print_decoder_info(handle->di);
> +	if (updated_fields & V4L2_RDS_ODA &&
> +			handle->decode_information & V4L2_RDS_ODA) {
> +		for (int i = 0; i < handle->rds_oda.size; ++i)
> +			printf("\nODA Group: %02u%c, AID: %08x",handle->rds_oda.oda[i].group_id,
> +			handle->rds_oda.oda[i].group_version, handle->rds_oda.oda[i].aid);
> +	}
> +	if (updated_fields & V4L2_RDS_AF && handle->valid_fields & V4L2_RDS_AF)
> +		print_rds_af(&handle->rds_af);
> +	if (params.options[OptPrintBlock])
> +		printf("\n");
> +}
> +
> +static void read_rds(struct v4l2_rds *handle, const int fd, const int wait_limit)
> +{
> +	int byte_cnt = 0;
> +	int error_cnt = 0;
> +	uint32_t updated_fields = 0x00;
> +	struct v4l2_rds_data rds_data; /* read buffer for rds blocks */
> +
> +	while(!params.terminate_decoding){
> +		memset(&rds_data, 0, sizeof(rds_data));
> +		if ((byte_cnt=read(fd, &rds_data, 3)) != 3) {
> +			if(byte_cnt == 0){
> +				printf("\nEnd of input file reached \n");
> +				break;
> +			} else if(++error_cnt > 2) {
> +				fprintf(stderr, "\nError reading from "\
> +					"device (no RDS data available)\n");
> +				break;
> +			}
> +			/* wait for new data to arrive: transmission of 1
> +			 * group takes ~88.7ms */
> +			usleep(wait_limit * 1000);
> +		}
> +		else if (byte_cnt == 3) {
> +			error_cnt = 0;
> +			/* true if a new group was decoded */
> +			if ((updated_fields = v4l2_rds_add(handle, &rds_data))) {
> +				print_rds_data(handle, updated_fields);
> +				if (params.options[OptVerbose])
> +					 print_rds_group(v4l2_rds_get_group(handle));
> +			}
> +		}
> +	}
> +}
> +
> +static void read_rds_from_fd(const int fd)
> +{
> +	struct v4l2_rds *rds_handle;
> +
> +	/* create an rds handle for the current device */
> +	if (!(rds_handle = v4l2_rds_create(true))) {
> +		fprintf(stderr, "Failed to init RDS lib: %s\n", strerror(errno));
> +		exit(1);
> +	}
> +
> +	/* try to receive and decode RDS data */
> +	read_rds(rds_handle, fd, params.wait_limit);
> +	print_rds_statistics(&rds_handle->rds_statistics);
> +
> +	v4l2_rds_destroy(rds_handle);
> +}
> +
> +static int parse_cl(int argc, char **argv)
> +{
> +	int i = 0;
> +	int idx = 0;
> +	int opt = 0;
> +	char short_options[26 * 2 * 2 + 1];
> +
> +	if (argc == 1) {
> +		usage_hint();
> +		exit(1);
> +	}
> +	for (i = 0; long_options[i].name; i++) {
> +		if (!isalpha(long_options[i].val))
> +			continue;
> +		short_options[idx++] = long_options[i].val;
> +		if (long_options[i].has_arg == required_argument)
> +			short_options[idx++] = ':';
> +	}
> +	while (1) {
> +		// TODO: remove option_index ?
> +		int option_index = 0;
> +
> +		short_options[idx] = 0;
> +		opt = getopt_long(argc, argv, short_options,
> +				 long_options, &option_index);
> +		if (opt == -1)
> +			break;
> +
> +		params.options[(int)opt] = 1;
> +		switch (opt) {
> +		case OptSetDevice:
> +			strncpy(params.fd_name, optarg, 80);
> +			if (optarg[0] >= '0' && optarg[0] <= '9' && optarg[1] == 0) {
> +				char newdev[20];
> +				char dev = optarg[0];
> +
> +				sprintf(newdev, "/dev/radio%c", dev);
> +				strncpy(params.fd_name, newdev, 20);
> +			}
> +			break;
> +		case OptSetFreq:
> +			params.freq = strtod(optarg, NULL);
> +			break;
> +		case OptListDevices:
> +			print_devices(list_devices());
> +			break;
> +		case OptFreqSeek:
> +			parse_freq_seek(optarg, params.freq_seek);
> +			break;
> +		case OptTunerIndex:
> +			params.tuner_index = strtoul(optarg, NULL, 0);
> +			break;
> +		case OptOpenFile:
> +		{	
> +			if (access(optarg, F_OK) != -1) {
> +				params.filemode_active = true;
> +				strncpy(params.fd_name, optarg, 80);
> +			} else {
> +				fprintf(stderr, "Unable to open file: %s\n", optarg);
> +				return -1;
> +			}
> +			/* enable the read-rds option by default for convenience */
> +			params.options[OptReadRds] = 1;
> +			break;
> +		}
> +		case OptWaitLimit:
> +			params.wait_limit = strtoul(optarg, NULL, 0);
> +			break;
> +		case ':':
> +			fprintf(stderr, "Option '%s' requires a value\n",
> +				argv[optind]);
> +			usage_hint();
> +			return 1;
> +		case '?':
> +			if (argv[optind])
> +				fprintf(stderr, "Unknown argument '%s'\n", argv[optind]);
> +			usage_hint();
> +			return 1;
> +		}
> +	}
> +	if (optind < argc) {
> +		printf("unknown arguments: ");
> +		while (optind < argc)
> +			printf("%s ", argv[optind++]);
> +		printf("\n");
> +		usage_hint();
> +		return 1;
> +	}
> +	if (params.options[OptAll]) {
> +		params.options[OptGetDriverInfo] = 1;	
> +		params.options[OptGetFreq] = 1;
> +		params.options[OptGetTuner] = 1;
> +		params.options[OptSilent] = 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static void print_driver_info(const struct v4l2_capability *vcap)
> +{
> +
> +	printf("Driver Info (%susing libv4l2):\n",
> +			params.options[OptUseWrapper] ? "" : "not ");
> +	printf("\tDriver name   : %s\n", vcap->driver);
> +	printf("\tCard type     : %s\n", vcap->card);
> +	printf("\tBus info      : %s\n", vcap->bus_info);
> +	printf("\tDriver version: %d.%d.%d\n",
> +			vcap->version >> 16,
> +			(vcap->version >> 8) & 0xff,
> +			vcap->version & 0xff);
> +	printf("\tCapabilities  : 0x%08X\n", vcap->capabilities);
> +	printf("%s", cap2s(vcap->capabilities).c_str());
> +	if (vcap->capabilities & V4L2_CAP_DEVICE_CAPS) {
> +		printf("\tDevice Caps   : 0x%08X\n", vcap->device_caps);
> +		printf("%s", cap2s(vcap->device_caps).c_str());
> +	}
> +}
> +
> +static void set_options(const int fd, const int capabilities, struct v4l2_frequency *vf,\
> +			struct v4l2_modulator *modulator, struct v4l2_tuner *tuner)
> +{
> +	int mode = -1;			/* set audio mode */
> +
> +	if (params.options[OptSetFreq]) {
> +		double fac = 16;
> +
> +		if (capabilities & V4L2_CAP_MODULATOR) {
> +			vf->type = V4L2_TUNER_RADIO;
> +			modulator->index = params.tuner_index;
> +			if (doioctl(fd, VIDIOC_G_MODULATOR, modulator) == 0) {
> +				fac = (modulator->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
> +			}
> +		} else {
> +			vf->type = V4L2_TUNER_ANALOG_TV;
> +			tuner->index = params.tuner_index;
> +			if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) {
> +				fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
> +				vf->type = tuner->type;
> +			}
> +		}
> +		vf->tuner = params.tuner_index;
> +		vf->frequency = __u32(params.freq * fac);
> +		if (doioctl(fd, VIDIOC_S_FREQUENCY, vf) == 0)
> +			printf("Frequency for tuner %d set to %d (%f MHz)\n",
> +				   vf->tuner, vf->frequency, vf->frequency / fac);
> +	}
> +	
> +	if (params.options[OptSetTuner]) {
> +		struct v4l2_tuner vt;
> +
> +		memset(&vt, 0, sizeof(struct v4l2_tuner));
> +		vt.index = params.tuner_index;
> +		if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) {
> +			if (mode != -1)
> +				vt.audmode = mode;
> +			doioctl(fd, VIDIOC_S_TUNER, &vt);
> +		}
> +	}
> +
> +	if (params.options[OptFreqSeek]) {
> +		params.freq_seek.tuner = params.tuner_index;
> +		params.freq_seek.type = V4L2_TUNER_RADIO;
> +		doioctl(fd, VIDIOC_S_HW_FREQ_SEEK, &params.freq_seek);
> +	}
> +}
> +
> +static void get_options(const int fd, const int capabilities, struct v4l2_frequency *vf,\
> +			struct v4l2_modulator *modulator, struct v4l2_tuner *tuner)
> +{
> +	if (params.options[OptGetFreq]) {
> +		double fac = 16;
> +
> +		if (capabilities & V4L2_CAP_MODULATOR) {
> +			vf->type = V4L2_TUNER_RADIO;
> +			modulator->index = params.tuner_index;
> +			if (doioctl(fd, VIDIOC_G_MODULATOR, modulator) == 0)
> +				fac = (modulator->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
> +		} else {
> +			vf->type = V4L2_TUNER_ANALOG_TV;
> +			tuner->index = params.tuner_index;
> +			if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) {
> +				fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
> +				vf->type = tuner->type;
> +			}
> +		}
> +		vf->tuner = params.tuner_index;
> +		if (doioctl(fd, VIDIOC_G_FREQUENCY, vf) == 0)
> +			printf("Frequency for tuner %d: %d (%f MHz)\n",
> +				   vf->tuner, vf->frequency, vf->frequency / fac);
> +	}
> +
> +	if (params.options[OptGetTuner]) {
> +		struct v4l2_tuner vt;
> +
> +		memset(&vt, 0, sizeof(struct v4l2_tuner));
> +		vt.index = params.tuner_index;
> +		if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) {
> +			printf("Tuner %d:\n", vt.index);
> +			printf("\tName                 : %s\n", vt.name);
> +			printf("\tCapabilities         : %s\n",\
> +				tcap2s(vt.capability).c_str());
> +			if (vt.capability & V4L2_TUNER_CAP_LOW)
> +				printf("\tFrequency range      : %.1f MHz - %.1f MHz\n",
> +					 vt.rangelow / 16000.0, vt.rangehigh / 16000.0);
> +			else
> +				printf("\tFrequency range      : %.1f MHz - %.1f MHz\n",
> +					 vt.rangelow / 16.0, vt.rangehigh / 16.0);
> +			printf("\tSignal strength/AFC  : %d%%/%d\n",\
> +				(int)((vt.signal / 655.35)+0.5), vt.afc);
> +			printf("\tCurrent audio mode   : %s\n", audmode2s(vt.audmode));
> +			printf("\tAvailable subchannels: %s\n",
> +					rxsubchans2s(vt.rxsubchans).c_str());
> +		}
> +	}
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int fd = -1;
> +
> +	/* command args */
> +	struct v4l2_tuner tuner;	/* set_freq/get_freq */
> +	struct v4l2_modulator modulator;/* set_freq/get_freq */
> +	struct v4l2_capability vcap;	/* list_cap */
> +	struct v4l2_frequency vf;	/* get_freq/set_freq */
> +
> +	memset(&tuner, 0, sizeof(tuner));
> +	memset(&modulator, 0, sizeof(modulator));
> +	memset(&vcap, 0, sizeof(vcap));
> +	memset(&vf, 0, sizeof(vf));
> +	strcpy(params.fd_name, "/dev/radio0");
> +	
> +	/* define locale for unicode support */
> +	if (!setlocale(LC_CTYPE, "")) {
> +		fprintf(stderr, "Can't set the specified locale!\n");
> +		return 1;
> +	}
> +	/* register signal handler for interrupt signal, to exit gracefully */
> +	signal(SIGINT, signal_handler_interrupt);
> +
> +	/* try to parse the command line */
> +	parse_cl(argc, argv);
> +	if (params.options[OptHelp]) {
> +		usage();
> +		exit(0);
> +	}
> +
> +	/* File Mode: disables all other features, except for RDS decoding */
> +	if (params.filemode_active) {
> +		if ((fd = open(params.fd_name, O_RDONLY|O_NONBLOCK)) < 0){
> +			perror("error opening file");
> +			exit(1);
> +		}
> +		read_rds_from_fd(fd);
> +		test_close(fd);
> +		exit(0);
> +	}
> +	
> +	/* Device Mode: open the radio device as read-only and non-blocking */
> +	if (!params.options[OptSetDevice]) {
> +		/* check the system for RDS capable devices */
> +		dev_vec devices = list_devices();
> +		if (devices.size() == 0) {
> +			fprintf(stderr, "No RDS-capable device found\n");
> +			exit(1);
> +		}
> +		strncpy(params.fd_name, devices[0].c_str(), 80);
> +		printf("Using device: %s\n", params.fd_name);
> +	}
> +	if ((fd = test_open(params.fd_name, O_RDONLY | O_NONBLOCK)) < 0) {
> +		fprintf(stderr, "Failed to open %s: %s\n", params.fd_name,
> +			strerror(errno));
> +		exit(1);
> +	}
> +	doioctl(fd, VIDIOC_QUERYCAP, &vcap);
> +
> +	/* Info options */
> +	if (params.options[OptGetDriverInfo])
> +		print_driver_info(&vcap);
> +	/* Set options */
> +	set_options(fd, vcap.capabilities, &vf, &modulator, &tuner);
> +	/* Get options */
> +	get_options(fd, vcap.capabilities, &vf, &modulator, &tuner);
> +	/* RDS decoding */
> +	if (params.options[OptReadRds])
> +		read_rds_from_fd(fd);
> +
> +	test_close(fd);
> +	exit(app_result);
> +}
>

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Hans Verkuil July 30, 2012, 6:50 a.m. UTC | #6
On Sat July 28 2012 13:11:13 Hans de Goede wrote:
> Hi,
> 
> Overall this looks good, but some of the code, for example the set/get freq and
> get/set tuner stuff seems to be a 1 on 1 copy of code from v4l2-ctrl, please
> factor this out into a common file which can be shared between both
> utilities (I think Hans V's recent work on splitting v4l2-ctl into multiple
> files comes a long way towards this).

I'm not sure how valuable this is. If we do this, then that means a fair amount
of work in v4l2-ctl (although the split was a major step forward in that direction).

There are a bunch of utilities that all share some common code in that respect.
But they are all stand-alone and I would have to think carefully how to organize
the code if you want to make it easy to share code.

Bottom-line: let's keep this as a separate project and not mix it with rds-ctl.

Regards,

	Hans

> 
> Regards,
> 
> Hans
> 
> On 07/25/2012 07:44 PM, Konke Radlow wrote:
> > ---
> >   Makefile.am               |    3 +-
> >   configure.ac              |    1 +
> >   utils/rds-ctl/Makefile.am |    5 +
> >   utils/rds-ctl/rds-ctl.cpp |  978 +++++++++++++++++++++++++++++++++++++++++++++
> >   4 files changed, 986 insertions(+), 1 deletion(-)
> >   create mode 100644 utils/rds-ctl/Makefile.am
> >   create mode 100644 utils/rds-ctl/rds-ctl.cpp
> >
> > diff --git a/Makefile.am b/Makefile.am
> > index 6707f5f..47103a1 100644
> > --- a/Makefile.am
> > +++ b/Makefile.am
> > @@ -18,7 +18,8 @@ SUBDIRS += \
> >   	utils/v4l2-compliance \
> >   	utils/v4l2-ctl \
> >   	utils/v4l2-dbg \
> > -	utils/v4l2-sysfs-path
> > +	utils/v4l2-sysfs-path \
> > +	utils/rds-ctl
> >
> >   if LINUX_OS
> >   SUBDIRS += \
> > diff --git a/configure.ac b/configure.ac
> > index 1d7eb29..1ad99e6 100644
> > --- a/configure.ac
> > +++ b/configure.ac
> > @@ -28,6 +28,7 @@ AC_CONFIG_FILES([Makefile
> >   	utils/v4l2-sysfs-path/Makefile
> >   	utils/xc3028-firmware/Makefile
> >   	utils/qv4l2/Makefile
> > +	utils/rds-ctl/Makefile
> >
> >   	contrib/freebsd/Makefile
> >   	contrib/test/Makefile
> > diff --git a/utils/rds-ctl/Makefile.am b/utils/rds-ctl/Makefile.am
> > new file mode 100644
> > index 0000000..9a84257
> > --- /dev/null
> > +++ b/utils/rds-ctl/Makefile.am
> > @@ -0,0 +1,5 @@
> > +bin_PROGRAMS = rds-ctl
> > +
> > +rds_ctl_SOURCES = rds-ctl.cpp
> > +rds_ctl_LDADD = ../../lib/libv4l2/libv4l2.la ../../lib/libv4l2rds/libv4l2rds.la
> > +
> > diff --git a/utils/rds-ctl/rds-ctl.cpp b/utils/rds-ctl/rds-ctl.cpp
> > new file mode 100644
> > index 0000000..8ddb969
> > --- /dev/null
> > +++ b/utils/rds-ctl/rds-ctl.cpp
> > @@ -0,0 +1,978 @@
> > +/*
> > + * rds-ctl.cpp is based on v4l2-ctl.cpp
> > + *
> > + * the following applies for all RDS related parts:
> > + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
> > + * Author: Konke Radlow <koradlow@gmail.com>
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU Lesser General Public License as published by
> > + * the Free Software Foundation; either version 2.1 of the License, or
> > + * (at your option) any later version.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + * GNU General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU General Public License
> > + * along with this program; if not, write to the Free Software
> > + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA
> > + */
> > +
> > +#include <unistd.h>
> > +#include <stdlib.h>
> > +#include <stdio.h>
> > +#include <string.h>
> > +#include <wchar.h>
> > +#include <locale.h>
> > +#include <inttypes.h>
> > +#include <getopt.h>
> > +#include <sys/types.h>
> > +#include <fcntl.h>
> > +#include <errno.h>
> > +#include <sys/ioctl.h>
> > +#include <sys/time.h>
> > +#include <dirent.h>
> > +#include <config.h>
> > +#include <signal.h>
> > +
> > +#ifdef HAVE_SYS_KLOG_H
> > +#include <sys/klog.h>
> > +#endif
> > +
> > +#include <linux/videodev2.h>
> > +#include <libv4l2.h>
> > +#include <libv4l2rds.h>
> > +
> > +#include <list>
> > +#include <vector>
> > +#include <map>
> > +#include <string>
> > +#include <algorithm>
> > +
> > +#define ARRAY_SIZE(arr) ((int)(sizeof(arr) / sizeof((arr)[0])))
> > +
> > +typedef std::vector<std::string> dev_vec;
> > +typedef std::map<std::string, std::string> dev_map;
> > +
> > +/* Short option list
> > +
> > +   Please keep in alphabetical order.
> > +   That makes it easier to see which short options are still free.
> > +
> > +   In general the lower case is used to set something and the upper
> > +   case is used to retrieve a setting. */
> > +enum Option {
> > +	OptSetDevice = 'd',
> > +	OptGetDriverInfo = 'D',
> > +	OptGetFreq = 'F',
> > +	OptSetFreq = 'f',
> > +	OptHelp = 'h',
> > +	OptReadRds = 'R',
> > +	OptGetTuner = 'T',
> > +	OptSetTuner = 't',
> > +	OptUseWrapper = 'w',
> > +	OptAll = 128,
> > +	OptFreqSeek,
> > +	OptListDevices,
> > +	OptOpenFile,
> > +	OptPrintBlock,
> > +	OptSilent,
> > +	OptTunerIndex,
> > +	OptVerbose,
> > +	OptWaitLimit,
> > +	OptLast = 256
> > +};
> > +
> > +struct ctl_parameters {
> > +	bool terminate_decoding;
> > +	char options[OptLast];
> > +	char fd_name[80];
> > +	bool filemode_active;
> > +	double freq;
> > +	uint32_t wait_limit;
> > +	uint8_t tuner_index;
> > +	struct v4l2_hw_freq_seek freq_seek;
> > +};
> > +
> > +static struct ctl_parameters params;
> > +static int app_result;
> > +
> > +static struct option long_options[] = {
> > +	{"all", no_argument, 0, OptAll},
> > +	{"device", required_argument, 0, OptSetDevice},
> > +	{"file", required_argument, 0, OptOpenFile},
> > +	{"freq-seek", required_argument, 0, OptFreqSeek},
> > +	{"get-freq", no_argument, 0, OptGetFreq},
> > +	{"get-tuner", no_argument, 0, OptGetTuner},
> > +	{"help", no_argument, 0, OptHelp},
> > +	{"info", no_argument, 0, OptGetDriverInfo},
> > +	{"list-devices", no_argument, 0, OptListDevices},
> > +	{"print-block", no_argument, 0, OptPrintBlock},
> > +	{"read-rds", no_argument, 0, OptReadRds},
> > +	{"set-freq", required_argument, 0, OptSetFreq},
> > +	{"tuner-index", required_argument, 0, OptTunerIndex},
> > +	{"verbose", no_argument, 0, OptVerbose},
> > +	{"wait-limit", required_argument, 0, OptWaitLimit},
> > +	{"wrapper", no_argument, 0, OptUseWrapper},
> > +	{0, 0, 0, 0}
> > +};
> > +
> > +static void usage_hint(void)
> > +{
> > +	fprintf(stderr, "Try 'rds-ctl --help' for more information.\n");
> > +}
> > +
> > +static void usage_common(void)
> > +{
> > +	printf("\nGeneral/Common options:\n"
> > +	       "  --all              display all information available\n"
> > +	       "  -D, --info         show driver info [VIDIOC_QUERYCAP]\n"
> > +	       "  -d, --device=<dev> use device <dev>\n"
> > +	       "                     if <dev> is a single digit, then /dev/video<dev> is used\n"
> > +	       "                     default: checks for RDS-capable devices,\n"
> > +	       "                     uses device with lowest ID\n"
> > +	       "  -h, --help         display this help message\n"
> > +	       "  -w, --wrapper      use the libv4l2 wrapper library.\n"
> > +	       "  --list-devices     list all v4l radio devices with RDS capabilities\n"
> > +	       );
> > +}
> > +
> > +static void usage_tuner(void)
> > +{
> > +	printf("\nTuner/Modulator options:\n"
> > +	       "  -F, --get-freq     query the frequency [VIDIOC_G_FREQUENCY]\n"
> > +	       "  -f, --set-freq=<freq>\n"
> > +	       "                     set the frequency to <freq> MHz [VIDIOC_S_FREQUENCY]\n"
> > +	       "  -T, --get-tuner    query the tuner settings [VIDIOC_G_TUNER]\n"
> > +	       "  --tuner-index=<idx> Use idx as tuner idx for tuner/modulator commands\n"
> > +	       "  --freq-seek=dir=<0/1>,wrap=<0/1>,spacing=<hz>\n"
> > +	       "                     perform a hardware frequency seek [VIDIOC_S_HW_FREQ_SEEK]\n"
> > +	       "                     dir is 0 (seek downward) or 1 (seek upward)\n"
> > +	       "                     wrap is 0 (do not wrap around) or 1 (wrap around)\n"
> > +	       "                     spacing sets the seek resolution (use 0 for default)\n"
> > +	       );
> > +}
> > +
> > +static void usage_rds(void)
> > +{
> > +	printf("\nRDS options: \n"
> > +	       "  -R, --read-rds\n"
> > +	       "                     enable reading of RDS data from device\n"
> > +	       "  --file=<path>\n"
> > +	       "                     open a RDS stream file dump instead of a device\n"
> > +	       "                     all General and Tuner Options are disabled in this mode\n"
> > +	       "  --wait-limit=<ms>\n"
> > +	       "                     defines the maximum wait duration for avaibility of new\n"
> > +	       "                     RDS data\n"
> > +	       "                     <default>: 5000ms\n"
> > +	       "  --print-block\n"
> > +	       "                     prints all valid RDS fields, whenever a value is updated\n"
> > +	       "                     instead of printing only updated values\n"
> > +	       "  --verbose\n"
> > +	       "                     turn on verbose mode - every received RDS group\n"
> > +	       "                     will be printed\n"
> > +	       );
> > +}
> > +
> > +static void usage(void)
> > +{
> > +	printf("Usage:\n");
> > +	usage_common();
> > +	usage_tuner();
> > +	usage_rds();
> > +}
> > +
> > +static void signal_handler_interrupt(int signum)
> > +{
> > +	fprintf(stderr, "Interrupt received: Terminating program\n");
> > +	params.terminate_decoding = true;
> > +}
> > +
> > +static int test_open(const char *file, int oflag)
> > +{
> > + 	return params.options[OptUseWrapper] ? v4l2_open(file, oflag) : open(file, oflag);
> > +}
> > +
> > +static int test_close(int fd)
> > +{
> > +	return params.options[OptUseWrapper] ? v4l2_close(fd) : close(fd);
> > +}
> > +
> > +static int test_ioctl(int fd, int cmd, void *arg)
> > +{
> > +	return params.options[OptUseWrapper] ? v4l2_ioctl(fd, cmd, arg) : ioctl(fd, cmd, arg);
> > +}
> > +
> > +static int doioctl_name(int fd, unsigned long int request, void *parm, const char *name)
> > +{
> > +	int retval = test_ioctl(fd, request, parm);
> > +
> > +	if (retval < 0) {
> > +		app_result = -1;
> > +	}
> > +	if (params.options[OptSilent]) return retval;
> > +	if (retval < 0)
> > +		printf("%s: failed: %s\n", name, strerror(errno));
> > +	else if (params.options[OptVerbose])
> > +		printf("%s: ok\n", name);
> > +
> > +	return retval;
> > +}
> > +
> > +#define doioctl(n, r, p) doioctl_name(n, r, p, #r)
> > +
> > +static const char *audmode2s(int audmode)
> > +{
> > +	switch (audmode) {
> > +		case V4L2_TUNER_MODE_STEREO: return "stereo";
> > +		case V4L2_TUNER_MODE_LANG1: return "lang1";
> > +		case V4L2_TUNER_MODE_LANG2: return "lang2";
> > +		case V4L2_TUNER_MODE_LANG1_LANG2: return "bilingual";
> > +		case V4L2_TUNER_MODE_MONO: return "mono";
> > +		default: return "unknown";
> > +	}
> > +}
> > +
> > +static std::string rxsubchans2s(int rxsubchans)
> > +{
> > +	std::string s;
> > +
> > +	if (rxsubchans & V4L2_TUNER_SUB_MONO)
> > +		s += "mono ";
> > +	if (rxsubchans & V4L2_TUNER_SUB_STEREO)
> > +		s += "stereo ";
> > +	if (rxsubchans & V4L2_TUNER_SUB_LANG1)
> > +		s += "lang1 ";
> > +	if (rxsubchans & V4L2_TUNER_SUB_LANG2)
> > +		s += "lang2 ";
> > +	if (rxsubchans & V4L2_TUNER_SUB_RDS)
> > +		s += "rds ";
> > +	return s;
> > +}
> > +
> > +static std::string tcap2s(unsigned cap)
> > +{
> > +	std::string s;
> > +
> > +	if (cap & V4L2_TUNER_CAP_LOW)
> > +		s += "62.5 Hz ";
> > +	else
> > +		s += "62.5 kHz ";
> > +	if (cap & V4L2_TUNER_CAP_NORM)
> > +		s += "multi-standard ";
> > +	if (cap & V4L2_TUNER_CAP_HWSEEK_BOUNDED)
> > +		s += "hwseek-bounded ";
> > +	if (cap & V4L2_TUNER_CAP_HWSEEK_WRAP)
> > +		s += "hwseek-wrap ";
> > +	if (cap & V4L2_TUNER_CAP_STEREO)
> > +		s += "stereo ";
> > +	if (cap & V4L2_TUNER_CAP_LANG1)
> > +		s += "lang1 ";
> > +	if (cap & V4L2_TUNER_CAP_LANG2)
> > +		s += "lang2 ";
> > +	if (cap & V4L2_TUNER_CAP_RDS)
> > +		s += "rds ";
> > +	if (cap & V4L2_TUNER_CAP_RDS_BLOCK_IO)
> > +		s += "rds block I/O ";
> > +	if (cap & V4L2_TUNER_CAP_RDS_CONTROLS)
> > +		s += "rds control ";
> > +	return s;
> > +}
> > +
> > +static std::string cap2s(unsigned cap)
> > +{
> > +	std::string s;
> > +
> > +	if (cap & V4L2_CAP_RDS_CAPTURE)
> > +		s += "\t\tRDS Capture\n";
> > +	if (cap & V4L2_CAP_RDS_OUTPUT)
> > +		s += "\t\tRDS Output\n";
> > +	if (cap & V4L2_CAP_TUNER)
> > +		s += "\t\tTuner\n";
> > +	if (cap & V4L2_CAP_MODULATOR)
> > +		s += "\t\tModulator\n";
> > +	if (cap & V4L2_CAP_AUDIO)
> > +		s += "\t\tAudio\n";
> > +	if (cap & V4L2_CAP_RADIO)
> > +		s += "\t\tRadio\n";
> > +	if (cap & V4L2_CAP_READWRITE)
> > +		s += "\t\tRead/Write\n";
> > +	if (cap & V4L2_CAP_ASYNCIO)
> > +		s += "\t\tAsync I/O\n";
> > +	if (cap & V4L2_CAP_STREAMING)
> > +		s += "\t\tStreaming\n";
> > +	if (cap & V4L2_CAP_DEVICE_CAPS)
> > +		s += "\t\tDevice Capabilities\n";
> > +	return s;
> > +}
> > +
> > +static bool is_radio_dev(const char *name)
> > +{
> > +	return !memcmp(name, "radio", 5);
> > +}
> > +
> > +static int calc_node_val(const char *s)
> > +{
> > +	int n = 0;
> > +
> > +	s = strrchr(s, '/') + 1;
> > +	if (!memcmp(s, "video", 5)) n = 0;
> > +	else if (!memcmp(s, "radio", 5)) n = 0x100;
> > +	else if (!memcmp(s, "vbi", 3)) n = 0x200;
> > +	else if (!memcmp(s, "vtx", 3)) n = 0x300;
> > +	n += atol(s + (n >= 0x200 ? 3 : 5));
> > +	return n;
> > +}
> > +
> > +static bool sort_on_device_name(const std::string &s1, const std::string &s2)
> > +{
> > +	int n1 = calc_node_val(s1.c_str());
> > +	int n2 = calc_node_val(s2.c_str());
> > +
> > +	return n1 < n2;
> > +}
> > +
> > +static void print_devices(dev_vec files)
> > +{
> > +	dev_map cards;
> > +	int fd = -1;
> > +	std::string bus_info;
> > +	struct v4l2_capability vcap;
> > +	
> > +	for (dev_vec::iterator iter = files.begin();
> > +		iter != files.end(); ++iter) {
> > +		fd = open(iter->c_str(), O_RDWR);
> > +		memset(&vcap, 0, sizeof(vcap));
> > +		if (fd < 0)
> > +			continue;
> > +		doioctl(fd, VIDIOC_QUERYCAP, &vcap);
> > +		close(fd);
> > +		bus_info = (const char *)vcap.bus_info;
> > +	if (cards[bus_info].empty())
> > +			cards[bus_info] += std::string((char *)vcap.card)\
> > +				+ " (" + bus_info + "):\n";
> > +		cards[bus_info] += "\t" + (*iter);
> > +		cards[bus_info] += "\n";
> > +	}
> > +	for (dev_map::iterator iter = cards.begin();
> > +			iter != cards.end(); ++iter) {
> > +		printf("%s\n", iter->second.c_str());
> > +	}
> > +}
> > +static dev_vec list_devices(void)
> > +{
> > +	DIR *dp;
> > +	struct dirent *ep;
> > +	dev_vec files;
> > +	dev_map links;
> > +	
> > +	struct v4l2_tuner vt;
> > +
> > +	dp = opendir("/dev");
> > +	if (dp == NULL) {
> > +		perror ("Couldn't open the directory");
> > +		exit(1);
> > +	}
> > +	while ((ep = readdir(dp)))
> > +		if (is_radio_dev(ep->d_name))
> > +			files.push_back(std::string("/dev/") + ep->d_name);
> > +	closedir(dp);
> > +
> > +	/* Find device nodes which are links to other device nodes */
> > +	for (dev_vec::iterator iter = files.begin(); \
> > +			iter != files.end(); ) {
> > +		char link[64+1];
> > +		int link_len;
> > +		std::string target;
> > +
> > +		link_len = readlink(iter->c_str(), link, 64);
> > +		if (link_len < 0) {	/* Not a link or error */
> > +			iter++;
> > +			continue;
> > +		}
> > +		link[link_len] = '\0';
> > +
> > +		/* Only remove from files list if target itself is in list */
> > +		if (link[0] != '/')	/* Relative link */
> > +			target = std::string("/dev/");
> > +		target += link;
> > +		if (find(files.begin(), files.end(), target) == files.end()) {
> > +			iter++;
> > +			continue;
> > +		}
> > +		files.erase(iter);
> > +	}
> > +	/* Iterate through all devices, and remove all non-accessible devices
> > +	 * and all devices that don't offer the RDS_BLOCK_IO capability */
> > +	std::sort(files.begin(), files.end(), sort_on_device_name);
> > +	for (dev_vec::iterator iter = files.begin(); \
> > +			iter != files.end(); ++iter) {
> > +		int fd = open(iter->c_str(), O_RDONLY | O_NONBLOCK);
> > +		std::string bus_info;
> > +
> > +		if (fd < 0) {
> > +			iter = files.erase(iter);
> > +			continue;
> > +		}
> > +		memset(&vt, 0, sizeof(vt));
> > +		if (doioctl(fd, VIDIOC_G_TUNER, &vt) != 0) {
> > +			close(fd);
> > +			iter = files.erase(iter);
> > +			continue;
> > +		}
> > +		/* remove device if it doesn't support rds block I/O */
> > +		if (!vt.capability & V4L2_TUNER_CAP_RDS_BLOCK_IO)
> > +			iter = files.erase(iter);
> > +	}
> > +	return files;
> > +}
> > +
> > +static int parse_subopt(char **subs, const char * const *subopts, char **value)
> > +{
> > +	int opt = getsubopt(subs, (char * const *)subopts, value);
> > +
> > +	if (opt == -1) {
> > +		fprintf(stderr, "Invalid suboptions specified\n");
> > +		return -1;
> > +	}
> > +	if (value == NULL) {
> > +		fprintf(stderr, "No value given to suboption <%s>\n",
> > +				subopts[opt]);
> > +		return -1;
> > +	}
> > +	return opt;
> > +}
> > +
> > +static void parse_freq_seek(char *optarg, struct v4l2_hw_freq_seek &seek)
> > +{
> > +	char *value;
> > +	char *subs = optarg;
> > +
> > +	while (*subs != '\0') {
> > +		static const char *const subopts[] = {
> > +			"dir",
> > +			"wrap",
> > +			"spacing",
> > +			NULL
> > +		};
> > +
> > +		switch (parse_subopt(&subs, subopts, &value)) {
> > +		case 0:
> > +			seek.seek_upward = strtol(value, 0L, 0);
> > +			break;
> > +		case 1:
> > +			seek.wrap_around = strtol(value, 0L, 0);
> > +			break;
> > +		case 2:
> > +			seek.spacing = strtol(value, 0L, 0);
> > +			break;
> > +		default:
> > +			usage_tuner();
> > +			exit(1);
> > +		}
> > +	}
> > +}
> > +
> > +static void print_byte(char byte, bool linebreak)
> > +{
> > +	int count=8;
> > +	printf(" ");
> > +	while(count--)
> > +	{
> > +		printf("%d", ( byte & 128 ) ? 1 : 0 );
> > +		byte <<= 1;
> > +	}
> > +	if(linebreak)  printf("\n");
> > +}
> > +
> > +static void print_rds_group(const struct v4l2_rds_group *rds_group)
> > +{
> > +	printf("\nComplete RDS data group received \n");
> > +	printf("PI: %04x\n", rds_group->pi);
> > +	printf("Group: %u%c \n", rds_group->group_id, rds_group->group_version);
> > +	printf("Traffic Program: %s \n", (rds_group->traffic_prog)? "yes" : "no");
> > +	printf("Program Type: %u \n", rds_group->pty);
> > +	printf("Data B:");
> > +	print_byte(rds_group->data_b_lsb, true);
> > +	printf("Data C:");
> > +	print_byte(rds_group->data_c_msb, false);
> > +	print_byte(rds_group->data_c_lsb, true);
> > +	printf("Data D:");
> > +	print_byte(rds_group->data_d_msb, false);
> > +	print_byte(rds_group->data_d_lsb, true);
> > +	printf("\n");
> > +}
> > +
> > +static void print_decoder_info(uint8_t di)
> > +{
> > +	printf("\nDI: ");
> > +	if (di & V4L2_RDS_FLAG_STEREO)
> > +		printf("Stereo, ");
> > +	else
> > +		printf("Mono, ");
> > +	if (di & V4L2_RDS_FLAG_ARTIFICIAL_HEAD)
> > +		printf("Artificial Head, ");
> > +	else
> > +		printf("No Artificial Head, ");
> > +	if (di & V4L2_RDS_FLAG_COMPRESSED)
> > +		printf("Compressed");
> > +	else
> > +		printf("Not Compressed");
> > +}
> > +
> > +static void print_rds_statistics(struct v4l2_rds_statistics *statistics)
> > +{
> > +	printf("\n\nRDS Statistics: \n");
> > +	printf("received blocks / received groups: %u / %u\n",\
> > +		statistics->block_cnt, statistics->group_cnt);
> > +
> > +	float block_error_percentage = \
> > +	((float)statistics->block_error_cnt / statistics->block_cnt) * 100.0;
> > +	printf("block errors / group errors: %u (%3.2f%%) / %u \n",\
> > +		statistics->block_error_cnt,
> > +		block_error_percentage, statistics->group_error_cnt);
> > +	float block_corrected_percentage = \
> > +	((float)statistics->block_corrected_cnt / statistics->block_cnt) * 100.0;
> > +	printf("corrected blocks: %u (%3.2f%%)\n",\
> > +		statistics->block_corrected_cnt, block_corrected_percentage);
> > +	for(int i=0; i<16; i++)
> > +		printf("Group %02d: %u\n", i, statistics->group_type_cnt[i]);
> > +}
> > +
> > +static void print_rds_af(struct v4l2_rds_af_set *af_set)
> > +{
> > +	int counter = 0;
> > +
> > +	printf("\nAnnounced AFs: %u", af_set->announced_af);
> > +	for (int i = 0; i < af_set->size && i < af_set->announced_af; i++, counter++) {
> > +		if (af_set->af[i] >= 87500000 ) {
> > +			printf("\nAF%02d: %.1fMHz", counter, af_set->af[i] / 1000000.0);
> > +			continue;
> > +		}
> > +		printf("\nAF%02d: %.1fkHz", counter, af_set->af[i] / 1000.0);
> > +	}
> > +}
> > +
> > +static void print_rds_pi(const struct v4l2_rds *handle)
> > +{
> > +	printf("\nArea Coverage: %s", v4l2_rds_get_coverage_str(handle));
> > +}
> > +
> > +static void print_rds_data(struct v4l2_rds *handle, uint32_t updated_fields)
> > +{
> > +	if (params.options[OptPrintBlock])
> > +		updated_fields = 0xFFFFFFFF;
> > +
> > +	if (updated_fields & V4L2_RDS_PI &&
> > +			handle->valid_fields & V4L2_RDS_PI) {
> > +		printf("\nPI: %04x", handle->pi);
> > +		print_rds_pi(handle);
> > +	}
> > +
> > +	if (updated_fields & V4L2_RDS_PS &&
> > +			handle->valid_fields & V4L2_RDS_PS) {
> > +		printf("\nPS: ");
> > +		for (int i = 0; i < 8; ++i) {
> > +			/* filter out special characters */
> > +			if (handle->ps[i] >= 0x20 && handle->ps[i] <= 0x7E)
> > +				printf("%lc",handle->ps[i]);
> > +			else
> > +				printf(" ");
> > +		}
> > +	}
> > +
> > +	if (updated_fields & V4L2_RDS_PTY && handle->valid_fields & V4L2_RDS_PTY)
> > +		printf("\nPTY: %0u -> %s",handle->pty,\
> > +			v4l2_rds_get_pty_str(handle));
> > +
> > +	if (updated_fields & V4L2_RDS_PTYN && handle->valid_fields & V4L2_RDS_PTYN) {
> > +		printf("\nPTYN: ");
> > +		for (int i = 0; i < 8; ++i) {
> > +			/* filter out special characters */
> > +			if (handle->ptyn[i] >= 0x20 && handle->ptyn[i] <= 0x7E)
> > +				printf("%lc",handle->ptyn[i]);
> > +			else
> > +				printf(" ");
> > +		}
> > +	}
> > +
> > +	if(updated_fields & V4L2_RDS_RT && handle->valid_fields & V4L2_RDS_RT){
> > +		printf("\nRT: ");
> > +		for (int i = 0; i < handle->rt_length; ++i) {
> > +			/* filter out special characters */
> > +			if (handle->rt[i] >= 0x20 && handle->rt[i] <= 0x7E)
> > +				printf("%lc",handle->rt[i]);
> > +			else
> > +				printf(" ");
> > +		}
> > +	}
> > +
> > +	if (updated_fields & V4L2_RDS_TP && handle->valid_fields & V4L2_RDS_TP)
> > +		printf("\nTP: %s  TA: %s", (handle->tp)? "yes":"no",\
> > +			handle->ta? "yes":"no");
> > +	if (updated_fields & V4L2_RDS_MS && handle->valid_fields & V4L2_RDS_MS)
> > +		printf("\nMS Flag: %s", (handle->ms)? "Music" : "Speech");
> > +	if (updated_fields & V4L2_RDS_ECC && handle->valid_fields & V4L2_RDS_ECC)
> > +		printf("\nECC: %X%x, Country: %u -> %s",\
> > +		handle->ecc >> 4, handle->ecc & 0x0f, handle->pi >> 12, \
> > +		v4l2_rds_get_country_str(handle));
> > +	if (updated_fields & V4L2_RDS_LC && handle->valid_fields & V4L2_RDS_LC)
> > +		printf("\nLanguage: %u -> %s ", handle->lc,\
> > +		v4l2_rds_get_language_str(handle));
> > +	if (updated_fields & V4L2_RDS_DI && handle->valid_fields & V4L2_RDS_DI)
> > +		print_decoder_info(handle->di);
> > +	if (updated_fields & V4L2_RDS_ODA &&
> > +			handle->decode_information & V4L2_RDS_ODA) {
> > +		for (int i = 0; i < handle->rds_oda.size; ++i)
> > +			printf("\nODA Group: %02u%c, AID: %08x",handle->rds_oda.oda[i].group_id,
> > +			handle->rds_oda.oda[i].group_version, handle->rds_oda.oda[i].aid);
> > +	}
> > +	if (updated_fields & V4L2_RDS_AF && handle->valid_fields & V4L2_RDS_AF)
> > +		print_rds_af(&handle->rds_af);
> > +	if (params.options[OptPrintBlock])
> > +		printf("\n");
> > +}
> > +
> > +static void read_rds(struct v4l2_rds *handle, const int fd, const int wait_limit)
> > +{
> > +	int byte_cnt = 0;
> > +	int error_cnt = 0;
> > +	uint32_t updated_fields = 0x00;
> > +	struct v4l2_rds_data rds_data; /* read buffer for rds blocks */
> > +
> > +	while(!params.terminate_decoding){
> > +		memset(&rds_data, 0, sizeof(rds_data));
> > +		if ((byte_cnt=read(fd, &rds_data, 3)) != 3) {
> > +			if(byte_cnt == 0){
> > +				printf("\nEnd of input file reached \n");
> > +				break;
> > +			} else if(++error_cnt > 2) {
> > +				fprintf(stderr, "\nError reading from "\
> > +					"device (no RDS data available)\n");
> > +				break;
> > +			}
> > +			/* wait for new data to arrive: transmission of 1
> > +			 * group takes ~88.7ms */
> > +			usleep(wait_limit * 1000);
> > +		}
> > +		else if (byte_cnt == 3) {
> > +			error_cnt = 0;
> > +			/* true if a new group was decoded */
> > +			if ((updated_fields = v4l2_rds_add(handle, &rds_data))) {
> > +				print_rds_data(handle, updated_fields);
> > +				if (params.options[OptVerbose])
> > +					 print_rds_group(v4l2_rds_get_group(handle));
> > +			}
> > +		}
> > +	}
> > +}
> > +
> > +static void read_rds_from_fd(const int fd)
> > +{
> > +	struct v4l2_rds *rds_handle;
> > +
> > +	/* create an rds handle for the current device */
> > +	if (!(rds_handle = v4l2_rds_create(true))) {
> > +		fprintf(stderr, "Failed to init RDS lib: %s\n", strerror(errno));
> > +		exit(1);
> > +	}
> > +
> > +	/* try to receive and decode RDS data */
> > +	read_rds(rds_handle, fd, params.wait_limit);
> > +	print_rds_statistics(&rds_handle->rds_statistics);
> > +
> > +	v4l2_rds_destroy(rds_handle);
> > +}
> > +
> > +static int parse_cl(int argc, char **argv)
> > +{
> > +	int i = 0;
> > +	int idx = 0;
> > +	int opt = 0;
> > +	char short_options[26 * 2 * 2 + 1];
> > +
> > +	if (argc == 1) {
> > +		usage_hint();
> > +		exit(1);
> > +	}
> > +	for (i = 0; long_options[i].name; i++) {
> > +		if (!isalpha(long_options[i].val))
> > +			continue;
> > +		short_options[idx++] = long_options[i].val;
> > +		if (long_options[i].has_arg == required_argument)
> > +			short_options[idx++] = ':';
> > +	}
> > +	while (1) {
> > +		// TODO: remove option_index ?
> > +		int option_index = 0;
> > +
> > +		short_options[idx] = 0;
> > +		opt = getopt_long(argc, argv, short_options,
> > +				 long_options, &option_index);
> > +		if (opt == -1)
> > +			break;
> > +
> > +		params.options[(int)opt] = 1;
> > +		switch (opt) {
> > +		case OptSetDevice:
> > +			strncpy(params.fd_name, optarg, 80);
> > +			if (optarg[0] >= '0' && optarg[0] <= '9' && optarg[1] == 0) {
> > +				char newdev[20];
> > +				char dev = optarg[0];
> > +
> > +				sprintf(newdev, "/dev/radio%c", dev);
> > +				strncpy(params.fd_name, newdev, 20);
> > +			}
> > +			break;
> > +		case OptSetFreq:
> > +			params.freq = strtod(optarg, NULL);
> > +			break;
> > +		case OptListDevices:
> > +			print_devices(list_devices());
> > +			break;
> > +		case OptFreqSeek:
> > +			parse_freq_seek(optarg, params.freq_seek);
> > +			break;
> > +		case OptTunerIndex:
> > +			params.tuner_index = strtoul(optarg, NULL, 0);
> > +			break;
> > +		case OptOpenFile:
> > +		{	
> > +			if (access(optarg, F_OK) != -1) {
> > +				params.filemode_active = true;
> > +				strncpy(params.fd_name, optarg, 80);
> > +			} else {
> > +				fprintf(stderr, "Unable to open file: %s\n", optarg);
> > +				return -1;
> > +			}
> > +			/* enable the read-rds option by default for convenience */
> > +			params.options[OptReadRds] = 1;
> > +			break;
> > +		}
> > +		case OptWaitLimit:
> > +			params.wait_limit = strtoul(optarg, NULL, 0);
> > +			break;
> > +		case ':':
> > +			fprintf(stderr, "Option '%s' requires a value\n",
> > +				argv[optind]);
> > +			usage_hint();
> > +			return 1;
> > +		case '?':
> > +			if (argv[optind])
> > +				fprintf(stderr, "Unknown argument '%s'\n", argv[optind]);
> > +			usage_hint();
> > +			return 1;
> > +		}
> > +	}
> > +	if (optind < argc) {
> > +		printf("unknown arguments: ");
> > +		while (optind < argc)
> > +			printf("%s ", argv[optind++]);
> > +		printf("\n");
> > +		usage_hint();
> > +		return 1;
> > +	}
> > +	if (params.options[OptAll]) {
> > +		params.options[OptGetDriverInfo] = 1;	
> > +		params.options[OptGetFreq] = 1;
> > +		params.options[OptGetTuner] = 1;
> > +		params.options[OptSilent] = 1;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void print_driver_info(const struct v4l2_capability *vcap)
> > +{
> > +
> > +	printf("Driver Info (%susing libv4l2):\n",
> > +			params.options[OptUseWrapper] ? "" : "not ");
> > +	printf("\tDriver name   : %s\n", vcap->driver);
> > +	printf("\tCard type     : %s\n", vcap->card);
> > +	printf("\tBus info      : %s\n", vcap->bus_info);
> > +	printf("\tDriver version: %d.%d.%d\n",
> > +			vcap->version >> 16,
> > +			(vcap->version >> 8) & 0xff,
> > +			vcap->version & 0xff);
> > +	printf("\tCapabilities  : 0x%08X\n", vcap->capabilities);
> > +	printf("%s", cap2s(vcap->capabilities).c_str());
> > +	if (vcap->capabilities & V4L2_CAP_DEVICE_CAPS) {
> > +		printf("\tDevice Caps   : 0x%08X\n", vcap->device_caps);
> > +		printf("%s", cap2s(vcap->device_caps).c_str());
> > +	}
> > +}
> > +
> > +static void set_options(const int fd, const int capabilities, struct v4l2_frequency *vf,\
> > +			struct v4l2_modulator *modulator, struct v4l2_tuner *tuner)
> > +{
> > +	int mode = -1;			/* set audio mode */
> > +
> > +	if (params.options[OptSetFreq]) {
> > +		double fac = 16;
> > +
> > +		if (capabilities & V4L2_CAP_MODULATOR) {
> > +			vf->type = V4L2_TUNER_RADIO;
> > +			modulator->index = params.tuner_index;
> > +			if (doioctl(fd, VIDIOC_G_MODULATOR, modulator) == 0) {
> > +				fac = (modulator->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
> > +			}
> > +		} else {
> > +			vf->type = V4L2_TUNER_ANALOG_TV;
> > +			tuner->index = params.tuner_index;
> > +			if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) {
> > +				fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
> > +				vf->type = tuner->type;
> > +			}
> > +		}
> > +		vf->tuner = params.tuner_index;
> > +		vf->frequency = __u32(params.freq * fac);
> > +		if (doioctl(fd, VIDIOC_S_FREQUENCY, vf) == 0)
> > +			printf("Frequency for tuner %d set to %d (%f MHz)\n",
> > +				   vf->tuner, vf->frequency, vf->frequency / fac);
> > +	}
> > +	
> > +	if (params.options[OptSetTuner]) {
> > +		struct v4l2_tuner vt;
> > +
> > +		memset(&vt, 0, sizeof(struct v4l2_tuner));
> > +		vt.index = params.tuner_index;
> > +		if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) {
> > +			if (mode != -1)
> > +				vt.audmode = mode;
> > +			doioctl(fd, VIDIOC_S_TUNER, &vt);
> > +		}
> > +	}
> > +
> > +	if (params.options[OptFreqSeek]) {
> > +		params.freq_seek.tuner = params.tuner_index;
> > +		params.freq_seek.type = V4L2_TUNER_RADIO;
> > +		doioctl(fd, VIDIOC_S_HW_FREQ_SEEK, &params.freq_seek);
> > +	}
> > +}
> > +
> > +static void get_options(const int fd, const int capabilities, struct v4l2_frequency *vf,\
> > +			struct v4l2_modulator *modulator, struct v4l2_tuner *tuner)
> > +{
> > +	if (params.options[OptGetFreq]) {
> > +		double fac = 16;
> > +
> > +		if (capabilities & V4L2_CAP_MODULATOR) {
> > +			vf->type = V4L2_TUNER_RADIO;
> > +			modulator->index = params.tuner_index;
> > +			if (doioctl(fd, VIDIOC_G_MODULATOR, modulator) == 0)
> > +				fac = (modulator->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
> > +		} else {
> > +			vf->type = V4L2_TUNER_ANALOG_TV;
> > +			tuner->index = params.tuner_index;
> > +			if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) {
> > +				fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
> > +				vf->type = tuner->type;
> > +			}
> > +		}
> > +		vf->tuner = params.tuner_index;
> > +		if (doioctl(fd, VIDIOC_G_FREQUENCY, vf) == 0)
> > +			printf("Frequency for tuner %d: %d (%f MHz)\n",
> > +				   vf->tuner, vf->frequency, vf->frequency / fac);
> > +	}
> > +
> > +	if (params.options[OptGetTuner]) {
> > +		struct v4l2_tuner vt;
> > +
> > +		memset(&vt, 0, sizeof(struct v4l2_tuner));
> > +		vt.index = params.tuner_index;
> > +		if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) {
> > +			printf("Tuner %d:\n", vt.index);
> > +			printf("\tName                 : %s\n", vt.name);
> > +			printf("\tCapabilities         : %s\n",\
> > +				tcap2s(vt.capability).c_str());
> > +			if (vt.capability & V4L2_TUNER_CAP_LOW)
> > +				printf("\tFrequency range      : %.1f MHz - %.1f MHz\n",
> > +					 vt.rangelow / 16000.0, vt.rangehigh / 16000.0);
> > +			else
> > +				printf("\tFrequency range      : %.1f MHz - %.1f MHz\n",
> > +					 vt.rangelow / 16.0, vt.rangehigh / 16.0);
> > +			printf("\tSignal strength/AFC  : %d%%/%d\n",\
> > +				(int)((vt.signal / 655.35)+0.5), vt.afc);
> > +			printf("\tCurrent audio mode   : %s\n", audmode2s(vt.audmode));
> > +			printf("\tAvailable subchannels: %s\n",
> > +					rxsubchans2s(vt.rxsubchans).c_str());
> > +		}
> > +	}
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > +	int fd = -1;
> > +
> > +	/* command args */
> > +	struct v4l2_tuner tuner;	/* set_freq/get_freq */
> > +	struct v4l2_modulator modulator;/* set_freq/get_freq */
> > +	struct v4l2_capability vcap;	/* list_cap */
> > +	struct v4l2_frequency vf;	/* get_freq/set_freq */
> > +
> > +	memset(&tuner, 0, sizeof(tuner));
> > +	memset(&modulator, 0, sizeof(modulator));
> > +	memset(&vcap, 0, sizeof(vcap));
> > +	memset(&vf, 0, sizeof(vf));
> > +	strcpy(params.fd_name, "/dev/radio0");
> > +	
> > +	/* define locale for unicode support */
> > +	if (!setlocale(LC_CTYPE, "")) {
> > +		fprintf(stderr, "Can't set the specified locale!\n");
> > +		return 1;
> > +	}
> > +	/* register signal handler for interrupt signal, to exit gracefully */
> > +	signal(SIGINT, signal_handler_interrupt);
> > +
> > +	/* try to parse the command line */
> > +	parse_cl(argc, argv);
> > +	if (params.options[OptHelp]) {
> > +		usage();
> > +		exit(0);
> > +	}
> > +
> > +	/* File Mode: disables all other features, except for RDS decoding */
> > +	if (params.filemode_active) {
> > +		if ((fd = open(params.fd_name, O_RDONLY|O_NONBLOCK)) < 0){
> > +			perror("error opening file");
> > +			exit(1);
> > +		}
> > +		read_rds_from_fd(fd);
> > +		test_close(fd);
> > +		exit(0);
> > +	}
> > +	
> > +	/* Device Mode: open the radio device as read-only and non-blocking */
> > +	if (!params.options[OptSetDevice]) {
> > +		/* check the system for RDS capable devices */
> > +		dev_vec devices = list_devices();
> > +		if (devices.size() == 0) {
> > +			fprintf(stderr, "No RDS-capable device found\n");
> > +			exit(1);
> > +		}
> > +		strncpy(params.fd_name, devices[0].c_str(), 80);
> > +		printf("Using device: %s\n", params.fd_name);
> > +	}
> > +	if ((fd = test_open(params.fd_name, O_RDONLY | O_NONBLOCK)) < 0) {
> > +		fprintf(stderr, "Failed to open %s: %s\n", params.fd_name,
> > +			strerror(errno));
> > +		exit(1);
> > +	}
> > +	doioctl(fd, VIDIOC_QUERYCAP, &vcap);
> > +
> > +	/* Info options */
> > +	if (params.options[OptGetDriverInfo])
> > +		print_driver_info(&vcap);
> > +	/* Set options */
> > +	set_options(fd, vcap.capabilities, &vf, &modulator, &tuner);
> > +	/* Get options */
> > +	get_options(fd, vcap.capabilities, &vf, &modulator, &tuner);
> > +	/* RDS decoding */
> > +	if (params.options[OptReadRds])
> > +		read_rds_from_fd(fd);
> > +
> > +	test_close(fd);
> > +	exit(app_result);
> > +}
> >
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Makefile.am b/Makefile.am
index 6707f5f..47103a1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -18,7 +18,8 @@  SUBDIRS += \
 	utils/v4l2-compliance \
 	utils/v4l2-ctl \
 	utils/v4l2-dbg \
-	utils/v4l2-sysfs-path
+	utils/v4l2-sysfs-path \
+	utils/rds-ctl
 
 if LINUX_OS
 SUBDIRS += \
diff --git a/configure.ac b/configure.ac
index 1d7eb29..1ad99e6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -28,6 +28,7 @@  AC_CONFIG_FILES([Makefile
 	utils/v4l2-sysfs-path/Makefile
 	utils/xc3028-firmware/Makefile
 	utils/qv4l2/Makefile
+	utils/rds-ctl/Makefile
 
 	contrib/freebsd/Makefile
 	contrib/test/Makefile
diff --git a/utils/rds-ctl/Makefile.am b/utils/rds-ctl/Makefile.am
new file mode 100644
index 0000000..9a84257
--- /dev/null
+++ b/utils/rds-ctl/Makefile.am
@@ -0,0 +1,5 @@ 
+bin_PROGRAMS = rds-ctl
+
+rds_ctl_SOURCES = rds-ctl.cpp
+rds_ctl_LDADD = ../../lib/libv4l2/libv4l2.la ../../lib/libv4l2rds/libv4l2rds.la
+
diff --git a/utils/rds-ctl/rds-ctl.cpp b/utils/rds-ctl/rds-ctl.cpp
new file mode 100644
index 0000000..8ddb969
--- /dev/null
+++ b/utils/rds-ctl/rds-ctl.cpp
@@ -0,0 +1,978 @@ 
+/*
+ * rds-ctl.cpp is based on v4l2-ctl.cpp
+ * 
+ * the following applies for all RDS related parts:
+ * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ * Author: Konke Radlow <koradlow@gmail.com>
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+#include <locale.h>
+#include <inttypes.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <config.h>
+#include <signal.h>
+
+#ifdef HAVE_SYS_KLOG_H
+#include <sys/klog.h>
+#endif
+
+#include <linux/videodev2.h>
+#include <libv4l2.h>
+#include <libv4l2rds.h>
+
+#include <list>
+#include <vector>
+#include <map>
+#include <string>
+#include <algorithm>
+
+#define ARRAY_SIZE(arr) ((int)(sizeof(arr) / sizeof((arr)[0])))
+
+typedef std::vector<std::string> dev_vec;
+typedef std::map<std::string, std::string> dev_map;
+
+/* Short option list
+
+   Please keep in alphabetical order.
+   That makes it easier to see which short options are still free.
+
+   In general the lower case is used to set something and the upper
+   case is used to retrieve a setting. */
+enum Option {
+	OptSetDevice = 'd',
+	OptGetDriverInfo = 'D',
+	OptGetFreq = 'F',
+	OptSetFreq = 'f',
+	OptHelp = 'h',
+	OptReadRds = 'R',
+	OptGetTuner = 'T',
+	OptSetTuner = 't',
+	OptUseWrapper = 'w',
+	OptAll = 128,
+	OptFreqSeek,
+	OptListDevices,
+	OptOpenFile,
+	OptPrintBlock,
+	OptSilent,
+	OptTunerIndex,
+	OptVerbose,
+	OptWaitLimit,
+	OptLast = 256
+};
+
+struct ctl_parameters {
+	bool terminate_decoding;
+	char options[OptLast];
+	char fd_name[80];
+	bool filemode_active;
+	double freq;
+	uint32_t wait_limit;
+	uint8_t tuner_index;
+	struct v4l2_hw_freq_seek freq_seek;
+};
+
+static struct ctl_parameters params;
+static int app_result;
+
+static struct option long_options[] = {
+	{"all", no_argument, 0, OptAll},
+	{"device", required_argument, 0, OptSetDevice},
+	{"file", required_argument, 0, OptOpenFile},
+	{"freq-seek", required_argument, 0, OptFreqSeek},
+	{"get-freq", no_argument, 0, OptGetFreq},
+	{"get-tuner", no_argument, 0, OptGetTuner},
+	{"help", no_argument, 0, OptHelp},
+	{"info", no_argument, 0, OptGetDriverInfo},
+	{"list-devices", no_argument, 0, OptListDevices},
+	{"print-block", no_argument, 0, OptPrintBlock},
+	{"read-rds", no_argument, 0, OptReadRds},
+	{"set-freq", required_argument, 0, OptSetFreq},
+	{"tuner-index", required_argument, 0, OptTunerIndex},
+	{"verbose", no_argument, 0, OptVerbose},
+	{"wait-limit", required_argument, 0, OptWaitLimit},
+	{"wrapper", no_argument, 0, OptUseWrapper},
+	{0, 0, 0, 0}
+};
+
+static void usage_hint(void)
+{
+	fprintf(stderr, "Try 'rds-ctl --help' for more information.\n");
+}
+
+static void usage_common(void)
+{
+	printf("\nGeneral/Common options:\n"
+	       "  --all              display all information available\n"
+	       "  -D, --info         show driver info [VIDIOC_QUERYCAP]\n"
+	       "  -d, --device=<dev> use device <dev>\n"
+	       "                     if <dev> is a single digit, then /dev/video<dev> is used\n"
+	       "                     default: checks for RDS-capable devices,\n"
+	       "                     uses device with lowest ID\n"
+	       "  -h, --help         display this help message\n"
+	       "  -w, --wrapper      use the libv4l2 wrapper library.\n"
+	       "  --list-devices     list all v4l radio devices with RDS capabilities\n"
+	       );
+}
+
+static void usage_tuner(void)
+{
+	printf("\nTuner/Modulator options:\n"
+	       "  -F, --get-freq     query the frequency [VIDIOC_G_FREQUENCY]\n"
+	       "  -f, --set-freq=<freq>\n"
+	       "                     set the frequency to <freq> MHz [VIDIOC_S_FREQUENCY]\n"
+	       "  -T, --get-tuner    query the tuner settings [VIDIOC_G_TUNER]\n"
+	       "  --tuner-index=<idx> Use idx as tuner idx for tuner/modulator commands\n"
+	       "  --freq-seek=dir=<0/1>,wrap=<0/1>,spacing=<hz>\n"
+	       "                     perform a hardware frequency seek [VIDIOC_S_HW_FREQ_SEEK]\n"
+	       "                     dir is 0 (seek downward) or 1 (seek upward)\n"
+	       "                     wrap is 0 (do not wrap around) or 1 (wrap around)\n"
+	       "                     spacing sets the seek resolution (use 0 for default)\n"
+	       );
+}
+
+static void usage_rds(void)
+{
+	printf("\nRDS options: \n"
+	       "  -R, --read-rds\n"
+	       "                     enable reading of RDS data from device\n"
+	       "  --file=<path>\n"
+	       "                     open a RDS stream file dump instead of a device\n"
+	       "                     all General and Tuner Options are disabled in this mode\n"
+	       "  --wait-limit=<ms>\n"
+	       "                     defines the maximum wait duration for avaibility of new\n"
+	       "                     RDS data\n"
+	       "                     <default>: 5000ms\n"
+	       "  --print-block\n"
+	       "                     prints all valid RDS fields, whenever a value is updated\n"
+	       "                     instead of printing only updated values\n"
+	       "  --verbose\n"
+	       "                     turn on verbose mode - every received RDS group\n"
+	       "                     will be printed\n"
+	       );
+}
+
+static void usage(void)
+{
+	printf("Usage:\n");
+	usage_common();
+	usage_tuner();
+	usage_rds();
+}
+
+static void signal_handler_interrupt(int signum)
+{
+	fprintf(stderr, "Interrupt received: Terminating program\n");
+	params.terminate_decoding = true;
+}
+
+static int test_open(const char *file, int oflag)
+{
+ 	return params.options[OptUseWrapper] ? v4l2_open(file, oflag) : open(file, oflag);
+}
+
+static int test_close(int fd)
+{
+	return params.options[OptUseWrapper] ? v4l2_close(fd) : close(fd);
+}
+
+static int test_ioctl(int fd, int cmd, void *arg)
+{
+	return params.options[OptUseWrapper] ? v4l2_ioctl(fd, cmd, arg) : ioctl(fd, cmd, arg);
+}
+
+static int doioctl_name(int fd, unsigned long int request, void *parm, const char *name)
+{
+	int retval = test_ioctl(fd, request, parm);
+
+	if (retval < 0) {
+		app_result = -1;
+	}
+	if (params.options[OptSilent]) return retval;
+	if (retval < 0)
+		printf("%s: failed: %s\n", name, strerror(errno));
+	else if (params.options[OptVerbose])
+		printf("%s: ok\n", name);
+
+	return retval;
+}
+
+#define doioctl(n, r, p) doioctl_name(n, r, p, #r)
+
+static const char *audmode2s(int audmode)
+{
+	switch (audmode) {
+		case V4L2_TUNER_MODE_STEREO: return "stereo";
+		case V4L2_TUNER_MODE_LANG1: return "lang1";
+		case V4L2_TUNER_MODE_LANG2: return "lang2";
+		case V4L2_TUNER_MODE_LANG1_LANG2: return "bilingual";
+		case V4L2_TUNER_MODE_MONO: return "mono";
+		default: return "unknown";
+	}
+}
+
+static std::string rxsubchans2s(int rxsubchans)
+{
+	std::string s;
+
+	if (rxsubchans & V4L2_TUNER_SUB_MONO)
+		s += "mono ";
+	if (rxsubchans & V4L2_TUNER_SUB_STEREO)
+		s += "stereo ";
+	if (rxsubchans & V4L2_TUNER_SUB_LANG1)
+		s += "lang1 ";
+	if (rxsubchans & V4L2_TUNER_SUB_LANG2)
+		s += "lang2 ";
+	if (rxsubchans & V4L2_TUNER_SUB_RDS)
+		s += "rds ";
+	return s;
+}
+
+static std::string tcap2s(unsigned cap)
+{
+	std::string s;
+
+	if (cap & V4L2_TUNER_CAP_LOW)
+		s += "62.5 Hz ";
+	else
+		s += "62.5 kHz ";
+	if (cap & V4L2_TUNER_CAP_NORM)
+		s += "multi-standard ";
+	if (cap & V4L2_TUNER_CAP_HWSEEK_BOUNDED)
+		s += "hwseek-bounded ";
+	if (cap & V4L2_TUNER_CAP_HWSEEK_WRAP)
+		s += "hwseek-wrap ";
+	if (cap & V4L2_TUNER_CAP_STEREO)
+		s += "stereo ";
+	if (cap & V4L2_TUNER_CAP_LANG1)
+		s += "lang1 ";
+	if (cap & V4L2_TUNER_CAP_LANG2)
+		s += "lang2 ";
+	if (cap & V4L2_TUNER_CAP_RDS)
+		s += "rds ";
+	if (cap & V4L2_TUNER_CAP_RDS_BLOCK_IO)
+		s += "rds block I/O ";
+	if (cap & V4L2_TUNER_CAP_RDS_CONTROLS)
+		s += "rds control ";
+	return s;
+}
+
+static std::string cap2s(unsigned cap)
+{
+	std::string s;
+
+	if (cap & V4L2_CAP_RDS_CAPTURE)
+		s += "\t\tRDS Capture\n";
+	if (cap & V4L2_CAP_RDS_OUTPUT)
+		s += "\t\tRDS Output\n";
+	if (cap & V4L2_CAP_TUNER)
+		s += "\t\tTuner\n";
+	if (cap & V4L2_CAP_MODULATOR)
+		s += "\t\tModulator\n";
+	if (cap & V4L2_CAP_AUDIO)
+		s += "\t\tAudio\n";
+	if (cap & V4L2_CAP_RADIO)
+		s += "\t\tRadio\n";
+	if (cap & V4L2_CAP_READWRITE)
+		s += "\t\tRead/Write\n";
+	if (cap & V4L2_CAP_ASYNCIO)
+		s += "\t\tAsync I/O\n";
+	if (cap & V4L2_CAP_STREAMING)
+		s += "\t\tStreaming\n";
+	if (cap & V4L2_CAP_DEVICE_CAPS)
+		s += "\t\tDevice Capabilities\n";
+	return s;
+}
+
+static bool is_radio_dev(const char *name)
+{
+	return !memcmp(name, "radio", 5);
+}
+
+static int calc_node_val(const char *s)
+{
+	int n = 0;
+
+	s = strrchr(s, '/') + 1;
+	if (!memcmp(s, "video", 5)) n = 0;
+	else if (!memcmp(s, "radio", 5)) n = 0x100;
+	else if (!memcmp(s, "vbi", 3)) n = 0x200;
+	else if (!memcmp(s, "vtx", 3)) n = 0x300;
+	n += atol(s + (n >= 0x200 ? 3 : 5));
+	return n;
+}
+
+static bool sort_on_device_name(const std::string &s1, const std::string &s2)
+{
+	int n1 = calc_node_val(s1.c_str());
+	int n2 = calc_node_val(s2.c_str());
+
+	return n1 < n2;
+}
+
+static void print_devices(dev_vec files)
+{
+	dev_map cards;
+	int fd = -1;
+	std::string bus_info;
+	struct v4l2_capability vcap;
+	
+	for (dev_vec::iterator iter = files.begin(); 
+		iter != files.end(); ++iter) {
+		fd = open(iter->c_str(), O_RDWR);
+		memset(&vcap, 0, sizeof(vcap));
+		if (fd < 0)
+			continue;
+		doioctl(fd, VIDIOC_QUERYCAP, &vcap);
+		close(fd);
+		bus_info = (const char *)vcap.bus_info;
+	if (cards[bus_info].empty())
+			cards[bus_info] += std::string((char *)vcap.card)\
+				+ " (" + bus_info + "):\n";
+		cards[bus_info] += "\t" + (*iter);
+		cards[bus_info] += "\n";
+	}
+	for (dev_map::iterator iter = cards.begin();
+			iter != cards.end(); ++iter) {
+		printf("%s\n", iter->second.c_str());
+	}
+}
+static dev_vec list_devices(void)
+{
+	DIR *dp;
+	struct dirent *ep;
+	dev_vec files;
+	dev_map links;
+	
+	struct v4l2_tuner vt;
+
+	dp = opendir("/dev");
+	if (dp == NULL) {
+		perror ("Couldn't open the directory");
+		exit(1);
+	}
+	while ((ep = readdir(dp)))
+		if (is_radio_dev(ep->d_name))
+			files.push_back(std::string("/dev/") + ep->d_name);
+	closedir(dp);
+
+	/* Find device nodes which are links to other device nodes */
+	for (dev_vec::iterator iter = files.begin(); \
+			iter != files.end(); ) {
+		char link[64+1];
+		int link_len;
+		std::string target;
+
+		link_len = readlink(iter->c_str(), link, 64);
+		if (link_len < 0) {	/* Not a link or error */
+			iter++;
+			continue;
+		}
+		link[link_len] = '\0';
+
+		/* Only remove from files list if target itself is in list */
+		if (link[0] != '/')	/* Relative link */
+			target = std::string("/dev/");
+		target += link;
+		if (find(files.begin(), files.end(), target) == files.end()) {
+			iter++;
+			continue;
+		}
+		files.erase(iter);
+	}
+	/* Iterate through all devices, and remove all non-accessible devices
+	 * and all devices that don't offer the RDS_BLOCK_IO capability */
+	std::sort(files.begin(), files.end(), sort_on_device_name);
+	for (dev_vec::iterator iter = files.begin(); \
+			iter != files.end(); ++iter) {
+		int fd = open(iter->c_str(), O_RDONLY | O_NONBLOCK);
+		std::string bus_info;
+
+		if (fd < 0) {
+			iter = files.erase(iter);
+			continue;
+		}
+		memset(&vt, 0, sizeof(vt));
+		if (doioctl(fd, VIDIOC_G_TUNER, &vt) != 0) {
+			close(fd);
+			iter = files.erase(iter);
+			continue;
+		}
+		/* remove device if it doesn't support rds block I/O */
+		if (!vt.capability & V4L2_TUNER_CAP_RDS_BLOCK_IO)
+			iter = files.erase(iter);
+	}
+	return files;
+}
+
+static int parse_subopt(char **subs, const char * const *subopts, char **value)
+{
+	int opt = getsubopt(subs, (char * const *)subopts, value);
+
+	if (opt == -1) {
+		fprintf(stderr, "Invalid suboptions specified\n");
+		return -1;
+	}
+	if (value == NULL) {
+		fprintf(stderr, "No value given to suboption <%s>\n",
+				subopts[opt]);
+		return -1;
+	}
+	return opt;
+}
+
+static void parse_freq_seek(char *optarg, struct v4l2_hw_freq_seek &seek)
+{
+	char *value;
+	char *subs = optarg;
+
+	while (*subs != '\0') {
+		static const char *const subopts[] = {
+			"dir",
+			"wrap",
+			"spacing",
+			NULL
+		};
+
+		switch (parse_subopt(&subs, subopts, &value)) {
+		case 0:
+			seek.seek_upward = strtol(value, 0L, 0);
+			break;
+		case 1:
+			seek.wrap_around = strtol(value, 0L, 0);
+			break;
+		case 2:
+			seek.spacing = strtol(value, 0L, 0);
+			break;
+		default:
+			usage_tuner();
+			exit(1);
+		}
+	}
+}
+
+static void print_byte(char byte, bool linebreak)
+{
+	int count=8;
+	printf(" ");
+	while(count--)
+	{
+		printf("%d", ( byte & 128 ) ? 1 : 0 );
+		byte <<= 1;
+	} 
+	if(linebreak)  printf("\n");
+}
+
+static void print_rds_group(const struct v4l2_rds_group *rds_group)
+{
+	printf("\nComplete RDS data group received \n");
+	printf("PI: %04x\n", rds_group->pi);
+	printf("Group: %u%c \n", rds_group->group_id, rds_group->group_version);
+	printf("Traffic Program: %s \n", (rds_group->traffic_prog)? "yes" : "no");
+	printf("Program Type: %u \n", rds_group->pty);
+	printf("Data B:");
+	print_byte(rds_group->data_b_lsb, true);
+	printf("Data C:");
+	print_byte(rds_group->data_c_msb, false);
+	print_byte(rds_group->data_c_lsb, true);
+	printf("Data D:");
+	print_byte(rds_group->data_d_msb, false);
+	print_byte(rds_group->data_d_lsb, true);
+	printf("\n");
+}
+
+static void print_decoder_info(uint8_t di)
+{
+	printf("\nDI: "); 
+	if (di & V4L2_RDS_FLAG_STEREO)
+		printf("Stereo, ");
+	else
+		printf("Mono, ");
+	if (di & V4L2_RDS_FLAG_ARTIFICIAL_HEAD)
+		printf("Artificial Head, ");
+	else
+		printf("No Artificial Head, ");
+	if (di & V4L2_RDS_FLAG_COMPRESSED)
+		printf("Compressed");
+	else
+		printf("Not Compressed");
+}
+
+static void print_rds_statistics(struct v4l2_rds_statistics *statistics)
+{
+	printf("\n\nRDS Statistics: \n");
+	printf("received blocks / received groups: %u / %u\n",\
+		statistics->block_cnt, statistics->group_cnt);
+
+	float block_error_percentage = \
+	((float)statistics->block_error_cnt / statistics->block_cnt) * 100.0;
+	printf("block errors / group errors: %u (%3.2f%%) / %u \n",\
+		statistics->block_error_cnt,
+		block_error_percentage, statistics->group_error_cnt);
+	float block_corrected_percentage = \
+	((float)statistics->block_corrected_cnt / statistics->block_cnt) * 100.0;
+	printf("corrected blocks: %u (%3.2f%%)\n",\
+		statistics->block_corrected_cnt, block_corrected_percentage);
+	for(int i=0; i<16; i++)
+		printf("Group %02d: %u\n", i, statistics->group_type_cnt[i]);
+}
+
+static void print_rds_af(struct v4l2_rds_af_set *af_set)
+{
+	int counter = 0;
+
+	printf("\nAnnounced AFs: %u", af_set->announced_af);
+	for (int i = 0; i < af_set->size && i < af_set->announced_af; i++, counter++) {
+		if (af_set->af[i] >= 87500000 ) {
+			printf("\nAF%02d: %.1fMHz", counter, af_set->af[i] / 1000000.0);
+			continue;
+		}
+		printf("\nAF%02d: %.1fkHz", counter, af_set->af[i] / 1000.0);
+	}
+}
+
+static void print_rds_pi(const struct v4l2_rds *handle)
+{
+	printf("\nArea Coverage: %s", v4l2_rds_get_coverage_str(handle));
+}
+
+static void print_rds_data(struct v4l2_rds *handle, uint32_t updated_fields)
+{
+	if (params.options[OptPrintBlock])
+		updated_fields = 0xFFFFFFFF;
+
+	if (updated_fields & V4L2_RDS_PI && 
+			handle->valid_fields & V4L2_RDS_PI) {
+		printf("\nPI: %04x", handle->pi);
+		print_rds_pi(handle);
+	}
+
+	if (updated_fields & V4L2_RDS_PS &&
+			handle->valid_fields & V4L2_RDS_PS) {
+		printf("\nPS: ");
+		for (int i = 0; i < 8; ++i) {
+			/* filter out special characters */
+			if (handle->ps[i] >= 0x20 && handle->ps[i] <= 0x7E)
+				printf("%lc",handle->ps[i]);
+			else
+				printf(" ");
+		}
+	}
+
+	if (updated_fields & V4L2_RDS_PTY && handle->valid_fields & V4L2_RDS_PTY)
+		printf("\nPTY: %0u -> %s",handle->pty,\
+			v4l2_rds_get_pty_str(handle));
+
+	if (updated_fields & V4L2_RDS_PTYN && handle->valid_fields & V4L2_RDS_PTYN) {
+		printf("\nPTYN: ");
+		for (int i = 0; i < 8; ++i) {
+			/* filter out special characters */
+			if (handle->ptyn[i] >= 0x20 && handle->ptyn[i] <= 0x7E)
+				printf("%lc",handle->ptyn[i]);
+			else 
+				printf(" ");
+		}
+	}
+
+	if(updated_fields & V4L2_RDS_RT && handle->valid_fields & V4L2_RDS_RT){
+		printf("\nRT: ");
+		for (int i = 0; i < handle->rt_length; ++i) {
+			/* filter out special characters */
+			if (handle->rt[i] >= 0x20 && handle->rt[i] <= 0x7E)
+				printf("%lc",handle->rt[i]);
+			else 
+				printf(" ");
+		}
+	}
+
+	if (updated_fields & V4L2_RDS_TP && handle->valid_fields & V4L2_RDS_TP)
+		printf("\nTP: %s  TA: %s", (handle->tp)? "yes":"no",\
+			handle->ta? "yes":"no");
+	if (updated_fields & V4L2_RDS_MS && handle->valid_fields & V4L2_RDS_MS)
+		printf("\nMS Flag: %s", (handle->ms)? "Music" : "Speech");
+	if (updated_fields & V4L2_RDS_ECC && handle->valid_fields & V4L2_RDS_ECC)
+		printf("\nECC: %X%x, Country: %u -> %s",\
+		handle->ecc >> 4, handle->ecc & 0x0f, handle->pi >> 12, \
+		v4l2_rds_get_country_str(handle));
+	if (updated_fields & V4L2_RDS_LC && handle->valid_fields & V4L2_RDS_LC)
+		printf("\nLanguage: %u -> %s ", handle->lc,\
+		v4l2_rds_get_language_str(handle));
+	if (updated_fields & V4L2_RDS_DI && handle->valid_fields & V4L2_RDS_DI)
+		print_decoder_info(handle->di);
+	if (updated_fields & V4L2_RDS_ODA && 
+			handle->decode_information & V4L2_RDS_ODA) {
+		for (int i = 0; i < handle->rds_oda.size; ++i)
+			printf("\nODA Group: %02u%c, AID: %08x",handle->rds_oda.oda[i].group_id, 
+			handle->rds_oda.oda[i].group_version, handle->rds_oda.oda[i].aid);
+	}
+	if (updated_fields & V4L2_RDS_AF && handle->valid_fields & V4L2_RDS_AF)
+		print_rds_af(&handle->rds_af);
+	if (params.options[OptPrintBlock])
+		printf("\n");
+}
+
+static void read_rds(struct v4l2_rds *handle, const int fd, const int wait_limit)
+{
+	int byte_cnt = 0;
+	int error_cnt = 0;
+	uint32_t updated_fields = 0x00;
+	struct v4l2_rds_data rds_data; /* read buffer for rds blocks */
+
+	while(!params.terminate_decoding){
+		memset(&rds_data, 0, sizeof(rds_data));
+		if ((byte_cnt=read(fd, &rds_data, 3)) != 3) {
+			if(byte_cnt == 0){
+				printf("\nEnd of input file reached \n");
+				break;
+			} else if(++error_cnt > 2) {
+				fprintf(stderr, "\nError reading from "\
+					"device (no RDS data available)\n");
+				break;
+			}
+			/* wait for new data to arrive: transmission of 1 
+			 * group takes ~88.7ms */
+			usleep(wait_limit * 1000);
+		}
+		else if (byte_cnt == 3) {
+			error_cnt = 0;
+			/* true if a new group was decoded */
+			if ((updated_fields = v4l2_rds_add(handle, &rds_data))) {
+				print_rds_data(handle, updated_fields);
+				if (params.options[OptVerbose])
+					 print_rds_group(v4l2_rds_get_group(handle));
+			}
+		}
+	}
+}
+
+static void read_rds_from_fd(const int fd)
+{
+	struct v4l2_rds *rds_handle;
+
+	/* create an rds handle for the current device */
+	if (!(rds_handle = v4l2_rds_create(true))) {
+		fprintf(stderr, "Failed to init RDS lib: %s\n", strerror(errno));
+		exit(1);
+	}
+
+	/* try to receive and decode RDS data */
+	read_rds(rds_handle, fd, params.wait_limit);
+	print_rds_statistics(&rds_handle->rds_statistics);
+
+	v4l2_rds_destroy(rds_handle);
+}
+
+static int parse_cl(int argc, char **argv)
+{
+	int i = 0;
+	int idx = 0;
+	int opt = 0;
+	char short_options[26 * 2 * 2 + 1];
+
+	if (argc == 1) {
+		usage_hint();
+		exit(1);
+	}
+	for (i = 0; long_options[i].name; i++) {
+		if (!isalpha(long_options[i].val))
+			continue;
+		short_options[idx++] = long_options[i].val;
+		if (long_options[i].has_arg == required_argument)
+			short_options[idx++] = ':';
+	}
+	while (1) {
+		// TODO: remove option_index ?
+		int option_index = 0;
+
+		short_options[idx] = 0;
+		opt = getopt_long(argc, argv, short_options,
+				 long_options, &option_index);
+		if (opt == -1)
+			break;
+
+		params.options[(int)opt] = 1;
+		switch (opt) {
+		case OptSetDevice:
+			strncpy(params.fd_name, optarg, 80);
+			if (optarg[0] >= '0' && optarg[0] <= '9' && optarg[1] == 0) {
+				char newdev[20];
+				char dev = optarg[0];
+
+				sprintf(newdev, "/dev/radio%c", dev);
+				strncpy(params.fd_name, newdev, 20);
+			}
+			break;
+		case OptSetFreq:
+			params.freq = strtod(optarg, NULL);
+			break;
+		case OptListDevices:
+			print_devices(list_devices());
+			break;
+		case OptFreqSeek:
+			parse_freq_seek(optarg, params.freq_seek);
+			break;
+		case OptTunerIndex:
+			params.tuner_index = strtoul(optarg, NULL, 0);
+			break;
+		case OptOpenFile:
+		{	
+			if (access(optarg, F_OK) != -1) {
+				params.filemode_active = true;
+				strncpy(params.fd_name, optarg, 80);
+			} else {
+				fprintf(stderr, "Unable to open file: %s\n", optarg);
+				return -1;
+			} 
+			/* enable the read-rds option by default for convenience */
+			params.options[OptReadRds] = 1;
+			break;
+		}
+		case OptWaitLimit:
+			params.wait_limit = strtoul(optarg, NULL, 0);
+			break;
+		case ':':
+			fprintf(stderr, "Option '%s' requires a value\n",
+				argv[optind]);
+			usage_hint();
+			return 1;
+		case '?':
+			if (argv[optind])
+				fprintf(stderr, "Unknown argument '%s'\n", argv[optind]);
+			usage_hint();
+			return 1;
+		}
+	}
+	if (optind < argc) {
+		printf("unknown arguments: ");
+		while (optind < argc)
+			printf("%s ", argv[optind++]);
+		printf("\n");
+		usage_hint();
+		return 1;
+	}
+	if (params.options[OptAll]) {
+		params.options[OptGetDriverInfo] = 1;	
+		params.options[OptGetFreq] = 1;
+		params.options[OptGetTuner] = 1;
+		params.options[OptSilent] = 1;
+	}
+
+	return 0;
+}
+
+static void print_driver_info(const struct v4l2_capability *vcap)
+{
+
+	printf("Driver Info (%susing libv4l2):\n",
+			params.options[OptUseWrapper] ? "" : "not ");
+	printf("\tDriver name   : %s\n", vcap->driver);
+	printf("\tCard type     : %s\n", vcap->card);
+	printf("\tBus info      : %s\n", vcap->bus_info);
+	printf("\tDriver version: %d.%d.%d\n",
+			vcap->version >> 16,
+			(vcap->version >> 8) & 0xff,
+			vcap->version & 0xff);
+	printf("\tCapabilities  : 0x%08X\n", vcap->capabilities);
+	printf("%s", cap2s(vcap->capabilities).c_str());
+	if (vcap->capabilities & V4L2_CAP_DEVICE_CAPS) {
+		printf("\tDevice Caps   : 0x%08X\n", vcap->device_caps);
+		printf("%s", cap2s(vcap->device_caps).c_str());
+	}
+}
+
+static void set_options(const int fd, const int capabilities, struct v4l2_frequency *vf,\
+			struct v4l2_modulator *modulator, struct v4l2_tuner *tuner)
+{
+	int mode = -1;			/* set audio mode */
+
+	if (params.options[OptSetFreq]) {
+		double fac = 16;
+
+		if (capabilities & V4L2_CAP_MODULATOR) {
+			vf->type = V4L2_TUNER_RADIO;
+			modulator->index = params.tuner_index;
+			if (doioctl(fd, VIDIOC_G_MODULATOR, modulator) == 0) {
+				fac = (modulator->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
+			}
+		} else {
+			vf->type = V4L2_TUNER_ANALOG_TV;
+			tuner->index = params.tuner_index;
+			if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) {
+				fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
+				vf->type = tuner->type;
+			}
+		}
+		vf->tuner = params.tuner_index;
+		vf->frequency = __u32(params.freq * fac);
+		if (doioctl(fd, VIDIOC_S_FREQUENCY, vf) == 0)
+			printf("Frequency for tuner %d set to %d (%f MHz)\n",
+				   vf->tuner, vf->frequency, vf->frequency / fac);
+	}
+	
+	if (params.options[OptSetTuner]) {
+		struct v4l2_tuner vt;
+
+		memset(&vt, 0, sizeof(struct v4l2_tuner));
+		vt.index = params.tuner_index;
+		if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) {
+			if (mode != -1)
+				vt.audmode = mode;
+			doioctl(fd, VIDIOC_S_TUNER, &vt);
+		}
+	}
+
+	if (params.options[OptFreqSeek]) {
+		params.freq_seek.tuner = params.tuner_index;
+		params.freq_seek.type = V4L2_TUNER_RADIO;
+		doioctl(fd, VIDIOC_S_HW_FREQ_SEEK, &params.freq_seek);
+	}
+}
+
+static void get_options(const int fd, const int capabilities, struct v4l2_frequency *vf,\
+			struct v4l2_modulator *modulator, struct v4l2_tuner *tuner)
+{
+	if (params.options[OptGetFreq]) {
+		double fac = 16;
+
+		if (capabilities & V4L2_CAP_MODULATOR) {
+			vf->type = V4L2_TUNER_RADIO;
+			modulator->index = params.tuner_index;
+			if (doioctl(fd, VIDIOC_G_MODULATOR, modulator) == 0)
+				fac = (modulator->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
+		} else {
+			vf->type = V4L2_TUNER_ANALOG_TV;
+			tuner->index = params.tuner_index;
+			if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) {
+				fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16;
+				vf->type = tuner->type;
+			}
+		}
+		vf->tuner = params.tuner_index;
+		if (doioctl(fd, VIDIOC_G_FREQUENCY, vf) == 0)
+			printf("Frequency for tuner %d: %d (%f MHz)\n",
+				   vf->tuner, vf->frequency, vf->frequency / fac);
+	}
+
+	if (params.options[OptGetTuner]) {
+		struct v4l2_tuner vt;
+
+		memset(&vt, 0, sizeof(struct v4l2_tuner));
+		vt.index = params.tuner_index;
+		if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) {
+			printf("Tuner %d:\n", vt.index);
+			printf("\tName                 : %s\n", vt.name);
+			printf("\tCapabilities         : %s\n",\
+				tcap2s(vt.capability).c_str());
+			if (vt.capability & V4L2_TUNER_CAP_LOW)
+				printf("\tFrequency range      : %.1f MHz - %.1f MHz\n",
+					 vt.rangelow / 16000.0, vt.rangehigh / 16000.0);
+			else
+				printf("\tFrequency range      : %.1f MHz - %.1f MHz\n",
+					 vt.rangelow / 16.0, vt.rangehigh / 16.0);
+			printf("\tSignal strength/AFC  : %d%%/%d\n",\
+				(int)((vt.signal / 655.35)+0.5), vt.afc);
+			printf("\tCurrent audio mode   : %s\n", audmode2s(vt.audmode));
+			printf("\tAvailable subchannels: %s\n",
+					rxsubchans2s(vt.rxsubchans).c_str());
+		}
+	}
+}
+
+int main(int argc, char **argv)
+{
+	int fd = -1;
+
+	/* command args */
+	struct v4l2_tuner tuner;	/* set_freq/get_freq */
+	struct v4l2_modulator modulator;/* set_freq/get_freq */
+	struct v4l2_capability vcap;	/* list_cap */
+	struct v4l2_frequency vf;	/* get_freq/set_freq */
+
+	memset(&tuner, 0, sizeof(tuner));
+	memset(&modulator, 0, sizeof(modulator));
+	memset(&vcap, 0, sizeof(vcap));
+	memset(&vf, 0, sizeof(vf));
+	strcpy(params.fd_name, "/dev/radio0");
+	
+	/* define locale for unicode support */
+	if (!setlocale(LC_CTYPE, "")) {
+		fprintf(stderr, "Can't set the specified locale!\n");
+		return 1;
+	}
+	/* register signal handler for interrupt signal, to exit gracefully */
+	signal(SIGINT, signal_handler_interrupt);
+
+	/* try to parse the command line */
+	parse_cl(argc, argv);
+	if (params.options[OptHelp]) {
+		usage();
+		exit(0);
+	}
+
+	/* File Mode: disables all other features, except for RDS decoding */
+	if (params.filemode_active) {
+		if ((fd = open(params.fd_name, O_RDONLY|O_NONBLOCK)) < 0){
+			perror("error opening file");
+			exit(1);
+		}
+		read_rds_from_fd(fd);
+		test_close(fd);
+		exit(0);
+	}
+	
+	/* Device Mode: open the radio device as read-only and non-blocking */
+	if (!params.options[OptSetDevice]) {
+		/* check the system for RDS capable devices */
+		dev_vec devices = list_devices();
+		if (devices.size() == 0) {
+			fprintf(stderr, "No RDS-capable device found\n");
+			exit(1);
+		}
+		strncpy(params.fd_name, devices[0].c_str(), 80);
+		printf("Using device: %s\n", params.fd_name);
+	}
+	if ((fd = test_open(params.fd_name, O_RDONLY | O_NONBLOCK)) < 0) {
+		fprintf(stderr, "Failed to open %s: %s\n", params.fd_name,
+			strerror(errno));
+		exit(1);
+	}
+	doioctl(fd, VIDIOC_QUERYCAP, &vcap);
+
+	/* Info options */
+	if (params.options[OptGetDriverInfo])
+		print_driver_info(&vcap);
+	/* Set options */
+	set_options(fd, vcap.capabilities, &vf, &modulator, &tuner);
+	/* Get options */
+	get_options(fd, vcap.capabilities, &vf, &modulator, &tuner);
+	/* RDS decoding */
+	if (params.options[OptReadRds])
+		read_rds_from_fd(fd);
+
+	test_close(fd);
+	exit(app_result);
+}