From 352dad23c7847d234e11c1034e1354fbd9a8349a Mon Sep 17 00:00:00 2001 From: "Javier S. Pedro" Date: Sat, 31 Dec 2011 17:50:06 +0100 Subject: initial import --- rds.c | 665 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 665 insertions(+) create mode 100644 rds.c (limited to 'rds.c') diff --git a/rds.c b/rds.c new file mode 100644 index 0000000..ed116a2 --- /dev/null +++ b/rds.c @@ -0,0 +1,665 @@ +/* + * fmrxd - a daemon to enable and multiplex access to the N950/N9 radio tuner + * Copyright (C) 2011 Javier S. Pedro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include +#include +#include + +#include "fmrxd.h" + +#if CRAPPY_WL1273_RDS +#define WL1273_RDS_BLOCK_A 0 +#define WL1273_RDS_BLOCK_B 1 +#define WL1273_RDS_BLOCK_C 2 +#define WL1273_RDS_BLOCK_C_ALT 3 +#define WL1273_RDS_BLOCK_D 4 +#define WL1273_RDS_BLOCK_E 5 +#define WL1273_RDS_BLOCK_MASK 0x7 +#define WL1273_RDS_CORRECTABLE_ERROR (1 << 3) +#define WL1273_RDS_UNCORRECTABLE_ERROR (1 << 4) +#endif + +#define RDS_PS_SIZE 8 + +#define RDS_RT_2A_SIZE 64 +#define RDS_RT_2B_SIZE 32 +#define RDS_RT_MAX_SIZE RDS_RT_2A_SIZE + +enum { + RDS_TYPE_0A = 0, + RDS_TYPE_0B = 1, + RDS_TYPE_1A = 2, + RDS_TYPE_1B = 3, + RDS_TYPE_2A = 4, + RDS_TYPE_2B = 5 +}; + +#define RDS_TYPE_IS_B(type) (type & 1) + +static guint rds_watch = 0; + +static uint16_t rds_pi; +static bool rds_pi_notified; + +static bool rds_tp; +static uint8_t rds_pty; + +static char rds_ps[RDS_PS_SIZE+1]; +static uint8_t rds_ps_segs; +static bool rds_ps_notified; + +static char rds_rt[RDS_RT_MAX_SIZE+1]; +static uint32_t rds_rt_segs; +static int8_t rds_rt_ab; +static bool rds_rt_notified; + +static int8_t last_type; +static int8_t last_spare; + +static void rds_decode(char *dst, const char *src); + +static void rds_pi_reset() +{ + rds_pi = 0; + rds_pi_notified = false; +} + +static void rds_pi_maybe_notify() +{ + if (!rds_pi_notified) { + server_notify_pi(rds_pi); + rds_pi_notified = true; + } +} + +static void rds_ps_reset() +{ + memset(rds_ps, '\0', sizeof(rds_ps)); + rds_ps_segs = 0; + rds_ps_notified = false; +} + +static void rds_ps_add(int seg, uint8_t msb, uint8_t lsb) +{ + g_return_if_fail(seg >= 0 && seg < 4); + rds_ps_segs |= 1 << seg; + rds_ps[2*seg+0] = msb; + rds_ps[2*seg+1] = lsb; +} + +static bool rds_ps_complete() +{ + return (rds_ps_segs & 0xf) == 0xf; +} + + +static void rds_ps_maybe_notify() +{ + if (!rds_ps_notified && rds_ps_complete()) { + // Each RDS char can be up to two UTF-8 bytes. + // 6 of extra safety margin (max size of UTF-8 char). + char decoded_ps[(RDS_PS_SIZE * 2) + 6]; + rds_decode(decoded_ps, rds_ps); + server_notify_ps(decoded_ps); + rds_ps_notified = true; + } +} + +static void rds_rt_reset(int new_ab) +{ + memset(rds_rt, '\0', sizeof(rds_rt)); + rds_rt_segs = 0; + rds_rt_ab = new_ab; + rds_rt_notified = false; +} + +static void rds_rt_add(bool is_type_2b, bool is_block_d, int ab, int seg, + uint8_t msb, uint8_t lsb) +{ + if (ab != rds_rt_ab) { + rds_rt_reset(ab); + } + if (is_type_2b) { + g_warn_if_fail(is_block_d); + rds_rt[RDS_RT_2B_SIZE] = '\r'; // Type2B RadioText is only 32bytes + rds_rt_segs |= 0xffff0000u; // Mark the upper segments as valid. + } else { + seg = 2*seg + (is_block_d ? 1 : 0); + } + + g_return_if_fail(seg >= 0 && seg < (RDS_RT_MAX_SIZE/2)); + + rds_rt_segs |= 1 << seg; + rds_rt[2*seg+0] = msb; + rds_rt[2*seg+1] = lsb; +} + +static bool rds_rt_complete() +{ + char *cr = strchr(rds_rt, '\r'); + if (cr) { + /* Only complete if all segments received up to the one with \r. */ + int index_of_cr = cr - rds_rt; + int seg_of_cr = index_of_cr / 2; + uint32_t mask = (1 << (seg_of_cr + 1)) - 1; + return (rds_rt_segs & mask) == mask; + } else { + /* Otherwise, only complete if all segments received. */ + return rds_rt_segs == 0xffffffffu; + } +} + +static void rds_rt_maybe_notify() +{ + if (!rds_rt_notified && rds_rt_complete()) { + // Each RDS char can be up to two UTF-8 bytes. + char decoded_rt[(RDS_RT_MAX_SIZE * 2) + 6]; + rds_decode(decoded_rt, rds_rt); + server_notify_rt(decoded_rt); + rds_rt_notified = true; + } +} +static gboolean rds_callback(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + struct v4l2_rds_data rds; + int res = read(tuner_fd, &rds, sizeof(rds)); + if (res < sizeof(rds)) { + g_warning("Failed to read RDS information"); + return TRUE; // Not fatal + } + + int block; + bool error; + +#if CRAPPY_WL1273_RDS + block = rds.block & WL1273_RDS_BLOCK_MASK; + // The following is, in a "more" readable way, swapping 3 and 4. + switch (block) { + case WL1273_RDS_BLOCK_C_ALT: + block = V4L2_RDS_BLOCK_C_ALT; + break; + case WL1273_RDS_BLOCK_D: + block = V4L2_RDS_BLOCK_D; + break; + } + error = rds.block & WL1273_RDS_UNCORRECTABLE_ERROR; +#else + block = rds.block & V4L2_RDS_BLOCK_MSK; + error = rds.block & V4L2_RDS_BLOCK_ERROR; +#endif + + if (error) { + g_debug("Incorrectable error in RDS block"); + return TRUE; // Skip block + } + + switch (block) { + case V4L2_RDS_BLOCK_A: + rds_pi = (rds.msb << 8) | rds.lsb; + rds_pi_maybe_notify(); + break; + case V4L2_RDS_BLOCK_B: + last_type = (rds.msb >> 3) & 0x1f; + last_spare = rds.lsb & 0x1f; + + rds_tp = (rds.msb >> 2) & 1; + rds_pty = ((rds.msb << 3) & 0x18) | ((rds.lsb >> 5) & 0x7); + break; + case V4L2_RDS_BLOCK_C: + if (RDS_TYPE_IS_B(last_type)) return TRUE; + // Desynchronization? Skip block + + switch (last_type) { + case RDS_TYPE_0A: + // Alternative frequency + // TODO + break; + case RDS_TYPE_2A: + // RadioText + rds_rt_add(false, false, + (last_spare & 0x10) >> 4, last_spare & 0x0f, + rds.msb, rds.lsb); + rds_rt_maybe_notify(); + break; + } + + break; + case V4L2_RDS_BLOCK_C_ALT: + if (!RDS_TYPE_IS_B(last_type)) return TRUE; + + switch (last_type) { + case RDS_TYPE_0B: + // PI + // TODO: Compare with one received in A? + break; + } + break; + case V4L2_RDS_BLOCK_D: + switch (last_type) { + case RDS_TYPE_0A: + case RDS_TYPE_0B: + // Program Service Name + rds_ps_add(last_spare & 0x03, rds.msb, rds.lsb); + rds_ps_maybe_notify(); + break; + case RDS_TYPE_2A: + case RDS_TYPE_2B: + // RadioText + rds_rt_add(RDS_TYPE_IS_B(last_type), true, + (last_spare & 0x10) >> 4, last_spare & 0x0f, + rds.msb, rds.lsb); + rds_rt_maybe_notify(); + break; + } + break; + default: + break; + } + + return TRUE; +} + +bool configure_rds(bool on) +{ + rds_reset(); + + if (on) { + g_return_val_if_fail(tuner_fd != -1, false); + GIOChannel *channel = g_io_channel_unix_new(tuner_fd); + rds_watch = g_io_add_watch(channel, G_IO_IN, rds_callback, NULL); + g_io_channel_unref(channel); + } else { + if (rds_watch) { + g_source_remove(rds_watch); + rds_watch = 0; + } + } + + return true; +} + +void rds_reset() +{ + last_type = -1; + last_spare = -1; + rds_pi_reset(); + rds_tp = false; + rds_pty = 0; + rds_ps_reset(); + rds_rt_reset(-1); +} + +unsigned short rds_get_pi() +{ + return rds_pi; +} + +unsigned char rds_get_pty() +{ + return rds_pty; +} + +const char * rds_get_pty_text() +{ + switch (rds_pty) { + case 0: + default: + return "None"; + case 1: + return "News"; + case 2: + return "Current affairs"; + case 3: + return "Information"; + case 4: + return "Sport"; + case 5: + return "Education"; + case 6: + return "Drama"; + case 7: + return "Culture"; + case 8: + return "Science"; + case 9: + return "Varied"; + case 10: + return "Pop music"; + case 11: + return "Rock music"; + case 12: + return "Easy listening music"; + case 13: + return "Light classical"; + case 14: + return "Serious classical"; + case 15: + return "Other music"; + case 16: + return "Weather"; + case 17: + return "Finance"; + case 18: + return "Children's programmes"; + case 19: + return "Social affairs"; + case 20: + return "Religion"; + case 21: + return "Phone In"; + case 22: + return "Travel"; + case 23: + return "Leisure"; + case 24: + return "Jazz music"; + case 25: + return "Country music"; + case 26: + return "National music"; + case 27: + return "Oldies music"; + case 28: + return "Folk music"; + case 29: + return "Documentary"; + case 30: + return "Alarm test"; + case 31: + return "Alarm"; + } +} + +/* This table comes from pyFMRadio source code. */ +static const gunichar rds_charset_table[256] = { + 0, /* 0 */ + 0, /* 1 */ + 0, /* 2 */ + 0, /* 3 */ + 0, /* 4 */ + 0, /* 5 */ + 0, /* 6 */ + 0, /* 7 */ + 0, /* 8 */ + 0, /* 9 */ + 10, /* 10 */ + 0, /* 11 */ + 0, /* 12 */ + 0, /* 13 */ + 0, /* 14 */ + 0, /* 15 */ + 0, /* 16 */ + 0, /* 17 */ + 0, /* 18 */ + 0, /* 19 */ + 0, /* 20 */ + 0, /* 21 */ + 0, /* 22 */ + 0, /* 23 */ + 0, /* 24 */ + 0, /* 25 */ + 0, /* 26 */ + 0, /* 27 */ + 0, /* 28 */ + 0, /* 29 */ + 0, /* 30 */ + 0, /* 31 */ + 32, /* 32 */ + 33, /* 33 */ + 34, /* 34 */ + 35, /* 35 */ + 164, /* 36 */ + 37, /* 37 */ + 38, /* 38 */ + 39, /* 39 */ + 40, /* 40 */ + 41, /* 41 */ + 42, /* 42 */ + 43, /* 43 */ + 44, /* 44 */ + 45, /* 45 */ + 46, /* 46 */ + 47, /* 47 */ + 48, /* 48 */ + 49, /* 49 */ + 50, /* 50 */ + 51, /* 51 */ + 52, /* 52 */ + 53, /* 53 */ + 54, /* 54 */ + 55, /* 55 */ + 56, /* 56 */ + 57, /* 57 */ + 58, /* 58 */ + 59, /* 59 */ + 60, /* 60 */ + 61, /* 61 */ + 62, /* 62 */ + 63, /* 63 */ + 64, /* 64 */ + 65, /* 65 */ + 66, /* 66 */ + 67, /* 67 */ + 68, /* 68 */ + 69, /* 69 */ + 70, /* 70 */ + 71, /* 71 */ + 72, /* 72 */ + 73, /* 73 */ + 74, /* 74 */ + 75, /* 75 */ + 76, /* 76 */ + 77, /* 77 */ + 78, /* 78 */ + 79, /* 79 */ + 80, /* 80 */ + 81, /* 81 */ + 82, /* 82 */ + 83, /* 83 */ + 84, /* 84 */ + 85, /* 85 */ + 86, /* 86 */ + 87, /* 87 */ + 88, /* 88 */ + 89, /* 89 */ + 90, /* 90 */ + 91, /* 91 */ + 92, /* 92 */ + 93, /* 93 */ + 32, /* 94 */ + 32, /* 95 */ + 32, /* 96 */ + 97, /* 97 */ + 98, /* 98 */ + 99, /* 99 */ + 100, /* 100 */ + 101, /* 101 */ + 102, /* 102 */ + 103, /* 103 */ + 104, /* 104 */ + 105, /* 105 */ + 106, /* 106 */ + 107, /* 107 */ + 108, /* 108 */ + 109, /* 109 */ + 110, /* 110 */ + 111, /* 111 */ + 112, /* 112 */ + 113, /* 113 */ + 114, /* 114 */ + 115, /* 115 */ + 116, /* 116 */ + 117, /* 117 */ + 118, /* 118 */ + 119, /* 119 */ + 120, /* 120 */ + 121, /* 121 */ + 122, /* 122 */ + 123, /* 123 */ + 124, /* 124 */ + 125, /* 125 */ + 32, /* 126 */ + 32, /* 127 */ + 225, /* 128 */ + 224, /* 129 */ + 233, /* 130 */ + 232, /* 131 */ + 237, /* 132 */ + 236, /* 133 */ + 243, /* 134 */ + 242, /* 135 */ + 250, /* 136 */ + 249, /* 137 */ + 209, /* 138 */ + 199, /* 139 */ + 350, /* 140 */ + 223, /* 141 */ + 161, /* 142 */ + 306, /* 143 */ + 226, /* 144 */ + 228, /* 145 */ + 234, /* 146 */ + 235, /* 147 */ + 238, /* 148 */ + 239, /* 149 */ + 244, /* 150 */ + 246, /* 151 */ + 251, /* 152 */ + 252, /* 153 */ + 241, /* 154 */ + 231, /* 155 */ + 351, /* 156 */ + 287, /* 157 */ + 63, /* 158 */ + 307, /* 159 */ + 170, /* 160 */ + 945, /* 161 */ + 169, /* 162 */ + 8240, /* 163 */ + 486, /* 164 */ + 277, /* 165 */ + 328, /* 166 */ + 337, /* 167 */ + 960, /* 168 */ + 63, /* 169 */ + 163, /* 170 */ + 36, /* 171 */ + 8592, /* 172 */ + 8593, /* 173 */ + 8594, /* 174 */ + 8595, /* 175 */ + 186, /* 176 */ + 185, /* 177 */ + 178, /* 178 */ + 179, /* 179 */ + 177, /* 180 */ + 304, /* 181 */ + 324, /* 182 */ + 369, /* 183 */ + 956, /* 184 */ + 191, /* 185 */ + 247, /* 186 */ + 176, /* 187 */ + 188, /* 188 */ + 189, /* 189 */ + 190, /* 190 */ + 167, /* 191 */ + 193, /* 192 */ + 192, /* 193 */ + 201, /* 194 */ + 200, /* 195 */ + 205, /* 196 */ + 204, /* 197 */ + 211, /* 198 */ + 210, /* 199 */ + 218, /* 200 */ + 217, /* 201 */ + 344, /* 202 */ + 268, /* 203 */ + 352, /* 204 */ + 381, /* 205 */ + 272, /* 206 */ + 317, /* 207 */ + 194, /* 208 */ + 196, /* 209 */ + 202, /* 210 */ + 203, /* 211 */ + 206, /* 212 */ + 207, /* 213 */ + 212, /* 214 */ + 214, /* 215 */ + 219, /* 216 */ + 220, /* 217 */ + 345, /* 218 */ + 269, /* 219 */ + 353, /* 220 */ + 382, /* 221 */ + 271, /* 222 */ + 318, /* 223 */ + 195, /* 224 */ + 197, /* 225 */ + 198, /* 226 */ + 338, /* 227 */ + 375, /* 228 */ + 221, /* 229 */ + 213, /* 230 */ + 216, /* 231 */ + 254, /* 232 */ + 330, /* 233 */ + 340, /* 234 */ + 262, /* 235 */ + 346, /* 236 */ + 377, /* 237 */ + 63, /* 238 */ + 240, /* 239 */ + 227, /* 240 */ + 229, /* 241 */ + 230, /* 242 */ + 339, /* 243 */ + 373, /* 244 */ + 253, /* 245 */ + 245, /* 246 */ + 248, /* 247 */ + 254, /* 248 */ + 331, /* 249 */ + 341, /* 250 */ + 263, /* 251 */ + 347, /* 252 */ + 378, /* 253 */ + 63, /* 254 */ + 32, /* 255 */ +}; + +static void rds_decode(char *dst, const char *src) +{ + guchar *s = (guchar*) src; + gchar *d = (gchar*) dst; + while (*s) { + gunichar unichar = rds_charset_table[*s]; + gint bytes = g_unichar_to_utf8(unichar, d); + g_warn_if_fail(bytes <= 2); + if (unichar == '\0') return; + s++; + d += bytes; + } + *d = '\0'; +} -- cgit v1.2.3