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