MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
cc608reader.cpp
Go to the documentation of this file.
1 extern "C" {
2 #include "vbitext/vbi.h"
3 }
4 
5 #include <algorithm>
6 using namespace std;
7 
8 #include "mythplayer.h"
9 #include "cc608reader.h"
10 
12  : m_parent(parent), m_enabled(false), m_readPosition(0),
13  m_writePosition(0), m_maxTextSize(0), m_ccMode(CC_CC1),
14  m_ccPageNum(0x888)
15 {
16  memset(&m_inputBuffers, 0, sizeof(m_inputBuffers));
17  m_maxTextSize = 8 * (sizeof(teletextsubtitle) + VT_WIDTH);
18  for (int i = 0; i < MAXTBUFFER; i++)
19  m_inputBuffers[i].buffer = new unsigned char[m_maxTextSize + 1];
20 }
21 
23 {
24  ClearBuffers(true, true);
25  for (int i = 0; i < MAXTBUFFER; i++)
26  {
27  if (m_inputBuffers[i].buffer)
28  {
29  delete [] m_inputBuffers[i].buffer;
30  m_inputBuffers[i].buffer = NULL;
31  }
32  }
33 }
34 
36 {
37  QMutexLocker locker(&m_inputBufLock);
39 }
40 
42 {
43  bool last_changed = true;
44  while (last_changed)
45  {
46  last_changed = false;
47  int streamIdx = -1;
48  CC608Buffer *tmp = GetOutputText(last_changed, streamIdx);
49  if (last_changed && (streamIdx == m_ccMode))
50  {
51  changed = true;
52  return tmp;
53  }
54  }
55 
56  return NULL;
57 }
58 
59 CC608Buffer *CC608Reader::GetOutputText(bool &changed, int &streamIdx)
60 {
61  streamIdx = -1;
62 
63  if (!m_enabled)
64  return NULL;
65 
66  if (!m_parent)
67  {
68  if (NumInputBuffers())
69  {
71  changed = true;
72 
73  QMutexLocker locker(&m_inputBufLock);
75  m_writePosition = (m_writePosition + 1) % MAXTBUFFER;
76  }
77 
78  if (streamIdx >= 0)
79  {
80  m_state[streamIdx].m_changed = false;
81  return &m_state[streamIdx].m_output;
82  }
83  else
84  return &m_state[MAXOUTBUFFERS - 1].m_output;
85  }
86 
87  VideoFrame *last = NULL;
88  if (m_parent->GetVideoOutput())
90 
91  if (NumInputBuffers() && m_inputBuffers[m_writePosition].timecode &&
92  (last && m_inputBuffers[m_writePosition].timecode <= last->timecode))
93  {
95  {
96  streamIdx = MAXOUTBUFFERS - 1;
97 
98  // display full page of teletext
99  //
100  // all formatting is always defined in the page itself,
101  // if scrolling is needed for live closed captions this
102  // is handled by the broadcaster:
103  // the pages are then very often transmitted (sometimes as often as
104  // every 2 frames) with small differences between them
105  unsigned char *inpos = m_inputBuffers[m_writePosition].buffer;
106  int pagenr;
107  memcpy(&pagenr, inpos, sizeof(int));
108  inpos += sizeof(int);
109 
110  if (pagenr == (m_ccPageNum<<16))
111  {
112  // show teletext subtitles
113  ClearBuffers(false, true, streamIdx);
114  (*inpos)++;
115  while (*inpos)
116  {
117  struct teletextsubtitle st;
118  memcpy(&st, inpos, sizeof(st));
119  inpos += sizeof(st);
120 
121 #if 0
122  // With the removal of support for cc608 teletext,
123  // we still want to skip over any teletext packets
124  // that might inadvertently be present.
125  CC608Text *cc = new CC608Text(
126  QString((const char*) inpos), st.row, st.col);
127 
128  m_state[streamIdx].m_output.lock.lock();
129  m_state[streamIdx].m_output.buffers.push_back(cc);
130  m_state[streamIdx].m_output.lock.unlock();
131 #endif // 0
132 
133  inpos += st.len;
134  }
135  //changed = true;
136  }
137  }
138  else if (m_inputBuffers[m_writePosition].type == 'C')
139  {
141  changed = true;
142  }
143 
144  QMutexLocker locker(&m_inputBufLock);
146  m_writePosition = (m_writePosition + 1) % MAXTBUFFER;
147  }
148 
149  if (streamIdx >= 0)
150  {
151  m_state[streamIdx].m_changed = false;
152  return &m_state[streamIdx].m_output;
153  }
154  else
155  {
156  return &m_state[MAXOUTBUFFERS - 1].m_output;
157  }
158 }
159 
161 {
162  // TODO why was the clearing code removed?
163  //int oldmode = m_ccMode;
164  m_ccMode = (mode <= 2) ? ((mode == 1) ? CC_CC1 : CC_CC2) :
165  ((mode == 3) ? CC_CC3 : CC_CC4);
166  //if (oldmode != m_ccMode)
167  // ClearBuffers(true, true);
168 }
169 
170 int CC608Reader::Update(unsigned char *inpos)
171 {
172  struct ccsubtitle subtitle;
173 
174  memcpy(&subtitle, inpos, sizeof(subtitle));
175  inpos += sizeof(ccsubtitle);
176 
177  const int streamIdx = (subtitle.resumetext & CC_MODE_MASK) >> 4;
178 
179  if (subtitle.row == 0)
180  subtitle.row = 1;
181 
182  if (subtitle.clr)
183  {
184 #if 0
185  LOG(VB_VBI, LOG_DEBUG, "erase displayed memory");
186 #endif
187  ClearBuffers(false, true, streamIdx);
188  if (!subtitle.len)
189  return streamIdx;
190  }
191 
192 // if (subtitle.len || !subtitle.clr)
193  {
194  unsigned char *end = inpos + subtitle.len;
195  int row = 0;
196  int linecont = (subtitle.resumetext & CC_LINE_CONT);
197 
198  vector<CC608Text*> *ccbuf = new vector<CC608Text*>;
199  vector<CC608Text*>::iterator ccp;
200  CC608Text *tmpcc = NULL;
201  int replace = linecont;
202  int scroll = 0;
203  bool scroll_prsv = false;
204  int scroll_yoff = 0;
205  int scroll_ymax = 15;
206 
207  do
208  {
209  if (linecont)
210  {
211  // append to last line; needs to be redrawn
212  replace = 1;
213  // backspace into existing line if needed
214  int bscnt = 0;
215  while ((inpos < end) && *inpos != 0 && (char)*inpos == '\b')
216  {
217  bscnt++;
218  inpos++;
219  }
220  if (bscnt)
221  {
222  m_state[streamIdx].m_outputText.remove(
223  m_state[streamIdx].m_outputText.length() - bscnt,
224  bscnt);
225  }
226  }
227  else
228  {
229  // new line: count spaces to calculate column position
230  row++;
231  m_state[streamIdx].m_outputCol = 0;
232  m_state[streamIdx].m_outputText = "";
233  while ((inpos < end) && *inpos != 0 && (char)*inpos == ' ')
234  {
235  inpos++;
236  m_state[streamIdx].m_outputCol++;
237  }
238  }
239 
240  m_state[streamIdx].m_outputRow = subtitle.row;
241  unsigned char *cur = inpos;
242 
243  //null terminate at EOL
244  while (cur < end && *cur != '\n' && *cur != 0)
245  cur++;
246  *cur = 0;
247 
248  if (*inpos != 0 || linecont)
249  {
250  if (linecont)
251  {
252  m_state[streamIdx].m_outputText +=
253  QString::fromUtf8((const char *)inpos, -1);
254  }
255  else
256  {
257  m_state[streamIdx].m_outputText =
258  QString::fromUtf8((const char *)inpos, -1);
259  }
260  tmpcc = new CC608Text(
261  m_state[streamIdx].m_outputText,
262  m_state[streamIdx].m_outputCol,
263  m_state[streamIdx].m_outputRow);
264  ccbuf->push_back(tmpcc);
265 #if 0
266  if (ccbuf->size() > 4)
267  LOG(VB_VBI, LOG_DEBUG, QString("CC overflow: %1 %2 %3")
268  .arg(m_outputCol) .arg(m_outputRow)
269  .arg(m_outputText));
270 #endif
271  }
272  subtitle.row++;
273  inpos = cur + 1;
274  linecont = 0;
275  } while (inpos < end);
276 
277  // adjust row position
278  if (subtitle.resumetext & CC_TXT_MASK)
279  {
280  // TXT mode
281  // - can use entire 15 rows
282  // - scroll up when reaching bottom
283  if (m_state[streamIdx].m_outputRow > 15)
284  {
285  if (row)
286  scroll = m_state[streamIdx].m_outputRow - 15;
287  if (tmpcc)
288  tmpcc->y = 15;
289  }
290  }
291  else if (subtitle.rowcount == 0 || row > 1)
292  {
293  // multi-line text
294  // - fix display of old (badly-encoded) files
295  if (m_state[streamIdx].m_outputRow > 15)
296  {
297  ccp = ccbuf->begin();
298  for (; ccp != ccbuf->end(); ++ccp)
299  {
300  tmpcc = *ccp;
301  tmpcc->y -= (m_state[streamIdx].m_outputRow - 15);
302  }
303  }
304  }
305  else
306  {
307  // scrolling text
308  // - scroll up previous lines if adding new line
309  // - if caption is at bottom, row address is for last
310  // row
311  // - if caption is at top, row address is for first row (?)
312  if (subtitle.rowcount > 4)
313  subtitle.rowcount = 4;
314  if (m_state[streamIdx].m_outputRow < subtitle.rowcount)
315  {
316  m_state[streamIdx].m_outputRow = subtitle.rowcount;
317  if (tmpcc)
318  tmpcc->y = m_state[streamIdx].m_outputRow;
319  }
320  if (row)
321  {
322  scroll = row;
323  scroll_prsv = true;
324  scroll_yoff =
325  m_state[streamIdx].m_outputRow - subtitle.rowcount;
326  scroll_ymax = m_state[streamIdx].m_outputRow;
327  }
328  }
329 
330  Update608Text(ccbuf, replace, scroll,
331  scroll_prsv, scroll_yoff, scroll_ymax, streamIdx);
332  delete ccbuf;
333  }
334 
335  return streamIdx;
336 }
337 
339  (void *, unsigned char *, int, int, int),
340  void * ptr)
341 {
342  QMutexLocker locker(&m_inputBufLock);
343  while (NumInputBuffers(false))
344  {
345  locker.unlock();
346  int pagenr = 0;
347  unsigned char *inpos = m_inputBuffers[m_readPosition].buffer;
348  if (m_inputBuffers[m_readPosition].type == 'T')
349  {
350  memcpy(&pagenr, inpos, sizeof(int));
351  inpos += sizeof(int);
352  m_inputBuffers[m_readPosition].len -= sizeof(int);
353  }
354  func(ptr, inpos, m_inputBuffers[m_readPosition].len,
355  m_inputBuffers[m_readPosition].timecode, pagenr);
356 
357  locker.relock();
358  m_readPosition = (m_readPosition + 1) % MAXTBUFFER;
359  }
360 }
361 
363  vector<CC608Text*> *ccbuf, int replace, int scroll, bool scroll_prsv,
364  int scroll_yoff, int scroll_ymax, int streamIdx)
365 // ccbuf : new text
366 // replace : replace last lines
367 // scroll : scroll amount
368 // scroll_prsv: preserve last lines and move into scroll window
369 // scroll_yoff: yoff < scroll window <= ymax
370 // scroll_ymax:
371 {
372  vector<CC608Text*>::iterator i;
373  int visible = 0;
374 
375  m_state[streamIdx].m_output.lock.lock();
376  if (m_state[streamIdx].m_output.buffers.size() && (scroll || replace))
377  {
378  CC608Text *cc;
379 
380  // get last row
381  int ylast = 0;
382  i = m_state[streamIdx].m_output.buffers.end() - 1;
383  cc = *i;
384  if (cc)
385  ylast = cc->y;
386 
387  // calculate row positions to delete, keep
388  int ydel = scroll_yoff + scroll;
389  int ykeep = scroll_ymax;
390  int ymove = 0;
391  if (scroll_prsv && ylast)
392  {
393  ymove = ylast - scroll_ymax;
394  ydel += ymove;
395  ykeep += ymove;
396  }
397 
398  i = m_state[streamIdx].m_output.buffers.begin();
399  while (i < m_state[streamIdx].m_output.buffers.end())
400  {
401  cc = (*i);
402  if (!cc)
403  {
404  i = m_state[streamIdx].m_output.buffers.erase(i);
405  continue;
406  }
407 
408  if (cc->y > (ylast - replace))
409  {
410  // delete last lines
411  delete cc;
412  i = m_state[streamIdx].m_output.buffers.erase(i);
413  }
414  else if (scroll)
415  {
416  if (cc->y > ydel && cc->y <= ykeep)
417  {
418  // scroll up
419  cc->y -= (scroll + ymove);
420  ++i;
421  }
422  else
423  {
424  // delete lines outside scroll window
425  i = m_state[streamIdx].m_output.buffers.erase(i);
426  delete cc;
427  }
428  }
429  else
430  {
431  ++i;
432  }
433  }
434  }
435 
436  visible += m_state[streamIdx].m_output.buffers.size();
437 
438  if (ccbuf)
439  {
440  // add new text
441  for (i = ccbuf->begin(); i != ccbuf->end(); ++i)
442  {
443  if (*i)
444  {
445  visible++;
446  m_state[streamIdx].m_output.buffers.push_back(*i);
447  }
448  }
449  }
450  m_state[streamIdx].m_changed = visible;
451  m_state[streamIdx].m_output.lock.unlock();
452 }
453 
454 void CC608Reader::ClearBuffers(bool input, bool output, int outputStreamIdx)
455 {
456  if (input)
457  {
458  for (int i = 0; i < MAXTBUFFER; i++)
459  {
460  m_inputBuffers[i].timecode = 0;
461  if (m_inputBuffers[i].buffer)
462  memset(m_inputBuffers[i].buffer, 0, m_maxTextSize);
463  }
464 
465  QMutexLocker locker(&m_inputBufLock);
466  m_readPosition = 0;
467  m_writePosition = 0;
468  }
469 
470  if (output && outputStreamIdx < 0)
471  {
472  for (int i = 0; i < MAXOUTBUFFERS; ++i)
473  m_state[i].Clear();
474  }
475 
476  if (output && outputStreamIdx >= 0)
477  {
478  outputStreamIdx = min(outputStreamIdx, MAXOUTBUFFERS - 1);
479  m_state[outputStreamIdx].Clear();
480  }
481 }
482 
483 int CC608Reader::NumInputBuffers(bool need_to_lock)
484 {
485  int ret;
486 
487  if (need_to_lock)
488  m_inputBufLock.lock();
489 
492  else
493  ret = MAXTBUFFER - (m_writePosition - m_readPosition);
494 
495  if (need_to_lock)
496  m_inputBufLock.unlock();
497 
498  return ret;
499 }
500 
501 void CC608Reader::AddTextData(unsigned char *buffer, int len,
502  int64_t timecode, char type)
503 {
504  if (m_parent)
505  m_parent->WrapTimecode(timecode, TC_CC);
506 
507  if (!m_enabled)
508  return;
509 
510  if (NumInputBuffers() >= MAXTBUFFER - 1)
511  {
512  LOG(VB_VBI, LOG_ERR, "AddTextData(): Text buffer overflow");
513  return;
514  }
515 
516  if (len > m_maxTextSize)
517  len = m_maxTextSize;
518 
519  QMutexLocker locker(&m_inputBufLock);
520  int prev_readpos = (m_readPosition - 1 + MAXTBUFFER) % MAXTBUFFER;
521  /* Check whether the reader appears to be waiting on a caption
522  whose timestamp is too large. We can guess this is the case
523  if we are adding a timestamp that is smaller than timestamp
524  being waited on but larger than the timestamp before that.
525  Note that even if the text buffer is full, the entry at index
526  m_readPosition-1 should still be valid.
527  */
528  if (NumInputBuffers(false) > 0 &&
529  m_inputBuffers[m_readPosition].timecode > timecode &&
530  timecode > m_inputBuffers[prev_readpos].timecode)
531  {
532  /* If so, reset the timestamp that the reader is waiting on
533  to a value reasonably close to the previously read
534  timestamp. This will probably cause one or more captions
535  to appear rapidly, but at least the captions won't
536  appear to be stuck.
537  */
538  LOG(VB_VBI, LOG_INFO,
539  QString("Writing caption timecode %1 but waiting on %2")
540  .arg(timecode).arg(m_inputBuffers[m_readPosition].timecode));
542  m_inputBuffers[prev_readpos].timecode + 500;
543  }
544 
548  memset(m_inputBuffers[m_readPosition].buffer, 0, m_maxTextSize);
549  memcpy(m_inputBuffers[m_readPosition].buffer, buffer, len);
550 
551  m_readPosition = (m_readPosition+1) % MAXTBUFFER;
552 }