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