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