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