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