Signed-off-by: Konke Radlow <korad...@gmail.com>
---
 lib/include/libv4l2rds.h    |   39 +++++++++++
 lib/libv4l2rds/libv4l2rds.c |  159 +++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 194 insertions(+), 4 deletions(-)

diff --git a/lib/include/libv4l2rds.h b/lib/include/libv4l2rds.h
index 62b28bc..45dc2d1 100644
--- a/lib/include/libv4l2rds.h
+++ b/lib/include/libv4l2rds.h
@@ -50,6 +50,10 @@ extern "C" {
                        * Additional data is limited to 112 bit, and the 
smallest
                        * optional tuple has a size of 4 bit (4 bit identifier +
                        * 0 bits of data) */
+#define MAX_TMC_ALT_STATIONS 32 /* defined by ISO 14819-1:2003, 7.5.3.3  */
+#define MAX_TMC_AF_CNT 4       /* limit for the numbers of AFs stored per 
alternative TMC
+                       * station. This value is not defined by the standard, 
but based on observation
+                       * of real-world RDS-TMC streams */
 #define MAX_EON_CNT 20 /* Maximal number of entries in the EON table (for 
storing
                        * information about other radio stations, broadcasted
                        * by the current station) */
@@ -75,6 +79,7 @@ extern "C" {
 #define V4L2_RDS_TMC_SYS       0x10000 /* RDS-TMC system information */
 #define V4L2_RDS_EON           0x20000 /* Enhanced Other Network Info */
 #define V4L2_RDS_LSF           0x40000 /* Linkage information */
+#define V4L2_RDS_TMC_TUNING    0x80000 /* RDS-TMC tuning information */
 
 /* Define Constants for the state of the RDS decoding process
  * used to address the relevant bit in the decode_information bitmask */
@@ -175,6 +180,37 @@ struct v4l2_rds_eon_set {
                                                 * radio channels */
 };
 
+/* struct to encapsulate alternative frequencies (AFs) for RDS-TMC stations.
+ * AFs listed in af[] can be used unconditionally. 
+ * AFs listed in mapped_af[n] should only be used if the current 
+ * tuner frequency matches the value in mapped_af_tuning[n] */
+struct v4l2_tmc_alt_freq {
+       uint8_t af_size;                /* number of known AFs */
+       uint8_t af_index;
+       uint8_t mapped_af_size;         /* number of mapped AFs */
+       uint8_t mapped_af_index;
+       uint32_t af[MAX_TMC_AF_CNT];            /* AFs defined in Hz */
+       uint32_t mapped_af[MAX_TMC_AF_CNT];             /* mapped AFs defined 
in Hz */
+       uint32_t mapped_af_tuning[MAX_TMC_AF_CNT];      /* mapped AFs defined 
in Hz */
+};
+
+/* struct to encapsulate information about stations carrying RDS-TMC services 
*/
+struct v4l2_tmc_station {
+       uint16_t pi;
+       uint8_t ltn;    /* database-ID of ON */
+       uint8_t msg;    /* msg parameters of ON */
+       uint8_t sid;    /* service-ID of ON */
+       struct v4l2_tmc_alt_freq afi;
+};
+
+/* struct to encapsulate tuning information for TMC */
+struct v4l2_tmc_tuning {
+       uint8_t station_cnt;    /* number of announced alternative stations */
+       uint8_t index;
+       struct v4l2_tmc_station station[MAX_TMC_ALT_STATIONS];  /* information
+                                                       * about other stations 
carrying the same RDS-TMC service */
+};
+
 /* struct to encapsulate an additional data field in a TMC message */
 struct v4l2_tmc_additional {
        uint8_t label;
@@ -225,6 +261,9 @@ struct v4l2_rds_tmc {
        uint8_t t_d;            /* delay time (only if mode = enhanced */
        uint8_t spn[9];         /* service provider name */
        struct v4l2_rds_tmc_msg tmc_msg;
+
+       /* tuning information for alternative service providers */
+       struct v4l2_tmc_tuning tuning;
 };
 
 /* struct to encapsulate state and RDS information for current decoding 
process */
diff --git a/lib/libv4l2rds/libv4l2rds.c b/lib/libv4l2rds/libv4l2rds.c
index 3a90a3b..3995c3d 100644
--- a/lib/libv4l2rds/libv4l2rds.c
+++ b/lib/libv4l2rds/libv4l2rds.c
@@ -93,7 +93,9 @@ enum rds_state {
 };
 
 /* function declarations to prevent the need to move large code blocks */
+static int rds_add_tmc_station(struct rds_private_state *priv_state, uint16_t 
pi);
 static uint32_t rds_decode_af(uint8_t af, bool is_vhf);
+static bool rds_add_tmc_af(struct rds_private_state *priv_state);
 
 static inline uint8_t set_bit(uint8_t input, uint8_t bitmask, bool bitvalue)
 {
@@ -437,6 +439,59 @@ static uint32_t rds_decode_tmc_multi_group(struct 
rds_private_state *priv_state)
        return V4L2_RDS_TMC_MG;
 }
 
+/* decode the RDS-TMC tuning information that is contained in type 8A groups
+ * (variants 4 to 9) that announce the presence alternative transmitters 
+ * providing the same RDS-TMC service */
+static uint32_t rds_decode_tmc_tuning(struct rds_private_state *priv_state)
+{
+       struct v4l2_rds_group *group = &priv_state->rds_group;
+       struct v4l2_rds_tmc *tmc = &priv_state->handle.tmc;
+       uint8_t variant_code = group->data_b_lsb & 0x0f;
+       uint16_t pi_on = (group->data_d_msb << 8) | group->data_d_lsb;
+       uint8_t index;
+
+       /* variants 4 and 5 carry the service provider name */
+       if (variant_code >= 4 && variant_code <= 5) {
+               int offset = 4 * (variant_code - 4);
+               tmc->spn[0 + offset] = group->data_c_msb;
+               tmc->spn[1 + offset] = group->data_c_lsb;
+               tmc->spn[2 + offset] = group->data_d_msb;
+               tmc->spn[3 + offset] = group->data_d_lsb;
+
+       /* variant 6 provides specific frequencies for the same RDS-TMC service
+        * on a network with a different PI code */
+       /* variant 7 provides mapped frequency pair information which should 
only
+        * be used if the terminal is tuned to the tuning frequency */
+       } else if (variant_code == 6 || variant_code == 7) {
+               rds_add_tmc_af(priv_state);
+
+       /* variant 8 indicates up to 2 PI codes of adjacent networks carrying 
+        * the same RDS-TMC service on all transmitters of the network */ 
+       } else if (variant_code == 8) {
+               uint16_t pi_on_2 = (group->data_c_msb << 8) | group->data_c_lsb;
+
+               /* try to add both transmitted PI codes to the table */
+               rds_add_tmc_station(priv_state, pi_on);
+               /* PI = 0 is used as a filler code */
+               if (pi_on_2 != 0) rds_add_tmc_station(priv_state, pi_on_2);
+
+       /* variant 9 provides PI codes of other networks with different system 
+        * parameters */
+       } else if (variant_code == 9) {
+               index = rds_add_tmc_station(priv_state, pi_on);
+
+               /* bits 0 - 5 contain the service-ID of the ON */
+               tmc->tuning.station[index].sid = group->data_c_lsb & 0x3F;
+               /* bits 6-10 contain the msg parameters of the ON */
+               tmc->tuning.station[index].msg = (group->data_c_msb & 0x03) << 
2;
+               tmc->tuning.station[index].msg |= (group->data_c_lsb >> 6) & 
0x03;
+               /* bits 11-15 contain the database-ID of the ON */
+               tmc->tuning.station[index].ltn = group->data_c_msb >> 2;
+       }
+
+       return V4L2_RDS_TMC_TUNING;
+}
+
 static bool rds_add_oda(struct rds_private_state *priv_state, struct 
v4l2_rds_oda oda)
 {
        struct v4l2_rds *handle = &priv_state->handle;
@@ -516,6 +571,103 @@ static bool rds_add_af(struct rds_private_state 
*priv_state)
        return updated_af;
 }
 
+/* checks if an entry for the given PI already exists and returns the index
+ * of that entry if so. Else it adds a new entry to the TMC-Tuning table and 
returns
+ * the index of the new field */
+static int rds_add_tmc_station(struct rds_private_state *priv_state, uint16_t 
pi)
+{
+       struct v4l2_tmc_tuning *tuning = &priv_state->handle.tmc.tuning;
+       uint8_t index = tuning->index;
+       uint8_t size = tuning->station_cnt;
+       
+       /* check if there's an entry for the given PI key */
+       for (int i = 0; i < tuning->station_cnt; i++) {
+               if (tuning->station[i].pi == pi) {
+                       return i;
+               }
+       }
+       /* if the the maximum table size is reached, overwrite old
+        * entries, starting at the oldest one = 0 */
+       tuning->station[index].pi = pi;
+       tuning->index = (index+1 < MAX_TMC_ALT_STATIONS)? (index+1) : 0;
+       tuning->station_cnt = (size+1 <= MAX_TMC_ALT_STATIONS)? (size+1) : 
MAX_TMC_ALT_STATIONS;
+       return index;
+}
+
+/* tries to add new AFs to the relevant entry in the list of RDS-TMC providers 
*/
+static bool rds_add_tmc_af(struct rds_private_state *priv_state)
+{
+       struct v4l2_rds_group *grp = &priv_state->rds_group;
+       struct v4l2_tmc_alt_freq *afi;
+       uint16_t pi_on = grp->data_d_msb << 8 | grp->data_d_lsb;
+       uint8_t variant = grp->data_b_lsb & 0x0f;
+       uint8_t station_index = rds_add_tmc_station(priv_state, pi_on);
+       uint8_t af_index;
+       uint8_t mapped_af_index;
+       uint32_t freq_a = rds_decode_af(grp->data_c_msb, true);
+       uint32_t freq_b = rds_decode_af(grp->data_c_lsb, true);
+       
+       afi = &priv_state->handle.tmc.tuning.station[station_index].afi;
+       af_index = afi->af_index;
+       mapped_af_index = afi->mapped_af_index;
+
+       /* specific frequencies */
+       if (variant == 6) {
+               /* compare the new AFs to the stored ones, reset them to 0 if 
the AFs are
+                * already known */
+               for (int i = 0; i < afi->af_size; i++) {
+                       freq_a = (freq_a == afi->af[i])? 0 : freq_a;
+                       freq_b = (freq_b == afi->af[i])? 0 : freq_b;
+               }
+               /* return early if there is nothing to do */
+               if (freq_a == 0 && freq_b == 0)
+                       return false;
+
+               /* add the new AFs if they were previously unknown */
+               if (freq_a != 0) {
+                       afi->af[af_index] = freq_a;
+                       af_index = (af_index+1 < MAX_TMC_AF_CNT)? af_index+1 : 
0;
+                       afi->af_size++; 
+               }
+               if (freq_b != 0) {
+                       afi->af[af_index] = freq_b;
+                       af_index = (af_index+1 < MAX_TMC_AF_CNT)? af_index+1 : 
0;
+                       afi->af_size++; 
+               }
+               /* update the information in the handle */
+               afi->af_index = af_index;
+               if (afi->af_size >= MAX_TMC_AF_CNT) 
+                       afi->af_size = MAX_TMC_AF_CNT;
+
+               return true;
+       }
+
+       /* mapped frequency pair */
+       else if (variant == 7) {
+               /* check the if there's already a frequency mapped to the new 
tuning
+                * frequency, update the mapped frequency in this case */
+               for (int i = 0; i < afi->mapped_af_size; i++) {
+                       if (freq_a == afi->mapped_af_tuning[i])
+                               afi->mapped_af[i] = freq_b;
+                               return true;
+               }
+               /* new pair is unknown, add it to the list */
+               if (freq_a != 0 && freq_b != 0) {
+                       mapped_af_index = (mapped_af_index+1 >= 
MAX_TMC_AF_CNT)? 0 : mapped_af_index + 1;
+                       afi->mapped_af[mapped_af_index] = freq_b;
+                       afi->mapped_af_tuning[mapped_af_index] = freq_a;
+                       afi->mapped_af_size++; 
+               }
+               /* update the information in the handle */
+               afi->mapped_af_index = mapped_af_index;
+               if (afi->mapped_af_size >= MAX_TMC_AF_CNT) 
+                       afi->mapped_af_size = MAX_TMC_AF_CNT;
+
+               return true;
+       }
+       return false;
+}
+
 /* 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)
@@ -968,13 +1120,12 @@ static uint32_t rds_decode_group8(struct 
rds_private_state *priv_state)
                !(grp->data_b_lsb & V4L2_TMC_TUNING_INFO)) {
                return rds_decode_tmc_multi_group(priv_state);
        }
-       /* -> tuning information message, defined for variants 4..9, submitted
-        * in bits 0-3 of block 2 */
+       /* -> tuning information message, defined for variants 4..9,
+        * submitted in bits 0-3 of block 2 */
        tuning_variant = grp->data_b_lsb & 0x0f;
        if ((grp->data_b_lsb & V4L2_TMC_TUNING_INFO) && tuning_variant >= 4 &&
                tuning_variant <= 9) {
-               /* TODO: Implement tuning information decoding */
-               return 0;
+               return rds_decode_tmc_tuning(priv_state);
        }
 
        return 0;
-- 
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