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]  = { "'",   "&",    """,  "&gt",      "&lt",      "©",   "×", " ",
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