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