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