linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 0/2] Add support for RDS decoding
@ 2012-07-25 17:43 Konke Radlow
  2012-07-25 17:44 ` [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com> Konke Radlow
  2012-07-26 18:41 ` [RFC PATCH 0/2] Add support for RDS decoding Gregor Jasny
  0 siblings, 2 replies; 18+ messages in thread
From: Konke Radlow @ 2012-07-25 17:43 UTC (permalink / raw)
  To: linux-media; +Cc: hverkuil, hdegoede

Hello,

Over the last couple of weeks I have been working on a library that adds
RDS decoding support to the v4l-utils repository. It currently supports
the core RDS standard but no advanced features yet like ODA (TMC).

I also wrote a control application that can be used to test the library
with any RDS-capable v4l device. This application is based on the v4l2-ctl
tool and many of the basic options have been taken over to ease the usage.

By default the tool will print all RDS fields that it was able to decode to the
std output.

The latest version of the code can always be found in my github repository:
https://github.com/koradlow/v4l2-rds-ctl

You can also find a tool that was created to generate test RDS data with 
the pcimax-3000+ card under Linux there. This program is functional,
but still a early version, and the functionality will be improved in the 
future: https://github.com/koradlow/pcimax-ctl

Upcoming:
TMC support (Traffic Message Channel)

