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