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