---
 Makefile.am                     |    3 +-
 configure.ac                    |    7 +-
 lib/include/libv4l2rds.h        |  228 ++++++++++
 lib/libv4l2rds/Makefile.am      |   11 +
 lib/libv4l2rds/libv4l2rds.c     |  953 +++++++++++++++++++++++++++++++++++++++
 lib/libv4l2rds/libv4l2rds.pc.in |   11 +
 6 files changed, 1211 insertions(+), 2 deletions(-)
 create mode 100644 lib/include/libv4l2rds.h
 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 a754955..621d8d9 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..1109c4d 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,9 +148,12 @@ 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"
diff --git a/lib/include/libv4l2rds.h b/lib/include/libv4l2rds.h
new file mode 100644
index 0000000..4aa8593
--- /dev/null
+++ b/lib/include/libv4l2rds.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights 
reserved.
+ * Author: Konke Radlow <korad...@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
+ */
+
+#ifndef __LIBV4L2RDS
+#define __LIBV4L2RDS
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <config.h>
+
+#include <linux/videodev2.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#if HAVE_VISIBILITY
+#define LIBV4L_PUBLIC __attribute__ ((visibility("default")))
+#else
+#define LIBV4L_PUBLIC
+#endif
+
+/* used to define the current version (version field) of the v4l2_rds struct */
+#define V4L2_RDS_VERSION (1)
+
+/* Constants used to define the size of arrays used to store RDS information */
+#define MAX_ODA_CNT 18         /* there are 16 groups each with type a or b. 
Of these
+                        * 32 distinct groups, 18 can be used for ODA purposes 
*/
+#define MAX_AF_CNT 25  /* AF Method A allows a maximum of 25 AFs to be defined
+                        * AF Method B does not impose a limit on the number of 
AFs
+                        * but it is not fully supported at the moment and will
+                        * not receive more than 25 AFs */
+
+/* Define Constants for the possible types of RDS information
+ * used to address the relevant bit in the valid_fields bitmask */
+#define V4L2_RDS_PI            0x01    /* Program Identification */
+#define V4L2_RDS_PTY           0x02    /* Program Type */
+#define V4L2_RDS_TP            0x04    /* Traffic Program */
+#define V4L2_RDS_PS            0x08    /* Program Service Name */
+#define V4L2_RDS_TA            0x10    /* Traffic Announcement */
+#define V4L2_RDS_DI            0x20    /* Decoder Information */
+#define V4L2_RDS_MS            0x40    /* Music / Speech flag */
+#define V4L2_RDS_PTYN          0x80    /* Program Type Name */
+#define V4L2_RDS_RT            0x100   /* Radio-Text */
+#define V4L2_RDS_TIME          0x200   /* Date and Time information */
+#define V4L2_RDS_TMC           0x400   /* TMC availability */
+#define V4L2_RDS_AF            0x800   /* AF (alternative freq) available */
+#define V4L2_RDS_ECC           0x1000  /* Extended County Code */
+#define V4L2_RDS_LC            0x2000  /* Language Code */
+
+/* Define Constants for the state of the RDS decoding process
+ * used to address the relevant bit in the decode_information bitmask */
+#define V4L2_RDS_GROUP_NEW     0x01    /* New group received */
+#define V4L2_RDS_ODA           0x02    /* Open Data Group announced */
+
+/* Decoder Information (DI) codes
+ * used to decode the DI information according to the RDS standard */
+#define V4L2_RDS_FLAG_STEREO           0x01
+#define V4L2_RDS_FLAG_ARTIFICIAL_HEAD  0x02
+#define V4L2_RDS_FLAG_COMPRESSED       0x04
+#define V4L2_RDS_FLAG_STATIC_PTY       0x08
+
+/* struct to encapsulate one complete RDS group */
+/* This structure is used internally to store data until a complete RDS
+ * group was received and group id dependent decoding can be done.
+ * It is also used to provide external access to uninterpreted RDS groups
+ * when manual decoding is required (e.g. special ODA types) */
+struct v4l2_rds_group {
+       uint16_t pi;            /* Program Identification */
+       char group_version;     /* group version ('A' / 'B') */
+       uint8_t group_id;       /* group number (0..16) */
+
+       /* uninterpreted data blocks for decoding (e.g. ODA) */
+       uint8_t data_b_lsb;
+       uint8_t data_c_msb;
+       uint8_t data_c_lsb;
+       uint8_t data_d_msb;
+       uint8_t data_d_lsb;
+};
+
+/* struct to encapsulate some statistical information about the decoding 
process */
+struct v4l2_rds_statistics {
+       uint32_t block_cnt;             /* total amount of received blocks */
+       uint32_t group_cnt;             /* total amount of successfully
+                                        * decoded groups */
+       uint32_t block_error_cnt;       /* blocks that were marked as erroneous
+                                        * and had to be dropped */
+       uint32_t group_error_cnt;       /* group decoding processes that had to 
be
+                                        * aborted because of erroneous blocks
+                                        * or wrong order of blocks */
+       uint32_t block_corrected_cnt;   /* blocks that contained 1-bit errors
+                                        * which were corrected */
+       uint32_t group_type_cnt[16];    /* number of occurrence for each
+                                        * defined RDS group */
+};
+
+/* struct to encapsulate the definition of one ODA (Open Data Application) 
type */
+struct v4l2_rds_oda {
+       uint8_t group_id;       /* RDS group used to broadcast this ODA */
+       char group_version;     /* group version (A / B) for this ODA */
+       uint16_t aid;           /* Application Identification for this ODA,
+                                * AIDs are centrally administered by the
+                                * RDS Registration Office (rds.org.uk) */
+};
+
+/* struct to encapsulate an array of all defined ODA types for a channel */
+/* This structure will grow with ODA announcements broadcasted in type 3A
+ * groups, that were verified not to be no duplicates or redefinitions */
+struct v4l2_rds_oda_set {
+       uint8_t size;           /* number of ODAs defined by this channel */
+       struct v4l2_rds_oda oda[MAX_ODA_CNT];
+};
+
+/* struct to encapsulate an array of Alternative Frequencies for a channel */
+/* Every channel can send out AFs for his program. The number of AFs that
+ * will be broadcasted is announced by the channel */
+struct v4l2_rds_af_set {
+       uint8_t size;                   /* size of the set (might be smaller
+                                        * than the announced size) */
+       uint8_t announced_af;           /* number of announced AF */
+       uint32_t af[MAX_AF_CNT];        /* AFs defined in Hz */
+};
+
+/* struct to encapsulate state and RDS information for current decoding 
process */
+/* This is the structure that will be used by external applications, to
+ * communicate with the library and get access to RDS data */
+struct v4l2_rds {
+       uint32_t version;       /* version number of this structure */
+
+       /** state information **/
+       uint32_t decode_information;    /* state of decoding process */
+       uint32_t valid_fields;          /* currently valid info fields
+                                        * of this structure */
+
+       /** RDS info fields **/
+       bool is_rbds;           /* use RBDS standard version of LUTs */
+       uint16_t pi;            /* Program Identification */
+       uint8_t ps[9];          /* Program Service Name, UTF-8 encoding,
+                                * '\0' terminated */
+       uint8_t pty;            /* Program Type */
+       uint8_t ptyn[9];        /* Program Type Name, UTF-8 encoding,
+                                * '\0' terminated */
+       bool ptyn_ab_flag;      /* PTYN A/B flag (toggled), to signal
+                                * change of PTYN */
+       uint8_t rt_length;      /* length of RT string */
+       uint8_t rt[65];         /* Radio-Text string, UTF-8 encoding,
+                                * '\0' terminated */
+       bool rt_ab_flag;        /* RT A/B flag (toggled), to signal
+                                * transmission of new RT */
+       bool ta;                /* Traffic Announcement */
+       bool tp;                /* Traffic Program */
+       bool ms;                /* Music / Speech flag */
+       uint8_t di;             /* Decoder Information */
+       uint8_t ecc;            /* Extended Country Code */
+       uint8_t lc;             /* Language Code */
+       time_t time;            /* Time and Date of transmission */
+
+       struct v4l2_rds_statistics rds_statistics;
+       struct v4l2_rds_oda_set rds_oda;        /* Open Data Services */
+       struct v4l2_rds_af_set rds_af;          /* Alternative Frequencies */
+};
+
+/* v4l2_rds_init() - initializes a new decoding process
+ * @is_rbds:   defines which standard is used: true=RBDS, false=RDS
+ *
+ * initialize a new instance of the RDS-decoding struct and return
+ * a handle containing state and RDS information, used to interact
+ * with the library functions */
+LIBV4L_PUBLIC struct v4l2_rds *v4l2_rds_create(bool is_rdbs);
+
+/* frees all memory allocated for the RDS-decoding struct */
+LIBV4L_PUBLIC void v4l2_rds_destroy(struct v4l2_rds *handle);
+
+/* resets the RDS information in the handle to initial values
+ * e.g. can be used when radio channel is changed
+ * @reset_statistics:  true = set all statistic values to 0, false = keep them 
untouched */
+LIBV4L_PUBLIC void v4l2_rds_reset(struct v4l2_rds *handle, bool 
reset_statistics);
+
+/* adds a raw RDS block to decode it into RDS groups
+ * @return:    bitmask with with updated fields set to 1
+ * @rds_data:  3 bytes of raw RDS data, obtained by calling read()
+ *                             on RDS capable V4L2 devices */
+LIBV4L_PUBLIC uint32_t v4l2_rds_add(struct v4l2_rds *handle, struct 
v4l2_rds_data *rds_data);
+
+/*
+ * group of functions to translate numerical RDS data into strings
+ *
+ * return program description string defined in the RDS/RBDS Standard
+ * ! return value deepens on selected Standard !*/
+LIBV4L_PUBLIC const char *v4l2_rds_get_pty_str(const struct v4l2_rds *handle);
+LIBV4L_PUBLIC const char *v4l2_rds_get_language_str(const struct v4l2_rds 
*handle);
+LIBV4L_PUBLIC const char *v4l2_rds_get_country_str(const struct v4l2_rds 
*handle);
+LIBV4L_PUBLIC const char *v4l2_rds_get_coverage_str(const struct v4l2_rds 
*handle);
+
+/* returns a pointer to the last decoded RDS group, in order to give raw
+ * access to RDS data if it is required (e.g. ODA decoding) */
+LIBV4L_PUBLIC const struct v4l2_rds_group *v4l2_rds_get_group
+       (const struct v4l2_rds *handle);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
diff --git a/lib/libv4l2rds/Makefile.am b/lib/libv4l2rds/Makefile.am
new file mode 100644
index 0000000..05c54b0
--- /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 $(ENFORCE_LIBV4L_STATIC)
diff --git a/lib/libv4l2rds/libv4l2rds.c b/lib/libv4l2rds/libv4l2rds.c
new file mode 100644
index 0000000..5aa3088
--- /dev/null
+++ b/lib/libv4l2rds/libv4l2rds.c
@@ -0,0 +1,953 @@
+/*
+ * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights 
reserved.
+ * Author: Konke Radlow <korad...@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 
*/
+/* the fields (except for handle) are for internal use only - new information
+ * is decoded and stored in them until it can be verified and copied to the
+ * public part of the  rds structure (handle) */
+/* for meaning of abbreviations check the library header libv4l2rds.h */
+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;
+
+       /* current state of rds group decoding */
+       uint8_t decode_state;
+
+       /* temporal storage locations for rds fields */
+       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 next_rt_segment;
+       uint8_t new_di;
+       uint8_t next_di_segment;
+       uint8_t new_ecc;
+       uint8_t new_lc;
+       /* RDS date / time representation */
+       uint32_t new_mjd;       /* modified Julian Day code */
+       uint8_t utc_hour;
+       uint8_t utc_minute;
+       uint8_t utc_offset;
+
+       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 = 0;
+       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;
+       bool traffic_prog;
+       uint8_t pty;
+       uint32_t updated_fields = 0;
+
+       /* 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 */
+       traffic_prog = (bool)rds_data->msb & 0x04;
+       if (handle->tp != traffic_prog) {
+               handle->tp = 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 */
+       pty = (rds_data->msb << 3) | (rds_data->lsb >> 5);
+       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 == pty) {
+               priv_state->new_pty = pty;
+               return updated_fields;
+       }
+
+       if (priv_state->new_pty == pty) {
+               handle->pty = priv_state->new_pty;
+               updated_fields |= V4L2_RDS_PTY;
+               handle->valid_fields |= V4L2_RDS_PTY;
+       } else {
+               priv_state->new_pty = 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;
+}
+
+/* extracts the AF information from Block 3 of type 0A groups, and tries
+ * to add them to the AF list with a helper function */
+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;
+
+       /* the 4 8-bit values in the block's data fields (c_msb/c_lsb,
+        * d_msb/d_lsb) represent either a carrier frequency (1..204)
+        * or a special meaning (205..255).
+        * Translation tables can be found in IEC 62106 section 6.2.1.6 */
+
+       /* 250: LF / MF frequency follows */
+       if (c_msb == 250) {
+               if (rds_add_af_to_list(af_set, c_lsb, false))
+                       updated_af = true;
+               c_lsb = 0; /* invalidate */
+       }
+       /* 224..249: announcement of AF count (224=0, 249=25)*/
+       if (c_msb >= 224 && c_msb <= 249)
+               af_set->announced_af = c_msb - 224;
+       /* check if the data represents an AF (for 1 =< val <= 204 the
+        * value represents an AF) */
+       if (c_msb < 205)
+               if (rds_add_af_to_list(af_set, c_msb, true))
+                       updated_af = true;
+       if (c_lsb < 205)
+               if (rds_add_af_to_list(af_set, c_lsb, true))
+                       updated_af = true;
+       /* 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 = 0;
+
+       /* 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->next_di_segment) {
+               switch (segment) {
+               case 0:
+                       priv_state->new_di = set_bit(priv_state->new_di,
+                               V4L2_RDS_FLAG_STEREO, bit2);
+                       priv_state->next_di_segment = 1;
+                       break;
+               case 1:
+                       priv_state->new_di = set_bit(priv_state->new_di,
+                               V4L2_RDS_FLAG_ARTIFICIAL_HEAD, bit2);
+                       priv_state->next_di_segment = 2;
+                       break;
+               case 2:
+                       priv_state->new_di = set_bit(priv_state->new_di,
+                               V4L2_RDS_FLAG_COMPRESSED, bit2);
+                       priv_state->next_di_segment = 3;
+                       break;
+               case 3:
+                       priv_state->new_di = set_bit(priv_state->new_di,
+                               V4L2_RDS_FLAG_STATIC_PTY, bit2);
+                       /* check if the value of DI has changed, and store
+                        * and signal DI update in case */
+                       if (handle->di != priv_state->new_di) {
+                               handle->di = priv_state->new_di;
+                               updated_fields |= V4L2_RDS_DI;
+                       }
+                       priv_state->next_di_segment = 0;
+                       handle->valid_fields |= V4L2_RDS_DI;
+                       break;
+               }
+       } else {
+               /* wrong order of DI segments -> restart */
+               priv_state->next_di_segment = 0;
+               priv_state->new_di = 0;
+       }
+
+       /* 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 = 0;
+       uint8_t variant_code = 0;
+
+       /* 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 0;
+
+       /* bit 14-12 of block c contain the variant code */
+       variant_code = (grp->data_c_msb >> 4) & 0x07;
+       if (variant_code == 0) {
+               /* var 0 -> 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 = 0;
+
+       /* 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, 0, 64);
+               handle->valid_fields &= ~V4L2_RDS_RT;
+               updated_fields |= V4L2_RDS_RT;
+               priv_state->next_rt_segment = 0;
+       }
+
+       /* 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->next_rt_segment) {
+                       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->next_rt_segment = segment + 1;
+                       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;
+                               }
+                               priv_state->next_rt_segment = 0;
+                       }
+               }
+       } else {
+               if (segment == 0 || segment == priv_state->next_rt_segment) {
+                       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->next_rt_segment = segment + 1;
+                       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;
+                               }
+                               priv_state->next_rt_segment = 0;
+                       }
+               }
+       }
+
+       /* 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) {
+                       /* replace CR with terminating character */
+                       priv_state->new_rt[i] = '\0';
+                       handle->rt_length = i;
+                       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;
+                               }
+                       priv_state->next_rt_segment = 0;
+               }
+       }
+       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 = 0;
+
+       if (grp->group_version != 'A')
+               return 0;
+
+       /* 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;
+}
+
+/* decodes the RDS date/time representation into a standard c representation
+ * that can be used with c-library functions */
+static time_t rds_decode_mjd(const struct rds_private_state *priv_state)
+{
+       struct tm new_time;
+       int y, m, d, k = 0;
+       /* offset is given in multiples of half hrs */
+       uint32_t offset = priv_state->utc_offset & 0x1f;
+       uint32_t local_mjd = priv_state->new_mjd;
+       uint8_t local_hour = priv_state->utc_hour;
+       uint8_t local_minute = priv_state->utc_minute;
+
+       /* add / subtract the local offset to get the local time */
+       /* local offset is expressed in multiples of half hours */
+       if (priv_state->utc_offset & 0x20) { /* bit 5 indicates -/+ */
+               local_hour -= (offset * 2);
+               local_minute -= (offset % 2) * 30;
+       } else {
+               local_hour += (offset * 2);
+               local_minute += (offset % 2) * 30;
+       }
+
+       /* the formulas for the conversion are taken from Annex G of the
+        * IEC 62106 RDS standard */
+       y = (int)((local_mjd - 15078.2) / 365.25);
+       m = (int)((local_mjd - 14956.1 - (int)(y * 365.25)) / 30.6001);
+       d = (int)(local_mjd - 14956 - (int)(y * 365.25) - (int)(m * 30.6001));
+       if (m == 14 || m == 15)
+               k = 1;
+       y = y + k;
+       m = m - 1 - k*12;
+
+       /* put the values into a tm struct for conversion into time_t value */
+       new_time.tm_sec = 0;
+       new_time.tm_min = priv_state->utc_minute;
+       new_time.tm_hour = priv_state->utc_hour;
+       new_time.tm_mday = d;
+       new_time.tm_mon = m;
+       new_time.tm_year = y;
+       if (priv_state->utc_offset & 0x20) /* bit 5 indicates -/+ */
+               new_time.tm_gmtoff = -2 * offset * 3600;
+       else
+               new_time.tm_gmtoff = 2 * offset * 3600;
+
+       /* convert tm struct to time_t value and return it */
+       return mktime(&new_time);
+}
+
+/* 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 mjd;
+       uint32_t updated_fields = 0;
+
+       if (grp->group_version != 'A')
+               return 0;
+
+       /* bits 0-1 of block b lsb contain bits 15 and 16 of Julian day code
+        * bits 0-7 of block c msb contain bits 7 to 14 of Julian day code
+        * bits 1-7 of block c lsb contain bits 0 to 6 of Julian day code */
+       mjd = ((grp->data_b_lsb & 0x03) << 15) |
+               (grp->data_c_msb << 7) | (grp->data_c_lsb >> 1);
+       /* the same mjd has to be received twice in order to accept the data */
+       if (priv_state->new_mjd != mjd) {
+               priv_state->new_mjd = mjd;
+               return 0;
+       }
+       /* same mjd received at least twice --> decode time & date */
+
+       /* 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 */
+       priv_state->utc_hour = ((grp->data_c_lsb & 0x01) << 4) |
+               (grp->data_d_msb >> 4);
+
+       /* 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 */
+       priv_state->utc_minute = ((grp->data_d_msb & 0x0f) << 2) |
+               (grp->data_d_lsb >> 6);
+
+       /* bits 0-5 of block d lsb contain bits 0 to 5 of local time offset */
+       priv_state->utc_offset = grp->data_d_lsb & 0x3f;
+
+       /* decode RDS time representation into commonly used c representation */
+       handle->time = rds_decode_mjd(priv_state);
+       updated_fields |= V4L2_RDS_TIME;
+       handle->valid_fields |= V4L2_RDS_TIME;
+       printf("\nLIB: time_t: %ld", handle->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 = 0;
+       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 0;
+
+       /* 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 0;
+}
+
+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 = 0;
+       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 0;
+}
+
+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 defined 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"
+       }, {
+               NULL, "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;
+       const char *language;
+
+       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",
+               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+               NULL, "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", "Azerbaijani",
+               "Assamese", "Armenian", "Arabic", "Amharic"
+       };
+
+       /* filter invalid values and undefined table entries */
+       language = (lc > max_lc) ? "Unknown" : language_lut[lc];
+       if (!language)
+               return "Unknown";
+       return language;
+}
+
+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

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to