/* * 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 WORKAROUND_BAD_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 WORKAROUND_BAD_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) { 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'; }