1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 //#define RDS_IMPROVE_CHECK 1
10
11 /*
12 * The RDS decoder bases partly on the source of the VDR radio plugin.
13 * http://www.egal-vdr.de/plugins/
14 * and reworked a bit with references from SPB 490, IEC62106
15 * and several other documents.
16 *
17 * A lot more information is sendet which is currently unused and partly
18 * not required.
19 */
20
21 #include "VideoPlayerRadioRDS.h"
22
23 #include "Application.h"
24 #include "DVDCodecs/DVDCodecs.h"
25 #include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h"
26 #include "DVDDemuxers/DVDDemuxUtils.h"
27 #include "DVDDemuxers/DVDFactoryDemuxer.h"
28 #include "DVDInputStreams/DVDInputStream.h"
29 #include "DVDStreamInfo.h"
30 #include "GUIInfoManager.h"
31 #include "GUIUserMessages.h"
32 #include "ServiceBroker.h"
33 #include "cores/FFmpeg.h"
34 #include "cores/VideoPlayer/Interface/TimingConstants.h"
35 #include "dialogs/GUIDialogKaiToast.h"
36 #include "guilib/GUIComponent.h"
37 #include "guilib/GUIWindowManager.h"
38 #include "guilib/LocalizeStrings.h"
39 #include "interfaces/AnnouncementManager.h"
40 #include "music/tags/MusicInfoTag.h"
41 #include "pictures/Picture.h"
42 #include "pvr/channels/PVRChannel.h"
43 #include "pvr/channels/PVRRadioRDSInfoTag.h"
44 #include "settings/Settings.h"
45 #include "settings/SettingsComponent.h"
46 #include "threads/SingleLock.h"
47 #include "utils/CharsetConverter.h"
48 #include "utils/StringUtils.h"
49 #include "utils/log.h"
50
51 using namespace XFILE;
52 using namespace PVR;
53 using namespace KODI::MESSAGING;
54
55 /**
56 * Universal Encoder Communication Protocol (UECP)
57 * List of defined commands
58 * iaw.: SPB 490
59 */
60
61 /// UECP Message element pointers (different on several commands)
62 #define UECP_ME_MEC 0 // Message Element Code
63 #define UECP_ME_DSN 1 // Data Set Number
64 #define UECP_ME_PSN 2 // Program Service Number
65 #define UECP_ME_MEL 3 // Message Element data Length
66 #define UECP_ME_DATA 4 //
67
68 /// RDS message commands
69 #define UECP_RDS_PI 0x01 // Program Identification
70 #define UECP_RDS_PS 0x02 // Program Service name
71 #define UECP_RDS_PIN 0x06 // Program Item Number
72 #define UECP_RDS_DI 0x04 // Decoder Identification and dynamic PTY indicator
73 #define UECP_RDS_TA_TP 0x03 // Traffic Announcement identification / Traffic Program identification
74 #define UECP_RDS_MS 0x05 // Music/Speech switch
75 #define UECP_RDS_PTY 0x07 // Program TYpe
76 #define UECP_RDS_PTYN 0x3A // Program TYpe Name
77 #define UECP_RDS_RT 0x0A // RadioText
78 #define UECP_RDS_AF 0x13 // Alternative Frequencies list
79 #define UECP_RDS_EON_AF 0x14 // Enhanced Other Networks information
80 #define UECP_SLOW_LABEL_CODES 0x1A // Slow Labeling codes
81 #define UECP_LINKAGE_INFO 0x2E // Linkage information
82
83 /// Open Data Application commands
84 #define UECP_ODA_CONF_SHORT_MSG_CMD 0x40 // ODA configuration and short message command
85 #define UECP_ODA_IDENT_GROUP_USAGE_SEQ 0x41 // ODA identification group usage sequence
86 #define UECP_ODA_FREE_FORMAT_GROUP 0x42 // ODA free-format group
87 #define UECP_ODA_REL_PRIOR_GROUP_SEQ 0x43 // ODA relative priority group sequence
88 #define UECP_ODA_BURST_MODE_CONTROL 0x44 // ODA “Burst mode” control
89 #define UECP_ODA_SPINN_WHEEL_TIMING_CTL 0x45 // ODA “Spinning Wheel” timing control
90 #define UECP_ODA_DATA 0x46 // ODA Data
91 #define UECP_ODA_DATA_CMD_ACCESS_RIGHT 0x47 // ODA data command access right
92
93 /// DAB
94 #define UECP_DAB_DYN_LABEL_CMD 0x48 // DAB: Dynamic Label command
95 #define UECP_DAB_DYN_LABEL_MSG 0xAA // DAB: Dynamic Label message (DL)
96
97 /// Transparent data commands
98 #define UECP_TDC_TDC 0x26 // TDC
99 #define UECP_TDC_EWS 0x2B // EWS
100 #define UECP_TDC_IH 0x25 // IH
101 #define UECP_TDC_TMC 0x30 // TMC
102 #define UECP_TDC_FREE_FMT_GROUP 0x24 // Free-format group
103
104 /// Paging commands
105 #define UECP_PAGING_CALL_WITHOUT_MESSAGE 0x0C
106 #define UECP_PAGING_CALL_NUMERIC_MESSAGE_10DIGITS 0x08
107 #define UECP_PAGING_CALL_NUMERIC_MESSAGE_18DIGITS 0x20
108 #define UECP_PAGING_CALL_ALPHANUMERIC_MESSAGE_80CHARACTERS 0x1B
109 #define UECP_INTERNATIONAL_PAGING_NUMERIC_MESSAGE_15DIGITS 0x11
110 #define UECP_INTERNATIONAL_PAGING_FUNCTIONS_MESSAGE 0x10
111 #define UECP_TRANSMITTER_NETWORK_GROUP_DESIGNATION 0x12
112 #define UECP_EPP_TM_INFO 0x31
113 #define UECP_EPP_CALL_WITHOUT_ADDITIONAL_MESSAGE 0x32
114 #define UECP_EPP_NATIONAL_INTERNATIONAL_CALL_ALPHANUMERIC_MESSAGE 0x33
115 #define UECP_EPP_NATIONAL_INTERNATIONAL_CALL_VARIABLE_LENGTH_NUMERIC_MESSAGE 0x34
116 #define UECP_EPP_NATIONAL_INTERNATIONAL_CALL_VARIABLE_LENGTH_FUNCTIONS_MESSAGE 0x35
117
118 /// Clock setting and control
119 #define UECP_CLOCK_RTC 0x0D // Real time clock
120 #define UECP_CLOCK_RTC_CORR 0x09 // Real time clock correction
121 #define UECP_CLOCK_CT_ON_OFF 0x19 // CT On/Off
122
123 /// RDS adjustment and control
124 #define RDS_ON_OFF 0x1E
125 #define RDS_PHASE 0x22
126 #define RDS_LEVEL 0x0E
127
128 /// ARI adjustment and control
129 #define UECP_ARI_ARI_ON_OFF 0x21
130 #define UECP_ARI_ARI_AREA (BK) 0x0F
131 #define UECP_ARI_ARI_LEVEL 0x1F
132
133 /// Control and set up commands
134 #define UECP_CTR_SITE_ADDRESS 0x23
135 #define UECP_CTR_ENCODER_ADDRESS 0x27
136 #define UECP_CTR_MAKE_PSN_LIST 0x28
137 #define UECP_CTR_PSN_ENABLE_DISABLE 0x0B
138 #define UECP_CTR_COMMUNICATION_MODE 0x2C
139 #define UECP_CTR_TA_CONTROL 0x2A
140 #define UECP_CTR_EON_TA_CONTROL 0x15
141 #define UECP_CTR_REFERENCE_INPUT_SEL 0x1D
142 #define UECP_CTR_DATA_SET_SELECT 0x1C // Data set select
143 #define UECP_CTR_GROUP_SEQUENCE 0x16
144 #define UECP_CTR_GROUP_VAR_CODE_SEQ 0x29
145 #define UECP_CTR_EXTENDED_GROUP_SEQ 0x38
146 #define UECP_CTR_PS_CHAR_CODE_TBL_SEL 0x2F
147 #define UECP_CTR_ENCODER_ACCESS_RIGHT 0x3A
148 #define UECP_CTR_COM_PORT_CONF_MODE 0x3B
149 #define UECP_CTR_COM_PORT_CONF_SPEED 0x3C
150 #define UECP_CTR_COM_PORT_CONF_TMEOUT 0x3D
151
152 /// Other commands
153 #define UECP_OTHER_RASS 0xda
154
155 /// Bi-directional commands (Remote and configuration commands)
156 #define BIDIR_MESSAGE_ACKNOWLEDGMENT 0x18
157 #define BIDIR_REQUEST_MESSAGE 0x17
158
159 /// Specific message commands
160 #define SPEC_MFG_SPECIFIC_CMD 0x2D
161
162 /**
163 * RDS and RBDS relevant
164 */
165
166 /// RDS Program type id's
167 enum {
168 RDS_PTY_NONE = 0,
169 RDS_PTY_NEWS,
170 RDS_PTY_CURRENT_AFFAIRS,
171 RDS_PTY_INFORMATION,
172 RDS_PTY_SPORT,
173 RDS_PTY_EDUCATION,
174 RDS_PTY_DRAMA,
175 RDS_PTY_CULTURE,
176 RDS_PTY_SCIENCE,
177 RDS_PTY_VARIED,
178 RDS_PTY_POP_MUSIC,
179 RDS_PTY_ROCK_MUSIC,
180 RDS_PTY_MOR_MUSIC,
181 RDS_PTY_LIGHT_CLASSICAL,
182 RDS_PTY_SERIOUS_CLASSICAL,
183 RDS_PTY_OTHER_MUSIC,
184 RDS_PTY_WEATHER,
185 RDS_PTY_FINANCE,
186 RDS_PTY_CHILDRENS_PROGRAMMES,
187 RDS_PTY_SOCIAL_AFFAIRS,
188 RDS_PTY_RELIGION,
189 RDS_PTY_PHONE_IN,
190 RDS_PTY_TRAVEL,
191 RDS_PTY_LEISURE,
192 RDS_PTY_JAZZ_MUSIC,
193 RDS_PTY_COUNTRY_MUSIC,
194 RDS_PTY_NATIONAL_MUSIC,
195 RDS_PTY_OLDIES_MUSIC,
196 RDS_PTY_FOLK_MUSIC,
197 RDS_PTY_DOCUMENTARY,
198 RDS_PTY_ALARM_TEST,
199 RDS_PTY_ALARM
200 };
201
202 /// RBDS Program type id's
203 enum {
204 RBDS_PTY_NONE = 0,
205 RBDS_PTY_NEWS,
206 RBDS_PTY_INFORMATION,
207 RBDS_PTY_SPORT,
208 RBDS_PTY_TALK,
209 RBDS_PTY_ROCK_MUSIC,
210 RBDS_PTY_CLASSIC_ROCK_MUSIC,
211 RBDS_PTY_ADULT_HITS,
212 RBDS_PTY_SOFT_ROCK,
213 RBDS_PTY_TOP_40,
214 RBDS_PTY_COUNTRY,
215 RBDS_PTY_OLDIES,
216 RBDS_PTY_SOFT,
217 RBDS_PTY_NOSTALGIA,
218 RBDS_PTY_JAZZ,
219 RBDS_PTY_CLASSICAL,
220 RBDS_PTY_R__B,
221 RBDS_PTY_SOFT_R__B,
222 RBDS_PTY_LANGUAGE,
223 RBDS_PTY_RELIGIOUS_MUSIC,
224 RBDS_PTY_RELIGIOUS_TALK,
225 RBDS_PTY_PERSONALITY,
226 RBDS_PTY_PUBLIC,
227 RBDS_PTY_COLLEGE,
228 RBDS_PTY_WEATHER = 29,
229 RBDS_PTY_EMERGENCY_TEST,
230 RBDS_PTY_EMERGENCY
231 };
232
233 /// RadioText+ message type id's
234 enum {
235 RTPLUS_DUMMY_CLASS = 0,
236
237 RTPLUS_ITEM_TITLE = 1,
238 RTPLUS_ITEM_ALBUM = 2,
239 RTPLUS_ITEM_TRACKNUMBER = 3,
240 RTPLUS_ITEM_ARTIST = 4,
241 RTPLUS_ITEM_COMPOSITION = 5,
242 RTPLUS_ITEM_MOVEMENT = 6,
243 RTPLUS_ITEM_CONDUCTOR = 7,
244 RTPLUS_ITEM_COMPOSER = 8,
245 RTPLUS_ITEM_BAND = 9,
246 RTPLUS_ITEM_COMMENT = 10,
247 RTPLUS_ITEM_GENRE = 11,
248
249 RTPLUS_INFO_NEWS = 12,
250 RTPLUS_INFO_NEWS_LOCAL = 13,
251 RTPLUS_INFO_STOCKMARKET = 14,
252 RTPLUS_INFO_SPORT = 15,
253 RTPLUS_INFO_LOTTERY = 16,
254 RTPLUS_INFO_HOROSCOPE = 17,
255 RTPLUS_INFO_DAILY_DIVERSION = 18,
256 RTPLUS_INFO_HEALTH = 19,
257 RTPLUS_INFO_EVENT = 20,
258 RTPLUS_INFO_SZENE = 21,
259 RTPLUS_INFO_CINEMA = 22,
260 RTPLUS_INFO_STUPIDITY_MACHINE = 23,
261 RTPLUS_INFO_DATE_TIME = 24,
262 RTPLUS_INFO_WEATHER = 25,
263 RTPLUS_INFO_TRAFFIC = 26,
264 RTPLUS_INFO_ALARM = 27,
265 RTPLUS_INFO_ADVERTISEMENT = 28,
266 RTPLUS_INFO_URL = 29,
267 RTPLUS_INFO_OTHER = 30,
268
269 RTPLUS_STATIONNAME_SHORT = 31,
270 RTPLUS_STATIONNAME_LONG = 32,
271
272 RTPLUS_PROGRAMME_NOW = 33,
273 RTPLUS_PROGRAMME_NEXT = 34,
274 RTPLUS_PROGRAMME_PART = 35,
275 RTPLUS_PROGRAMME_HOST = 36,
276 RTPLUS_PROGRAMME_EDITORIAL_STAFF = 37,
277 RTPLUS_PROGRAMME_FREQUENCY= 38,
278 RTPLUS_PROGRAMME_HOMEPAGE = 39,
279 RTPLUS_PROGRAMME_SUBCHANNEL = 40,
280
281 RTPLUS_PHONE_HOTLINE = 41,
282 RTPLUS_PHONE_STUDIO = 42,
283 RTPLUS_PHONE_OTHER = 43,
284
285 RTPLUS_SMS_STUDIO = 44,
286 RTPLUS_SMS_OTHER = 45,
287
288 RTPLUS_EMAIL_HOTLINE = 46,
289 RTPLUS_EMAIL_STUDIO = 47,
290 RTPLUS_EMAIL_OTHER = 48,
291
292 RTPLUS_MMS_OTHER = 49,
293
294 RTPLUS_CHAT = 50,
295 RTPLUS_CHAT_CENTER = 51,
296
297 RTPLUS_VOTE_QUESTION = 52,
298 RTPLUS_VOTE_CENTER = 53,
299
300 RTPLUS_PLACE = 59,
301 RTPLUS_APPOINTMENT = 60,
302 RTPLUS_IDENTIFIER = 61,
303 RTPLUS_PURCHASE = 62,
304 RTPLUS_GET_DATA = 63
305 };
306
307 /* page 71, Annex D, table D.1 in the standard and Annex N */
308 static const char *piCountryCodes_A[15][7]=
309 {
310 // 0 1 2 3 4 5 6
311 {"US","__","AI","BO","GT","__","__"}, // 1
312 {"US","__","AG","CO","HN","__","__"}, // 2
313 {"US","__","EC","JM","AW","__","__"}, // 3
314 {"US","__","FK","MQ","__","__","__"}, // 4
315 {"US","__","BB","GF","MS","__","__"}, // 5
316 {"US","__","BZ","PY","TT","__","__"}, // 6
317 {"US","__","KY","NI","PE","__","__"}, // 7
318 {"US","__","CR","__","SR","__","__"}, // 8
319 {"US","__","CU","PA","UY","__","__"}, // 9
320 {"US","__","AR","DM","KN","__","__"}, // A
321 {"US","CA","BR","DO","LC","MX","__"}, // B
322 {"__","CA","BM","CL","SV","VC","__"}, // C
323 {"US","CA","AN","GD","HT","MX","__"}, // D
324 {"US","CA","GP","TC","VE","MX","__"}, // E
325 {"__","GL","BS","GY","__","VG","PM"} // F
326 };
327
328 static const char *piCountryCodes_D[15][7]=
329 {
330 // 0 1 2 3 4 5 6
331 {"CM","NA","SL","__","__","__","__"}, // 1
332 {"CF","LR","ZW","__","__","__","__"}, // 2
333 {"DJ","GH","MZ","EH","__","__","__"}, // 3
334 {"MG","MR","UG","xx","__","__","__"}, // 4
335 {"ML","ST","SZ","RW","__","__","__"}, // 5
336 {"AO","CV","KE","LS","__","__","__"}, // 6
337 {"GQ","SN","SO","__","__","__","__"}, // 7
338 {"GA","GM","NE","SC","__","__","__"}, // 8
339 {"GN","BI","TD","__","__","__","__"}, // 9
340 {"ZA","AC","GW","MU","__","__","__"}, // A
341 {"BF","BW","ZR","__","__","__","__"}, // B
342 {"CG","KM","CI","SD","__","__","__"}, // C
343 {"TG","TZ","Zanzibar","__","__","__","__"}, // D
344 {"BJ","ET","ZM","__","__","__","__"}, // E
345 {"MW","NG","__","__","__","__","__"} // F
346 };
347
348 static const char *piCountryCodes_E[15][7]=
349 {
350 // 0 1 2 3 4 5 6
351 {"DE","GR","MA","__","MD","__","__"},
352 {"DZ","CY","CZ","IE","EE","__","__"},
353 {"AD","SM","PL","TR","KG","__","__"},
354 {"IL","CH","VA","MK","__","__","__"},
355 {"IT","JO","SK","TJ","__","__","__"},
356 {"BE","FI","SY","__","UA","__","__"},
357 {"RU","LU","TN","__","__","__","__"},
358 {"PS","BG","__","NL","PT","__","__"},
359 {"AL","DK","LI","LV","SI","__","__"}, // 9
360 {"AT","GI","IS","LB","AM","__","__"}, // A
361 {"HU","IQ","MC","AZ","UZ","__","__"}, // B
362 {"MT","GB","LT","HR","GE","__","__"}, // C
363 {"DE","LY","YU","KZ","__","__","__"}, // D
364 {"__","RO","ES","SE","TM","__","__"}, // E
365 {"EG","FR","NO","BY","BA","__","__"} // F
366 };
367
368 static const char *piCountryCodes_F[15][7]=
369 {
370 // 0 1 2 3 4 5 6
371 {"AU","KI","KW","LA","__","__","__"}, // 1
372 {"AU","BT","QA","TH","__","__","__"}, // 2
373 {"AU","BD","KH","TO","__","__","__"}, // 3
374 {"AU","PK","WS","__","__","__","__"}, // 4
375 {"AU","FJ","IN","__","__","__","__"}, // 5
376 {"AU","OM","MO","__","__","__","__"}, // 6
377 {"AU","NR","VN","__","__","__","__"}, // 7
378 {"AU","IR","PH","__","__","__","__"}, // 8
379 {"SA","NZ","JP","PG","__","__","__"}, // 9
380 {"AF","SB","SG","__","__","__","__"}, // A
381 {"MM","BN","MV","YE","__","__","__"}, // B
382 {"CN","LK","ID","__","__","__","__"}, // C
383 {"KP","TW","AE","__","__","__","__"}, // D
384 {"BH","KR","NP","FM","__","__","__"}, // E
385 {"MY","HK","VU","MN","__","__","__"} // F
386 };
387
388 /* see page 84, Annex J in the standard */
389 static const std::string piRDSLanguageCodes[128]=
390 {
391 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
392 "___", "alb", "bre", "cat", "hrv", "wel", "cze", "dan", "ger", "eng", "spa", "epo", "est", "baq", "fae", "fre", // 0
393 "fry", "gle", "gla", "glg", "ice", "ita", "smi", "lat", "lav", "ltz", "lit", "hun", "mlt", "dut", "nor", "oci", // 1
394 "pol", "por", "rum", "rom", "srp", "slo", "slv", "fin", "swe", "tur", "nld", "wln", "___", "___", "___", "___", // 2
395 "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", "___", // 3
396 "___", "___", "___", "___", "___", "zul", "vie", "uzb", "urd", "ukr", "tha", "tel", "tat", "tam", "tgk", "swa", // 4
397 "srn", "som", "sin", "sna", "scc", "rue", "rus", "que", "pus", "pan", "per", "pap", "ori", "nep", "nde", "mar", // 5
398 "mol", "mys", "mlg", "mkd", "_?_", "kor", "khm", "kaz", "kan", "jpn", "ind", "hin", "heb", "hau", "grn", "guj", // 6
399 "gre", "geo", "ful", "prs", "chv", "chi", "bur", "bul", "ben", "bel", "bam", "aze", "asm", "arm", "ara", "amh" // 7
400 };
401
402 /* ----------------------------------------------------------------------------------------------------------- */
403
404 #define EntityChars 56
405 static const char *entitystr[EntityChars] = { "'", "&", """, ">", "<", "©", "×", " ",
406 "Ä", "ä", "Ö", "ö", "Ü", "ü", "ß", "°",
407 "À", "Á", "Â", "Ã", "à", "á", "â", "ã",
408 "È", "É", "Ê", "Ë", "è", "é", "ê", "ë",
409 "Ì", "Í", "Î", "Ï", "ì", "í", "î", "ï",
410 "Ò", "Ó", "Ô", "Õ", "ò", "ó", "ô", "õ",
411 "Ù", "Ú", "Û", "Ñ", "ù", "ú", "û", "ñ" };
412 static const char *entitychar[EntityChars] = { "'", "&", "\"", ">", "<", "c", "*", " ",
413 "Ä", "ä", "Ö", "ö", "Ü", "ü", "ß", "°",
414 "À", "Á", "Â", "Ã", "à", "á", "â", "ã",
415 "È", "É", "Ê", "Ë", "è", "é", "ê", "ë",
416 "Ì", "Í", "Î", "Ï", "ì", "í", "î", "ï",
417 "Ò", "Ó", "Ô", "Õ", "ò", "ó", "ô", "õ",
418 "Ù", "Ú", "Û", "Ñ", "ù", "ú", "û", "ñ" };
419
420 // RDS-Chartranslation: 0x80..0xff
421 static unsigned char sRDSAddChar[128] =
422 {
423 0xe1, 0xe0, 0xe9, 0xe8, 0xed, 0xec, 0xf3, 0xf2,
424 0xfa, 0xf9, 0xd1, 0xc7, 0x8c, 0xdf, 0x8e, 0x8f,
425 0xe2, 0xe4, 0xea, 0xeb, 0xee, 0xef, 0xf4, 0xf6,
426 0xfb, 0xfc, 0xf1, 0xe7, 0x9c, 0x9d, 0x9e, 0x9f,
427 0xaa, 0xa1, 0xa9, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
428 0xa8, 0xa9, 0xa3, 0xab, 0xac, 0xad, 0xae, 0xaf,
429 0xba, 0xb9, 0xb2, 0xb3, 0xb1, 0xa1, 0xb6, 0xb7,
430 0xb5, 0xbf, 0xf7, 0xb0, 0xbc, 0xbd, 0xbe, 0xa7,
431 0xc1, 0xc0, 0xc9, 0xc8, 0xcd, 0xcc, 0xd3, 0xd2,
432 0xda, 0xd9, 0xca, 0xcb, 0xcc, 0xcd, 0xd0, 0xcf,
433 0xc2, 0xc4, 0xca, 0xcb, 0xce, 0xcf, 0xd4, 0xd6,
434 0xdb, 0xdc, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
435 0xc3, 0xc5, 0xc6, 0xe3, 0xe4, 0xdd, 0xd5, 0xd8,
436 0xde, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xf0,
437 0xe3, 0xe5, 0xe6, 0xf3, 0xf4, 0xfd, 0xf5, 0xf8,
438 0xfe, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
439 };
440
rds_entitychar(char * text)441 static char *rds_entitychar(char *text)
442 {
443 int i = 0, l, lof, lre, space;
444 char *temp;
445
446 while (i < EntityChars)
447 {
448 if ((temp = strstr(text, entitystr[i])) != NULL)
449 {
450 l = strlen(entitystr[i]);
451 lof = (temp-text);
452 if (strlen(text) < RT_MEL)
453 {
454 lre = strlen(text) - lof - l;
455 space = 1;
456 }
457 else
458 {
459 lre = RT_MEL - 1 - lof - l;
460 space = 0;
461 }
462 memmove(text+lof, entitychar[i], 1);
463 memmove(text+lof+1, temp+l, lre);
464 if (space != 0)
465 memmove(text+lof+1+lre, " ", l-1);
466 }
467 else
468 ++i;
469 }
470
471 return text;
472 }
473
crc16_ccitt(const unsigned char * data,int len,bool skipfirst)474 static unsigned short crc16_ccitt(const unsigned char *data, int len, bool skipfirst)
475 {
476 // CRC16-CCITT: x^16 + x^12 + x^5 + 1
477 // with start 0xffff and result inverse
478 unsigned short crc = 0xffff;
479
480 if (skipfirst)
481 ++data;
482
483 while (len--)
484 {
485 crc = (crc >> 8) | (crc << 8);
486 crc ^= *data++;
487 crc ^= (crc & 0xff) >> 4;
488 crc ^= (crc << 8) << 4;
489 crc ^= ((crc & 0xff) << 4) << 1;
490 }
491
492 return ~(crc);
493 }
494
495
496 /// --- CDVDRadioRDSData ------------------------------------------------------------
497
CDVDRadioRDSData(CProcessInfo & processInfo)498 CDVDRadioRDSData::CDVDRadioRDSData(CProcessInfo &processInfo)
499 : CThread("DVDRDSData")
500 , IDVDStreamPlayer(processInfo)
501 , m_speed(DVD_PLAYSPEED_NORMAL)
502 , m_messageQueue("rds")
503 {
504 CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - new %s", __FUNCTION__);
505
506 m_messageQueue.SetMaxDataSize(40 * 256 * 1024);
507 }
508
~CDVDRadioRDSData()509 CDVDRadioRDSData::~CDVDRadioRDSData()
510 {
511 CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - delete %s", __FUNCTION__);
512 StopThread();
513 }
514
CheckStream(CDVDStreamInfo & hints)515 bool CDVDRadioRDSData::CheckStream(CDVDStreamInfo &hints)
516 {
517 if (hints.type == STREAM_RADIO_RDS)
518 return true;
519
520 return false;
521 }
522
OpenStream(CDVDStreamInfo hints)523 bool CDVDRadioRDSData::OpenStream(CDVDStreamInfo hints)
524 {
525 CloseStream(true);
526
527 m_messageQueue.Init();
528 if (hints.type == STREAM_RADIO_RDS)
529 {
530 Flush();
531 CLog::Log(LOGINFO, "Creating UECP (RDS) data thread");
532 Create();
533 return true;
534 }
535 return false;
536 }
537
CloseStream(bool bWaitForBuffers)538 void CDVDRadioRDSData::CloseStream(bool bWaitForBuffers)
539 {
540 m_messageQueue.Abort();
541
542 // wait for decode_video thread to end
543 CLog::Log(LOGINFO, "Radio UECP (RDS) Processor - waiting for data thread to exit");
544
545 StopThread(); // will set this->m_bStop to true
546
547 m_messageQueue.End();
548 m_currentInfoTag.reset();
549 if (m_currentChannel)
550 m_currentChannel->SetRadioRDSInfoTag(m_currentInfoTag);
551 m_currentChannel.reset();
552 }
553
ResetRDSCache()554 void CDVDRadioRDSData::ResetRDSCache()
555 {
556 CSingleLock lock(m_critSection);
557
558 m_currentFileUpdate = false;
559
560 m_UECPDataStart = false;
561 m_UECPDatabStuff = false;
562 m_UECPDataIndex = 0;
563
564 m_RDS_IsRBDS = false;
565 m_RDS_SlowLabelingCodesPresent = false;
566
567 m_PI_Current = 0;
568 m_PI_CountryCode = 0;
569 m_PI_ProgramType = 0;
570 m_PI_ProgramReferenceNumber = 0;
571
572 m_EPP_TM_INFO_ExtendedCountryCode = 0;
573
574 m_PS_Present = false;
575 m_PS_Index = 0;
576 for (auto& text : m_PS_Text)
577 {
578 memset(text, 0x20, 8);
579 text[8] = 0;
580 }
581
582 m_DI_IsStereo = true;
583 m_DI_ArtificialHead = false;
584 m_DI_Compressed = false;
585 m_DI_DynamicPTY = false;
586
587 m_TA_TP_TrafficAdvisory = false;
588 m_TA_TP_TrafficVolume = 0.0;
589
590 m_MS_SpeechActive = false;
591
592 m_PTY = 0;
593 memset(m_PTYN, 0x20, 8);
594 m_PTYN[8] = 0;
595 m_PTYN_Present = false;
596
597 m_RT_Present = false;
598 m_RT_MaxSize = 4;
599 m_RT_NewItem = false;
600 m_RT_Index = 0;
601 for (int i = 0; i < 5; ++i)
602 memset(m_RT_Text[i], 0, RT_MEL);
603 m_RT.clear();
604
605 m_RTPlus_TToggle = false;
606 m_RTPlus_Present = false;
607 m_RTPlus_Show = false;
608 m_RTPlus_iToggle = 0;
609 m_RTPlus_ItemToggle = 1;
610 m_RTPlus_Title[0] = 0;
611 m_RTPlus_Artist[0] = 0;
612 m_RTPlus_Starttime = time(NULL);
613 m_RTPlus_GenrePresent = false;
614
615 m_currentInfoTag = std::make_shared<CPVRRadioRDSInfoTag>();
616 m_currentChannel = g_application.CurrentFileItem().GetPVRChannelInfoTag();
617 if (m_currentChannel)
618 m_currentChannel->SetRadioRDSInfoTag(m_currentInfoTag);
619
620 // send a message to all windows to tell them to update the radiotext
621 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_RADIOTEXT);
622 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
623 }
624
Process()625 void CDVDRadioRDSData::Process()
626 {
627 CLog::Log(LOGINFO, "Radio UECP (RDS) Processor - running thread");
628
629 while (!m_bStop)
630 {
631 CDVDMsg* pMsg;
632 int iPriority = (m_speed == DVD_PLAYSPEED_PAUSE) ? 1 : 0;
633 MsgQueueReturnCode ret = m_messageQueue.Get(&pMsg, 2000, iPriority);
634
635 if (ret == MSGQ_TIMEOUT)
636 {
637 /* Timeout for RDS is not a bad thing, so we continue without error */
638 continue;
639 }
640
641 if (MSGQ_IS_ERROR(ret))
642 {
643 CLog::Log(LOGERROR, "Got MSGQ_ABORT or MSGO_IS_ERROR return true (%i)", ret);
644 break;
645 }
646
647 if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET))
648 {
649 CSingleLock lock(m_critSection);
650
651 DemuxPacket* pPacket = static_cast<CDVDMsgDemuxerPacket*>(pMsg)->GetPacket();
652
653 ProcessUECP(pPacket->pData, pPacket->iSize);
654 }
655 else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED))
656 {
657 m_speed = static_cast<CDVDMsgInt*>(pMsg)->m_value;
658 }
659 else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)
660 || pMsg->IsType(CDVDMsg::GENERAL_RESET))
661 {
662 ResetRDSCache();
663 }
664 pMsg->Release();
665 }
666 }
667
Flush()668 void CDVDRadioRDSData::Flush()
669 {
670 if(!m_messageQueue.IsInited())
671 return;
672 /* flush using message as this get's called from VideoPlayer thread */
673 /* and any demux packet that has been taken out of queue need to */
674 /* be disposed of before we flush */
675 m_messageQueue.Flush();
676 m_messageQueue.Put(new CDVDMsg(CDVDMsg::GENERAL_FLUSH));
677 }
678
OnExit()679 void CDVDRadioRDSData::OnExit()
680 {
681 CLog::Log(LOGINFO, "Radio UECP (RDS) Processor - thread end");
682 }
683
GetRadioText(unsigned int line)684 std::string CDVDRadioRDSData::GetRadioText(unsigned int line)
685 {
686 std::string str = "";
687
688 if (m_RT_Present)
689 {
690 if (line > MAX_RADIOTEXT_LISTSIZE)
691 return "";
692
693 if ((int)line+1 > m_RT_MaxSize)
694 {
695 m_RT_MaxSize = line+1;
696 return "";
697 }
698 if (m_RT.size() <= line)
699 return "";
700
701 return m_RT[line];
702 }
703 else if (m_PS_Present)
704 {
705 std::string temp = "";
706 int ind = (m_PS_Index == 0) ? 11 : m_PS_Index - 1;
707 for (int i = ind+1; i < PS_TEXT_ENTRIES; ++i)
708 {
709 temp += m_PS_Text[i];
710 temp += ' ';
711 }
712 for (int i = 0; i <= ind; ++i)
713 {
714 temp += m_PS_Text[i];
715 temp += ' ';
716 }
717
718 if (line == 0)
719 str.insert(0, temp, 6*9, 6*9);
720 else if (line == 1)
721 str.insert(0, temp.c_str(), 6*9);
722 }
723 return str;
724 }
725
SetRadioStyle(const std::string & genre)726 void CDVDRadioRDSData::SetRadioStyle(const std::string& genre)
727 {
728 g_application.CurrentFileItem().GetMusicInfoTag()->SetGenre(genre);
729 m_currentInfoTag->SetProgStyle(genre);
730 m_currentFileUpdate = true;
731
732 CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - %s - Stream genre set to %s", __FUNCTION__, genre.c_str());
733 }
734
ProcessUECP(const unsigned char * data,unsigned int len)735 void CDVDRadioRDSData::ProcessUECP(const unsigned char *data, unsigned int len)
736 {
737 for (unsigned int i = 0; i < len; ++i)
738 {
739 if (data[i] == UECP_DATA_START) //!< Start
740 {
741 m_UECPDataIndex = -1;
742 m_UECPDataStart = true;
743 m_UECPDatabStuff = false;
744 }
745
746 if (m_UECPDataStart)
747 {
748 //! byte-stuffing reverse: 0xfd00->0xfd, 0xfd01->0xfe, 0xfd02->0xff
749 if (m_UECPDatabStuff == true)
750 {
751 switch (data[i])
752 {
753 case 0x00: m_UECPData[m_UECPDataIndex] = 0xfd; break;
754 case 0x01: m_UECPData[m_UECPDataIndex] = 0xfe; break;
755 case 0x02: m_UECPData[m_UECPDataIndex] = 0xff; break;
756 default: m_UECPData[++m_UECPDataIndex] = data[i]; // should never be
757 }
758 m_UECPDatabStuff = false;
759 }
760 else
761 {
762 m_UECPData[++m_UECPDataIndex] = data[i];
763 }
764
765 if (data[i] == 0xfd && m_UECPDataIndex > 0) //!< stuffing found
766 m_UECPDatabStuff = true;
767
768 if (m_UECPDataIndex >= UECP_SIZE_MAX) //!< max. UECP data length, garbage ?
769 {
770 CLog::Log(LOGERROR, "Radio UECP (RDS) Processor - Error(TS): too long, garbage ?");
771 m_UECPDataStart = false;
772 }
773 }
774
775 if (m_UECPDataStart == true && data[i] == UECP_DATA_STOP && m_currentInfoTag) //!< End
776 {
777 m_UECPDataStart = false;
778
779 if (m_UECPDataIndex < 9)
780 {
781 CLog::Log(LOGERROR, "Radio UECP (RDS) Processor - Error(TS): too short -> garbage ?");
782 }
783 else
784 {
785 //! crc16-check
786 unsigned short crc16 = crc16_ccitt(m_UECPData, m_UECPDataIndex-3, true);
787 if (crc16 != (m_UECPData[m_UECPDataIndex-2]<<8) + m_UECPData[m_UECPDataIndex-1])
788 {
789 CLog::Log(LOGERROR, "Radio UECP (RDS) Processor - Error(TS): wrong CRC # calc = %04x <> transmit = %02x%02x",
790 crc16, m_UECPData[m_UECPDataIndex-2], m_UECPData[m_UECPDataIndex-1]);
791 }
792 else
793 {
794 m_UECPDataDeadBreak = false;
795
796 unsigned int ret = 0;
797 unsigned int ptr = 5;
798 unsigned int len = m_UECPDataIndex-7;
799 do
800 {
801 uint8_t *msg = m_UECPData+ptr; //!< Current selected UECP message element (increased if more as one element is in frame)
802 switch (msg[UECP_ME_MEC])
803 {
804 case UECP_RDS_PI: ret = DecodePI(msg); break; //!< Program Identification
805 case UECP_RDS_PS: ret = DecodePS(msg); break; //!< Program Service name (PS)
806 case UECP_RDS_DI: ret = DecodeDI(msg); break; //!< Decoder Identification and dynamic PTY indicator
807 case UECP_RDS_TA_TP: ret = DecodeTA_TP(msg); break; //!< Traffic Announcement and Traffic Programme bits.
808 case UECP_RDS_MS: ret = DecodeMS(msg); break; //!< Music/Speech switch
809 case UECP_RDS_PTY: ret = DecodePTY(msg); break; //!< Program Type
810 case UECP_RDS_PTYN: ret = DecodePTYN(msg); break; //!< Program Type Name
811 case UECP_RDS_RT: ret = DecodeRT(msg, len); break; //!< RadioText
812 case UECP_ODA_DATA: ret = DecodeODA(msg, len); break; //!< Open Data Application
813 case UECP_OTHER_RASS: m_UECPDataDeadBreak = true; break; //!< Radio screen show (RaSS) (not present, before on SWR radio)
814 case UECP_CLOCK_RTC: ret = DecodeRTC(msg); break; //!< Real time clock
815 case UECP_TDC_TMC: ret = DecodeTMC(msg, len); break; //!< Traffic message channel
816 case UECP_EPP_TM_INFO: ret = DecodeEPPTransmitterInfo(msg); break; //!< EPP transmitter information
817 case UECP_SLOW_LABEL_CODES: ret = DecodeSlowLabelingCodes(msg); break; //!< Slow Labeling codes
818 case UECP_DAB_DYN_LABEL_CMD: ret = DecodeDABDynLabelCmd(msg, len); break; //!< DAB: Dynamic Label command
819 case UECP_DAB_DYN_LABEL_MSG: ret = DecodeDABDynLabelMsg(msg, len); break; //!< DAB: Dynamic Label message (DL)
820 case UECP_RDS_AF: ret = DecodeAF(msg, len); break; //!< Alternative Frequencies list
821 case UECP_RDS_EON_AF: ret = DecodeEonAF(msg, len); break; //!< EON Alternative Frequencies list
822 case UECP_TDC_TDC: ret = DecodeTDC(msg, len); break; //!< Transparent Data Channel
823 case UECP_LINKAGE_INFO: ret = 5; break; //!< Linkage information
824 case UECP_TDC_EWS: ret = 6; break; //!< Emergency warning system
825 case UECP_RDS_PIN: ret = 5; break; //!< Program Item Number
826 case UECP_TDC_IH: ret = 7; break; //!< In-house applications (Should be ignored)
827 case UECP_TDC_FREE_FMT_GROUP: ret = 7; break; //!< Free-format group (unused)
828 case UECP_ODA_CONF_SHORT_MSG_CMD: ret = 8; break; //!< ODA Configuration and Short Message Command (unused)
829 case UECP_CLOCK_RTC_CORR: ret = 3; break; //!< Real time clock correction (unused)
830 case UECP_CLOCK_CT_ON_OFF: ret = 2; break; //!< Real time clock on/off (unused)
831 default:
832 #ifdef RDS_IMPROVE_CHECK
833 printf("Unknown UECP data packet = 0x%02X\n", msg[UECP_ME_MEC]);
834 #endif
835 m_UECPDataDeadBreak = true;
836 break;
837 }
838 ptr += ret;
839 len -= ret;
840 }
841 while (ptr < m_UECPDataIndex-5 && !m_UECPDataDeadBreak && !m_bStop);
842
843 if (m_currentFileUpdate && !m_bStop)
844 {
845 CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(g_application.CurrentFileItem());
846 m_currentFileUpdate = false;
847 }
848 }
849 }
850 }
851 }
852 }
853
DecodePI(uint8_t * msgElement)854 unsigned int CDVDRadioRDSData::DecodePI(uint8_t *msgElement)
855 {
856 uint16_t PICode = (msgElement[3] << 8) | msgElement[4];
857 if (m_PI_Current != PICode)
858 {
859 m_PI_Current = PICode;
860
861 m_PI_CountryCode = (m_PI_Current>>12) & 0x0F;
862 m_PI_ProgramType = (m_PI_Current>>8) & 0x0F;
863 m_PI_ProgramReferenceNumber = m_PI_Current & 0xFF;
864
865 CLog::Log(LOGINFO, "Radio UECP (RDS) Processor - PI code changed to Country %X, Type %X and reference no. %i", m_PI_CountryCode, m_PI_ProgramType, m_PI_ProgramReferenceNumber);
866 }
867
868 return 5;
869 }
870
DecodePS(uint8_t * msgElement)871 unsigned int CDVDRadioRDSData::DecodePS(uint8_t *msgElement)
872 {
873 uint8_t *text = msgElement+3;
874
875 for (int i = 0; i < 8; ++i)
876 {
877 if (text[i] <= 0xfe)
878 m_PS_Text[m_PS_Index][i] = (text[i] >= 0x80) ? sRDSAddChar[text[i]-0x80] : text[i]; //!< additional rds-character, see RBDS-Standard, Annex E
879 }
880
881 ++m_PS_Index;
882 if (m_PS_Index >= PS_TEXT_ENTRIES)
883 m_PS_Index = 0;
884
885 m_PS_Present = true;
886 return 11;
887 }
888
DecodeDI(uint8_t * msgElement)889 unsigned int CDVDRadioRDSData::DecodeDI(uint8_t *msgElement)
890 {
891 bool value;
892
893 value = (msgElement[3] & 1) != 0;
894 if (m_DI_IsStereo != value)
895 {
896 CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - %s - Stream changed over to %s", __FUNCTION__, value ? "Stereo" : "Mono");
897 m_DI_IsStereo = value;
898 }
899
900 value = (msgElement[3] & 2) != 0;
901 if (m_DI_ArtificialHead != value)
902 {
903 CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - %s - Stream changed over to %sArtificial Head", __FUNCTION__, value ? "" : "Not ");
904 m_DI_ArtificialHead = value;
905 }
906
907 value = (msgElement[3] & 4) != 0;
908 if (m_DI_ArtificialHead != value)
909 {
910 CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - %s - Stream changed over to %sCompressed Head", __FUNCTION__, value ? "" : "Not ");
911 m_DI_ArtificialHead = value;
912 }
913
914 value = (msgElement[3] & 8) != 0;
915 if (m_DI_DynamicPTY != value)
916 {
917 CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - %s - Stream changed over to %s PTY", __FUNCTION__, value ? "dynamic" : "static");
918 m_DI_DynamicPTY = value;
919 }
920
921 return 4;
922 }
923
DecodeTA_TP(uint8_t * msgElement)924 unsigned int CDVDRadioRDSData::DecodeTA_TP(uint8_t *msgElement)
925 {
926 uint8_t dsn = msgElement[1];
927 bool traffic_announcement = (msgElement[3] & 1) != 0;
928 bool traffic_programme = (msgElement[3] & 2) != 0;
929
930 if (traffic_announcement && !m_TA_TP_TrafficAdvisory && traffic_programme && dsn == 0 && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("pvrplayback.trafficadvisory"))
931 {
932 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(19021), g_localizeStrings.Get(29930));
933 m_TA_TP_TrafficAdvisory = true;
934 m_TA_TP_TrafficVolume = g_application.GetVolumePercent();
935 float trafAdvVol = (float)CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt("pvrplayback.trafficadvisoryvolume");
936 if (trafAdvVol)
937 g_application.SetVolume(m_TA_TP_TrafficVolume+trafAdvVol);
938
939 CVariant data(CVariant::VariantTypeObject);
940 data["on"] = true;
941 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::PVR, "RDSRadioTA", data);
942 }
943
944 if (!traffic_announcement && m_TA_TP_TrafficAdvisory && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool("pvrplayback.trafficadvisory"))
945 {
946 m_TA_TP_TrafficAdvisory = false;
947 g_application.SetVolume(m_TA_TP_TrafficVolume);
948
949 CVariant data(CVariant::VariantTypeObject);
950 data["on"] = false;
951 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::PVR, "RDSRadioTA", data);
952 }
953
954 return 4;
955 }
956
DecodeMS(uint8_t * msgElement)957 unsigned int CDVDRadioRDSData::DecodeMS(uint8_t *msgElement)
958 {
959 bool speechActive = msgElement[3] == 0;
960 if (m_MS_SpeechActive != speechActive)
961 {
962 m_currentInfoTag->SetSpeechActive(m_MS_SpeechActive);
963 CLog::Log(LOGDEBUG, "Radio UECP (RDS) Processor - %s - Stream changed over to %s", __FUNCTION__, speechActive ? "Speech" : "Music");
964 m_MS_SpeechActive = speechActive;
965 }
966
967 return 4;
968 }
969
970 /*!
971 * EBU - SPB 490 - 3.3.7 and 62106IEC:1999 - 3.2.1.2, Message Name: Programme Type
972 * Message Element Code: 07
973 */
974 //! @todo Improve and test alarm message
975 typedef struct { const char *style_name; int name; } pty_skin_info;
976 pty_skin_info pty_skin_info_table[32][2] =
977 {
978 { { "none", 29940 }, { "none", 29940 } },
979 { { "news", 29941 }, { "news", 29941 } },
980 { { "currentaffairs", 29942 }, { "information", 29943 } },
981 { { "information", 29943 }, { "sport", 29944 } },
982 { { "sport", 29944 }, { "talk", 29939 } },
983 { { "education", 29945 }, { "rockmusic", 29951 } },
984 { { "drama", 29946 }, { "classicrockmusic",29977 } },
985 { { "cultures", 29947 }, { "adulthits", 29937 } },
986 { { "science", 29948 }, { "softrock", 29938 } },
987 { { "variedspeech", 29949 }, { "top40", 29972 } },
988 { { "popmusic", 29950 }, { "countrymusic", 29965 } },
989 { { "rockmusic", 29951 }, { "oldiesmusic", 29967 } },
990 { { "easylistening", 29952 }, { "softmusic", 29936 } },
991 { { "lightclassics", 29953 }, { "nostalgia", 29979 } },
992 { { "seriousclassics",29954 }, { "jazzmusic", 29964 } },
993 { { "othermusic", 29955 }, { "classical", 29978 } },
994 { { "weather", 29956 }, { "randb", 29975 } },
995 { { "finance", 29957 }, { "softrandb", 29976 } },
996 { { "childrensprogs", 29958 }, { "language", 29932 } },
997 { { "socialaffairs", 29959 }, { "religiousmusic", 29973 } },
998 { { "religion", 29960 }, { "religioustalk", 29974 } },
999 { { "phonein", 29961 }, { "personality", 29934 } },
1000 { { "travelandtouring",29962 },{ "public", 29935 } },
1001 { { "leisureandhobby",29963 }, { "college", 29933 } },
1002 { { "jazzmusic", 29964 }, { "spanishtalk", 29927 } },
1003 { { "countrymusic", 29965 }, { "spanishmusic", 29928 } },
1004 { { "nationalmusic", 29966 }, { "hiphop", 29929 } },
1005 { { "oldiesmusic", 29967 }, { "", -1 } },
1006 { { "folkmusic", 29968 }, { "", -1 } },
1007 { { "documentary", 29969 }, { "weather", 29956 } },
1008 { { "alarmtest", 29970 }, { "alarmtest", 29970 } },
1009 { { "alarm-alarm", 29971 }, { "alarm-alarm", 29971 } }
1010 };
1011
DecodePTY(uint8_t * msgElement)1012 unsigned int CDVDRadioRDSData::DecodePTY(uint8_t *msgElement)
1013 {
1014 int pty = msgElement[3];
1015 if (pty >= 0 && pty < 32 && m_PTY != pty)
1016 {
1017 m_PTY = pty;
1018
1019 // save info
1020 m_currentInfoTag->SetRadioStyle(pty_skin_info_table[m_PTY][m_RDS_IsRBDS].style_name);
1021 if (!m_RTPlus_GenrePresent && !m_PTYN_Present)
1022 SetRadioStyle(g_localizeStrings.Get(pty_skin_info_table[m_PTY][m_RDS_IsRBDS].name));
1023
1024 if (m_PTY == RDS_PTY_ALARM_TEST)
1025 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(29931), g_localizeStrings.Get(29970), TOAST_DISPLAY_TIME, false);
1026
1027 if (m_PTY == RDS_PTY_ALARM)
1028 {
1029 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(29931), g_localizeStrings.Get(29971), TOAST_DISPLAY_TIME*2, true);
1030 }
1031 }
1032
1033 return 4;
1034 }
1035
DecodePTYN(uint8_t * msgElement)1036 unsigned int CDVDRadioRDSData::DecodePTYN(uint8_t *msgElement)
1037 {
1038 // decode Text
1039 uint8_t *text = msgElement+3;
1040
1041 for (int i = 0; i < 8; ++i)
1042 {
1043 if (text[i] <= 0xfe)
1044 m_PTYN[i] = (text[i] >= 0x80) ? sRDSAddChar[text[i]-0x80] : text[i];
1045 }
1046
1047 m_PTYN_Present = true;
1048
1049 if (!m_RTPlus_GenrePresent)
1050 {
1051 std::string progTypeName = StringUtils::Format("%s: %s", g_localizeStrings.Get(pty_skin_info_table[m_PTY][m_RDS_IsRBDS].name).c_str(), m_PTYN);
1052 SetRadioStyle(progTypeName);
1053 }
1054
1055 return 11;
1056 }
1057
rtrim_str(std::string & text)1058 inline void rtrim_str(std::string &text)
1059 {
1060 for (int i = text.length()-1; i >= 0; --i)
1061 {
1062 if (text[i] == ' ' || text[i] == '\t' || text[i] == '\n' || text[i] == '\r')
1063 text[i] = 0;
1064 else
1065 break;
1066 }
1067 }
1068
DecodeRT(uint8_t * msgElement,unsigned int len)1069 unsigned int CDVDRadioRDSData::DecodeRT(uint8_t *msgElement, unsigned int len)
1070 {
1071 if (!m_RT_Present)
1072 {
1073 m_currentInfoTag->SetPlayingRadiotext(true);
1074 m_RT_Present = true;
1075 }
1076
1077 int bufConf = (msgElement[UECP_ME_DATA] >> 5) & 0x03;
1078 unsigned int msgLength = msgElement[UECP_ME_MEL];
1079 if (msgLength > len-2)
1080 {
1081 CLog::Log(LOGERROR,
1082 "Radio UECP (RDS) - %s - RT-Error: Length=0 or not correct (MFL= %d, MEL= %d)",
1083 __FUNCTION__, len, msgLength);
1084 m_UECPDataDeadBreak = true;
1085 return 0;
1086 }
1087 else if (msgLength == 0 || (msgLength == 1 && bufConf == 0))
1088 {
1089 m_RT.clear();
1090 m_RT_Index = 0;
1091 for (int i = 0; i < 5; ++i)
1092 memset(m_RT_Text[i], 0, RT_MEL);
1093 }
1094 else
1095 {
1096 // bool flagToggle = msgElement[UECP_ME_DATA] & 0x01 ? true : false;
1097 // int txQty = (msgElement[UECP_ME_DATA] >> 1) & 0x0F;
1098 // int bufConf = (msgElement[UECP_ME_DATA] >> 5) & 0x03;
1099
1100 //! byte 4 = RT-Status bitcodet (0=AB-flagcontrol, 1-4=Transmission-Number, 5+6=Buffer-Config, ignored, always 0x01 ?)
1101 char temptext[RT_MEL];
1102 memset(temptext, 0x0, RT_MEL);
1103 for (unsigned int i = 1, ii = 0; i < msgLength; ++i)
1104 {
1105 if (msgElement[UECP_ME_DATA+i] <= 0xfe) // additional rds-character, see RBDS-Standard, Annex E
1106 temptext[ii++] = (msgElement[UECP_ME_DATA+i] >= 0x80) ? sRDSAddChar[msgElement[UECP_ME_DATA+i]-0x80] : msgElement[UECP_ME_DATA+i];
1107 }
1108 memcpy(m_RTPlus_WorkText, temptext, RT_MEL);
1109 rds_entitychar(temptext);
1110
1111 // check repeats
1112 bool repeat = false;
1113 for (int ind = 0; ind < m_RT_MaxSize; ++ind)
1114 {
1115 if (memcmp(m_RT_Text[ind], temptext, RT_MEL) == 0)
1116 repeat = true;
1117 }
1118 if (!repeat)
1119 {
1120 memcpy(m_RT_Text[m_RT_Index], temptext, RT_MEL);
1121
1122 std::string rdsline = m_RT_Text[m_RT_Index];
1123 rtrim_str(rdsline);
1124 g_charsetConverter.unknownToUTF8(rdsline);
1125 m_RT.push_front(StringUtils::Trim(rdsline));
1126
1127 if ((int)m_RT.size() > m_RT_MaxSize)
1128 m_RT.pop_back();
1129
1130 ++m_RT_Index;
1131 if (m_RT_Index >= m_RT_MaxSize)
1132 m_RT_Index = 0;
1133 }
1134 m_RTPlus_iToggle = 0x03; // Bit 0/1 = Title/Artist
1135 }
1136 return msgLength+4;
1137 }
1138
1139 #define UECP_CLOCK_YEAR 1
1140 #define UECP_CLOCK_MONTH 2
1141 #define UECP_CLOCK_DAY 3
1142 #define UECP_CLOCK_HOURS 4
1143 #define UECP_CLOCK_MINUTES 5
1144 #define UECP_CLOCK_SECONDS 6
1145 #define UECP_CLOCK_CENTSEC 7
1146 #define UECP_CLOCK_LOCALOFFSET 8
DecodeRTC(uint8_t * msgElement)1147 unsigned int CDVDRadioRDSData::DecodeRTC(uint8_t *msgElement)
1148 {
1149 uint8_t hours = msgElement[UECP_CLOCK_HOURS];
1150 uint8_t minutes = msgElement[UECP_CLOCK_MINUTES];
1151 bool minus = (msgElement[UECP_CLOCK_LOCALOFFSET] & 0x20) != 0;
1152 if (minus)
1153 {
1154 if (msgElement[UECP_CLOCK_LOCALOFFSET] >> 1)
1155 hours -= msgElement[UECP_CLOCK_LOCALOFFSET] >> 1;
1156 if (msgElement[UECP_CLOCK_LOCALOFFSET] & 1)
1157 minutes -= 30;
1158 }
1159 else
1160 {
1161 if (msgElement[UECP_CLOCK_LOCALOFFSET] >> 1)
1162 hours += msgElement[UECP_CLOCK_LOCALOFFSET] >> 1;
1163 if (msgElement[UECP_CLOCK_LOCALOFFSET] & 1)
1164 minutes += 30;
1165 }
1166 m_RTC_DateTime.SetDateTime(msgElement[UECP_CLOCK_YEAR], msgElement[UECP_CLOCK_MONTH], msgElement[UECP_CLOCK_DAY],
1167 hours, minutes, msgElement[UECP_CLOCK_SECONDS]);
1168
1169 CLog::Log(LOGDEBUG, "Radio UECP (RDS) - %s - Current RDS Data Time: %02i.%02i.%02i - UTC: %02i:%02i:%02i,0.%is - Local: %c%i min",
1170 __FUNCTION__, msgElement[UECP_CLOCK_DAY], msgElement[UECP_CLOCK_MONTH], msgElement[UECP_CLOCK_YEAR],
1171 msgElement[UECP_CLOCK_HOURS], msgElement[UECP_CLOCK_MINUTES], msgElement[UECP_CLOCK_SECONDS],
1172 msgElement[UECP_CLOCK_CENTSEC], minus ? '-' : '+', msgElement[UECP_CLOCK_LOCALOFFSET]*30);
1173
1174 CVariant data(CVariant::VariantTypeObject);
1175 data["dateTime"] = (m_RTC_DateTime.IsValid()) ? m_RTC_DateTime.GetAsRFC1123DateTime() : "";
1176 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::PVR, "RDSRadioRTC", data);
1177
1178 return 8;
1179 }
1180
DecodeODA(uint8_t * msgElement,unsigned int len)1181 unsigned int CDVDRadioRDSData::DecodeODA(uint8_t *msgElement, unsigned int len)
1182 {
1183 unsigned int procData = msgElement[1];
1184 if (procData == 0 || procData > len-2)
1185 {
1186 CLog::Log(LOGERROR, "Radio UECP (RDS) - Invalid ODA data size");
1187 m_UECPDataDeadBreak = true;
1188 return 0;
1189 }
1190
1191 switch ((msgElement[2]<<8)+msgElement[3]) // ODA-ID
1192 {
1193 case 0x4bd7: //!< RT+
1194 procData = DecodeRTPlus(msgElement, len);
1195 break;
1196 case 0x0d45: //!< TMC Alert-C
1197 case 0xcd46:
1198 SendTMCSignal(msgElement[4], msgElement+5);
1199 break;
1200 default:
1201 m_UECPDataDeadBreak = true;
1202 #ifdef RDS_IMPROVE_CHECK
1203 printf("[RDS-ODA AID '%02x%02x' not used -> End]\n", msgElement[2], msgElement[3]);
1204 #endif // RDS_IMPROVE_CHECK
1205 break;
1206 }
1207 return procData;
1208 }
1209
DecodeRTPlus(uint8_t * msgElement,unsigned int len)1210 unsigned int CDVDRadioRDSData::DecodeRTPlus(uint8_t *msgElement, unsigned int len)
1211 {
1212 if (m_RTPlus_iToggle == 0) // RTplus tags V2.1, only if RT
1213 return 10;
1214
1215 if (!m_RTPlus_Present)
1216 {
1217 m_currentInfoTag->SetPlayingRadiotextPlus(true);
1218 m_RTPlus_Present = true;
1219 }
1220
1221 if (msgElement[1] > len-2 || msgElement[1] != 8) // byte 6 = MEL, only 8 byte for 2 tags
1222 {
1223 CLog::Log(LOGERROR, "Radio UECP (RDS) - %s - RTp-Error: Length not correct (MEL= %d)", __FUNCTION__, msgElement[1]);
1224 m_UECPDataDeadBreak = true;
1225 return 0;
1226 }
1227 unsigned int rtp_typ[2], rtp_start[2], rtp_len[2];
1228 // byte 2+3 = ApplicationID, always 0x4bd7
1229 // byte 4 = Applicationgroup Typecode / PTY ?
1230 // bit 10#4 = Item Togglebit
1231 // bit 10#3 = Item Runningbit321
1232 // Tag1: bit 10#2..11#5 = Contenttype, 11#4..12#7 = Startmarker, 12#6..12#1 = Length
1233 rtp_typ[0] = (0x38 & msgElement[5]<<3) | msgElement[6]>>5;
1234 rtp_start[0] = (0x3e & msgElement[6]<<1) | msgElement[7]>>7;
1235 rtp_len[0] = 0x3f & msgElement[7]>>1;
1236 // Tag2: bit 12#0..13#3 = Contenttype, 13#2..14#5 = Startmarker, 14#4..14#0 = Length(5bit)
1237 rtp_typ[1] = (0x20 & msgElement[7]<<5) | msgElement[8]>>3;
1238 rtp_start[1] = (0x38 & msgElement[8]<<3) | msgElement[9]>>5;
1239 rtp_len[1] = 0x1f & msgElement[9];
1240
1241 /// Hack for error on BR Classic
1242 if ((msgElement[5]&0x10) && (msgElement[5]&0x08) && rtp_typ[0] == RTPLUS_INFO_URL && rtp_typ[1] == RTPLUS_ITEM_ARTIST)
1243 return 10;
1244
1245 // save info
1246 MUSIC_INFO::CMusicInfoTag *currentMusic = g_application.CurrentFileItem().GetMusicInfoTag();
1247
1248 for (int i = 0; i < 2; ++i)
1249 {
1250 if (rtp_start[i]+rtp_len[i]+1 >= RT_MEL) // length-error
1251 {
1252 CLog::Log(LOGERROR, "Radio UECP (RDS) - %s - (tag#%d = Typ/Start/Len): %d/%d/%d (Start+Length > 'RT-MEL' !)", __FUNCTION__, i+1, rtp_typ[i], rtp_start[i], rtp_len[i]);
1253 }
1254 else
1255 {
1256 // +Memory
1257 memset(m_RTPlus_Temptext, 0x20, RT_MEL);
1258 memcpy(m_RTPlus_Temptext, m_RTPlus_WorkText+rtp_start[i], rtp_len[i]+1);
1259 m_RTPlus_Temptext[rtp_len[i]+1] = 0;
1260 rds_entitychar(m_RTPlus_Temptext);
1261 switch (rtp_typ[i])
1262 {
1263 case RTPLUS_DUMMY_CLASS:
1264 break;
1265 case RTPLUS_ITEM_TITLE: // Item-Title...
1266 if ((msgElement[5] & 0x08) > 0 && (m_RTPlus_iToggle & 0x01) == 0x01)
1267 {
1268 m_RTPlus_iToggle -= 0x01;
1269 if (memcmp(m_RTPlus_Title, m_RTPlus_Temptext, RT_MEL) != 0 || (msgElement[5] & 0x10) != m_RTPlus_ItemToggle)
1270 {
1271 memcpy(m_RTPlus_Title, m_RTPlus_Temptext, RT_MEL);
1272 if (m_RTPlus_Show && m_RTPlus_iTime.GetElapsedSeconds() > 1)
1273 m_RTPlus_iDiffs = (int) m_RTPlus_iTime.GetElapsedSeconds();
1274 if (!m_RT_NewItem)
1275 {
1276 m_RTPlus_Starttime = time(NULL);
1277 m_RTPlus_iTime.StartZero();
1278 m_RTPlus_Artist[0] = 0;
1279 }
1280 m_RT_NewItem = (!m_RT_NewItem) ? true : false;
1281 m_RTPlus_Show = m_RTPlus_TToggle = true;
1282 }
1283 }
1284 break;
1285 case RTPLUS_ITEM_ALBUM:
1286 m_currentInfoTag->SetAlbum(m_RTPlus_Temptext);
1287 currentMusic->SetAlbum(m_RTPlus_Temptext);
1288 break;
1289 case RTPLUS_ITEM_TRACKNUMBER:
1290 m_currentInfoTag->SetAlbumTrackNumber(atoi(m_RTPlus_Temptext));
1291 currentMusic->SetAlbumId(atoi(m_RTPlus_Temptext));
1292 break;
1293 case RTPLUS_ITEM_ARTIST: // Item-Artist..
1294 if ((msgElement[5] & 0x08) > 0 && (m_RTPlus_iToggle & 0x02) == 0x02)
1295 {
1296 m_RTPlus_iToggle -= 0x02;
1297 if (memcmp(m_RTPlus_Artist, m_RTPlus_Temptext, RT_MEL) != 0 || (msgElement[5] & 0x10) != m_RTPlus_ItemToggle)
1298 {
1299 memcpy(m_RTPlus_Artist, m_RTPlus_Temptext, RT_MEL);
1300 if (m_RTPlus_Show && m_RTPlus_iTime.GetElapsedSeconds() > 1)
1301 m_RTPlus_iDiffs = (int) m_RTPlus_iTime.GetElapsedSeconds();
1302 if (!m_RT_NewItem)
1303 {
1304 m_RTPlus_Starttime = time(NULL);
1305 m_RTPlus_iTime.StartZero();
1306 m_RTPlus_Title[0] = 0;
1307 }
1308 m_RT_NewItem = (!m_RT_NewItem) ? true : false;
1309 m_RTPlus_Show = m_RTPlus_TToggle = true;
1310 }
1311 }
1312 break;
1313 case RTPLUS_ITEM_CONDUCTOR:
1314 m_currentInfoTag->SetConductor(m_RTPlus_Temptext);
1315 break;
1316 case RTPLUS_ITEM_COMPOSER:
1317 case RTPLUS_ITEM_COMPOSITION:
1318 m_currentInfoTag->SetComposer(m_RTPlus_Temptext);
1319 if (m_currentInfoTag->GetRadioStyle() == "unknown")
1320 m_currentInfoTag->SetRadioStyle("classical");
1321 break;
1322 case RTPLUS_ITEM_BAND:
1323 m_currentInfoTag->SetBand(m_RTPlus_Temptext);
1324 break;
1325 case RTPLUS_ITEM_COMMENT:
1326 m_currentInfoTag->SetComment(m_RTPlus_Temptext);
1327 break;
1328 case RTPLUS_ITEM_GENRE:
1329 {
1330 std::string str = m_RTPlus_Temptext;
1331 g_charsetConverter.unknownToUTF8(str);
1332 m_RTPlus_GenrePresent = true;
1333 m_currentInfoTag->SetProgStyle(str);
1334 }
1335 break;
1336 case RTPLUS_INFO_NEWS: // Info_News
1337 m_currentInfoTag->SetInfoNews(m_RTPlus_Temptext);
1338 break;
1339 case RTPLUS_INFO_NEWS_LOCAL: // Info_NewsLocal
1340 m_currentInfoTag->SetInfoNewsLocal(m_RTPlus_Temptext);
1341 break;
1342 case RTPLUS_INFO_STOCKMARKET: // Info_Stockmarket
1343 m_currentInfoTag->SetInfoStock(m_RTPlus_Temptext);
1344 break;
1345 case RTPLUS_INFO_SPORT: // Info_Sport
1346 m_currentInfoTag->SetInfoSport(m_RTPlus_Temptext);
1347 break;
1348 case RTPLUS_INFO_LOTTERY: // Info_Lottery
1349 m_currentInfoTag->SetInfoLottery(m_RTPlus_Temptext);
1350 break;
1351 case RTPLUS_INFO_HOROSCOPE:
1352 m_currentInfoTag->SetInfoHoroscope(m_RTPlus_Temptext);
1353 break;
1354 case RTPLUS_INFO_CINEMA:
1355 m_currentInfoTag->SetInfoCinema(m_RTPlus_Temptext);
1356 break;
1357 case RTPLUS_INFO_WEATHER: // Info_Weather/
1358 m_currentInfoTag->SetInfoWeather(m_RTPlus_Temptext);
1359 break;
1360 case RTPLUS_INFO_URL: // Info_Url
1361 if (m_currentInfoTag->GetProgWebsite().empty())
1362 m_currentInfoTag->SetProgWebsite(m_RTPlus_Temptext);
1363 break;
1364 case RTPLUS_INFO_OTHER: // Info_Other
1365 m_currentInfoTag->SetInfoOther(m_RTPlus_Temptext);
1366 break;
1367 case RTPLUS_STATIONNAME_LONG: // Programme_Stationname.Long
1368 m_currentInfoTag->SetProgStation(m_RTPlus_Temptext);
1369 break;
1370 case RTPLUS_PROGRAMME_NOW: // Programme_Now
1371 m_currentInfoTag->SetProgNow(m_RTPlus_Temptext);
1372 break;
1373 case RTPLUS_PROGRAMME_NEXT: // Programme_Next
1374 m_currentInfoTag->SetProgNext(m_RTPlus_Temptext);
1375 break;
1376 case RTPLUS_PROGRAMME_HOST: // Programme_Host
1377 m_currentInfoTag->SetProgHost(m_RTPlus_Temptext);
1378 break;
1379 case RTPLUS_PROGRAMME_EDITORIAL_STAFF: // Programme_EditorialStaff
1380 m_currentInfoTag->SetEditorialStaff(m_RTPlus_Temptext);
1381 break;
1382 case RTPLUS_PROGRAMME_HOMEPAGE: // Programme_Homepage
1383 m_currentInfoTag->SetProgWebsite(m_RTPlus_Temptext);
1384 break;
1385 case RTPLUS_PHONE_HOTLINE: // Phone_Hotline
1386 m_currentInfoTag->SetPhoneHotline(m_RTPlus_Temptext);
1387 break;
1388 case RTPLUS_PHONE_STUDIO: // Phone_Studio
1389 m_currentInfoTag->SetPhoneStudio(m_RTPlus_Temptext);
1390 break;
1391 case RTPLUS_SMS_STUDIO: // SMS_Studio
1392 m_currentInfoTag->SetSMSStudio(m_RTPlus_Temptext);
1393 break;
1394 case RTPLUS_EMAIL_HOTLINE: // Email_Hotline
1395 m_currentInfoTag->SetEMailHotline(m_RTPlus_Temptext);
1396 break;
1397 case RTPLUS_EMAIL_STUDIO: // Email_Studio
1398 m_currentInfoTag->SetEMailStudio(m_RTPlus_Temptext);
1399 break;
1400 /**
1401 * Currently unused radiotext plus messages
1402 * Must be check where present and if it is usable
1403 */
1404 case RTPLUS_ITEM_MOVEMENT:
1405 case RTPLUS_INFO_DAILY_DIVERSION:
1406 case RTPLUS_INFO_HEALTH:
1407 case RTPLUS_INFO_EVENT:
1408 case RTPLUS_INFO_SZENE:
1409 case RTPLUS_INFO_STUPIDITY_MACHINE:
1410 case RTPLUS_INFO_TRAFFIC:
1411 case RTPLUS_INFO_ALARM:
1412 case RTPLUS_INFO_ADVERTISEMENT:
1413 case RTPLUS_PROGRAMME_PART:
1414 case RTPLUS_PROGRAMME_FREQUENCY:
1415 case RTPLUS_PROGRAMME_SUBCHANNEL:
1416 case RTPLUS_PHONE_OTHER:
1417 case RTPLUS_SMS_OTHER:
1418 case RTPLUS_EMAIL_OTHER:
1419 case RTPLUS_MMS_OTHER:
1420 case RTPLUS_CHAT:
1421 case RTPLUS_CHAT_CENTER:
1422 case RTPLUS_VOTE_QUESTION:
1423 case RTPLUS_VOTE_CENTER:
1424 case RTPLUS_PLACE:
1425 case RTPLUS_APPOINTMENT:
1426 case RTPLUS_IDENTIFIER:
1427 case RTPLUS_PURCHASE:
1428 case RTPLUS_GET_DATA:
1429 #ifdef RDS_IMPROVE_CHECK
1430 printf(" RTp-Unkn. : %02i - %s\n", rtp_typ[i], m_RTPlus_Temptext);
1431 break;
1432 #endif // RDS_IMPROVE_CHECK
1433 /// Unused and not needed data informations
1434 case RTPLUS_STATIONNAME_SHORT: //!< Must be rechecked under DAB
1435 case RTPLUS_INFO_DATE_TIME:
1436 break;
1437 default:
1438 break;
1439 }
1440 }
1441 }
1442
1443 // Title-end @ no Item-Running'
1444 if ((msgElement[5] & 0x08) == 0)
1445 {
1446 m_RTPlus_Title[0] = 0;
1447 m_RTPlus_Artist[0] = 0;
1448 m_currentInfoTag->ResetSongInformation();
1449 currentMusic->SetAlbum("");
1450 if (m_RTPlus_GenrePresent)
1451 {
1452 m_currentInfoTag->SetProgStyle("");
1453 m_RTPlus_GenrePresent = false;
1454 }
1455
1456 if (m_RTPlus_Show)
1457 {
1458 m_RTPlus_Show = false;
1459 m_RTPlus_TToggle = true;
1460 m_RTPlus_iDiffs = (int) m_RTPlus_iTime.GetElapsedSeconds();
1461 m_RTPlus_Starttime = time(NULL);
1462 }
1463 m_RT_NewItem = false;
1464 }
1465
1466 if (m_RTPlus_TToggle)
1467 {
1468 #ifdef RDS_IMPROVE_CHECK
1469 {
1470 struct tm tm_store;
1471 struct tm *ts = localtime_r(&m_RTPlus_Starttime, &tm_store);
1472 if (m_RTPlus_iDiffs > 0)
1473 printf(" StartTime : %02d:%02d:%02d (last Title elapsed = %d s)\n", ts->tm_hour, ts->tm_min, ts->tm_sec, m_RTPlus_iDiffs);
1474 else
1475 printf(" StartTime : %02d:%02d:%02d\n", ts->tm_hour, ts->tm_min, ts->tm_sec);
1476 printf(" RTp-Title : %s\n RTp-Artist: %s\n", m_RTPlus_Title, m_RTPlus_Artist);
1477 }
1478 #endif // RDS_IMPROVE_CHECK
1479 m_RTPlus_ItemToggle = msgElement[5] & 0x10;
1480 m_RTPlus_TToggle = false;
1481 m_RTPlus_iDiffs = 0;
1482
1483 std::string str;
1484
1485 str = m_RTPlus_Artist;
1486 m_currentInfoTag->SetArtist(str);
1487 if (str.empty() && !m_currentInfoTag->GetComposer().empty())
1488 str = m_currentInfoTag->GetComposer();
1489 else if (str.empty() && !m_currentInfoTag->GetConductor().empty())
1490 str = m_currentInfoTag->GetConductor();
1491 else if (str.empty() && !m_currentInfoTag->GetBand().empty())
1492 str = m_currentInfoTag->GetBand();
1493
1494 if (!str.empty())
1495 g_charsetConverter.unknownToUTF8(str);
1496 currentMusic->SetArtist(str);
1497
1498 str = m_RTPlus_Title;
1499 g_charsetConverter.unknownToUTF8(str);
1500 currentMusic->SetTitle(str);
1501 m_currentInfoTag->SetTitle(str);
1502 m_currentFileUpdate = true;
1503 }
1504 m_RTPlus_iToggle = 0;
1505
1506 return 10;
1507 }
1508
DecodeTMC(uint8_t * msgElement,unsigned int len)1509 unsigned int CDVDRadioRDSData::DecodeTMC(uint8_t *msgElement, unsigned int len)
1510 {
1511 unsigned int msgElementLength = msgElement[1];
1512 if (msgElementLength == 0)
1513 msgElementLength = 6;
1514 if (msgElementLength + 2 > len)
1515 {
1516 m_UECPDataDeadBreak = true;
1517 return 0;
1518 }
1519
1520 for (unsigned int i = 0; i < msgElementLength; i += 5)
1521 SendTMCSignal(msgElement[2], msgElement+3+i);
1522
1523 return msgElementLength + 2;
1524 }
1525
DecodeEPPTransmitterInfo(uint8_t * msgElement)1526 unsigned int CDVDRadioRDSData::DecodeEPPTransmitterInfo(uint8_t *msgElement)
1527 {
1528 if (!m_RDS_SlowLabelingCodesPresent && m_PI_CountryCode != 0)
1529 {
1530 int codeHigh = msgElement[2]&0xF0;
1531 int codeLow = msgElement[2]&0x0F;
1532 if (codeLow > 7)
1533 {
1534 CLog::Log(LOGERROR, "Radio RDS - %s - invalid country code 0x%02X%02X", __FUNCTION__, codeHigh, codeLow);
1535 return 7;
1536 }
1537
1538 std::string countryName;
1539 switch (codeHigh)
1540 {
1541 case 0xA0:
1542 countryName = piCountryCodes_A[m_PI_CountryCode-1][codeLow];
1543 break;
1544 case 0xD0:
1545 countryName = piCountryCodes_D[m_PI_CountryCode-1][codeLow];
1546 break;
1547 case 0xE0:
1548 countryName = piCountryCodes_E[m_PI_CountryCode-1][codeLow];
1549 break;
1550 case 0xF0:
1551 countryName = piCountryCodes_F[m_PI_CountryCode-1][codeLow];
1552 break;
1553 default:
1554 CLog::Log(LOGERROR, "Radio RDS - %s - invalid extended country region code:%02X%02X", __FUNCTION__, codeHigh, codeLow);
1555 return 7;
1556 }
1557
1558 m_RDS_IsRBDS = countryName == "US" ? true : false;
1559
1560 m_currentInfoTag->SetCountry(countryName);
1561 }
1562
1563 return 7;
1564 }
1565
1566 /* SLOW LABELLING: see page 23 in the standard
1567 * for paging see page 90, Annex M in the standard (NOT IMPLEMENTED)
1568 * for extended country codes see page 69, Annex D.2 in the standard
1569 * for language codes see page 84, Annex J in the standard
1570 * for emergency warning systems (EWS) see page 53 in the standard */
1571 #define VARCODE_PAGING_EXTCOUNTRYCODE 0
1572 #define VARCODE_TMC_IDENT 1
1573 #define VARCODE_PAGING_IDENT 2
1574 #define VARCODE_LANGUAGE_CODES 3
1575 #define VARCODE_OWN_BROADCASTER 6
1576 #define VARCODE_EWS_CHANNEL_IDENT 7
DecodeSlowLabelingCodes(uint8_t * msgElement)1577 unsigned int CDVDRadioRDSData::DecodeSlowLabelingCodes(uint8_t *msgElement)
1578 {
1579 uint16_t slowLabellingCode = (msgElement[2]<<8 | msgElement[3]) & 0xfff;
1580 int VariantCode = (msgElement[2]>>4) & 0x7;
1581
1582 switch (VariantCode)
1583 {
1584 case VARCODE_PAGING_EXTCOUNTRYCODE: // paging + ecc
1585 {
1586 // int paging = (slowLabellingCode>>8)&0x0f; unused
1587
1588 if (m_PI_CountryCode != 0)
1589 {
1590 int codeHigh = slowLabellingCode&0xF0;
1591 int codeLow = slowLabellingCode&0x0F;
1592 if (codeLow > 5)
1593 {
1594 CLog::Log(LOGERROR, "Radio RDS - %s - invalid country code 0x%02X%02X", __FUNCTION__, codeHigh, codeLow);
1595 return 4;
1596 }
1597
1598 std::string countryName;
1599 switch (codeHigh)
1600 {
1601 case 0xA0:
1602 countryName = piCountryCodes_A[m_PI_CountryCode-1][codeLow];
1603 break;
1604 case 0xD0:
1605 countryName = piCountryCodes_D[m_PI_CountryCode-1][codeLow];
1606 break;
1607 case 0xE0:
1608 countryName = piCountryCodes_E[m_PI_CountryCode-1][codeLow];
1609 break;
1610 case 0xF0:
1611 countryName = piCountryCodes_F[m_PI_CountryCode-1][codeLow];
1612 break;
1613 default:
1614 CLog::Log(LOGERROR, "Radio RDS - %s - invalid extended country region code:%02X%02X", __FUNCTION__, codeHigh, codeLow);
1615 return 4;
1616 }
1617
1618 m_currentInfoTag->SetCountry(countryName);
1619 }
1620 break;
1621 }
1622 case VARCODE_LANGUAGE_CODES: // language codes
1623 if (slowLabellingCode > 1 && slowLabellingCode < 0x80)
1624 m_currentInfoTag->SetLanguage(piRDSLanguageCodes[slowLabellingCode]);
1625 else
1626 CLog::Log(LOGERROR, "Radio RDS - %s - invalid language code %i", __FUNCTION__, slowLabellingCode);
1627 break;
1628
1629 case VARCODE_TMC_IDENT: // TMC identification
1630 case VARCODE_PAGING_IDENT: // Paging identification
1631 case VARCODE_OWN_BROADCASTER:
1632 case VARCODE_EWS_CHANNEL_IDENT:
1633 default:
1634 break;
1635 }
1636
1637 m_RDS_SlowLabelingCodesPresent = true;
1638 return 4;
1639 }
1640
1641 /*!
1642 * currently unused need to be checked on DAB, processed here to have length of it
1643 */
DecodeDABDynLabelCmd(uint8_t * msgElement,unsigned int len)1644 unsigned int CDVDRadioRDSData::DecodeDABDynLabelCmd(uint8_t *msgElement, unsigned int len)
1645 {
1646 unsigned int msgElementLength = msgElement[1];
1647 if (msgElementLength < 1 || msgElementLength + 2 > len)
1648 {
1649 m_UECPDataDeadBreak = true;
1650 return 0;
1651 }
1652
1653 return msgElementLength+2;
1654 }
1655
1656 /*!
1657 * currently unused need to be checked on DAB, processed here to have length of it
1658 */
DecodeDABDynLabelMsg(uint8_t * msgElement,unsigned int len)1659 unsigned int CDVDRadioRDSData::DecodeDABDynLabelMsg(uint8_t *msgElement, unsigned int len)
1660 {
1661 unsigned int msgElementLength = msgElement[1];
1662 if (msgElementLength < 2 || msgElementLength + 2 > len)
1663 {
1664 m_UECPDataDeadBreak = true;
1665 return 0;
1666 }
1667
1668 return msgElementLength+2;
1669 }
1670
1671 /*!
1672 * unused processed here to have length of it
1673 */
DecodeAF(uint8_t * msgElement,unsigned int len)1674 unsigned int CDVDRadioRDSData::DecodeAF(uint8_t *msgElement, unsigned int len)
1675 {
1676 unsigned int msgElementLength = msgElement[3];
1677 if (msgElementLength < 3 || msgElementLength + 4 > len)
1678 {
1679 m_UECPDataDeadBreak = true;
1680 return 0;
1681 }
1682
1683 return msgElementLength+4;
1684 }
1685
1686 /*!
1687 * unused processed here to have length of it
1688 */
DecodeEonAF(uint8_t * msgElement,unsigned int len)1689 unsigned int CDVDRadioRDSData::DecodeEonAF(uint8_t *msgElement, unsigned int len)
1690 {
1691 unsigned int msgElementLength = msgElement[3];
1692 if (msgElementLength < 4 || msgElementLength + 4 > len)
1693 {
1694 m_UECPDataDeadBreak = true;
1695 return 0;
1696 }
1697
1698 return msgElementLength+4;
1699 }
1700
1701 /*!
1702 * unused processed here to have length of it
1703 */
DecodeTDC(uint8_t * msgElement,unsigned int len)1704 unsigned int CDVDRadioRDSData::DecodeTDC(uint8_t *msgElement, unsigned int len)
1705 {
1706 unsigned int msgElementLength = msgElement[1];
1707 if (msgElementLength < 2 || msgElementLength+2 > len)
1708 {
1709 m_UECPDataDeadBreak = true;
1710 return 0;
1711 }
1712
1713 return msgElementLength+2;
1714 }
1715
SendTMCSignal(unsigned int flags,uint8_t * data)1716 void CDVDRadioRDSData::SendTMCSignal(unsigned int flags, uint8_t *data)
1717 {
1718 if (!(flags & 0x80) && (memcmp(data, m_TMC_LastData, 5) == 0))
1719 return;
1720
1721 memcpy(m_TMC_LastData, data, 5);
1722
1723 if (m_currentChannel)
1724 {
1725 CVariant msg(CVariant::VariantTypeObject);
1726 msg["channel"] = m_currentChannel->ChannelName();
1727 msg["ident"] = m_PI_Current;
1728 msg["flags"] = flags;
1729 msg["x"] = m_TMC_LastData[0];
1730 msg["y"] = (unsigned int)(m_TMC_LastData[1]<<8 | m_TMC_LastData[2]);
1731 msg["z"] = (unsigned int)(m_TMC_LastData[3]<<8 | m_TMC_LastData[4]);
1732
1733 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::PVR, "RDSRadioTMC", msg);
1734 }
1735 }
1736