This work is being done as part of a summerjob I`m doing at Cisco Systems
Norway with Hans Verkuil as my mentor.

Comments and remarks are very welcome.

Regards,
Konke 


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-25 17:43 [RFC PATCH 0/2] Add support for RDS decoding Konke Radlow
@ 2012-07-25 17:44 ` Konke Radlow
  2012-07-25 17:44   ` [RFC PATCH 2/2] Initial version of RDS Control utility " Konke Radlow
                     ` (3 more replies)
  2012-07-26 18:41 ` [RFC PATCH 0/2] Add support for RDS decoding Gregor Jasny
  1 sibling, 4 replies; 18+ messages in thread
From: Konke Radlow @ 2012-07-25 17:44 UTC (permalink / raw)
  To: linux-media; +Cc: hverkuil, hdegoede

---
 Makefile.am                     |    3 +-
 configure.ac                    |   10 +-
 lib/libv4l2rds/Makefile.am      |   11 +
 lib/libv4l2rds/libv4l2rds.c     |  871 +++++++++++++++++++++++++++++++++++++++
 lib/libv4l2rds/libv4l2rds.pc.in |   11 +
 5 files changed, 904 insertions(+), 2 deletions(-)
 create mode 100644 lib/libv4l2rds/Makefile.am
 create mode 100644 lib/libv4l2rds/libv4l2rds.c
 create mode 100644 lib/libv4l2rds/libv4l2rds.pc.in

diff --git a/Makefile.am b/Makefile.am
index b7b356c..6707f5f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,7 +5,8 @@ SUBDIRS = \
 	lib/libv4lconvert \
 	lib/libv4l2 \
 	lib/libv4l1 \
-	lib/libdvbv5
+	lib/libdvbv5 \
+	lib/libv4l2rds
 
 if WITH_V4LUTILS
 SUBDIRS += \
diff --git a/configure.ac b/configure.ac
index 8ddcc9d..1d7eb29 100644
--- a/configure.ac
+++ b/configure.ac
@@ -14,6 +14,7 @@ AC_CONFIG_FILES([Makefile
 	lib/libv4l2/Makefile
 	lib/libv4l1/Makefile
 	lib/libdvbv5/Makefile
+	lib/libv4l2rds/Makefile
 
 	utils/libv4l2util/Makefile
 	utils/libmedia_dev/Makefile
@@ -36,6 +37,7 @@ AC_CONFIG_FILES([Makefile
 	lib/libv4l1/libv4l1.pc
 	lib/libv4l2/libv4l2.pc
 	lib/libdvbv5/libdvbv5.pc
+	lib/libv4l2rds/libv4l2rds.pc
 ])
 
 AM_INIT_AUTOMAKE([1.9 no-dist-gzip dist-bzip2 -Wno-portability]) # 1.10 is needed for target_LIBTOOLFLAGS
@@ -146,13 +148,17 @@ AC_ARG_WITH(libv4l2subdir, AS_HELP_STRING(--with-libv4l2subdir=DIR,set libv4l2 l
 AC_ARG_WITH(libv4lconvertsubdir, AS_HELP_STRING(--with-libv4lconvertsubdir=DIR,set libv4lconvert library subdir [default=libv4l]),
    libv4lconvertsubdir=$withval, libv4lconvertsubdir="libv4l")
 
+AC_ARG_WITH(libv4l2rdssubdir, AS_HELP_STRING(--with-libv4l2rdssubdir=DIR,set libv4l2rds library subdir [default=libv4l]),
+   libv4l2rdssubdir=$withval, libv4l2rdssubdir="libv4l")
+
 AC_ARG_WITH(udevdir, AS_HELP_STRING(--with-udevdir=DIR,set udev directory [default=/lib/udev]),
    udevdir=$withval, udevdir="/lib/udev")
-
+   
 libv4l1privdir="$libdir/$libv4l1subdir"
 libv4l2privdir="$libdir/$libv4l2subdir"
 libv4l2plugindir="$libv4l2privdir/plugins"
 libv4lconvertprivdir="$libdir/$libv4lconvertsubdir"
+libv4l2rdsprivdir="$libdir/$libv4l2rdssubdir"
 
 keytablesystemdir="$udevdir/rc_keymaps"
 keytableuserdir="$sysconfdir/rc_keymaps"
@@ -166,6 +172,7 @@ AC_SUBST(libv4lconvertprivdir)
 AC_SUBST(keytablesystemdir)
 AC_SUBST(keytableuserdir)
 AC_SUBST(udevrulesdir)
+AC_SUBST(libv4l2rdsprivdir)
 AC_SUBST(pkgconfigdir)
 
 AC_DEFINE_UNQUOTED([V4L_UTILS_VERSION], ["$PACKAGE_VERSION"], [v4l-utils version string])
@@ -173,6 +180,7 @@ AC_DEFINE_DIR([LIBV4L1_PRIV_DIR], [libv4l1privdir], [libv4l1 private lib directo
 AC_DEFINE_DIR([LIBV4L2_PRIV_DIR], [libv4l2privdir], [libv4l2 private lib directory])
 AC_DEFINE_DIR([LIBV4L2_PLUGIN_DIR], [libv4l2plugindir], [libv4l2 plugin directory])
 AC_DEFINE_DIR([LIBV4LCONVERT_PRIV_DIR], [libv4lconvertprivdir], [libv4lconvert private lib directory])
+AC_DEFINE_DIR([LIBV4L2RDS_PRIV_DIR], [libv4l2rdsprivdir], [libv4l2rds private lib directory])
 AC_DEFINE_DIR([IR_KEYTABLE_SYSTEM_DIR], [keytablesystemdir], [ir-keytable preinstalled tables directory])
 AC_DEFINE_DIR([IR_KEYTABLE_USER_DIR], [keytableuserdir], [ir-keytable user defined tables directory])
 
diff --git a/lib/libv4l2rds/Makefile.am b/lib/libv4l2rds/Makefile.am
new file mode 100644
index 0000000..5796890
--- /dev/null
+++ b/lib/libv4l2rds/Makefile.am
@@ -0,0 +1,11 @@
+if WITH_LIBV4L
+lib_LTLIBRARIES = libv4l2rds.la
+include_HEADERS = ../include/libv4l2rds.h
+pkgconfig_DATA = libv4l2rds.pc
+else
+noinst_LTLIBRARIES = libv4l2rds.la
+endif
+
+libv4l2rds_la_SOURCES = libv4l2rds.c
+libv4l2rds_la_CPPFLAGS = -fvisibility=hidden $(ENFORCE_LIBV4L_STATIC) -std=c99
+libv4l2rds_la_LDFLAGS = -version-info 0 -lpthread $(DLOPEN_LIBS) $(ENFORCE_LIBV4L_STATIC)
diff --git a/lib/libv4l2rds/libv4l2rds.c b/lib/libv4l2rds/libv4l2rds.c
new file mode 100644
index 0000000..0bacaa2
--- /dev/null
+++ b/lib/libv4l2rds/libv4l2rds.c
@@ -0,0 +1,871 @@
+/*
+ * 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 <linux/videodev2.h>
+
+#include "../include/libv4l2rds.h"
+
+/* struct to encapsulate the private state information of the decoding process */
+struct rds_private_state {
+	/* v4l2_rds has to be in first position, to allow typecasting between
+	 * v4l2_rds and rds_private_state pointers */
+	struct v4l2_rds handle;
+
+	uint8_t decode_state;
+	uint16_t new_pi;
+	uint8_t new_ps[8];
+	uint8_t new_ps_valid[8];
+	uint8_t new_pty;
+	uint8_t new_ptyn[2][4];
+	bool new_ptyn_valid[2];
+	uint8_t new_rt[64];
+	uint8_t new_rt_pos;
+	uint8_t new_ecc;
+	uint8_t new_lc;
+	uint8_t new_di;
+	uint8_t new_di_pos;
+	struct v4l2_rds_group rds_group;
+	struct v4l2_rds_data rds_data_raw[4];
+};
+
+/* states of the RDS block into group decoding state machine */
+enum rds_state {
+	RDS_EMPTY,
+	RDS_A_RECEIVED,
+	RDS_B_RECEIVED,
+	RDS_C_RECEIVED,
+};
+
+static inline uint8_t set_bit(uint8_t input, uint8_t bitmask, bool bitvalue)
+{
+	return bitvalue ? input | bitmask : input & ~bitmask;
+}
+
+/* rds_decode_a-d(..): group of functions to decode different RDS blocks
+ * into the RDS group that's currently being received
+ *
+ * block A of RDS group always contains PI code of program */
+static uint32_t rds_decode_a(struct rds_private_state *priv_state, struct v4l2_rds_data *rds_data)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+	uint32_t updated_fields = 0x00;
+	uint16_t pi = (rds_data->msb << 8) | rds_data->lsb;
+
+	/* data in RDS group is uninterpreted */
+	priv_state->rds_group.pi = pi;
+
+	/* compare PI values to detect PI update (Channel Switch)
+	 * --> new PI is only accepted, if the same PI is received
+	 * at least 2 times in a row */
+	if (pi != handle->pi && pi == priv_state->new_pi) {
+		handle->pi = pi;
+		handle->valid_fields |= V4L2_RDS_PI;
+		updated_fields |= V4L2_RDS_PI;
+	} else if (pi != handle->pi && pi != priv_state->new_pi) {
+		priv_state->new_pi = pi;
+	}
+
+	return updated_fields;
+}
+
+/* block B of RDS group always contains Group Type Code, Group Type information
+ * Traffic Program Code and Program Type Code as well as 5 bits of Group Type
+ * depending information */
+static uint32_t rds_decode_b(struct rds_private_state *priv_state, struct v4l2_rds_data *rds_data)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+	struct v4l2_rds_group *grp = &priv_state->rds_group;
+	uint32_t updated_fields = 0x00;
+
+	/* bits 12-15 (4-7 of msb) contain the Group Type Code */
+	grp->group_id = rds_data->msb >> 4 ;
+
+	/* bit 11 (3 of msb) defines Group Type info: 0 = A, 1 = B */
+	grp->group_version = (rds_data->msb & 0x08) ? 'B' : 'A';
+
+	/* bit 10 (2 of msb) defines Traffic program Code */
+	grp->traffic_prog = rds_data->msb & 0x04;
+	if (handle->tp != grp->traffic_prog) {
+		handle->tp = grp->traffic_prog;
+		updated_fields |= V4L2_RDS_TP;
+	}
+	handle->valid_fields |= V4L2_RDS_TP;
+
+	/* bits 0-4 contains Group Type depending information */
+	grp->data_b_lsb = (rds_data->lsb & 0x1f);
+
+	/* bits 5-9 contain the PTY code */
+	grp->pty = (rds_data->msb << 3) | (rds_data->lsb >> 5);
+	grp->pty &= 0x1f; /* mask out 3 irrelevant bits */
+	/* only accept new PTY if same PTY is received twice in a row
+	 * and filter out cases where the PTY is already known */
+	if (handle->pty == grp->pty) {
+		priv_state->new_pty = grp->pty;
+		return updated_fields;
+	}
+
+	if (priv_state->new_pty == grp->pty) {
+		handle->pty = priv_state->new_pty;
+		updated_fields |= V4L2_RDS_PTY;
+		handle->valid_fields |= V4L2_RDS_PTY;
+	} else {
+		priv_state->new_pty = grp->pty;
+	}
+
+	return updated_fields;
+}
+
+/* block C of RDS group contains either data or the PI code, depending
+ * on the Group Type - store the raw data for later decoding */
+static void rds_decode_c(struct rds_private_state *priv_state, struct v4l2_rds_data *rds_data)
+{
+	struct v4l2_rds_group *grp = &priv_state->rds_group;
+
+	grp->data_c_msb = rds_data->msb;
+	grp->data_c_lsb = rds_data->lsb;
+	/* we could decode the PI code here, because we already know if the
+	 * group is of type A or B, but it doesn't give any advantage because
+	 * we only get here after the PI code has been decoded in the first
+	 * state of the state machine */
+}
+
+/* block D of RDS group contains data - store the raw data for later decoding */
+static void rds_decode_d(struct rds_private_state *priv_state, struct v4l2_rds_data *rds_data)
+{
+	struct v4l2_rds_group *grp = &priv_state->rds_group;
+
+	grp->data_d_msb = rds_data->msb;
+	grp->data_d_lsb = rds_data->lsb;
+}
+
+static bool rds_add_oda(struct rds_private_state *priv_state, struct v4l2_rds_oda oda)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+
+	/* check if there was already an ODA announced for this group type */
+	for (int i = 0; i < handle->rds_oda.size; i++) {
+		if (handle->rds_oda.oda[i].group_id == oda.group_id)
+			/* update the AID for this ODA */
+			handle->rds_oda.oda[i].aid = oda.aid;
+			return false;
+	}
+	/* add the new ODA */
+	if (handle->rds_oda.size >= MAX_ODA_CNT)
+		return false;
+	handle->rds_oda.oda[handle->rds_oda.size++] = oda;
+	return true;
+}
+
+/* add a new AF to the list, if it doesn't exist yet */
+static bool rds_add_af_to_list(struct v4l2_rds_af_set *af_set, uint8_t af, bool is_vhf)
+{
+	uint32_t freq = 0;
+
+	/* AF0 -> "Not to be used" */
+	if (af == 0)
+		return false;
+
+	/* calculate the AF values in HZ */
+	if (is_vhf)
+		freq = 87500000 + af * 100000;
+	else if (freq <= 15)
+		freq = 152000 + af * 9000;
+	else
+		freq = 531000 + af * 9000;
+
+	/* prevent buffer overflows */
+	if (af_set->size >= MAX_AF_CNT || af_set->size >= af_set->announced_af)
+		return false;
+	/* check if AF already exists */
+	for (int i = 0; i < af_set->size; i++) {
+		if (af_set->af[i] == freq)
+			return false;
+	}
+	/* it's a new AF, add it to the list */
+	af_set->af[(af_set->size)++] = freq;
+	return true;
+}
+
+static bool rds_add_af(struct rds_private_state *priv_state)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+
+	/* AFs are submitted in Block 3 of type 0A groups */
+	uint8_t c_msb = priv_state->rds_group.data_c_msb;
+	uint8_t c_lsb = priv_state->rds_group.data_c_lsb;
+	bool updated_af = false;
+	struct v4l2_rds_af_set *af_set = &handle->rds_af;
+
+	/* LF / MF frequency follows */
+	if (c_msb == 250) {
+		updated_af = (rds_add_af_to_list(af_set, c_lsb, false)) ?
+			true : updated_af;
+		c_lsb = 0; /* invalidate */
+	}
+	/* announcement of AF count */
+	if (c_msb >= 225 && c_msb <= 249)
+		af_set->announced_af = c_msb - 224;
+	/* check if the data represents an AF */
+	if (c_msb < 205)
+		updated_af = (rds_add_af_to_list(af_set, c_msb, true)) ?
+			true : updated_af;
+	if (c_lsb < 205)
+		updated_af = (rds_add_af_to_list(af_set, c_lsb, true)) ?
+			true : updated_af;
+	/* did we receive all announced AFs?*/
+	if (af_set->size >= af_set->announced_af && af_set->announced_af != 0)
+		handle->valid_fields |= V4L2_RDS_AF;
+	return updated_af;
+}
+
+/* adds one char of the ps name to temporal storage, the value is validated
+ * if it is received twice in a row
+ * @pos:	position of the char within the PS name (0..7)
+ * @ps_char:	the new character to be added
+ * @return:	true, if all 8 temporal ps chars have been validated */
+static bool rds_add_ps(struct rds_private_state *priv_state, uint8_t pos, uint8_t ps_char)
+{
+	if (ps_char == priv_state->new_ps[pos]) {
+		priv_state->new_ps_valid[pos] = 1;
+	} else {
+		priv_state->new_ps[pos] = ps_char;
+		memset(priv_state->new_ps_valid, 0, 8);
+	}
+
+	/* check if all ps positions have been validated */
+	for (int i = 0; i < 8; i++)
+		if (priv_state->new_ps_valid[i] != 1)
+			return false;
+	return true;
+}
+
+/* group of functions to decode successfully received RDS groups into
+ * easily accessible data fields
+ *
+ * group 0: basic tuning and switching */
+static uint32_t rds_decode_group0(struct rds_private_state *priv_state)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+	struct v4l2_rds_group *grp = &priv_state->rds_group;
+	bool new_ps = false;
+	bool tmp;
+	uint32_t updated_fields = 0x00;
+
+	/* bit 4 of block B contains the TA flag */
+	tmp = grp->data_b_lsb & 0x10;
+	if (handle->ta != tmp) {
+		handle->ta = tmp;
+		updated_fields |= V4L2_RDS_TA;
+	}
+	handle->valid_fields |= V4L2_RDS_TA;
+
+	/* bit 3 of block B contains the Music/Speech flag */
+	tmp = grp->data_b_lsb & 0x08;
+	if (handle->ms != tmp) {
+		handle->ms = tmp;
+		updated_fields |= V4L2_RDS_MS;
+	}
+	handle->valid_fields |= V4L2_RDS_MS;
+
+	/* bit 0-1 of block b contain program service name and decoder
+	 * control segment address */
+	uint8_t segment = (grp->data_b_lsb & 0x03);
+
+	/* put the received station-name characters into the correct position
+	 * of the station name, and check if the new PS is validated */
+	rds_add_ps(priv_state, segment * 2, grp->data_d_msb);
+	new_ps = rds_add_ps(priv_state, segment * 2 + 1, grp->data_d_lsb);
+	if (new_ps) {
+		/* check if new PS is the same as the old one */
+		if (memcmp(priv_state->new_ps, handle->ps, 8) != 0) {
+			memcpy(handle->ps, priv_state->new_ps, 8);
+			updated_fields |= V4L2_RDS_PS;
+		}
+		handle->valid_fields |= V4L2_RDS_PS;
+	}
+
+	/* bit 2 of block B contains 1 bit of the Decoder Control Information (DI)
+	 * the segment number defines the bit position
+	 * New bits are only accepted the segments arrive in the correct order */
+	bool bit2 = grp->data_b_lsb & 0x04;
+	if (segment == 0 || segment == priv_state->new_di_pos + 1) {
+		switch (segment) {
+		case 0:
+			priv_state->new_di = set_bit(priv_state->new_di,
+				V4L2_RDS_FLAG_STEREO, bit2);
+			break;
+		case 1:
+			priv_state->new_di = set_bit(priv_state->new_di,
+				V4L2_RDS_FLAG_ARTIFICIAL_HEAD, bit2);
+			priv_state->new_di_pos = segment;
+			break;
+		case 2:
+			priv_state->new_di = set_bit(priv_state->new_di,
+				V4L2_RDS_FLAG_COMPRESSED, bit2);
+			priv_state->new_di_pos = segment;
+			break;
+		case 3:
+			priv_state->new_di = set_bit(priv_state->new_di,
+				V4L2_RDS_FLAG_STATIC_PTY, bit2);
+			if (handle->di != priv_state->new_di)
+				updated_fields |= V4L2_RDS_DI;
+			handle->di = priv_state->new_di;
+			handle->valid_fields |= V4L2_RDS_DI;
+			break;
+		}
+	} else {
+		/* wrong order of DI segments -> restart */
+		priv_state->new_di_pos = 0;
+		priv_state->new_di = 0x00;
+	}
+
+	/* version A groups contain AFs in block C */
+	if (grp->group_version == 'A')
+		if (rds_add_af(priv_state))
+			updated_fields |= V4L2_RDS_AF;
+
+	return updated_fields;
+}
+
+/* group 1: slow labeling codes & program item number */
+static uint32_t rds_decode_group1(struct rds_private_state *priv_state)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+	struct v4l2_rds_group *grp = &priv_state->rds_group;
+	uint32_t updated_fields = 0x00;
+	uint8_t variant_code = 0x00;
+
+	/* version A groups contain slow labeling codes,
+	 * version B groups only contain program item number which is a
+	 * very uncommonly used feature */
+	if (grp->group_version != 'A')
+		return 0x00;
+
+	/* bit 14-12 of block c contain the variant code */
+	variant_code = (grp->data_c_msb >> 4) & 0x07;
+	if (variant_code == 0x00) {
+		/* var 0x00 -> ECC, only accept if same lc is
+		 * received twice */
+		if (grp->data_c_lsb == priv_state->new_ecc) {
+			handle->valid_fields |= V4L2_RDS_ECC;
+			if (handle->ecc != grp->data_c_lsb)
+				updated_fields |= V4L2_RDS_ECC;
+			handle->ecc = grp->data_c_lsb;
+		} else {
+			priv_state->new_ecc = grp->data_c_lsb;
+		}
+	} else if (variant_code == 0x03) {
+		/* var 0x03 -> Language Code, only accept if same lc is
+		 * received twice */
+		if (grp->data_c_lsb == priv_state->new_lc) {
+			handle->valid_fields |= V4L2_RDS_LC;
+			updated_fields |= V4L2_RDS_LC;
+			handle->lc = grp->data_c_lsb;
+		} else {
+			priv_state->new_lc = grp->data_c_lsb;
+		}
+	}
+	return updated_fields;
+}
+
+/* group 2: radio text */
+static uint32_t rds_decode_group2(struct rds_private_state *priv_state)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+	struct v4l2_rds_group *grp = &priv_state->rds_group;
+	uint32_t updated_fields = 0x00;
+
+	/* bit 0-3 of block B contain the segment code */
+	uint8_t segment = grp->data_b_lsb & 0x0f;
+	/* bit 4 of block b contains the A/B text flag (new radio text
+	 * will be transmitted) */
+	bool rt_ab_flag_n = grp->data_b_lsb & 0x10;
+
+	/* new Radio Text will be transmitted */
+	if (rt_ab_flag_n != handle->rt_ab_flag) {
+		handle->rt_ab_flag = rt_ab_flag_n;
+		memset(handle->rt, 0x00, 64);
+		handle->valid_fields &= ~V4L2_RDS_RT;
+		updated_fields |= V4L2_RDS_RT;
+	}
+
+	/* further decoding of data depends on type of message (A or B)
+	 * Type A allows RTs with a max length of 64 chars
+	 * Type B allows RTs with a max length of 32 chars */
+	if (grp->group_version == 'A') {
+		if (segment == 0 || segment == priv_state->new_rt_pos + 1) {
+			priv_state->new_rt[segment * 4] = grp->data_c_msb;
+			priv_state->new_rt[segment * 4 + 1] = grp->data_c_lsb;
+			priv_state->new_rt[segment * 4 + 2] = grp->data_d_msb;
+			priv_state->new_rt[segment * 4 + 3] = grp->data_d_lsb;
+			priv_state->new_rt_pos = segment;
+			if (segment == 0x0f) {
+				handle->rt_length = 64;
+				handle->valid_fields |= V4L2_RDS_RT;
+				if (memcmp(handle->rt, priv_state->new_rt, 64)) {
+					memcpy(handle->rt, priv_state->new_rt, 64);
+					updated_fields |= V4L2_RDS_RT;
+				}
+			}
+		}
+	} else {
+		if (segment == 0 || segment == priv_state->new_rt_pos + 1) {
+			priv_state->new_rt[segment * 2] = grp->data_d_msb;
+			priv_state->new_rt[segment * 2 + 1] = grp->data_d_lsb;
+			/* PI code in block C will be ignored */
+			priv_state->new_rt_pos = segment;
+			if (segment == 0x0f) {
+				handle->rt_length = 32;
+				handle->valid_fields |= V4L2_RDS_RT;
+				updated_fields |= V4L2_RDS_RT;
+				if (memcmp(handle->rt, priv_state->new_rt, 32)) {
+					memcpy(handle->rt, priv_state->new_rt, 32);
+					updated_fields |= V4L2_RDS_RT;
+				}
+			}
+		}
+	}
+
+	/* determine if complete rt was received
+	 * a carriage return (0x0d) can end a message early */
+	for (int i = 0; i < 64; i++) {
+		if (priv_state->new_rt[i] == 0x0d) {
+			handle->rt_length = i + 1;
+			handle->valid_fields |= V4L2_RDS_RT;
+			if (memcmp(handle->rt, priv_state->new_rt, handle->rt_length)) {
+					memcpy(handle->rt, priv_state->new_rt,
+						handle->rt_length);
+					updated_fields |= V4L2_RDS_RT;
+				}
+		}
+	}
+	return updated_fields;
+}
+
+/* group 3: Open Data Announcements */
+static uint32_t rds_decode_group3(struct rds_private_state *priv_state)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+	struct v4l2_rds_group *grp = &priv_state->rds_group;
+	struct v4l2_rds_oda new_oda;
+	uint32_t updated_fields = 0x00;
+
+	if (grp->group_version != 'A')
+		return 0x00;
+
+	/* 0th bit of block b contains Group Type Info version of announced ODA
+	 * Group Type info: 0 = A, 1 = B */
+	new_oda.group_version = (grp->data_b_lsb & 0x01) ? 'B' : 'A';
+	/* 1st to 4th bit contain Group ID of announced ODA */
+	new_oda.group_id = (grp->data_b_lsb & 0x1e) >> 1;
+	/* block D contains the 16bit Application Identification Code */
+	new_oda.aid = (grp->data_d_msb << 8) | grp->data_d_lsb;
+
+	/* try to add the new ODA to the set of defined ODAs */
+	if (rds_add_oda(priv_state, new_oda)) {
+		handle->decode_information |= V4L2_RDS_ODA;
+		updated_fields |= V4L2_RDS_ODA;
+	}
+	return updated_fields;
+}
+
+/* group 4: Date and Time */
+static uint32_t rds_decode_group4(struct rds_private_state *priv_state)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+	struct v4l2_rds_group *grp = &priv_state->rds_group;
+	uint32_t julian_date;
+	uint8_t utc_hour;
+	uint8_t utc_minute;
+	uint8_t utc_offset;
+	uint32_t updated_fields = 0x00;
+
+	if (grp->group_version != 'A')
+		return 0x00;
+
+	/* bits 0-1 of block b lsb contain bits 15 and 16 of Julian date
+	 * bits 0-7 of block c msb contain bits 7 to 14 of Julian date
+	 * bits 1-7 of block c lsb contain bits 0 to 6 of Julian date */
+	julian_date = ((grp->data_b_lsb & 0x03) << 15) |
+				(grp->data_c_msb << 7) | (grp->data_c_lsb >> 1);
+	if (handle->julian_date != julian_date) {
+		handle->julian_date = julian_date;
+		updated_fields |= V4L2_RDS_TIME;
+	}
+
+	/* bit 0 of block c lsb contains bit 4 of utc_hour
+	 * bits 4-7 of block d contains bits 0 to 3 of utc_hour */
+	utc_hour = ((grp->data_c_lsb & 0x01) << 4) | (grp->data_d_msb >> 4);
+	if (handle->utc_hour != utc_hour) {
+		handle->utc_hour = utc_hour;
+		updated_fields |= V4L2_RDS_TIME;
+	}
+
+	/* bits 0-3 of block d msb contain bits 2 to 5 of utc_minute
+	 * bits 6-7 of block d lsb contain bits 0 and 1 utc_minute */
+	utc_minute = ((grp->data_d_msb & 0x0f) << 2) | (grp->data_d_lsb >> 6);
+	if (handle->utc_minute != utc_minute) {
+		handle->utc_minute = utc_minute;
+		updated_fields |= V4L2_RDS_TIME;
+	}
+
+	/* bits 0-5 of block d lsb contain bits 0 to 5 of local time offset */
+	utc_offset = grp->data_d_lsb & 0x3f;
+	if (handle->utc_offset != utc_offset) {
+		handle->utc_offset = utc_offset;
+		updated_fields |= V4L2_RDS_TIME;
+	}
+
+	handle->valid_fields |= V4L2_RDS_TIME;
+	return updated_fields;
+}
+
+/* group 10: Program Type Name */
+static uint32_t rds_decode_group10(struct rds_private_state *priv_state)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+	struct v4l2_rds_group *grp = &priv_state->rds_group;
+	uint32_t updated_fields = 0x00;
+	uint8_t ptyn_tmp[4];
+
+	/* bit 0 of block B contain the segment code */
+	uint8_t segment_code = grp->data_b_lsb & 0x01;
+	/* bit 4 of block b contains the A/B text flag (new ptyn
+	 * will be transmitted) */
+	bool ptyn_ab_flag_n = grp->data_b_lsb & 0x10;
+
+	if (grp->group_version != 'A')
+		return 0x00;
+
+	/* new Program Type Text will be transmitted */
+	if (ptyn_ab_flag_n != handle->ptyn_ab_flag) {
+		handle->ptyn_ab_flag = ptyn_ab_flag_n;
+		memset(handle->ptyn, 0, 8 * sizeof(char));
+		memset(priv_state->new_ptyn, 0, 8 * sizeof(char));
+		memset(priv_state->new_ptyn_valid, 0, 2 * sizeof(bool));
+		handle->valid_fields &= ~V4L2_RDS_PTYN;
+		updated_fields |= V4L2_RDS_PTYN;
+	}
+	/* copy chars to designated position within temp text field */
+	ptyn_tmp[0] = grp->data_c_msb;
+	ptyn_tmp[1] = grp->data_c_lsb;
+	ptyn_tmp[2] = grp->data_d_msb;
+	ptyn_tmp[3] = grp->data_d_lsb;
+
+	/* only validate ptyn segment if the same data is received twice */
+	if (memcmp(ptyn_tmp, priv_state->new_ptyn[segment_code], 4) == 0) {
+		priv_state->new_ptyn_valid[segment_code] = true;
+	} else {
+		for (int i = 0; i < 4; i++)
+			priv_state->new_ptyn[segment_code][i] = ptyn_tmp[i];
+		priv_state->new_ptyn_valid[segment_code] = false;
+	}
+
+	/* if both ptyn segments have been validated, accept the new ptyn */
+	if (priv_state->new_ptyn_valid[0] && priv_state->new_ptyn_valid[1]) {
+		for (int i = 0; i < 4; i++) {
+			handle->ptyn[i] = priv_state->new_ptyn[0][i];
+			handle->ptyn[4 + i] = priv_state->new_ptyn[1][i];
+		}
+		handle->valid_fields |= V4L2_RDS_PTYN;
+		updated_fields |= V4L2_RDS_PTYN;
+	}
+	return updated_fields;
+}
+
+typedef uint32_t (*decode_group_func)(struct rds_private_state *);
+
+/* array of function pointers to contain all group specific decoding functions */
+static const decode_group_func decode_group[16] = {
+	[0] = rds_decode_group0,
+	[1] = rds_decode_group1,
+	[2] = rds_decode_group2,
+	[3] = rds_decode_group3,
+	[4] = rds_decode_group4,
+	[10] = rds_decode_group10,
+};
+
+static uint32_t rds_decode_group(struct rds_private_state *priv_state)
+{
+	struct v4l2_rds *handle = &priv_state->handle;
+	uint8_t group_id = priv_state->rds_group.group_id;
+
+	/* count the group type, and decode it if it is supported */
+	handle->rds_statistics.group_type_cnt[group_id]++;
+	if (decode_group[group_id])
+		return (*decode_group[group_id])(priv_state);
+	return 0x00;
+}
+
+struct v4l2_rds *v4l2_rds_create(bool is_rbds)
+{
+	struct rds_private_state *internal_handle =
+		calloc(1, sizeof(struct rds_private_state));
+	internal_handle->handle.version = V4L2_RDS_VERSION;
+	internal_handle->handle.is_rbds = is_rbds;
+
+	return (struct v4l2_rds *)internal_handle;
+}
+
+void v4l2_rds_destroy(struct v4l2_rds *handle)
+{
+	if (handle)
+		free(handle);
+}
+
+void v4l2_rds_reset(struct v4l2_rds *handle, bool reset_statistics)
+{
+	/* treat the private & the public part of the handle */
+	struct rds_private_state *priv_state = (struct rds_private_state *) handle;
+
+	/* store members of handle that shouldn't be affected by reset*/
+	bool is_rbds = handle->is_rbds;
+	struct v4l2_rds_statistics rds_statistics = handle->rds_statistics;
+
+	/* reset the handle */
+	memset(priv_state, 0, sizeof(*priv_state));
+	/* re-initialize members */
+	handle->is_rbds = is_rbds;
+	if (!reset_statistics)
+		handle->rds_statistics = rds_statistics;
+}
+
+/* function decodes raw RDS data blocks into complete groups. Once a full group is
+ * successfully received, the group is decoded into the fields of the RDS handle.
+ * Decoding is only done once a complete group was received. This is slower compared
+ * to decoding the group type independent information up front, but adds a barrier
+ * against corrupted data (happens regularly when reception is weak) */
+uint32_t v4l2_rds_add(struct v4l2_rds *handle, struct v4l2_rds_data *rds_data)
+{
+	struct rds_private_state *priv_state = (struct rds_private_state *) handle;
+	struct v4l2_rds_data *rds_data_raw = priv_state->rds_data_raw;
+	struct v4l2_rds_statistics *rds_stats = &handle->rds_statistics;
+	uint32_t updated_fields = 0x00;
+	uint8_t *decode_state = &(priv_state->decode_state);
+
+	/* get the block id by masking out irrelevant bits */
+	int block_id = rds_data->block & V4L2_RDS_BLOCK_MSK;
+
+	rds_stats->block_cnt++;
+	/* check for corrected / uncorrectable errors in the data */
+	if (rds_data->block & V4L2_RDS_BLOCK_ERROR) {
+		block_id = -1;
+		rds_stats->block_error_cnt++;
+	} else if (rds_data->block & V4L2_RDS_BLOCK_CORRECTED) {
+		rds_stats->block_corrected_cnt++;
+	}
+
+	switch (*decode_state) {
+	case RDS_EMPTY:
+		if (block_id == 0) {
+			*decode_state = RDS_A_RECEIVED;
+			/* begin reception of a new data group, reset raw buffer to 0 */
+			memset(rds_data_raw, 0, sizeof(rds_data_raw));
+			rds_data_raw[0] = *rds_data;
+		} else {
+			/* ignore block if it is not the first block of a group */
+			rds_stats->group_error_cnt++;
+		}
+		break;
+
+	case RDS_A_RECEIVED:
+		if (block_id == 1) {
+			*decode_state = RDS_B_RECEIVED;
+			rds_data_raw[1] = *rds_data;
+		} else {
+			/* received block with unexpected block id, reset state machine */
+			rds_stats->group_error_cnt++;
+			*decode_state = RDS_EMPTY;
+		}
+		break;
+
+	case RDS_B_RECEIVED:
+		/* handle type C and C' blocks alike */
+		if (block_id == 2 || block_id ==  4) {
+			*decode_state = RDS_C_RECEIVED;
+			rds_data_raw[2] = *rds_data;
+		} else {
+			rds_stats->group_error_cnt++;
+			*decode_state = RDS_EMPTY;
+		}
+		break;
+
+	case RDS_C_RECEIVED:
+		if (block_id == 3) {
+			*decode_state = RDS_EMPTY;
+			rds_data_raw[3] = *rds_data;
+			/* a full group was received */
+			rds_stats->group_cnt++;
+			/* decode group type independent fields */
+			memset(&priv_state->rds_group, 0, sizeof(priv_state->rds_group));
+			updated_fields |= rds_decode_a(priv_state, &rds_data_raw[0]);
+			updated_fields |= rds_decode_b(priv_state, &rds_data_raw[1]);
+			rds_decode_c(priv_state, &rds_data_raw[2]);
+			rds_decode_d(priv_state, &rds_data_raw[3]);
+			/* decode group type dependent fields */
+			updated_fields |= rds_decode_group(priv_state);
+			return updated_fields;
+		}
+		rds_stats->group_error_cnt++;
+		*decode_state = RDS_EMPTY;
+		break;
+
+	default:
+		/* every unexpected block leads to a reset of the sm */
+		rds_stats->group_error_cnt++;
+		*decode_state = RDS_EMPTY;
+	}
+	/* if we reach here, no RDS group was completed */
+	return 0x00;
+}
+
+const char *v4l2_rds_get_pty_str(const struct v4l2_rds *handle)
+{
+	const uint8_t pty = handle->pty;
+
+	if (pty >= 32)
+		return NULL;
+
+	static const char *rds_lut[32] = {
+		"None", "News", "Affairs", "Info", "Sport", "Education", "Drama",
+		"Culture", "Science", "Varied Speech", "Pop Music",
+		"Rock Music", "Easy Listening", "Light Classics M",
+		"Serious Classics", "Other Music", "Weather", "Finance",
+		"Children", "Social Affairs", "Religion", "Phone In",
+		"Travel & Touring", "Leisure & Hobby", "Jazz Music",
+		"Country Music", "National Music", "Oldies Music", "Folk Music",
+		"Documentary", "Alarm Test", "Alarm!"
+	};
+	static const char *rbds_lut[32] = {
+		"None", "News", "Information", "Sports", "Talk", "Rock",
+		"Classic Rock", "Adult Hits", "Soft Rock", "Top 40", "Country",
+		"Oldies", "Soft", "Nostalgia", "Jazz", "Classical",
+		"R&B", "Soft R&B", "Foreign Language", "Religious Music",
+		"Religious Talk", "Personality", "Public", "College",
+		"Spanish Talk", "Spanish Music", "Hip-Hop", "Unassigned",
+		"Unassigned", "Weather", "Emergency Test", "Emergency"
+	};
+
+	return handle->is_rbds ? rbds_lut[pty] : rds_lut[pty];
+}
+
+const char *v4l2_rds_get_country_str(const struct v4l2_rds *handle)
+{
+	/* defines the  region of the world
+	 * 0x0e = Europe, 0x0d = Africa, 0x0a = ITU Region 2,
+	 * 0x0f = ITU Region 3*/
+	uint8_t ecc_h = handle->ecc >> 4;
+	/* sub identifier for the region, valid range 0..4 */
+	uint8_t ecc_l = handle->ecc & 0x0f;
+	/* bits 12-15 pi contain the country code */
+	uint8_t country_code = handle->pi >> 12;
+
+	/* LUT for European countries
+	 * the standard doesn't define every possible value but leaves some
+	 * undefined. An exception is e4-7 which is defines as a dash ("-") */
+	static const char *e_lut[5][16] = {
+	{
+		NULL, "DE", "DZ", "AD", "IL", "IT", "BE", "RU", "PS", "AL",
+		"AT", "HU", "MT", "DE", NULL, "EG"
+	}, {
+		NULL, "GR", "CY", "SM", "CH", "JO", "FI", "LU", "BG", "DK",
+		"GI", "IQ", "GB", "LY", "RO", "FR"
+	}, {
+		" ", "MA", "CZ", "PL", "VA", "SK", "SY", "TN", NULL, "LI",
+		"IS", "MC", "LT", "RS", "ES", "NO"
+	}, {
+		NULL, "ME", "IE", "TR", "MK", NULL, NULL, NULL, "NL", "LV",
+		"LB", "AZ", "HR", "KZ", "SE", "BY"
+	}, {
+		NULL, "MD", "EE", "KG", NULL, NULL, "UA", "-", "PT", "SI",
+		"AM", NULL, "GE", NULL, NULL, "BA"
+	}
+	};
+
+	/* for now only European countries are supported -> ECC E0 - E4
+	 * but the standard defines country codes for the whole world,
+	 * that's the reason for returning "unknown" instead of a NULL
+	 * pointer until all defined countries are supported */
+	if (ecc_h == 0x0e && ecc_l <= 0x04)
+		return e_lut[ecc_l][country_code];
+	return "Unknown";
+}
+
+static const char *rds_language_lut(const uint8_t lc)
+{
+	const uint8_t max_lc = 127;
+	static const char *language_lut[128] = {
+		"Unknown", "Albanian", "Breton", "Catalan",
+		"Croatian", "Welsh", "Czech", "Danish",
+		"German", "English", "Spanish", "Esperanto",
+		"Estonian", "Basque", "Faroese", "French",
+		"Frisian", "Irish", "Gaelic", "Galician",
+		"Icelandic", "Italian", "Lappish", "Latin",
+		"Latvian", "Luxembourgian", "Lithuanian", "Hungarian",
+		"Maltese", "Dutch", "Norwegian", "Occitan",
+		"Polish", "Portuguese", "Romanian", "Ramansh",
+		"Serbian", "Slovak", "Slovene", "Finnish",
+		"Swedish", "Turkish", "Flemish", "Walloon",
+		" ", " ", " ", " ", " ", " ", " ", " ",
+		" ", " ", " ", " ", " ", " ", " ", " ",
+		" ", " ", " ", " ", " ", " ", " ", " ",
+		" ", "Zulu", "Vietnamese", "Uzbek",
+		"Urdu", "Ukrainian", "Thai", "Telugu",
+		"Tatar", "Tamil", "Tadzhik", "Swahili",
+		"Sranan Tongo", "Somali", "Sinhalese", "Shona",
+		"Serbo-Croat", "Ruthenian", "Russian", "Quechua",
+		"Pushtu", "Punjabi", "Persian", "Papamiento",
+		"Oriya", "Nepali", "Ndebele", "Marathi",
+		"Moldavian", "Malaysian", "Malagasay", "Macedonian",
+		"Laotian", "Korean", "Khmer", "Kazahkh",
+		"Kannada", "Japanese", "Indonesian", "Hindi",
+		"Hebrew", "Hausa", "Gurani", "Gujurati",
+		"Greek", "Georgian", "Fulani", "Dani",
+		"Churash", "Chinese", "Burmese", "Bulgarian",
+		"Bengali", "Belorussian", "Bambora", "Azerbijani",
+		"Assamese", "Armenian", "Arabic", "Amharic"
+	};
+	return (lc > max_lc) ? "Unknown" : language_lut[lc];
+}
+
+const char *v4l2_rds_get_language_str(const struct v4l2_rds *handle)
+{
+	return rds_language_lut(handle->lc);
+}
+
+const char *v4l2_rds_get_coverage_str(const struct v4l2_rds *handle)
+{
+	/* bits 8-11 contain the area coverage code */
+	uint8_t coverage = (handle->pi >> 8) & 0X0f;
+	static const char *coverage_lut[16] = {
+		"Local", "International", "National", "Supra-Regional",
+		"Regional 1", "Regional 2", "Regional 3", "Regional 4",
+		"Regional 5", "Regional 6", "Regional 7", "Regional 8",
+		"Regional 9", "Regional 10", "Regional 11", "Regional 12"
+	};
+
+	return coverage_lut[coverage];
+}
+
+const struct v4l2_rds_group *v4l2_rds_get_group
+	(const struct v4l2_rds *handle)
+{
+	struct rds_private_state *priv_state = (struct rds_private_state *) handle;
+	return &priv_state->rds_group;
+}
diff --git a/lib/libv4l2rds/libv4l2rds.pc.in b/lib/libv4l2rds/libv4l2rds.pc.in
new file mode 100644
index 0000000..cc1d5f6
--- /dev/null
+++ b/lib/libv4l2rds/libv4l2rds.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+includedir=@includedir@
+libdir=@libdir@
+
+Name: libv4l2rds
+Description: v4l2 RDS decode library
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lv4l2rds
+Libs.private: -lpthread
+Cflags: -I${includedir}
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH 2/2] Initial version of RDS Control utility Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-25 17:44 ` [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com> Konke Radlow
@ 2012-07-25 17:44   ` Konke Radlow
  2012-07-26 19:02     ` Gregor Jasny
                       ` (2 more replies)
  2012-07-26 14:28   ` [RFC PATCH 1/2] Initial version of the RDS-decoder library " Ezequiel Garcia
                     ` (2 subsequent siblings)
  3 siblings, 3 replies; 18+ messages in thread
From: Konke Radlow @ 2012-07-25 17:44 UTC (permalink / raw)
  To: linux-media; +Cc: hverkuil, hdegoede

---
 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);
+}
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-25 17:44 ` [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com> Konke Radlow
  2012-07-25 17:44   ` [RFC PATCH 2/2] Initial version of RDS Control utility " Konke Radlow
@ 2012-07-26 14:28   ` Ezequiel Garcia
  2012-07-26 14:39     ` Hans Verkuil
  2012-07-26 18:46   ` Gregor Jasny
  2012-07-26 18:49   ` Gregor Jasny
  3 siblings, 1 reply; 18+ messages in thread
From: Ezequiel Garcia @ 2012-07-26 14:28 UTC (permalink / raw)
  To: Konke Radlow; +Cc: linux-media, hverkuil, hdegoede

Hi Konke,

> +
> +libv4l2rds_la_SOURCES = libv4l2rds.c
> +libv4l2rds_la_CPPFLAGS = -fvisibility=hidden $(ENFORCE_LIBV4L_STATIC) -std=c99
> +libv4l2rds_la_LDFLAGS = -version-info 0 -lpthread $(DLOPEN_LIBS) $(ENFORCE_LIBV4L_STATIC)
> diff --git a/lib/libv4l2rds/libv4l2rds.c b/lib/libv4l2rds/libv4l2rds.c
> new file mode 100644
> index 0000000..0bacaa2
> --- /dev/null
> +++ b/lib/libv4l2rds/libv4l2rds.c
> @@ -0,0 +1,871 @@
> +/*
> + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
[snip]
> + * This program is free software; you can redistribute it and/or modify

Just a -probably silly- question...

How can it be "free software" yet claim "All rights reserved" ? Is this correct?

Regards,
Ezequiel.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-26 14:28   ` [RFC PATCH 1/2] Initial version of the RDS-decoder library " Ezequiel Garcia
@ 2012-07-26 14:39     ` Hans Verkuil
  2012-07-26 14:41       ` Ezequiel Garcia
  0 siblings, 1 reply; 18+ messages in thread
From: Hans Verkuil @ 2012-07-26 14:39 UTC (permalink / raw)
  To: Ezequiel Garcia; +Cc: Konke Radlow, linux-media, hdegoede

On Thu 26 July 2012 16:28:20 Ezequiel Garcia wrote:
> Hi Konke,
> 
> > +
> > +libv4l2rds_la_SOURCES = libv4l2rds.c
> > +libv4l2rds_la_CPPFLAGS = -fvisibility=hidden $(ENFORCE_LIBV4L_STATIC) -std=c99
> > +libv4l2rds_la_LDFLAGS = -version-info 0 -lpthread $(DLOPEN_LIBS) $(ENFORCE_LIBV4L_STATIC)
> > diff --git a/lib/libv4l2rds/libv4l2rds.c b/lib/libv4l2rds/libv4l2rds.c
> > new file mode 100644
> > index 0000000..0bacaa2
> > --- /dev/null
> > +++ b/lib/libv4l2rds/libv4l2rds.c
> > @@ -0,0 +1,871 @@
> > +/*
> > + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
> [snip]
> > + * This program is free software; you can redistribute it and/or modify
> 
> Just a -probably silly- question...
> 
> How can it be "free software" yet claim "All rights reserved" ? Is this correct?

Yeah, it's correct. I had the same question when I was told that this was the
correct phrase to use. Check other copyright lines in the kernel and you'll see
the same line.

Since it is covered by the LGPLv2 license there aren't many rights to reserve :-)

The only right there is in practice is the right to decide whether or not to
allow other licenses as well.

Regards,

	Hans

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-26 14:39     ` Hans Verkuil
@ 2012-07-26 14:41       ` Ezequiel Garcia
  0 siblings, 0 replies; 18+ messages in thread
From: Ezequiel Garcia @ 2012-07-26 14:41 UTC (permalink / raw)
  To: Hans Verkuil; +Cc: Konke Radlow, linux-media, hdegoede

On Thu, Jul 26, 2012 at 11:39 AM, Hans Verkuil <hverkuil@xs4all.nl> wrote:
> On Thu 26 July 2012 16:28:20 Ezequiel Garcia wrote:
>> Hi Konke,
>>
>> > +
>> > +libv4l2rds_la_SOURCES = libv4l2rds.c
>> > +libv4l2rds_la_CPPFLAGS = -fvisibility=hidden $(ENFORCE_LIBV4L_STATIC) -std=c99
>> > +libv4l2rds_la_LDFLAGS = -version-info 0 -lpthread $(DLOPEN_LIBS) $(ENFORCE_LIBV4L_STATIC)
>> > diff --git a/lib/libv4l2rds/libv4l2rds.c b/lib/libv4l2rds/libv4l2rds.c
>> > new file mode 100644
>> > index 0000000..0bacaa2
>> > --- /dev/null
>> > +++ b/lib/libv4l2rds/libv4l2rds.c
>> > @@ -0,0 +1,871 @@
>> > +/*
>> > + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
>> [snip]
>> > + * This program is free software; you can redistribute it and/or modify
>>
>> Just a -probably silly- question...
>>
>> How can it be "free software" yet claim "All rights reserved" ? Is this correct?
>
> Yeah, it's correct. I had the same question when I was told that this was the
> correct phrase to use. Check other copyright lines in the kernel and you'll see
> the same line.
>
> Since it is covered by the LGPLv2 license there aren't many rights to reserve :-)
>
> The only right there is in practice is the right to decide whether or not to
> allow other licenses as well.
>

For some reason, I must read this several times before really catching it.

Thanks for the explanation,
Ezequiel.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 0/2] Add support for RDS decoding
  2012-07-25 17:43 [RFC PATCH 0/2] Add support for RDS decoding Konke Radlow
  2012-07-25 17:44 ` [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com> Konke Radlow
@ 2012-07-26 18:41 ` Gregor Jasny
  2012-07-27 14:27   ` Konke Radlow
  1 sibling, 1 reply; 18+ messages in thread
From: Gregor Jasny @ 2012-07-26 18:41 UTC (permalink / raw)
  To: Konke Radlow; +Cc: linux-media, hverkuil, hdegoede

Hello Konke,

On 7/25/12 7:43 PM, Konke Radlow wrote:
> The latest version of the code can always be found in my github repository:
> https://github.com/koradlow/v4l2-rds-ctl

In the github dir is a lib/include/libv4l2rd.h file. I cannot find it in
the patchset. It also looks like you're bases on an older revision of
the tree. Please rebase your patches.

In the mentioned header files please replace the condition in

#if __GNUC__ >= 4
#define LIBV4L_PUBLIC __attribute__ ((visibility("default")))
#else
#define LIBV4L_PUBLIC
#endif

with the ones found in libv4l2.h.

Thanks,
Gregor

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-25 17:44 ` [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com> Konke Radlow
  2012-07-25 17:44   ` [RFC PATCH 2/2] Initial version of RDS Control utility " Konke Radlow
  2012-07-26 14:28   ` [RFC PATCH 1/2] Initial version of the RDS-decoder library " Ezequiel Garcia
@ 2012-07-26 18:46   ` Gregor Jasny
  2012-07-26 18:49   ` Gregor Jasny
  3 siblings, 0 replies; 18+ messages in thread
From: Gregor Jasny @ 2012-07-26 18:46 UTC (permalink / raw)
  To: Konke Radlow; +Cc: linux-media, hverkuil, hdegoede

On 7/25/12 7:44 PM, Konke Radlow wrote:
> diff --git a/configure.ac b/configure.ac
> index 8ddcc9d..1d7eb29 100644
> --- a/configure.ac
> +++ b/configure.ac
...
> @@ -146,13 +148,17 @@ AC_ARG_WITH(libv4l2subdir, AS_HELP_STRING(--with-libv4l2subdir=DIR,set libv4l2 l
>  AC_ARG_WITH(libv4lconvertsubdir, AS_HELP_STRING(--with-libv4lconvertsubdir=DIR,set libv4lconvert library subdir [default=libv4l]),
>     libv4lconvertsubdir=$withval, libv4lconvertsubdir="libv4l")
>  
> +AC_ARG_WITH(libv4l2rdssubdir, AS_HELP_STRING(--with-libv4l2rdssubdir=DIR,set libv4l2rds library subdir [default=libv4l]),
> +   libv4l2rdssubdir=$withval, libv4l2rdssubdir="libv4l")
> +
>  AC_ARG_WITH(udevdir, AS_HELP_STRING(--with-udevdir=DIR,set udev directory [default=/lib/udev]),
>     udevdir=$withval, udevdir="/lib/udev")
> -
> +   
>  libv4l1privdir="$libdir/$libv4l1subdir"
>  libv4l2privdir="$libdir/$libv4l2subdir"
>  libv4l2plugindir="$libv4l2privdir/plugins"
>  libv4lconvertprivdir="$libdir/$libv4lconvertsubdir"
> +libv4l2rdsprivdir="$libdir/$libv4l2rdssubdir"
>  
>  keytablesystemdir="$udevdir/rc_keymaps"
>  keytableuserdir="$sysconfdir/rc_keymaps"
> @@ -166,6 +172,7 @@ AC_SUBST(libv4lconvertprivdir)
>  AC_SUBST(keytablesystemdir)
>  AC_SUBST(keytableuserdir)
>  AC_SUBST(udevrulesdir)
> +AC_SUBST(libv4l2rdsprivdir)
>  AC_SUBST(pkgconfigdir)
>  
>  AC_DEFINE_UNQUOTED([V4L_UTILS_VERSION], ["$PACKAGE_VERSION"], [v4l-utils version string])
> @@ -173,6 +180,7 @@ AC_DEFINE_DIR([LIBV4L1_PRIV_DIR], [libv4l1privdir], [libv4l1 private lib directo
>  AC_DEFINE_DIR([LIBV4L2_PRIV_DIR], [libv4l2privdir], [libv4l2 private lib directory])
>  AC_DEFINE_DIR([LIBV4L2_PLUGIN_DIR], [libv4l2plugindir], [libv4l2 plugin directory])
>  AC_DEFINE_DIR([LIBV4LCONVERT_PRIV_DIR], [libv4lconvertprivdir], [libv4lconvert private lib directory])
> +AC_DEFINE_DIR([LIBV4L2RDS_PRIV_DIR], [libv4l2rdsprivdir], [libv4l2rds private lib directory])
>  AC_DEFINE_DIR([IR_KEYTABLE_SYSTEM_DIR], [keytablesystemdir], [ir-keytable preinstalled tables directory])
>  AC_DEFINE_DIR([IR_KEYTABLE_USER_DIR], [keytableuserdir], [ir-keytable user defined tables directory])
>  

I don't think you need these changes. In libv4l these are for the
wrapper libraries. You don't have one.

Thanks,
Gregor

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-25 17:44 ` [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com> Konke Radlow
                     ` (2 preceding siblings ...)
  2012-07-26 18:46   ` Gregor Jasny
@ 2012-07-26 18:49   ` Gregor Jasny
  3 siblings, 0 replies; 18+ messages in thread
From: Gregor Jasny @ 2012-07-26 18:49 UTC (permalink / raw)
  To: Konke Radlow; +Cc: linux-media, hverkuil, hdegoede

On 7/25/12 7:44 PM, Konke Radlow wrote:
> --- /dev/null
> +++ b/lib/libv4l2rds/Makefile.am
> @@ -0,0 +1,11 @@
> +if WITH_LIBV4L
> +lib_LTLIBRARIES = libv4l2rds.la
> +include_HEADERS = ../include/libv4l2rds.h
> +pkgconfig_DATA = libv4l2rds.pc
> +else
> +noinst_LTLIBRARIES = libv4l2rds.la
> +endif
> +
> +libv4l2rds_la_SOURCES = libv4l2rds.c
> +libv4l2rds_la_CPPFLAGS = -fvisibility=hidden $(ENFORCE_LIBV4L_STATIC) -std=c99
> +libv4l2rds_la_LDFLAGS = -version-info 0 -lpthread $(DLOPEN_LIBS) $(ENFORCE_LIBV4L_STATIC)

You don't call dlopen, so you can drop $(DLOPEN_LIBS)


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 2/2] Initial version of RDS Control utility Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-25 17:44   ` [RFC PATCH 2/2] Initial version of RDS Control utility " Konke Radlow
@ 2012-07-26 19:02     ` Gregor Jasny
  2012-07-26 19:13     ` Gregor Jasny
  2012-07-28 11:11     ` Hans de Goede
  2 siblings, 0 replies; 18+ messages in thread
From: Gregor Jasny @ 2012-07-26 19:02 UTC (permalink / raw)
  To: Konke Radlow; +Cc: linux-media

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 2/2] Initial version of RDS Control utility Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-25 17:44   ` [RFC PATCH 2/2] Initial version of RDS Control utility " Konke Radlow
  2012-07-26 19:02     ` Gregor Jasny
@ 2012-07-26 19:13     ` Gregor Jasny
  2012-07-26 19:46       ` Konke Radlow
  2012-07-27  6:50       ` Hans Verkuil
  2012-07-28 11:11     ` Hans de Goede
  2 siblings, 2 replies; 18+ messages in thread
From: Gregor Jasny @ 2012-07-26 19:13 UTC (permalink / raw)
  To: Konke Radlow; +Cc: linux-media

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?


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 2/2] Initial version of RDS Control utility Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-26 19:13     ` Gregor Jasny
@ 2012-07-26 19:46       ` Konke Radlow
  2012-07-27  6:50       ` Hans Verkuil
  1 sibling, 0 replies; 18+ messages in thread
From: Konke Radlow @ 2012-07-26 19:46 UTC (permalink / raw)
  To: Gregor Jasny; +Cc: Konke Radlow, linux-media

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 2/2] Initial version of RDS Control utility Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-26 19:13     ` Gregor Jasny
  2012-07-26 19:46       ` Konke Radlow
@ 2012-07-27  6:50       ` Hans Verkuil
  1 sibling, 0 replies; 18+ messages in thread
From: Hans Verkuil @ 2012-07-27  6:50 UTC (permalink / raw)
  To: Gregor Jasny; +Cc: Konke Radlow, linux-media

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
> 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 0/2] Add support for RDS decoding
  2012-07-26 18:41 ` [RFC PATCH 0/2] Add support for RDS decoding Gregor Jasny
@ 2012-07-27 14:27   ` Konke Radlow
  2012-07-29 17:07     ` Gregor Jasny
  0 siblings, 1 reply; 18+ messages in thread
From: Konke Radlow @ 2012-07-27 14:27 UTC (permalink / raw)
  To: Gregor Jasny; +Cc: linux-media, hverkuil, hdegoede

Yes, I realized too late that the library header file was missing from the 
submitted patch. I created a additional patch and attached it to the [RFC 
PATCH 1/2] thread.

I updated the repository. It should now be based on the most recent version of 
the tree (I used git://git.linuxtv.org/v4l-utils.git as the origin)

changing the condition in the library header from 
> #if __GNUC__ >= 4
> #define LIBV4L_PUBLIC __attribute__ ((visibility("default")))
> #else
> #define LIBV4L_PUBLIC
> #endif

to 
> #if HAVE_VISIBILITY
> #define LIBV4L_PUBLIC __attribute__ ((visibility("default")))
> #else
> #define LIBV4L_PUBLIC
> #endif

causes linker problems for me. The public library functions can no longer be 
found. I cannot figure out why it's working for programs using libv4l2.la but 
not for programs using libv4l2rds.la

greetings,
Konke 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 2/2] Initial version of RDS Control utility Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-25 17:44   ` [RFC PATCH 2/2] Initial version of RDS Control utility " Konke Radlow
  2012-07-26 19:02     ` Gregor Jasny
  2012-07-26 19:13     ` Gregor Jasny
@ 2012-07-28 11:11     ` Hans de Goede
  2012-07-30  6:50       ` Hans Verkuil
  2 siblings, 1 reply; 18+ messages in thread
From: Hans de Goede @ 2012-07-28 11:11 UTC (permalink / raw)
  To: Konke Radlow; +Cc: linux-media, hverkuil

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);
> +}
>


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 0/2] Add support for RDS decoding
  2012-07-27 14:27   ` Konke Radlow
@ 2012-07-29 17:07     ` Gregor Jasny
  2012-07-30  9:36       ` Konke Radlow
  0 siblings, 1 reply; 18+ messages in thread
From: Gregor Jasny @ 2012-07-29 17:07 UTC (permalink / raw)
  To: Konke Radlow; +Cc: linux-media

Hello Konke,

On 7/27/12 4:27 PM, Konke Radlow wrote:
> changing the condition in the library header from 
>> #if __GNUC__ >= 4
>> #define LIBV4L_PUBLIC __attribute__ ((visibility("default")))
>> #else
>> #define LIBV4L_PUBLIC
>> #endif
> 
> to 
>> #if HAVE_VISIBILITY
>> #define LIBV4L_PUBLIC __attribute__ ((visibility("default")))
>> #else
>> #define LIBV4L_PUBLIC
>> #endif
> 
> causes linker problems for me. The public library functions can no longer be 
> found. I cannot figure out why it's working for programs using libv4l2.la but 
> not for programs using libv4l2rds.la

You need to include <config.h> before including this file in the utility
and library to get the HAVE_VISIBILITY definition activated.
The other option would be switching from defining HAVE_VISIBILITY in
config.h to a command line define.

Thanks,
Gregor

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 2/2] Initial version of RDS Control utility Signed-off-by: Konke Radlow <kradlow@cisco.com>
  2012-07-28 11:11     ` Hans de Goede
@ 2012-07-30  6:50       ` Hans Verkuil
  0 siblings, 0 replies; 18+ messages in thread
From: Hans Verkuil @ 2012-07-30  6:50 UTC (permalink / raw)
  To: Hans de Goede; +Cc: Konke Radlow, linux-media

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
> 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 0/2] Add support for RDS decoding
  2012-07-29 17:07     ` Gregor Jasny
@ 2012-07-30  9:36       ` Konke Radlow
  0 siblings, 0 replies; 18+ messages in thread
From: Konke Radlow @ 2012-07-30  9:36 UTC (permalink / raw)
  To: Gregor Jasny; +Cc: Konke Radlow, linux-media

thank you for the info, it's working now
(I went for the include solution)

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2012-07-30  9:36 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-07-25 17:43 [RFC PATCH 0/2] Add support for RDS decoding Konke Radlow
2012-07-25 17:44 ` [RFC PATCH 1/2] Initial version of the RDS-decoder library Signed-off-by: Konke Radlow <kradlow@cisco.com> Konke Radlow
2012-07-25 17:44   ` [RFC PATCH 2/2] Initial version of RDS Control utility " Konke Radlow
2012-07-26 19:02     ` Gregor Jasny
2012-07-26 19:13     ` Gregor Jasny
2012-07-26 19:46       ` Konke Radlow
2012-07-27  6:50       ` Hans Verkuil
2012-07-28 11:11     ` Hans de Goede
2012-07-30  6:50       ` Hans Verkuil
2012-07-26 14:28   ` [RFC PATCH 1/2] Initial version of the RDS-decoder library " Ezequiel Garcia
2012-07-26 14:39     ` Hans Verkuil
2012-07-26 14:41       ` Ezequiel Garcia
2012-07-26 18:46   ` Gregor Jasny
2012-07-26 18:49   ` Gregor Jasny
2012-07-26 18:41 ` [RFC PATCH 0/2] Add support for RDS decoding Gregor Jasny
2012-07-27 14:27   ` Konke Radlow
2012-07-29 17:07     ` Gregor Jasny
2012-07-30  9:36       ` Konke Radlow

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).