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