MythTV  master
cc608reader.cpp
Go to the documentation of this file.
1 #include <algorithm>
2 
3 #include "captions/cc608reader.h"
4 #include "mythplayer.h"
6 
8  : m_parent(parent)
9 {
10  m_maxTextSize = 8 * (sizeof(teletextsubtitle) + VT_WIDTH);
11  for (int i = 0; i < MAXTBUFFER; i++)
12  m_inputBuffers[i].buffer = new unsigned char[m_maxTextSize + 1];
13 }
14 
16 {
17  ClearBuffers(true, true);
18  for (int i = 0; i < MAXTBUFFER; i++)
19  {
20  if (m_inputBuffers[i].buffer)
21  {
22  delete [] m_inputBuffers[i].buffer;
23  m_inputBuffers[i].buffer = nullptr;
24  }
25  }
26 }
27 
29 {
30  QMutexLocker locker(&m_inputBufLock);
32 }
33 
35 {
36  bool last_changed = true;
37  while (last_changed)
38  {
39  last_changed = false;
40  int streamIdx = -1;
41  CC608Buffer *tmp = GetOutputText(last_changed, streamIdx);
42  if (last_changed && (((streamIdx << 4) & CC_MODE_MASK) == m_ccMode))
43  {
44  changed = true;
45  return tmp;
46  }
47  }
48 
49  return nullptr;
50 }
51 
52 CC608Buffer *CC608Reader::GetOutputText(bool &changed, int &streamIdx)
53 {
54  streamIdx = -1;
55 
56  if (!m_enabled)
57  return nullptr;
58 
59  if (!m_parent)
60  {
61  if (NumInputBuffers())
62  {
63  streamIdx = Update(m_inputBuffers[m_writePosition].buffer);
64  changed = true;
65 
66  QMutexLocker locker(&m_inputBufLock);
69  }
70 
71  if (streamIdx >= 0)
72  {
73  m_state[streamIdx].m_changed = false;
74  return &m_state[streamIdx].m_output;
75  }
76  return &m_state[MAXOUTBUFFERS - 1].m_output;
77  }
78 
79  MythVideoFrame *last = nullptr;
80  if (m_parent->GetVideoOutput())
82 
83  if (NumInputBuffers() && (m_inputBuffers[m_writePosition].timecode > 0ms) &&
84  (last && m_inputBuffers[m_writePosition].timecode <= last->m_timecode))
85  {
87  {
88  streamIdx = MAXOUTBUFFERS - 1;
89 
90  // display full page of teletext
91  //
92  // all formatting is always defined in the page itself,
93  // if scrolling is needed for live closed captions this
94  // is handled by the broadcaster:
95  // the pages are then very often transmitted (sometimes as often as
96  // every 2 frames) with small differences between them
97  unsigned char *inpos = m_inputBuffers[m_writePosition].buffer;
98  int pagenr = 0;
99  memcpy(&pagenr, inpos, sizeof(int));
100  inpos += sizeof(int);
101 
102  if (pagenr == (m_ccPageNum<<16))
103  {
104  // show teletext subtitles
105  ClearBuffers(false, true, streamIdx);
106  (*inpos)++;
107  while (*inpos)
108  {
109  struct teletextsubtitle st {};
110  memcpy(&st, inpos, sizeof(st));
111  inpos += sizeof(st);
112 
113 #if 0
114  // With the removal of support for cc608 teletext,
115  // we still want to skip over any teletext packets
116  // that might inadvertently be present.
117  CC608Text *cc = new CC608Text(
118  QString((const char*) inpos), st.row, st.col);
119 
120  m_state[streamIdx].m_output.lock.lock();
121  m_state[streamIdx].m_output.buffers.push_back(cc);
122  m_state[streamIdx].m_output.lock.unlock();
123 #endif // 0
124 
125  inpos += st.len;
126  }
127  //changed = true;
128  }
129  }
130  else if (m_inputBuffers[m_writePosition].type == 'C')
131  {
132  streamIdx = Update(m_inputBuffers[m_writePosition].buffer);
133  changed = true;
134  }
135 
136  QMutexLocker locker(&m_inputBufLock);
139  }
140 
141  if (streamIdx >= 0)
142  {
143  m_state[streamIdx].m_changed = false;
144  return &m_state[streamIdx].m_output;
145  }
146  return &m_state[MAXOUTBUFFERS - 1].m_output;
147 }
148 
149 void CC608Reader::SetMode(int mode)
150 {
151  // TODO why was the clearing code removed?
152  //int oldmode = m_ccMode;
153  m_ccMode = (mode <= 2) ? ((mode == 1) ? CC_CC1 : CC_CC2) :
154  ((mode == 3) ? CC_CC3 : CC_CC4);
155  //if (oldmode != m_ccMode)
156  // ClearBuffers(true, true);
157 }
158 
159 int CC608Reader::Update(unsigned char *inpos)
160 {
161  struct ccsubtitle subtitle {};
162 
163  memcpy(&subtitle, inpos, sizeof(subtitle));
164  inpos += sizeof(ccsubtitle);
165 
166  const int streamIdx = (subtitle.resumetext & CC_MODE_MASK) >> 4;
167 
168  if (subtitle.row == 0)
169  subtitle.row = 1;
170 
171  if (subtitle.clr)
172  {
173 #if 0
174  LOG(VB_VBI, LOG_DEBUG, "erase displayed memory");
175 #endif
176  ClearBuffers(false, true, streamIdx);
177  if (!subtitle.len)
178  return streamIdx;
179  }
180 
181 // if (subtitle.len || !subtitle.clr)
182  {
183  unsigned char *end = inpos + subtitle.len;
184  int row = 0;
185  int linecont = (subtitle.resumetext & CC_LINE_CONT);
186 
187  auto *ccbuf = new std::vector<CC608Text*>;
188  CC608Text *tmpcc = nullptr;
189  int replace = linecont;
190  int scroll = 0;
191  bool scroll_prsv = false;
192  int scroll_yoff = 0;
193  int scroll_ymax = 15;
194 
195  do
196  {
197  if (linecont)
198  {
199  // append to last line; needs to be redrawn
200  replace = 1;
201  // backspace into existing line if needed
202  int bscnt = 0;
203  while ((inpos < end) && *inpos != 0 && (char)*inpos == '\b')
204  {
205  bscnt++;
206  inpos++;
207  }
208  if (bscnt)
209  {
210  m_state[streamIdx].m_outputText.remove(
211  m_state[streamIdx].m_outputText.length() - bscnt,
212  bscnt);
213  }
214  }
215  else
216  {
217  // new line: count spaces to calculate column position
218  row++;
219  m_state[streamIdx].m_outputCol = 0;
220  m_state[streamIdx].m_outputText = "";
221  while ((inpos < end) && *inpos != 0 && (char)*inpos == ' ')
222  {
223  inpos++;
224  m_state[streamIdx].m_outputCol++;
225  }
226  }
227 
228  m_state[streamIdx].m_outputRow = subtitle.row;
229  unsigned char *cur = inpos;
230 
231  //null terminate at EOL
232  while (cur < end && *cur != '\n' && *cur != 0)
233  cur++;
234  *cur = 0;
235 
236  if (*inpos != 0 || linecont)
237  {
238  if (linecont)
239  {
240  m_state[streamIdx].m_outputText +=
241  QString::fromUtf8((const char *)inpos, -1);
242  }
243  else
244  {
245  m_state[streamIdx].m_outputText =
246  QString::fromUtf8((const char *)inpos, -1);
247  }
248  tmpcc = new CC608Text(
249  m_state[streamIdx].m_outputText,
250  m_state[streamIdx].m_outputCol,
251  m_state[streamIdx].m_outputRow);
252  ccbuf->push_back(tmpcc);
253 #if 0
254  if (ccbuf->size() > 4)
255  LOG(VB_VBI, LOG_DEBUG, QString("CC overflow: %1 %2 %3")
256  .arg(m_state[streamIdx].m_outputCol)
257  .arg(m_state[streamIdx].m_outputRow)
258  .arg(m_state[streamIdx].m_outputText));
259 #endif
260  }
261  subtitle.row++;
262  inpos = cur + 1;
263  linecont = 0;
264  } while (inpos < end);
265 
266  // adjust row position
267  if (subtitle.resumetext & CC_TXT_MASK)
268  {
269  // TXT mode
270  // - can use entire 15 rows
271  // - scroll up when reaching bottom
272  if (m_state[streamIdx].m_outputRow > 15)
273  {
274  if (row)
275  scroll = m_state[streamIdx].m_outputRow - 15;
276  if (tmpcc)
277  tmpcc->m_y = 15;
278  }
279  }
280  else if (subtitle.rowcount == 0 || row > 1)
281  {
282  // multi-line text
283  // - fix display of old (badly-encoded) files
284  if (m_state[streamIdx].m_outputRow > 15)
285  {
286  for (auto & ccp : *ccbuf)
287  {
288  tmpcc = ccp;
289  tmpcc->m_y -= (m_state[streamIdx].m_outputRow - 15);
290  }
291  }
292  }
293  else
294  {
295  // scrolling text
296  // - scroll up previous lines if adding new line
297  // - if caption is at bottom, row address is for last
298  // row
299  // - if caption is at top, row address is for first row (?)
300  if (subtitle.rowcount > 4)
301  subtitle.rowcount = 4;
302  if (m_state[streamIdx].m_outputRow < subtitle.rowcount)
303  {
304  m_state[streamIdx].m_outputRow = subtitle.rowcount;
305  if (tmpcc)
306  tmpcc->m_y = m_state[streamIdx].m_outputRow;
307  }
308  if (row)
309  {
310  scroll = row;
311  scroll_prsv = true;
312  scroll_yoff =
313  m_state[streamIdx].m_outputRow - subtitle.rowcount;
314  scroll_ymax = m_state[streamIdx].m_outputRow;
315  }
316  }
317 
318  Update608Text(ccbuf, replace, scroll,
319  scroll_prsv, scroll_yoff, scroll_ymax, streamIdx);
320  delete ccbuf;
321  }
322 
323  return streamIdx;
324 }
325 
327 {
328  QMutexLocker locker(&m_inputBufLock);
329  while (NumInputBuffers(false))
330  {
331  locker.unlock();
332  int pagenr = 0;
333  unsigned char *inpos = m_inputBuffers[m_readPosition].buffer;
334  if (m_inputBuffers[m_readPosition].type == 'T')
335  {
336  memcpy(&pagenr, inpos, sizeof(int));
337  inpos += sizeof(int);
338  m_inputBuffers[m_readPosition].len -= sizeof(int);
339  }
340  func(ptr, inpos, m_inputBuffers[m_readPosition].len,
341  m_inputBuffers[m_readPosition].timecode, pagenr);
342 
343  locker.relock();
345  }
346 }
347 
349  std::vector<CC608Text*> *ccbuf, int replace, int scroll, bool scroll_prsv,
350  int scroll_yoff, int scroll_ymax, int streamIdx)
351 // ccbuf : new text
352 // replace : replace last lines
353 // scroll : scroll amount
354 // scroll_prsv: preserve last lines and move into scroll window
355 // scroll_yoff: yoff < scroll window <= ymax
356 // scroll_ymax:
357 {
358 #if 0
359  LOG(VB_VBI, LOG_DEBUG, QString("%1:%2 ").arg(__FUNCTION__).arg(__LINE__) +
360  QString("replace:%1 ").arg(replace) +
361  QString("scroll:%1 ").arg(scroll) +
362  QString("scroll_prsv:%1 ").arg(scroll_prsv) +
363  QString("scroll_yoff:%1 ").arg(scroll_yoff) +
364  QString("scroll_ymax:%1 ").arg(scroll_ymax) +
365  QString("streamIdx:%1 ").arg(streamIdx));
366 #endif
367  std::vector<CC608Text*>::iterator i;
368  int visible = 0;
369 
370  m_state[streamIdx].m_output.m_lock.lock();
371  if (!m_state[streamIdx].m_output.m_buffers.empty() && (scroll || replace))
372  {
373  // get last row
374  int ylast = 0;
375  i = m_state[streamIdx].m_output.m_buffers.end() - 1;
376  CC608Text *cc = *i;
377  if (cc)
378  ylast = cc->m_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.m_buffers.begin();
392  while (i < m_state[streamIdx].m_output.m_buffers.end())
393  {
394  cc = (*i);
395  if (!cc)
396  {
397  i = m_state[streamIdx].m_output.m_buffers.erase(i);
398  continue;
399  }
400 
401  if (cc->m_y > (ylast - replace))
402  {
403  // delete last lines
404  delete cc;
405  i = m_state[streamIdx].m_output.m_buffers.erase(i);
406  }
407  else if (scroll)
408  {
409  if (cc->m_y > ydel && cc->m_y <= ykeep)
410  {
411  // scroll up
412  cc->m_y -= (scroll + ymove);
413  ++i;
414  }
415  else
416  {
417  // delete lines outside scroll window
418  i = m_state[streamIdx].m_output.m_buffers.erase(i);
419  delete cc;
420  }
421  }
422  else
423  {
424  ++i;
425  }
426  }
427  }
428 
429  visible += m_state[streamIdx].m_output.m_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.m_buffers.push_back(*i);
440  }
441  }
442  }
443  m_state[streamIdx].m_changed = (visible != 0);
444  m_state[streamIdx].m_output.m_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 = 0ms;
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 (auto & state : m_state)
466  state.Clear();
467  }
468 
469  if (output && outputStreamIdx >= 0)
470  {
471  outputStreamIdx = std::min(outputStreamIdx, MAXOUTBUFFERS - 1);
472  m_state[outputStreamIdx].Clear();
473  }
474 }
475 
476 int CC608Reader::NumInputBuffers(bool need_to_lock)
477 {
478  int ret = 0;
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  std::chrono::milliseconds 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.count()).arg(m_inputBuffers[m_readPosition].timecode.count()));
534  m_inputBuffers[m_readPosition].timecode =
535  m_inputBuffers[prev_readpos].timecode + 500ms;
536  }
537 
538  m_inputBuffers[m_readPosition].timecode = timecode;
540  m_inputBuffers[m_readPosition].len = len;
541  memset(m_inputBuffers[m_readPosition].buffer, 0, m_maxTextSize);
542  memcpy(m_inputBuffers[m_readPosition].buffer, buffer, len);
543 
545 }
CC608Reader::m_enabled
bool m_enabled
Definition: cc608reader.h:105
CC608Text
Definition: cc608reader.h:17
vbi.h
teletextsubtitle::row
unsigned char row
Definition: format.h:175
MythPlayer::GetVideoOutput
MythVideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:167
CC608Reader::AddTextData
void AddTextData(unsigned char *buf, int len, std::chrono::milliseconds timecode, char type) override
Definition: cc608reader.cpp:494
CC608Reader::Update608Text
void Update608Text(std::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:348
CC608Reader::m_inputBufLock
QMutex m_inputBufLock
Definition: cc608reader.h:109
CC608Reader::CC608Reader
CC608Reader(MythPlayer *parent)
Definition: cc608reader.cpp:7
CC_CC3
@ CC_CC3
Definition: format.h:209
cc
Definition: cc.h:9
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
cc608reader.h
mythplayer.h
CC_TXT_MASK
static constexpr uint8_t CC_TXT_MASK
Definition: format.h:203
MythPlayer
Definition: mythplayer.h:84
CC608WriteFn
void(*)(void *, unsigned char *, int, std::chrono::milliseconds, int) CC608WriteFn
Definition: cc608reader.h:76
CC_MODE_MASK
static constexpr uint8_t CC_MODE_MASK
Definition: format.h:202
ccsubtitle::len
unsigned char len
Definition: format.h:190
ccsubtitle::clr
unsigned char clr
Definition: format.h:189
tmp
static guint32 * tmp
Definition: goom_core.cpp:26
CC608Reader::GetOutputText
CC608Buffer * GetOutputText(bool &changed, int &streamIdx)
Definition: cc608reader.cpp:52
VT_WIDTH
#define VT_WIDTH
Definition: vt.h:4
ccsubtitle::resumetext
unsigned char resumetext
Definition: format.h:188
CC_CC4
@ CC_CC4
Definition: format.h:210
CC608Reader::SetMode
void SetMode(int mode)
Definition: cc608reader.cpp:149
CC608Reader::m_writePosition
int m_writePosition
Definition: cc608reader.h:108
CC608Reader::FlushTxtBuffers
void FlushTxtBuffers(void)
Definition: cc608reader.cpp:28
teletextsubtitle
Definition: format.h:173
CC608Buffer
Definition: cc608reader.h:36
MythPlayer::WrapTimecode
void WrapTimecode(std::chrono::milliseconds &timecode, TCTypes tc_type)
Definition: mythplayer.cpp:1307
CC608Reader::m_state
std::array< CC608StateTracker, MAXOUTBUFFERS > m_state
Definition: cc608reader.h:115
CC608Reader::~CC608Reader
~CC608Reader() override
Definition: cc608reader.cpp:15
CC_CC1
@ CC_CC1
Definition: format.h:205
MAXOUTBUFFERS
static constexpr int8_t MAXOUTBUFFERS
Definition: cc608reader.h:15
CC_CC2
@ CC_CC2
Definition: format.h:206
CC608Reader::m_readPosition
int m_readPosition
Definition: cc608reader.h:107
CC608Reader::Update
int Update(unsigned char *inpos)
Definition: cc608reader.cpp:159
CC608Reader::m_maxTextSize
int m_maxTextSize
Definition: cc608reader.h:110
TC_CC
@ TC_CC
Definition: mythplayer.h:60
MAXTBUFFER
static constexpr int8_t MAXTBUFFER
Definition: cc608reader.h:14
CC608Reader::NumInputBuffers
int NumInputBuffers(bool need_to_lock=true)
Definition: cc608reader.cpp:476
CC608Reader::m_ccPageNum
int m_ccPageNum
Definition: cc608reader.h:113
teletextsubtitle::len
unsigned char len
Definition: format.h:180
CC608Reader::m_ccMode
int m_ccMode
Definition: cc608reader.h:112
ccsubtitle::rowcount
unsigned char rowcount
Definition: format.h:186
teletextsubtitle::col
unsigned char col
Definition: format.h:176
CC_LINE_CONT
static constexpr uint8_t CC_LINE_CONT
Definition: format.h:201
ccsubtitle::row
unsigned char row
Definition: format.h:185
CC608Reader::ClearBuffers
void ClearBuffers(bool input, bool output, int outputStreamIdx=-1)
Definition: cc608reader.cpp:447
MythVideoFrame
Definition: mythframe.h:88
MythVideoOutput::GetLastShownFrame
virtual MythVideoFrame * GetLastShownFrame()
Returns frame from the head of the ready to be displayed queue, if StartDisplayingFrame has been call...
Definition: mythvideoout.cpp:316
ccsubtitle
Definition: format.h:183
CC608Reader::m_parent
MythPlayer * m_parent
Definition: cc608reader.h:104
output
#define output
Definition: synaesthesia.cpp:220
CC608Text::m_y
int m_y
Definition: cc608reader.h:25
CC608Reader::TranscodeWriteText
void TranscodeWriteText(CC608WriteFn func, void *ptr)
Definition: cc608reader.cpp:326
CC608Reader::m_inputBuffers
std::array< TextContainer, MAXTBUFFER+1 > m_inputBuffers
Definition: cc608reader.h:111