MythTV master
cc608decoder.cpp
Go to the documentation of this file.
1// -*- Mode: c++ -*-
2
3// Some of the XDS was inspired by code in TVTime. -- dtk 03/30/2006
5
6#include <algorithm>
7#include <cstdint>
8#include <vector>
9
10// Qt headers
11#include <QStringList>
12#include <QCoreApplication>
13
14// MythTV headers
16
17#define DEBUG_XDS 0
18
19static const std::array<const uint8_t,256> vbi_bit_reverse
20{
21 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
22 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
23 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
24 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
25 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
26 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
27 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
28 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
29 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
30 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
31 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
32 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
33 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
34 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
35 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
36 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
37 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
38 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
39 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
40 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
41 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
42 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
43 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
44 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
45 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
46 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
47 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
48 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
49 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
50 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
51 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
52 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
53};
54
55static const std::array<const std::string, 8> formats
56{
57 "Full format 4:3, 576 lines",
58 "Letterbox 14:9 centre, 504 lines",
59 "Letterbox 14:9 top, 504 lines",
60 "Letterbox 16:9 centre, 430 lines",
61 "Letterbox 16:9 top, 430 lines",
62 "Letterbox > 16:9 centre",
63 "Full format 14:9 centre, 576 lines",
64 "Anamorphic 16:9, 576 lines"
65};
66
67static const std::array<const std::string,4> subtitles
68{
69 "none",
70 "in active image area",
71 "out of active image area",
72 "?"
73};
74
75static void init_xds_program_type(CC608ProgramType& xds_program_type);
76
78 : m_reader(ccr),
79 m_rbuf(new unsigned char[sizeof(ccsubtitle)+255])
80{
81 // fill translation table
82 for (uint8_t i = 0; i < 128; i++)
83 m_stdChar[i] = QChar(i);
84 m_stdChar[42] = QChar(0xE1); // á
85 m_stdChar[92] = QChar(0xE9); // é
86 m_stdChar[94] = QChar(0xED); // í
87 m_stdChar[95] = QChar(0xF3); // ó
88 m_stdChar[96] = QChar(0xFA); // ú
89 m_stdChar[123] = QChar(0xE7); // ç
90 m_stdChar[124] = QChar(0xF7); // ÷
91 m_stdChar[125] = QChar(0xD1); // Ñ
92 m_stdChar[126] = QChar(0xF1); // ñ
93 m_stdChar[127] = QChar(0x2588); // full block
94
96}
97
99{
100 delete [] m_rbuf;
101}
102
103void CC608Decoder::FormatCC(std::chrono::milliseconds tc, int code1, int code2)
104{
105 FormatCCField(tc, 0, code1);
106 FormatCCField(tc, 1, code2);
107}
108
109void CC608Decoder::GetServices(std::chrono::seconds seconds, CC608Seen& seen) const
110{
111 auto then = SystemClock::now() - seconds;
112 for (uint i = 0; i < 4; i++)
113 seen[i] = (m_lastSeen[i] >= then);
114}
115
116static const std::array<const int,16> rowdata =
117{
118 11, -1, 1, 2, 3, 4, 12, 13,
119 14, 15, 5, 6, 7, 8, 9, 10
120};
121
122static const std::array<const QChar,16> specialchar =
123{
124 QChar(0xAE), QChar(0xB0), QChar(0xBD), QChar(0xBF), // ®°½¿
125 QChar(0x2122), QChar(0xA2), QChar(0xA3), QChar(0x266A), // ™¢£♪
126 QChar(0xE0), QChar(' '), QChar(0xE8), QChar(0xE2), // à èâ
127 QChar(0xEA), QChar(0xEE), QChar(0xF4), QChar(0xFB) // êîôû
128};
129
130static const std::array<const QChar,32> extendedchar2 =
131{
132 QChar(0xC1), QChar(0xC9), QChar(0xD3), QChar(0xDA), // ÁÉÓÚ
133 QChar(0xDC), QChar(0xFC), QChar('`'), QChar(0xA1), // Üü`¡
134 QChar('*'), QChar('\''), QChar(0x2014), QChar(0xA9), // *'-©
135 QChar(0x2120), QChar(0xB7), QChar(0x201C), QChar(0x201D), // ℠·“”
136 QChar(0xC0), QChar(0xC2), QChar(0xC7), QChar(0xC8), // ÀÂÇÈ
137 QChar(0xCA), QChar(0xCB), QChar(0xEB), QChar(0xCE), // ÊËëÎ
138 QChar(0xCF), QChar(0xEF), QChar(0xD4), QChar(0xD9), // ÏïÔÙ
139 QChar(0xF9), QChar(0xDB), QChar(0xAB), QChar(0xBB) // ùÛ«»
140};
141
142static const std::array<const QChar,32> extendedchar3 =
143{
144 QChar(0xC3), QChar(0xE3), QChar(0xCD), QChar(0xCC), // ÃãÍÌ
145 QChar(0xEC), QChar(0xD2), QChar(0xF2), QChar(0xD5), // ìÒòÕ
146 QChar(0xF5), QChar('{'), QChar('}'), QChar('\\'), // õ{}
147 QChar('^'), QChar('_'), QChar(0xA6), QChar('~'), // ^_¦~
148 QChar(0xC4), QChar(0xE4), QChar(0xD6), QChar(0xF6), // ÄäÖö
149 QChar(0xDF), QChar(0xA5), QChar(0xA4), QChar('|'), // ߥ¤|
150 QChar(0xC5), QChar(0xE5), QChar(0xD8), QChar(0xF8), // Å娸
151 QChar(0x250C), QChar(0x2510), QChar(0x2514), QChar(0x2518) // ┌┐└┘
152};
153
154void CC608Decoder::FormatTextCode(std::chrono::milliseconds tc, size_t field, size_t mode, size_t len, int b1, int b2)
155{
156 if (mode == std::numeric_limits<std::size_t>::max())
157 return;
158
159 m_lastCodeTc[field] += 33ms;
160 m_timeCode[mode] = tc;
161
162 // commit row number only when first text code
163 // comes in
164 if (m_newRow[mode])
165 NewRowCC(mode, len);
166
167 m_ccBuf[mode] += CharCC(b1);
168 m_col[mode]++;
169 if (b2 & 0x60)
170 {
171 m_ccBuf[mode] += CharCC(b2);
172 m_col[mode]++;
173 }
174}
175
176void CC608Decoder::FormatControlCode(std::chrono::milliseconds tc, size_t field, int b1, int b2)
177{
178 m_lastCodeTc[field] += 67ms;
179
180 int newccmode = (b1 >> 3) & 1;
181 int newtxtmode = m_txtMode[(field*2) + newccmode];
182 if ((b1 & 0x06) == 0x04)
183 {
184 switch (b2)
185 {
186 case 0x29:
187 case 0x2C:
188 case 0x20:
189 case 0x2F:
190 case 0x25:
191 case 0x26:
192 case 0x27:
193 // CC1,2
194 newtxtmode = 0;
195 break;
196 case 0x2A:
197 case 0x2B:
198 // TXT1,2
199 newtxtmode = 1;
200 break;
201 }
202 }
203 m_ccMode[field] = newccmode;
204 m_txtMode[(field*2) + newccmode] = newtxtmode;
205 size_t mode = (field << 2) | (newtxtmode << 1) | m_ccMode[field];
206
207 m_timeCode[mode] = tc;
208 size_t len = m_ccBuf[mode].length();
209
210 if (b2 & 0x40) //preamble address code (row & indent)
211 {
212 if (newtxtmode)
213 // no address codes in TXT mode?
214 return;
215
216 m_newRow[mode] = rowdata[((b1 << 1) & 14) | ((b2 >> 5) & 1)];
217 if (m_newRow[mode] == -1)
218 // bogus code?
219 m_newRow[mode] = m_lastRow[mode] + 1;
220
221 if (b2 & 0x10) //row contains indent flag
222 {
223 m_newCol[mode] = (b2 & 0x0E) << 1;
224 // Encode as 0x7020 or 0x7021 depending on the
225 // underline flag.
226 m_newAttr[mode] = (b2 & 0x1) + 0x20;
227 LOG(VB_VBI, LOG_INFO,
228 QString("cc608 preamble indent, b2=%1")
229 .arg(b2, 2, 16));
230 }
231 else
232 {
233 m_newCol[mode] = 0;
234 m_newAttr[mode] = (b2 & 0xf) + 0x10;
235 // Encode as 0x7010 through 0x702f for the 16 possible
236 // values of b2.
237 LOG(VB_VBI, LOG_INFO,
238 QString("cc608 preamble color change, b2=%1")
239 .arg(b2, 2, 16));
240 }
241
242 // row, indent, attribute settings are not final
243 // until text code arrives
244 return;
245 }
246
247 switch (b1 & 0x07)
248 {
249 case 0x00: //attribute
250#if 0
251 LOG(VB_VBI, LOG_DEBUG,
252 QString("<ATTRIBUTE %1 %2>").arg(b1).arg(b2));
253#endif
254 break;
255 case 0x01: //midrow or char
256 if (m_newRow[mode])
257 NewRowCC(mode, len);
258
259 switch (b2 & 0x70)
260 {
261 case 0x20: //midrow attribute change
262 LOG(VB_VBI, LOG_INFO,
263 QString("cc608 mid-row color change, b2=%1")
264 .arg(b2, 2, 16));
265 // Encode as 0x7000 through 0x700f for the
266 // 16 possible values of b2.
267 m_ccBuf[mode] += ' ';
268 m_ccBuf[mode] += QChar(0x7000 + (b2 & 0xf));
269 m_col[mode]++;
270 break;
271 case 0x30: //special character..
272 m_ccBuf[mode] += specialchar[b2 & 0x0f];
273 m_col[mode]++;
274 break;
275 }
276 break;
277 case 0x02: //extended char
278 // extended char is preceded by alternate char
279 // - if there's no alternate, it could be noise
280 if (!len)
281 break;
282
283 if (b2 & 0x30)
284 {
285 m_ccBuf[mode].remove(len - 1, 1);
286 m_ccBuf[mode] += extendedchar2[b2 - 0x20];
287 break;
288 }
289 break;
290 case 0x03: //extended char
291 // extended char is preceded by alternate char
292 // - if there's no alternate, it could be noise
293 if (!len)
294 break;
295
296 if (b2 & 0x30)
297 {
298 m_ccBuf[mode].remove(len - 1, 1);
299 m_ccBuf[mode] += extendedchar3[b2 - 0x20];
300 break;
301 }
302 break;
303 case 0x04: //misc
304 case 0x05: //misc + F
305#if 0
306 LOG(VB_VBI, LOG_DEBUG,
307 QString("ccmode %1 cmd %2").arg(m_ccMode)
308 .arg(b2, 2, 16, '0'));
309#endif
310 switch (b2)
311 {
312 case 0x21: //backspace
313 // add backspace if line has been encoded already
314 if (m_newRow[mode])
315 len = NewRowCC(mode, len);
316
317 if (len == 0 ||
318 m_ccBuf[mode].startsWith("\b"))
319 {
320 m_ccBuf[mode] += '\b';
321 m_col[mode]--;
322 }
323 else
324 {
325 m_ccBuf[mode].remove(len - 1, 1);
326 m_col[mode]--;
327 }
328 break;
329 case 0x25: //2 row caption
330 case 0x26: //3 row caption
331 case 0x27: //4 row caption
332 if (m_style[mode] == CC_STYLE_PAINT && len)
333 {
334 // flush
335 BufferCC(mode, len, 0);
336 m_ccBuf[mode] = "";
337 m_row[mode] = 0;
338 m_col[mode] = 0;
339 }
340 else if (m_style[mode] == CC_STYLE_POPUP)
341 {
342 ResetCC(mode);
343 }
344
345 m_rowCount[mode] = b2 - 0x25 + 2;
346 m_style[mode] = CC_STYLE_ROLLUP;
347 break;
348 case 0x2D: //carriage return
349 if (m_style[mode] != CC_STYLE_ROLLUP)
350 break;
351
352 if (m_newRow[mode])
353 m_row[mode] = m_newRow[mode];
354
355 // flush if there is text or need to scroll
356 // TODO: decode ITV (WebTV) link in TXT2
357 if (len || (m_row[mode] != 0 && !m_lineCont[mode] &&
358 (!newtxtmode || m_row[mode] >= 16)))
359 {
360 BufferCC(mode, len, 0);
361 }
362
363 if (newtxtmode)
364 {
365 if (m_row[mode] < 16)
366 m_newRow[mode] = m_row[mode] + 1;
367 else
368 // scroll up previous lines
369 m_newRow[mode] = 16;
370 }
371
372 m_ccBuf[mode] = "";
373 m_col[mode] = 0;
374 m_lineCont[mode] = 0;
375 break;
376
377 case 0x29:
378 // resume direct caption (paint-on style)
379 if (m_style[mode] == CC_STYLE_ROLLUP && len)
380 {
381 // flush
382 BufferCC(mode, len, 0);
383 m_ccBuf[mode] = "";
384 m_row[mode] = 0;
385 m_col[mode] = 0;
386 }
387 else if (m_style[mode] == CC_STYLE_POPUP)
388 {
389 ResetCC(mode);
390 }
391
392 m_style[mode] = CC_STYLE_PAINT;
393 m_rowCount[mode] = 0;
394 m_lineCont[mode] = 0;
395 break;
396
397 case 0x2B: //resume text display
398 m_resumeText[mode] = 1;
399 if (m_row[mode] == 0)
400 {
401 m_newRow[mode] = 1;
402 m_newCol[mode] = 0;
403 m_newAttr[mode] = 0;
404 }
405 m_style[mode] = CC_STYLE_ROLLUP;
406 break;
407 case 0x2C: //erase displayed memory
408 if (m_ignoreTimeCode ||
409 (tc - m_lastClr[mode]) > 5s ||
410 m_lastClr[mode] == 0ms)
411 {
412 // don't overflow the frontend with
413 // too many redundant erase codes
414 BufferCC(mode, 0, 1);
415 }
416 if (m_style[mode] != CC_STYLE_POPUP)
417 {
418 m_row[mode] = 0;
419 m_col[mode] = 0;
420 }
421 m_lineCont[mode] = 0;
422 break;
423
424 case 0x20: //resume caption (pop-up style)
425 if (m_style[mode] != CC_STYLE_POPUP)
426 {
427 if (len)
428 // flush
429 BufferCC(mode, len, 0);
430 m_ccBuf[mode] = "";
431 m_row[mode] = 0;
432 m_col[mode] = 0;
433 }
434 m_style[mode] = CC_STYLE_POPUP;
435 m_rowCount[mode] = 0;
436 m_lineCont[mode] = 0;
437 break;
438 case 0x2F: //end caption + swap memory
439 if (m_style[mode] != CC_STYLE_POPUP)
440 {
441 if (len)
442 // flush
443 BufferCC(mode, len, 0);
444 }
445 else if (m_ignoreTimeCode ||
446 (tc - m_lastClr[mode]) > 5s ||
447 m_lastClr[mode] == 0ms)
448 {
449 // clear and flush
450 BufferCC(mode, len, 1);
451 }
452 else if (len)
453 {
454 // flush
455 BufferCC(mode, len, 0);
456 }
457 m_ccBuf[mode] = "";
458 m_row[mode] = 0;
459 m_col[mode] = 0;
460 m_style[mode] = CC_STYLE_POPUP;
461 m_rowCount[mode] = 0;
462 m_lineCont[mode] = 0;
463 break;
464
465 case 0x2A: //text restart
466 // clear display
467 BufferCC(mode, 0, 1);
468 ResetCC(mode);
469 // TXT starts at row 1
470 m_newRow[mode] = 1;
471 m_newCol[mode] = 0;
472 m_newAttr[mode] = 0;
473 m_style[mode] = CC_STYLE_ROLLUP;
474 break;
475
476 case 0x2E: //erase non-displayed memory
477 ResetCC(mode);
478 break;
479 }
480 break;
481 case 0x07: //misc (TAB)
482 if (m_newRow[mode])
483 {
484 m_newCol[mode] += (b2 & 0x03);
485 NewRowCC(mode, len);
486 }
487 else
488 {
489 // illegal?
490 for (int x = 0; x < (b2 & 0x03); x++)
491 {
492 m_ccBuf[mode] += ' ';
493 m_col[mode]++;
494 }
495 }
496 break;
497 }
498}
499
500void CC608Decoder::FormatCCField(std::chrono::milliseconds tc, size_t field, int data)
501{
502 size_t len = 0;
503 size_t mode = 0;
504
505 if (data == -1) // invalid data. flush buffers to be safe.
506 {
507 // TODO: flush reader buffer
508 if (m_ccMode[field] != -1)
509 {
510 for (mode = field*4; mode < ((field*4) + 4); mode++)
511 ResetCC(mode);
512 m_xds[field] = 0;
513 m_badVbi[field] = 0;
514 m_ccMode[field] = -1;
515 m_txtMode[field*2] = 0;
516 m_txtMode[(field*2) + 1] = 0;
517 }
518 return;
519 }
520
521 if ((m_lastFormatData[field&1] == data) &&
522 (m_lastFormatTc[field&1] == tc))
523 {
524 LOG(VB_VBI, LOG_DEBUG, "Format CC -- Duplicate");
525 return;
526 }
527
528 m_lastFormatTc[field&1] = tc;
529 m_lastFormatData[field&1] = data;
530
531 int b1 = data & 0x7f;
532 int b2 = (data >> 8) & 0x7f;
533#if 1
534 LOG(VB_VBI, LOG_DEBUG,
535 QString("Format CC @%1/%2 = %3 %4, %5/%6 = '%7' '%8'")
536 .arg(tc.count()).arg(field)
537 .arg((data&0xff), 2, 16)
538 .arg((data&0xff00)>>8, 2, 16)
539 .arg(b1, 2, 16, QChar('0'))
540 .arg(b2, 2, 16, QChar('0'))
541 .arg(QChar((b1 & 0x60) ? b1 : '_'))
542 .arg(QChar((b2 & 0x60) ? b2 : '_')));
543#endif
544 if (m_ccMode[field] >= 0)
545 {
546 mode = field << 2 |
547 (m_txtMode[(field*2) + m_ccMode[field]] << 1) |
548 m_ccMode[field];
549 if (mode != std::numeric_limits<std::size_t>::max())
550 len = m_ccBuf[mode].length();
551 else
552 len = 0;
553 }
554 else
555 {
556 mode = std::numeric_limits<std::size_t>::max();
557 len = 0;
558 }
559
560 if (FalseDup(tc, field, data))
561 {
563 return;
564 }
565 else if (XDSDecode(field, b1, b2))
566 {
567 return;
568 }
569 else if (b1 & 0x60)
570 {
571 // 0x20 <= b1 <= 0x7F
572 // text codes
573 FormatTextCode(tc, field, mode, len, b1, b2);
574 }
575 else if ((b1 & 0x10) && (b2 > 0x1F))
576 {
577 // 0x10 <= b1 <= 0x1F
578 // control codes
579 FormatControlCode(tc, field, b1, b2);
580 }
581
582 for (size_t mode2 = field*4; mode2 < ((field*4) + 4); mode2++)
583 {
584 size_t len2 = m_ccBuf[mode2].length();
585 if ((m_ignoreTimeCode || ((tc - m_timeCode[mode2]) > 100ms)) &&
586 (m_style[mode2] != CC_STYLE_POPUP) && len2)
587 {
588 // flush unfinished line if waiting too long
589 // in paint-on or scroll-up mode
590 m_timeCode[mode2] = tc;
591 BufferCC(mode2, len2, 0);
592 m_ccBuf[mode2] = "";
593 m_row[mode2] = m_lastRow[mode2];
594 m_lineCont[mode2] = 1;
595 }
596 }
597
598 if (data != m_lastCode[field])
599 {
600 m_lastCode[field] = data;
601 m_lastCodeTc[field] = tc;
602 }
603 m_lastTc[field] = tc;
604}
605
606bool CC608Decoder::FalseDup(std::chrono::milliseconds tc, size_t field, int data)
607{
608 int b1 = data & 0x7f;
609 int b2 = (data >> 8) & 0x7f;
610
612 {
613 // most digital streams with encoded VBI
614 // have duplicate control codes;
615 // suppress every other repeated control code
616 if ((data == m_lastCode[field]) &&
617 ((b1 & 0x70) == 0x10))
618 {
619 m_lastCode[field] = -1;
620 return true;
621 }
622 return false;
623 }
624
625 // bttv-0.9 VBI reads are pretty reliable (1 read/33367us).
626 // bttv-0.7 reads don't seem to work as well so if read intervals
627 // vary from this, be more conservative in detecting duplicate
628 // CC codes.
629 std::chrono::milliseconds dup_text_fudge = 0ms;
630 std::chrono::milliseconds dup_ctrl_fudge = 0ms;
631 if (m_badVbi[field] < 100 && b1 != 0 && b2 != 0)
632 {
633 std::chrono::milliseconds d = tc - m_lastTc[field];
634 if (d < 25ms || d > 42ms)
635 m_badVbi[field]++;
636 else if (m_badVbi[field] > 0)
637 m_badVbi[field]--;
638 }
639 if (m_badVbi[field] < 4)
640 {
641 // this should pick up all codes
642 dup_text_fudge = -2ms;
643 // this should pick up 1st, 4th, 6th, 8th, ... codes
644 dup_ctrl_fudge = 33ms - 4ms;
645 }
646 else
647 {
648 dup_text_fudge = 4ms;
649 dup_ctrl_fudge = 33ms - 4ms;
650 }
651
652 if (data == m_lastCode[field])
653 {
654 if ((b1 & 0x70) == 0x10)
655 {
656 if (tc > (m_lastCodeTc[field] + 67ms + dup_ctrl_fudge))
657 return false;
658 }
659 else if (b1)
660 {
661 // text, XDS
662 if (tc > (m_lastCodeTc[field] + 33ms + dup_text_fudge))
663 return false;
664 }
665
666 return true;
667 }
668
669 return false;
670}
671
672void CC608Decoder::ResetCC(size_t mode)
673{
674// m_lastRow[mode] = 0;
675// m_newRow[mode] = 0;
676// m_newCol[mode] = 0;
677// m_timeCode[mode] = 0;
678 m_row[mode] = 0;
679 m_col[mode] = 0;
680 m_rowCount[mode] = 0;
681// m_style[mode] = CC_STYLE_POPUP;
682 m_lineCont[mode] = 0;
683 m_resumeText[mode] = 0;
684 m_lastClr[mode] = 0ms;
685 m_ccBuf[mode] = "";
686}
687
688QString CC608Decoder::ToASCII(const QString &cc608str, bool suppress_unknown)
689{
690 QString ret = "";
691
692 for (const auto& cp : std::as_const(cc608str))
693 {
694 int cpu = cp.unicode();
695 if (cpu == 0)
696 break;
697 switch (cpu)
698 {
699 case 0x2120 : ret += "(SM)"; break;
700 case 0x2122 : ret += "(TM)"; break;
701 case 0x2014 : ret += "(--)"; break;
702 case 0x201C : ret += "``"; break;
703 case 0x201D : ret += "''"; break;
704 case 0x250C : ret += "|-"; break;
705 case 0x2510 : ret += "-|"; break;
706 case 0x2514 : ret += "|_"; break;
707 case 0x2518 : ret += "_|"; break;
708 case 0x2588 : ret += "[]"; break;
709 case 0x266A : ret += "o/~"; break;
710 case '\b' : ret += "\\b"; break;
711 default :
712 if (cpu >= 0x7000 && cpu < 0x7000 + 0x30)
713 {
714 if (!suppress_unknown)
715 ret += QString("[%1]").arg(cpu, 2, 16);
716 }
717 else if (cpu <= 0x80)
718 {
719 ret += QString(cp.toLatin1());
720 }
721 else if (!suppress_unknown)
722 {
723 ret += QString("{%1}").arg(cpu, 2, 16);
724 }
725 }
726 }
727
728 return ret;
729}
730
731void CC608Decoder::BufferCC(size_t mode, size_t len, int clr)
732{
733 QByteArray tmpbuf;
734 if (len)
735 {
736 // calculate UTF-8 encoding length
737 tmpbuf = m_ccBuf[mode].toUtf8();
738#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
739 len = std::min(tmpbuf.length(), 255);
740#else
741 len = std::min(tmpbuf.length(), static_cast<qsizetype>(255));
742#endif
743 }
744
745 unsigned char *bp = m_rbuf;
746 *(bp++) = m_row[mode];
747 *(bp++) = m_rowCount[mode];
748 *(bp++) = m_style[mode];
749 // overload resumetext field
750 unsigned char f = m_resumeText[mode];
751 f |= mode << 4;
752 if (m_lineCont[mode])
753 f |= CC_LINE_CONT;
754 *(bp++) = f;
755 *(bp++) = clr;
756 *(bp++) = len;
757 if (len)
758 {
759 memcpy(bp,
760 tmpbuf.constData(),
761 len);
762 len += sizeof(ccsubtitle);
763 }
764 else
765 {
766 len = sizeof(ccsubtitle);
767 }
768
769 if ((len != 0) && VERBOSE_LEVEL_CHECK(VB_VBI, LOG_INFO))
770 {
771 LOG(VB_VBI, LOG_INFO, QString("### %1 %2 %3 %4 %5 %6 %7 - '%8'")
772 .arg(m_timeCode[mode].count(), 10)
773 .arg(m_row[mode], 2).arg(m_rowCount[mode])
774 .arg(m_style[mode]).arg(f, 2, 16)
775 .arg(clr).arg(len, 3)
776 .arg(ToASCII(QString::fromUtf8(tmpbuf.constData(), len), false)));
777 }
778
779 m_reader->AddTextData(m_rbuf, len, m_timeCode[mode], 'C');
780 uint8_t ccmode = m_rbuf[3] & CC_MODE_MASK;
781 int stream = -1;
782 switch (ccmode)
783 {
784 case CC_CC1: stream = 0; break;
785 case CC_CC2: stream = 1; break;
786 case CC_CC3: stream = 2; break;
787 case CC_CC4: stream = 3; break;
788 }
789 if (stream >= 0)
790 m_lastSeen[stream] = std::chrono::system_clock::now();
791
792 m_resumeText[mode] = 0;
793 if (clr && !len)
794 m_lastClr[mode] = m_timeCode[mode];
795 else if (len)
796 m_lastClr[mode] = 0ms;
797}
798
799size_t CC608Decoder::NewRowCC(size_t mode, size_t len)
800{
801 if (m_style[mode] == CC_STYLE_ROLLUP)
802 {
803 // previous line was likely missing a carriage return
804 m_row[mode] = m_newRow[mode];
805 if (len)
806 {
807 BufferCC(mode, len, 0);
808 m_ccBuf[mode] = "";
809 len = 0;
810 }
811 m_col[mode] = 0;
812 m_lineCont[mode] = 0;
813 }
814 else
815 {
816 // popup/paint style
817
818 if (m_row[mode] == 0)
819 {
820 if (len == 0)
821 {
822 m_row[mode] = m_newRow[mode];
823 }
824 else
825 {
826 // previous line was missing a row address
827 // - assume it was one row up
828 m_ccBuf[mode] += '\n';
829 len++;
830 if (m_row[mode] == 0)
831 m_row[mode] = m_newRow[mode] - 1;
832 else
833 m_row[mode]--;
834 }
835 }
836 else if (m_newRow[mode] > m_lastRow[mode])
837 {
838 // next line can be more than one row away
839 for (int i = 0; i < (m_newRow[mode] - m_lastRow[mode]); i++)
840 {
841 m_ccBuf[mode] += '\n';
842 len++;
843 }
844 m_col[mode] = 0;
845 }
846 else if (m_newRow[mode] == m_lastRow[mode])
847 {
848 // same row
849 if (m_newCol[mode] >= m_col[mode])
850 {
851 // new line appends to current line
852 m_newCol[mode] -= m_col[mode];
853 }
854 else
855 {
856 // new line overwrites current line;
857 // could be legal (overwrite spaces?) but
858 // more likely we have bad address codes
859 // - just move to next line; may exceed row 15
860 // but frontend will adjust
861 m_ccBuf[mode] += '\n';
862 len++;
863 m_col[mode] = 0;
864 }
865 }
866 else
867 {
868 // next line goes upwards (not legal?)
869 // - flush
870 BufferCC(mode, len, 0);
871 m_ccBuf[mode] = "";
872 m_row[mode] = m_newRow[mode];
873 m_col[mode] = 0;
874 m_lineCont[mode] = 0;
875 len = 0;
876 }
877 }
878
879 m_lastRow[mode] = m_newRow[mode];
880 m_newRow[mode] = 0;
881
882 int limit = m_newCol[mode];
883 for (int x = 0; x < limit; x++)
884 {
885 m_ccBuf[mode] += ' ';
886 len++;
887 m_col[mode]++;
888 }
889
890 if (m_newAttr[mode])
891 {
892 m_ccBuf[mode] += QChar(static_cast<uint8_t>(m_newAttr[mode]) + 0x7000);
893 len++;
894 }
895
896 m_newCol[mode] = 0;
897 m_newAttr[mode] = 0;
898
899 return len;
900}
901
902
903static bool IsPrintable(uint8_t c)
904{
905 return (c & 0x7F) >= 0x20 && (c & 0x7F) <= 0x7E;
906}
907
908static int8_t Printable(uint8_t c)
909{
910 return IsPrintable(c) ? (c & 0x7F) : '.';
911}
912
913#if 0
914static int OddParity(unsigned char c)
915{
916 c ^= (c >> 4); c ^= (c >> 2); c ^= (c >> 1);
917 return c & 1;
918}
919#endif
920
921// // // // // // // // // // // // // // // // // // // // // // // //
922// // // // // // // // // // // VPS // // // // // // // // // // //
923// // // // // // // // // // // // // // // // // // // // // // // //
924
925static constexpr int PIL_TIME(int day, int mon, int hour, int min)
926{ return (day << 15) + (mon << 11) + (hour << 6) + min; }
927
928static void DumpPIL(int pil)
929{
930 int day = (pil >> 15);
931 int mon = (pil >> 11) & 0xF;
932 int hour = (pil >> 6 ) & 0x1F;
933 int min = pil & 0x3F;
934
935 if (pil == PIL_TIME(0, 15, 31, 63))
936 LOG(VB_VBI, LOG_INFO, " PDC: Timer-control (no PDC)");
937 else if (pil == PIL_TIME(0, 15, 30, 63))
938 LOG(VB_VBI, LOG_INFO, " PDC: Recording inhibit/terminate");
939 else if (pil == PIL_TIME(0, 15, 29, 63))
940 LOG(VB_VBI, LOG_INFO, " PDC: Interruption");
941 else if (pil == PIL_TIME(0, 15, 28, 63))
942 LOG(VB_VBI, LOG_INFO, " PDC: Continue");
943 else if (pil == PIL_TIME(31, 15, 31, 63))
944 LOG(VB_VBI, LOG_INFO, " PDC: No time");
945 else
946 LOG(VB_VBI, LOG_INFO, QString(" PDC: %1, 200X-%2-%3 %4:%5")
947 .arg(pil).arg(mon).arg(day).arg(hour).arg(min));
948#undef PIL_TIME
949}
950
951void CC608Decoder::DecodeVPS(const unsigned char *buf)
952{
953 int c = vbi_bit_reverse[buf[1]];
954
955 if ((int8_t) c < 0)
956 {
957 m_vpsLabel[m_vpsL] = 0;
959 m_vpsL = 0;
960 }
961 c &= 0x7F;
963 m_vpsL = (m_vpsL + 1) % 16;
964
965 LOG(VB_VBI, LOG_INFO, QString("VPS: 3-10: %1 %2 %3 %4 %5 %6 %7 %8 (\"%9\")")
966 .arg(buf[0]).arg(buf[1]).arg(buf[2]).arg(buf[3]).arg(buf[4])
967 .arg(buf[5]).arg(buf[6]).arg(buf[7]).arg(m_vpsPrLabel.data()));
968
969 int pcs = buf[2] >> 6;
970 int cni = + ((buf[10] & 3) << 10)
971 + ((buf[11] & 0xC0) << 2)
972 + ((buf[8] & 0xC0) << 0)
973 + (buf[11] & 0x3F);
974 int pil = ((buf[8] & 0x3F) << 14) + (buf[9] << 6) + (buf[10] >> 2);
975 int pty = buf[12];
976
977 LOG(VB_VBI, LOG_INFO, QString("CNI: %1 PCS: %2 PTY: %3 ")
978 .arg(cni).arg(pcs).arg(pty));
979
980 DumpPIL(pil);
981
982 // c & 0xf0;
983}
984
985// // // // // // // // // // // // // // // // // // // // // // // //
986// // // // // // // // // // // WSS // // // // // // // // // // //
987// // // // // // // // // // // // // // // // // // // // // // // //
988
989void CC608Decoder::DecodeWSS(const unsigned char *buf)
990{
991 static const std::array<const int,8> kWssBits { 0, 0, 0, 1, 0, 1, 1, 1 };
992 uint wss = 0;
993
994 for (uint i = 0; i < 16; i++)
995 {
996 uint b1 = kWssBits[buf[i] & 7];
997 uint b2 = kWssBits[(buf[i] >> 3) & 7];
998
999 if (b1 == b2)
1000 return;
1001 wss |= b2 << i;
1002 }
1003 unsigned char parity = wss & 0xf;
1004 parity ^= parity >> 2;
1005 parity ^= parity >> 1;
1006
1007 LOG(VB_VBI, LOG_INFO,
1008 QString("WSS: %1; %2 mode; %3 color coding;\n\t\t\t"
1009 " %4 helper; reserved b7=%5; %6\n\t\t\t"
1010 " open subtitles: %7; %scopyright %8; copying %9")
1011 .arg(QString::fromStdString(formats[wss & 7]),
1012 (wss & 0x0010) ? "film" : "camera",
1013 (wss & 0x0020) ? "MA/CP" : "standard",
1014 (wss & 0x0040) ? "modulated" : "no",
1015 (wss & 0x0080) ? "1" : "0",
1016 (wss & 0x0100) ? "have TTX subtitles; " : "",
1017 QString::fromStdString(subtitles[(wss >> 9) & 3]),
1018 (wss & 0x0800) ? "surround sound; " : "",
1019 (wss & 0x1000) ? "asserted" : "unknown")
1020 // Qt < 5.14 only allows a max of nin string arguments in a single call
1021 .arg((wss & 0x2000) ? "restricted" : "not restricted")); // clazy:exclude=qstring-arg
1022
1023 if (parity & 1)
1024 {
1025 m_wssFlags = wss;
1026 m_wssValid = true;
1027 }
1028}
1029
1030QString CC608Decoder::XDSDecodeString(const std::vector<unsigned char> &buf,
1031 uint start, uint end) const
1032{
1033#if DEBUG_XDS
1034 for (uint i = start; (i < buf.size()) && (i < end); i++)
1035 {
1036 LOG(VB_VBI, LOG_INFO, QString("%1: 0x%2 -> 0x%3 %4")
1037 .arg(i,2)
1038 .arg(buf[i],2,16,QChar('0'))
1039 .arg(CharCC(buf[i]).unicode(),2,16,QChar('0'))
1040 .arg(CharCC(buf[i])));
1041 }
1042#endif // DEBUG_XDS
1043
1044 QString tmp = "";
1045 for (uint i = start; (i < buf.size()) && (i < end); i++)
1046 {
1047 if (buf[i] > 0x0)
1048 tmp += CharCC(buf[i]);
1049 }
1050
1051#if DEBUG_XDS
1052 LOG(VB_VBI, LOG_INFO, QString("XDSDecodeString: '%1'").arg(tmp));
1053#endif // DEBUG_XDS
1054
1055 return tmp.trimmed();
1056}
1057
1058static bool is_better(const QString &newStr, const QString &oldStr)
1059{
1060 if (!newStr.isEmpty() && newStr != oldStr &&
1061 (newStr != oldStr.left(newStr.length())))
1062 {
1063 if (oldStr.isEmpty())
1064 return true;
1065
1066 // check if the string contains any bogus characters
1067 return std::ranges::all_of(std::as_const(newStr),
1068 [](auto ch){ return ch.toLatin1() >= 0x20; } );
1069 }
1070 return false;
1071}
1072
1074{
1075 QMutexLocker locker(&m_xdsLock);
1076 return m_xdsRatingSystems[future ? 1 : 0];
1077}
1078
1079uint CC608Decoder::GetRating(uint i, bool future) const
1080{
1081 QMutexLocker locker(&m_xdsLock);
1082 return m_xdsRating[future ? 1 : 0][i & 0x3] & 0x7;
1083}
1084
1085QString CC608Decoder::GetRatingString(uint i, bool future) const
1086{
1087 QMutexLocker locker(&m_xdsLock);
1088
1089 const std::array<const QString,4> prefix { "MPAA-", "TV-", "CE-", "CF-" };
1090 const std::array<const std::array<const QString,8>,4> mainStr
1091 {{
1092 { "NR", "G", "PG", "PG-13", "R", "NC-17", "X", "NR" },
1093 { "NR", "Y", "Y7", "G", "PG", "14", "MA", "NR" },
1094 { "E", "C", "C8+", "G", "PG", "14+", "18+", "NR" },
1095 { "E", "G", "8+", "13+", "16+", "18+", "NR", "NR" },
1096 }};
1097
1098 QString main = prefix[i] + mainStr[i][GetRating(i, future)];
1099
1100 if (kRatingTPG == i)
1101 {
1102 uint cf = future ? 1 : 0;
1103 if (!(m_xdsRating[cf][i]&0xF0))
1104 return main;
1105
1106 main += " ";
1107 // TPG flags
1108 if (m_xdsRating[cf][i] & 0x80)
1109 main += "D"; // Dialog
1110 if (m_xdsRating[cf][i] & 0x40)
1111 main += "V"; // Violence
1112 if (m_xdsRating[cf][i] & 0x20)
1113 main += "S"; // Sex
1114 if (m_xdsRating[cf][i] & 0x10)
1115 main += "L"; // Language
1116 }
1117
1118 return main;
1119}
1120
1121QString CC608Decoder::GetProgramName(bool future) const
1122{
1123 QMutexLocker locker(&m_xdsLock);
1124 return m_xdsProgramName[future ? 1 : 0];
1125}
1126
1127QString CC608Decoder::GetProgramType(bool future) const
1128{
1129 QMutexLocker locker(&m_xdsLock);
1130 const std::vector<uint> &program_type = m_xdsProgramType[future ? 1 : 0];
1131 QString tmp = "";
1132
1133 for (size_t i = 0; i < program_type.size(); i++)
1134 {
1135 if (i != 0)
1136 tmp += ", ";
1137 tmp += m_xdsProgramTypeString[program_type[i]];
1138 }
1139
1140 return tmp;
1141}
1142
1143QString CC608Decoder::GetXDS(const QString &key) const
1144{
1145 QMutexLocker locker(&m_xdsLock);
1146
1147 if (key == "ratings")
1148 return QString::number(GetRatingSystems(false));
1149 if (key.startsWith("has_rating_"))
1150 return ((1<<key.right(1).toUInt()) & GetRatingSystems(false))?"1":"0";
1151 if (key.startsWith("rating_"))
1152 return GetRatingString(key.right(1).toUInt(), false);
1153
1154 if (key == "future_ratings")
1155 return QString::number(GetRatingSystems(true));
1156 if (key.startsWith("has_future_rating_"))
1157 return ((1<<key.right(1).toUInt()) & GetRatingSystems(true))?"1":"0";
1158 if (key.startsWith("future_rating_"))
1159 return GetRatingString(key.right(1).toUInt(), true);
1160
1161 if (key == "programname")
1162 return GetProgramName(false);
1163 if (key == "future_programname")
1164 return GetProgramName(true);
1165
1166 if (key == "programtype")
1167 return GetProgramType(false);
1168 if (key == "future_programtype")
1169 return GetProgramType(true);
1170
1171 if (key == "callsign")
1172 return m_xdsNetCall;
1173 if (key == "channame")
1174 return m_xdsNetName;
1175 if (key == "tsid")
1176 return QString::number(m_xdsTsid);
1177
1178 return {};
1179}
1180
1181static std::array<const int,16> b1_to_service
1182{ -1, // 0x0
1183 0, 0, //0x1,0x2 -- Current
1184 1, 1, //0x3,0x4 -- Future
1185 2, 2, //0x5,0x6 -- Channel
1186 3, 3, //0x7,0x8 -- Misc
1187 4, 4, //0x9,0xA -- Public Service
1188 5, 5, //0xB,0xC -- Reserved
1189 6, 6, //0xD,0xE -- Private Data
1190 -1, // 0xF
1191};
1192
1193bool CC608Decoder::XDSDecode([[maybe_unused]] size_t field, int b1, int b2)
1194{
1195 if (field == 0)
1196 return false; // XDS is only on second field
1197
1198#if DEBUG_XDS
1199 LOG(VB_VBI, LOG_INFO,
1200 QString("XDSDecode: 0x%1 0x%2 '%3%4' xds[%5]=%6 in XDS %7")
1201 .arg(b1,2,16,QChar('0')).arg(b2,2,16,QChar('0'))
1202 .arg((CharCC(b1).unicode()>0x20) ? CharCC(b1) : QChar(' '))
1203 .arg((CharCC(b2).unicode()>0x20) ? CharCC(b2) : QChar(' '))
1204 .arg(field).arg(m_xds[field])
1205 .arg(m_xdsCurService));
1206#endif // DEBUG_XDS
1207
1208 if (m_xdsCurService < 0)
1209 {
1210 if (b1 > 0x0f)
1211 return false;
1212
1214
1215 if (m_xdsCurService < 0)
1216 return false;
1217
1218 if (b1 & 1)
1219 {
1220 m_xdsBuf[m_xdsCurService].clear(); // if start of service clear buffer
1221#if DEBUG_XDS
1222 LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Starting XDS %1").arg(m_xdsCurService));
1223#endif // DEBUG_XDS
1224 }
1225 }
1226 else if ((0x0 < b1) && (b1 < 0x0f))
1227 { // switch to different service
1229#if DEBUG_XDS
1230 LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Resuming XDS %1").arg(m_xdsCurService));
1231#endif // DEBUG_XDS
1232 }
1233
1234 if (m_xdsCurService < 0)
1235 return false;
1236
1237 m_xdsBuf[m_xdsCurService].push_back(b1);
1238 m_xdsBuf[m_xdsCurService].push_back(b2);
1239
1240 if (b1 == 0x0f) // end of packet
1241 {
1242#if DEBUG_XDS
1243 LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Ending XDS %1").arg(m_xdsCurService));
1244#endif // DEBUG_XDS
1247 m_xdsBuf[m_xdsCurService].clear();
1248 m_xdsCurService = -1;
1249 }
1250 else if ((0x10 <= b1) && (b1 <= 0x1f)) // suspension of XDS packet
1251 {
1252#if DEBUG_XDS
1253 LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Suspending XDS %1 on 0x%2")
1254 .arg(m_xdsCurService).arg(b1,2,16,QChar('0')));
1255#endif // DEBUG_XDS
1256 m_xdsCurService = -1;
1257 }
1258
1259 return true;
1260}
1261
1262void CC608Decoder::XDSPacketParse(const std::vector<unsigned char> &xds_buf)
1263{
1264 QMutexLocker locker(&m_xdsLock);
1265
1266 bool handled = false;
1267 int xds_class = xds_buf[0];
1268
1269 if (!xds_class)
1270 return;
1271
1272 if ((xds_class == 0x01) || (xds_class == 0x03)) // cont code 2 and 4, resp.
1273 handled = XDSPacketParseProgram(xds_buf, (xds_class == 0x03));
1274 else if (xds_class == 0x05) // cont code: 0x06
1275 handled = XDSPacketParseChannel(xds_buf);
1276 else if ((xds_class == 0x07) || // cont code: 0x08 // misc.
1277 (xds_class == 0x09) || // cont code: 0x0a // public (aka weather)
1278 (xds_class == 0x0b)) // cont code: 0x0c // reserved
1279 ;
1280 else if (xds_class == 0x0d) // cont code: 0x0e
1281 handled = true; // undefined
1282
1283 if (!handled)
1284 {
1285#if DEBUG_XDS
1286 LOG(VB_VBI, LOG_INFO, QString("XDS: ") +
1287 QString("Unhandled packet (0x%1 0x%2) sz(%3) '%4'")
1288 .arg(xds_buf[0],0,16).arg(xds_buf[1],0,16)
1289 .arg(xds_buf.size())
1290 .arg(XDSDecodeString(xds_buf, 2, xds_buf.size() - 2)));
1291#endif
1292 }
1293}
1294
1295bool CC608Decoder::XDSPacketCRC(const std::vector<unsigned char> &xds_buf)
1296{
1297 /* Check the checksum for validity of the packet. */
1298 int sum = 0;
1299 for (size_t i = 0; i < xds_buf.size() - 1; i++)
1300 sum += xds_buf[i];
1301
1302 if ((((~sum) & 0x7f) + 1) != xds_buf[xds_buf.size() - 1])
1303 {
1305
1306 LOG(VB_VBI, LOG_ERR, QString("XDS: failed CRC %1 of %2")
1308
1309 return false;
1310 }
1311
1313 return true;
1314}
1315
1317 const std::vector<unsigned char> &xds_buf, bool future)
1318{
1319 bool handled = true;
1320 int b2 = xds_buf[1];
1321 int cf = future ? 1 : 0;
1322 QString loc = future ? "XDS: Future " : "XDS: Current ";
1323
1324 if ((b2 == 0x01) && (xds_buf.size() >= 6))
1325 {
1326 uint min = xds_buf[2] & 0x3f;
1327 uint hour = xds_buf[3] & 0x0f;
1328 uint day = xds_buf[4] & 0x1f;
1329 uint month = xds_buf[5] & 0x0f;
1330 month = (month < 1 || month > 12) ? 0 : month;
1331
1332 LOG(VB_VBI, LOG_INFO, loc +
1333 QString("Start Time %1/%2 %3:%4%5")
1334 .arg(month).arg(day).arg(hour).arg(min / 10).arg(min % 10));
1335 }
1336 else if ((b2 == 0x02) && (xds_buf.size() >= 4))
1337 {
1338 uint length_min = xds_buf[2] & 0x3f;
1339 uint length_hour = xds_buf[3] & 0x3f;
1340 uint length_elapsed_min = 0;
1341 uint length_elapsed_hour = 0;
1342 uint length_elapsed_secs = 0;
1343 if (xds_buf.size() > 6)
1344 {
1345 length_elapsed_min = xds_buf[4] & 0x3f;
1346 length_elapsed_hour = xds_buf[5] & 0x3f;
1347 }
1348 if (xds_buf.size() > 8 && xds_buf[7] == 0x40)
1349 length_elapsed_secs = xds_buf[6] & 0x3f;
1350
1351 QString msg = QString("Program Length %1:%2%3 "
1352 "Time in Show %4:%5%6.%7%8")
1353 .arg(length_hour).arg(length_min / 10).arg(length_min % 10)
1354 .arg(length_elapsed_hour)
1355 .arg(length_elapsed_min / 10).arg(length_elapsed_min % 10)
1356 .arg(length_elapsed_secs / 10).arg(length_elapsed_secs % 10);
1357
1358 LOG(VB_VBI, LOG_INFO, loc + msg);
1359 }
1360 else if ((b2 == 0x03) && (xds_buf.size() >= 6))
1361 {
1362 QString tmp = XDSDecodeString(xds_buf, 2, xds_buf.size() - 2);
1363 if (is_better(tmp, m_xdsProgramName[cf]))
1364 {
1365 m_xdsProgramName[cf] = tmp;
1366 LOG(VB_VBI, LOG_INFO, loc + QString("Program Name: '%1'")
1367 .arg(GetProgramName(future)));
1368 }
1369 }
1370 else if ((b2 == 0x04) && (xds_buf.size() >= 6))
1371 {
1372 std::vector<uint> program_type;
1373 for (size_t i = 2; i < xds_buf.size() - 2; i++)
1374 {
1375 int cur = xds_buf[i] - 0x20;
1376 if (cur >= 0 && cur < 96)
1377 program_type.push_back(cur);
1378 }
1379
1380 bool unchanged = m_xdsProgramType[cf].size() == program_type.size();
1381 for (uint i = 0; (i < program_type.size()) && unchanged; i++)
1382 unchanged = m_xdsProgramType[cf][i] == program_type[i];
1383
1384 if (!unchanged)
1385 {
1386 m_xdsProgramType[cf] = program_type;
1387 LOG(VB_VBI, LOG_INFO, loc + QString("Program Type '%1'")
1388 .arg(GetProgramType(future)));
1389 }
1390 }
1391 else if ((b2 == 0x05) && (xds_buf.size() >= 4))
1392 {
1393 uint movie_rating = xds_buf[2] & 0x7;
1394 uint rating_system = (xds_buf[2] >> 3) & 0x7;
1395 uint tv_rating = xds_buf[3] & 0x7;
1396 uint VSL = xds_buf[3] & (0x7 << 3);
1397 uint sel = VSL | rating_system;
1398 if (sel == 3)
1399 {
1400 if (!(kHasCanEnglish & m_xdsRatingSystems[cf]) ||
1401 (tv_rating != GetRating(kRatingCanEnglish, future)))
1402 {
1404 m_xdsRating[cf][kRatingCanEnglish] = tv_rating;
1405 LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
1406 .arg(GetRatingString(kRatingCanEnglish, future)));
1407 }
1408 }
1409 else if (sel == 7)
1410 {
1411 if (!(kHasCanFrench & m_xdsRatingSystems[cf]) ||
1412 (tv_rating != GetRating(kRatingCanFrench, future)))
1413 {
1415 m_xdsRating[cf][kRatingCanFrench] = tv_rating;
1416 LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
1417 .arg(GetRatingString(kRatingCanFrench, future)));
1418 }
1419 }
1420 else if (sel == 0x13 || sel == 0x1f)
1421 {
1422 ; // Reserved according to TVTime code
1423 }
1424 else if ((rating_system & 0x3) == 1)
1425 {
1426 if (!(kHasTPG & m_xdsRatingSystems[cf]) ||
1427 (tv_rating != GetRating(kRatingTPG, future)))
1428 {
1429 uint f = ((xds_buf[0]<<3) & 0x80) | ((xds_buf[1]<<1) & 0x70);
1431 m_xdsRating[cf][kRatingTPG] = tv_rating | f;
1432 LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
1433 .arg(GetRatingString(kRatingTPG, future)));
1434 }
1435 }
1436 else if (rating_system == 0)
1437 {
1438 if (!(kHasMPAA & m_xdsRatingSystems[cf]) ||
1439 (movie_rating != GetRating(kRatingMPAA, future)))
1440 {
1442 m_xdsRating[cf][kRatingMPAA] = movie_rating;
1443 LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
1444 .arg(GetRatingString(kRatingMPAA, future)));
1445 }
1446 }
1447 else
1448 {
1449 LOG(VB_VBI, LOG_ERR, loc +
1450 QString("VChip Unhandled -- rs(%1) rating(%2:%3)")
1451 .arg(rating_system).arg(tv_rating).arg(movie_rating));
1452 }
1453 }
1454#if 0
1455 else if (b2 == 0x07)
1456 ; // caption
1457 else if (b2 == 0x08)
1458 ; // cgms
1459 else if (b2 == 0x09)
1460 ; // unknown
1461 else if (b2 == 0x0c)
1462 ; // misc
1463 else if (b2 == 0x10 || b2 == 0x13 || b2 == 0x15 || b2 == 0x16 ||
1464 b2 == 0x91 || b2 == 0x92 || b2 == 0x94 || b2 == 0x97)
1465 ; // program description
1466 else if (b2 == 0x86)
1467 ; // audio
1468 else if (b2 == 0x89)
1469 ; // aspect
1470 else if (b2 == 0x8c)
1471 ; // program data
1472#endif
1473 else
1474 {
1475 handled = false;
1476 }
1477
1478 return handled;
1479}
1480
1481bool CC608Decoder::XDSPacketParseChannel(const std::vector<unsigned char> &xds_buf)
1482{
1483 bool handled = true;
1484
1485 int b2 = xds_buf[1];
1486 if ((b2 == 0x01) && (xds_buf.size() >= 6))
1487 {
1488 QString tmp = XDSDecodeString(xds_buf, 2, xds_buf.size() - 2);
1489 if (is_better(tmp, m_xdsNetName))
1490 {
1491 LOG(VB_VBI, LOG_INFO, QString("XDS: Network Name '%1'").arg(tmp));
1492 m_xdsNetName = tmp;
1493 }
1494 }
1495 else if ((b2 == 0x02) && (xds_buf.size() >= 6))
1496 {
1497 QString tmp = XDSDecodeString(xds_buf, 2, xds_buf.size() - 2);
1498 if (is_better(tmp, m_xdsNetCall) && (tmp.indexOf(" ") < 0))
1499 {
1500 LOG(VB_VBI, LOG_INFO, QString("XDS: Network Call '%1'").arg(tmp));
1501 m_xdsNetCall = tmp;
1502 }
1503 }
1504 else if ((b2 == 0x04) && (xds_buf.size() >= 6))
1505 {
1506 uint tsid = (xds_buf[2] << 24 | xds_buf[3] << 16 |
1507 xds_buf[4] << 8 | xds_buf[5]);
1508 if (tsid != m_xdsTsid)
1509 {
1510 LOG(VB_VBI, LOG_INFO, QString("XDS: TSID 0x%1").arg(tsid,0,16));
1511 m_xdsTsid = tsid;
1512 }
1513 }
1514 else
1515 {
1516 handled = false;
1517 }
1518
1519 return handled;
1520}
1521
1522static void init_xds_program_type(CC608ProgramType& xds_program_type)
1523{
1524 xds_program_type[0] = QCoreApplication::translate("(Categories)",
1525 "Education");
1526 xds_program_type[1] = QCoreApplication::translate("(Categories)",
1527 "Entertainment");
1528 xds_program_type[2] = QCoreApplication::translate("(Categories)",
1529 "Movie");
1530 xds_program_type[3] = QCoreApplication::translate("(Categories)",
1531 "News");
1532 xds_program_type[4] = QCoreApplication::translate("(Categories)",
1533 "Religious");
1534 xds_program_type[5] = QCoreApplication::translate("(Categories)",
1535 "Sports");
1536 xds_program_type[6] = QCoreApplication::translate("(Categories)",
1537 "Other");
1538 xds_program_type[7] = QCoreApplication::translate("(Categories)",
1539 "Action");
1540 xds_program_type[8] = QCoreApplication::translate("(Categories)",
1541 "Advertisement");
1542 xds_program_type[9] = QCoreApplication::translate("(Categories)",
1543 "Animated");
1544 xds_program_type[10] = QCoreApplication::translate("(Categories)",
1545 "Anthology");
1546 xds_program_type[11] = QCoreApplication::translate("(Categories)",
1547 "Automobile");
1548 xds_program_type[12] = QCoreApplication::translate("(Categories)",
1549 "Awards");
1550 xds_program_type[13] = QCoreApplication::translate("(Categories)",
1551 "Baseball");
1552 xds_program_type[14] = QCoreApplication::translate("(Categories)",
1553 "Basketball");
1554 xds_program_type[15] = QCoreApplication::translate("(Categories)",
1555 "Bulletin");
1556 xds_program_type[16] = QCoreApplication::translate("(Categories)",
1557 "Business");
1558 xds_program_type[17] = QCoreApplication::translate("(Categories)",
1559 "Classical");
1560 xds_program_type[18] = QCoreApplication::translate("(Categories)",
1561 "College");
1562 xds_program_type[19] = QCoreApplication::translate("(Categories)",
1563 "Combat");
1564 xds_program_type[20] = QCoreApplication::translate("(Categories)",
1565 "Comedy");
1566 xds_program_type[21] = QCoreApplication::translate("(Categories)",
1567 "Commentary");
1568 xds_program_type[22] = QCoreApplication::translate("(Categories)",
1569 "Concert");
1570 xds_program_type[23] = QCoreApplication::translate("(Categories)",
1571 "Consumer");
1572 xds_program_type[24] = QCoreApplication::translate("(Categories)",
1573 "Contemporary");
1574 xds_program_type[25] = QCoreApplication::translate("(Categories)",
1575 "Crime");
1576 xds_program_type[26] = QCoreApplication::translate("(Categories)",
1577 "Dance");
1578 xds_program_type[27] = QCoreApplication::translate("(Categories)",
1579 "Documentary");
1580 xds_program_type[28] = QCoreApplication::translate("(Categories)",
1581 "Drama");
1582 xds_program_type[29] = QCoreApplication::translate("(Categories)",
1583 "Elementary");
1584 xds_program_type[30] = QCoreApplication::translate("(Categories)",
1585 "Erotica");
1586 xds_program_type[31] = QCoreApplication::translate("(Categories)",
1587 "Exercise");
1588 xds_program_type[32] = QCoreApplication::translate("(Categories)",
1589 "Fantasy");
1590 xds_program_type[33] = QCoreApplication::translate("(Categories)",
1591 "Farm");
1592 xds_program_type[34] = QCoreApplication::translate("(Categories)",
1593 "Fashion");
1594 xds_program_type[35] = QCoreApplication::translate("(Categories)",
1595 "Fiction");
1596 xds_program_type[36] = QCoreApplication::translate("(Categories)",
1597 "Food");
1598 xds_program_type[37] = QCoreApplication::translate("(Categories)",
1599 "Football");
1600 xds_program_type[38] = QCoreApplication::translate("(Categories)",
1601 "Foreign");
1602 xds_program_type[39] = QCoreApplication::translate("(Categories)",
1603 "Fundraiser");
1604 xds_program_type[40] = QCoreApplication::translate("(Categories)",
1605 "Game/Quiz");
1606 xds_program_type[41] = QCoreApplication::translate("(Categories)",
1607 "Garden");
1608 xds_program_type[42] = QCoreApplication::translate("(Categories)",
1609 "Golf");
1610 xds_program_type[43] = QCoreApplication::translate("(Categories)",
1611 "Government");
1612 xds_program_type[44] = QCoreApplication::translate("(Categories)",
1613 "Health");
1614 xds_program_type[45] = QCoreApplication::translate("(Categories)",
1615 "High School");
1616 xds_program_type[46] = QCoreApplication::translate("(Categories)",
1617 "History");
1618 xds_program_type[47] = QCoreApplication::translate("(Categories)",
1619 "Hobby");
1620 xds_program_type[48] = QCoreApplication::translate("(Categories)",
1621 "Hockey");
1622 xds_program_type[49] = QCoreApplication::translate("(Categories)",
1623 "Home");
1624 xds_program_type[50] = QCoreApplication::translate("(Categories)",
1625 "Horror");
1626 xds_program_type[51] = QCoreApplication::translate("(Categories)",
1627 "Information");
1628 xds_program_type[52] = QCoreApplication::translate("(Categories)",
1629 "Instruction");
1630 xds_program_type[53] = QCoreApplication::translate("(Categories)",
1631 "International");
1632 xds_program_type[54] = QCoreApplication::translate("(Categories)",
1633 "Interview");
1634 xds_program_type[55] = QCoreApplication::translate("(Categories)",
1635 "Language");
1636 xds_program_type[56] = QCoreApplication::translate("(Categories)",
1637 "Legal");
1638 xds_program_type[57] = QCoreApplication::translate("(Categories)",
1639 "Live");
1640 xds_program_type[58] = QCoreApplication::translate("(Categories)",
1641 "Local");
1642 xds_program_type[59] = QCoreApplication::translate("(Categories)",
1643 "Math");
1644 xds_program_type[60] = QCoreApplication::translate("(Categories)",
1645 "Medical");
1646 xds_program_type[61] = QCoreApplication::translate("(Categories)",
1647 "Meeting");
1648 xds_program_type[62] = QCoreApplication::translate("(Categories)",
1649 "Military");
1650 xds_program_type[63] = QCoreApplication::translate("(Categories)",
1651 "Miniseries");
1652 xds_program_type[64] = QCoreApplication::translate("(Categories)",
1653 "Music");
1654 xds_program_type[65] = QCoreApplication::translate("(Categories)",
1655 "Mystery");
1656 xds_program_type[66] = QCoreApplication::translate("(Categories)",
1657 "National");
1658 xds_program_type[67] = QCoreApplication::translate("(Categories)",
1659 "Nature");
1660 xds_program_type[68] = QCoreApplication::translate("(Categories)",
1661 "Police");
1662 xds_program_type[69] = QCoreApplication::translate("(Categories)",
1663 "Politics");
1664 xds_program_type[70] = QCoreApplication::translate("(Categories)",
1665 "Premiere");
1666 xds_program_type[71] = QCoreApplication::translate("(Categories)",
1667 "Prerecorded");
1668 xds_program_type[72] = QCoreApplication::translate("(Categories)",
1669 "Product");
1670 xds_program_type[73] = QCoreApplication::translate("(Categories)",
1671 "Professional");
1672 xds_program_type[74] = QCoreApplication::translate("(Categories)",
1673 "Public");
1674 xds_program_type[75] = QCoreApplication::translate("(Categories)",
1675 "Racing");
1676 xds_program_type[76] = QCoreApplication::translate("(Categories)",
1677 "Reading");
1678 xds_program_type[77] = QCoreApplication::translate("(Categories)",
1679 "Repair");
1680 xds_program_type[78] = QCoreApplication::translate("(Categories)",
1681 "Repeat");
1682 xds_program_type[79] = QCoreApplication::translate("(Categories)",
1683 "Review");
1684 xds_program_type[80] = QCoreApplication::translate("(Categories)",
1685 "Romance");
1686 xds_program_type[81] = QCoreApplication::translate("(Categories)",
1687 "Science");
1688 xds_program_type[82] = QCoreApplication::translate("(Categories)",
1689 "Series");
1690 xds_program_type[83] = QCoreApplication::translate("(Categories)",
1691 "Service");
1692 xds_program_type[84] = QCoreApplication::translate("(Categories)",
1693 "Shopping");
1694 xds_program_type[85] = QCoreApplication::translate("(Categories)",
1695 "Soap Opera");
1696 xds_program_type[86] = QCoreApplication::translate("(Categories)",
1697 "Special");
1698 xds_program_type[87] = QCoreApplication::translate("(Categories)",
1699 "Suspense");
1700 xds_program_type[88] = QCoreApplication::translate("(Categories)",
1701 "Talk");
1702 xds_program_type[89] = QCoreApplication::translate("(Categories)",
1703 "Technical");
1704 xds_program_type[90] = QCoreApplication::translate("(Categories)",
1705 "Tennis");
1706 xds_program_type[91] = QCoreApplication::translate("(Categories)",
1707 "Travel");
1708 xds_program_type[92] = QCoreApplication::translate("(Categories)",
1709 "Variety");
1710 xds_program_type[93] = QCoreApplication::translate("(Categories)",
1711 "Video");
1712 xds_program_type[94] = QCoreApplication::translate("(Categories)",
1713 "Weather");
1714 xds_program_type[95] = QCoreApplication::translate("(Categories)",
1715 "Western");
1716}
static const std::array< const uint8_t, 256 > vbi_bit_reverse
static int8_t Printable(uint8_t c)
static std::array< const int, 16 > b1_to_service
static const std::array< const QChar, 32 > extendedchar2
static const std::array< const std::string, 4 > subtitles
static const std::array< const int, 16 > rowdata
static const std::array< const QChar, 16 > specialchar
static void DumpPIL(int pil)
static void init_xds_program_type(CC608ProgramType &xds_program_type)
static const std::array< const std::string, 8 > formats
static constexpr int PIL_TIME(int day, int mon, int hour, int min)
static bool IsPrintable(uint8_t c)
static const std::array< const QChar, 32 > extendedchar3
static bool is_better(const QString &newStr, const QString &oldStr)
std::array< bool, 4 > CC608Seen
Definition: cc608decoder.h:59
@ kHasCanEnglish
Definition: cc608decoder.h:78
@ kHasCanFrench
Definition: cc608decoder.h:79
@ kHasMPAA
Definition: cc608decoder.h:76
@ kHasTPG
Definition: cc608decoder.h:77
std::array< QString, 96 > CC608ProgramType
Definition: cc608decoder.h:60
static constexpr uint8_t CC_LINE_CONT
Definition: cc608decoder.h:45
@ CC_CC4
Definition: cc608decoder.h:54
@ CC_CC3
Definition: cc608decoder.h:53
@ CC_CC2
Definition: cc608decoder.h:50
@ CC_CC1
Definition: cc608decoder.h:49
@ kRatingCanFrench
Definition: cc608decoder.h:86
@ kRatingCanEnglish
Definition: cc608decoder.h:85
@ kRatingMPAA
Definition: cc608decoder.h:83
@ kRatingTPG
Definition: cc608decoder.h:84
@ CC_STYLE_PAINT
Definition: cc608decoder.h:40
@ CC_STYLE_POPUP
Definition: cc608decoder.h:39
@ CC_STYLE_ROLLUP
Definition: cc608decoder.h:41
static constexpr uint8_t CC_MODE_MASK
Definition: cc608decoder.h:46
CC608PerField m_badVbi
Definition: cc608decoder.h:142
uint m_xdsCrcPassed
Definition: cc608decoder.h:184
CC608PerFieldTc m_lastFormatTc
Definition: cc608decoder.h:170
bool FalseDup(std::chrono::milliseconds tc, size_t field, int data)
CC608Input * m_reader
Definition: cc608decoder.h:135
QString m_xdsNetName
Definition: cc608decoder.h:194
bool m_ignoreTimeCode
Definition: cc608decoder.h:137
CC608PerMode m_lineCont
Definition: cc608decoder.h:160
QString GetRatingString(uint i, bool future) const
CC608PerField m_xds
Definition: cc608decoder.h:147
void XDSPacketParse(const std::vector< unsigned char > &xds_buf)
CC608PerFieldTc m_lastTc
Definition: cc608decoder.h:143
QString GetProgramType(bool future) const
void BufferCC(size_t mode, size_t len, int clr)
static QString ToASCII(const QString &cc608, bool suppress_unknown)
std::array< uint, 2 > m_xdsRatingSystems
Definition: cc608decoder.h:188
size_t NewRowCC(size_t mode, size_t len)
QString GetXDS(const QString &key) const
CC608PerMode m_col
Definition: cc608decoder.h:157
QString XDSDecodeString(const std::vector< unsigned char > &buf, uint start, uint end) const
std::array< QString, 2 > m_xdsProgramName
Definition: cc608decoder.h:190
uint m_xdsCrcFailed
Definition: cc608decoder.h:185
void DecodeWSS(const unsigned char *buf)
CC608PerField m_lastFormatData
Definition: cc608decoder.h:171
CC608PerMode m_resumeText
Definition: cc608decoder.h:161
CC608PerModeTc m_lastClr
Definition: cc608decoder.h:162
std::array< QChar, 128 > m_stdChar
Definition: cc608decoder.h:166
void FormatControlCode(std::chrono::milliseconds tc, size_t field, int b1, int b2)
void FormatTextCode(std::chrono::milliseconds tc, size_t field, size_t mode, size_t len, int b1, int b2)
QRecursiveMutex m_xdsLock
Definition: cc608decoder.h:187
std::array< std::array< uint, 4 >, 2 > m_xdsRating
Definition: cc608decoder.h:189
void FormatCC(std::chrono::milliseconds tc, int code1, int code2)
bool XDSPacketParseChannel(const std::vector< unsigned char > &xds_buf)
bool XDSDecode(size_t field, int b1, int b2)
bool XDSPacketParseProgram(const std::vector< unsigned char > &xds_buf, bool future)
void DecodeVPS(const unsigned char *buf)
QChar CharCC(int code) const
Definition: cc608decoder.h:118
CC608PerMode m_lastRow
Definition: cc608decoder.h:151
QString m_xdsNetCall
Definition: cc608decoder.h:193
CC608PerField m_lastCode
Definition: cc608decoder.h:144
std::array< QString, 8 > m_ccBuf
Definition: cc608decoder.h:163
QString GetProgramName(bool future) const
CC608ProgramType m_xdsProgramTypeString
Definition: cc608decoder.h:197
uint GetRatingSystems(bool future) const
CC608PerFieldTc m_lastCodeTc
Definition: cc608decoder.h:145
void GetServices(std::chrono::seconds seconds, CC608Seen &seen) const
std::array< std::vector< unsigned char >, 7 > m_xdsBuf
Definition: cc608decoder.h:183
void ResetCC(size_t mode)
CC608PerMode m_newAttr
Definition: cc608decoder.h:154
bool XDSPacketCRC(const std::vector< unsigned char > &xds_buf)
std::array< std::vector< uint >, 2 > m_xdsProgramType
Definition: cc608decoder.h:191
unsigned char * m_rbuf
Definition: cc608decoder.h:169
std::array< char, 20 > m_vpsLabel
Definition: cc608decoder.h:175
CC608Decoder(CC608Input *ccr)
std::array< int, 4 > m_txtMode
Definition: cc608decoder.h:148
CC608PerMode m_style
Definition: cc608decoder.h:159
CC608PerMode m_newRow
Definition: cc608decoder.h:152
CC608PerMode m_rowCount
Definition: cc608decoder.h:158
std::array< SystemTime, 4 > m_lastSeen
Definition: cc608decoder.h:139
CC608PerField m_ccMode
Definition: cc608decoder.h:146
uint GetRating(uint i, bool future) const
CC608PerMode m_newCol
Definition: cc608decoder.h:153
std::array< char, 20 > m_vpsPrLabel
Definition: cc608decoder.h:174
CC608PerModeTc m_timeCode
Definition: cc608decoder.h:155
void FormatCCField(std::chrono::milliseconds tc, size_t field, int data)
CC608PerMode m_row
Definition: cc608decoder.h:156
virtual void AddTextData(unsigned char *buf, size_t len, std::chrono::milliseconds timecode, char type)=0
unsigned int uint
Definition: compat.h:60
static const iso6937table * d
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39