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