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