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  m_maxTextSize(8 * (sizeof(teletextsubtitle) + VT_WIDTH))
10 {
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  if (mode <= 2)
154  m_ccMode = (mode == 1) ? CC_CC1 : CC_CC2;
155  else
156  m_ccMode = (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 std::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_state[streamIdx].m_outputCol)
259  .arg(m_state[streamIdx].m_outputRow)
260  .arg(m_state[streamIdx].m_outputText));
261 #endif
262  }
263  subtitle.row++;
264  inpos = cur + 1;
265  linecont = 0;
266  } while (inpos < end);
267 
268  // adjust row position
269  if (subtitle.resumetext & CC_TXT_MASK)
270  {
271  // TXT mode
272  // - can use entire 15 rows
273  // - scroll up when reaching bottom
274  if (m_state[streamIdx].m_outputRow > 15)
275  {
276  if (row)
277  scroll = m_state[streamIdx].m_outputRow - 15;
278  if (tmpcc)
279  tmpcc->m_y = 15;
280  }
281  }
282  else if (subtitle.rowcount == 0 || row > 1)
283  {
284  // multi-line text
285  // - fix display of old (badly-encoded) files
286  if (m_state[streamIdx].m_outputRow > 15)
287  {
288  for (auto & ccp : *ccbuf)
289  {
290  tmpcc = ccp;
291  tmpcc->m_y -= (m_state[streamIdx].m_outputRow - 15);
292  }
293  }
294  }
295  else
296  {
297  // scrolling text
298  // - scroll up previous lines if adding new line
299  // - if caption is at bottom, row address is for last
300  // row
301  // - if caption is at top, row address is for first row (?)
302  subtitle.rowcount = std::min<int>(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 {
329  QMutexLocker locker(&m_inputBufLock);
330  while (NumInputBuffers(false))
331  {
332  locker.unlock();
333  int pagenr = 0;
334  unsigned char *inpos = m_inputBuffers[m_readPosition].buffer;
335  if (m_inputBuffers[m_readPosition].type == 'T')
336  {
337  memcpy(&pagenr, inpos, sizeof(int));
338  inpos += sizeof(int);
339  m_inputBuffers[m_readPosition].len -= sizeof(int);
340  }
341  func(ptr, inpos, m_inputBuffers[m_readPosition].len,
342  m_inputBuffers[m_readPosition].timecode, pagenr);
343 
344  locker.relock();
346  }
347 }
348 
350  std::vector<CC608Text*> *ccbuf, int replace, int scroll, bool scroll_prsv,
351  int scroll_yoff, int scroll_ymax, int streamIdx)
352 // ccbuf : new text
353 // replace : replace last lines
354 // scroll : scroll amount
355 // scroll_prsv: preserve last lines and move into scroll window
356 // scroll_yoff: yoff < scroll window <= ymax
357 // scroll_ymax:
358 {
359 #if 0
360  LOG(VB_VBI, LOG_DEBUG, QString("%1:%2 ").arg(__FUNCTION__).arg(__LINE__) +
361  QString("replace:%1 ").arg(replace) +
362  QString("scroll:%1 ").arg(scroll) +
363  QString("scroll_prsv:%1 ").arg(scroll_prsv) +
364  QString("scroll_yoff:%1 ").arg(scroll_yoff) +
365  QString("scroll_ymax:%1 ").arg(scroll_ymax) +
366  QString("streamIdx:%1 ").arg(streamIdx));
367 #endif
368  std::vector<CC608Text*>::iterator i;
369  int visible = 0;
370 
371  m_state[streamIdx].m_output.m_lock.lock();
372  if (!m_state[streamIdx].m_output.m_buffers.empty() && (scroll || replace))
373  {
374  // get last row
375  int ylast = 0;
376  i = m_state[streamIdx].m_output.m_buffers.end() - 1;
377  CC608Text *cc = *i;
378  if (cc)
379  ylast = cc->m_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.m_buffers.begin();
393  while (i < m_state[streamIdx].m_output.m_buffers.end())
394  {
395  cc = (*i);
396  if (!cc)
397  {
398  i = m_state[streamIdx].m_output.m_buffers.erase(i);
399  continue;
400  }
401 
402  if (cc->m_y > (ylast - replace))
403  {
404  // delete last lines
405  delete cc;
406  i = m_state[streamIdx].m_output.m_buffers.erase(i);
407  }
408  else if (scroll)
409  {
410  if (cc->m_y > ydel && cc->m_y <= ykeep)
411  {
412  // scroll up
413  cc->m_y -= (scroll + ymove);
414  ++i;
415  }
416  else
417  {
418  // delete lines outside scroll window
419  i = m_state[streamIdx].m_output.m_buffers.erase(i);
420  delete cc;
421  }
422  }
423  else
424  {
425  ++i;
426  }
427  }
428  }
429 
430  visible += m_state[streamIdx].m_output.m_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.m_buffers.push_back(*i);
441  }
442  }
443  }
444  m_state[streamIdx].m_changed = (visible != 0);
445  m_state[streamIdx].m_output.m_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 = 0ms;
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 (auto & state : m_state)
467  state.Clear();
468  }
469 
470  if (output && outputStreamIdx >= 0)
471  {
472  outputStreamIdx = std::min(outputStreamIdx, MAXOUTBUFFERS - 1);
473  m_state[outputStreamIdx].Clear();
474  }
475 }
476 
477 int CC608Reader::NumInputBuffers(bool need_to_lock)
478 {
479  int ret = 0;
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  std::chrono::milliseconds 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  len = std::min(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 }
CC_CC1
@ CC_CC1
Definition: format.h:205
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:165
CC608Reader::AddTextData
void AddTextData(unsigned char *buf, int len, std::chrono::milliseconds timecode, char type) override
Definition: cc608reader.cpp:495
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:349
CC608Reader::m_inputBufLock
QMutex m_inputBufLock
Definition: cc608reader.h:109
CC608Reader::CC608Reader
CC608Reader(MythPlayer *parent)
Definition: cc608reader.cpp:7
cc
Definition: cc.h:9
CC_CC2
@ CC_CC2
Definition: format.h:206
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:83
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
CC608Reader::SetMode
void SetMode(int mode)
Definition: cc608reader.cpp:149
CC608Reader::m_writePosition
int m_writePosition
Definition: cc608reader.h:108
TC_CC
@ TC_CC
Definition: mythplayer.h:59
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:1301
CC608Reader::m_state
std::array< CC608StateTracker, MAXOUTBUFFERS > m_state
Definition: cc608reader.h:115
CC_CC4
@ CC_CC4
Definition: format.h:210
CC608Reader::~CC608Reader
~CC608Reader() override
Definition: cc608reader.cpp:15
MAXOUTBUFFERS
static constexpr int8_t MAXOUTBUFFERS
Definition: cc608reader.h:15
CC608Reader::m_readPosition
int m_readPosition
Definition: cc608reader.h:107
CC608Reader::Update
int Update(unsigned char *inpos)
Definition: cc608reader.cpp:161
CC608Reader::m_maxTextSize
int m_maxTextSize
Definition: cc608reader.h:110
CC_CC3
@ CC_CC3
Definition: format.h:209
MAXTBUFFER
static constexpr int8_t MAXTBUFFER
Definition: cc608reader.h:14
CC608Reader::NumInputBuffers
int NumInputBuffers(bool need_to_lock=true)
Definition: cc608reader.cpp:477
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:448
MythVideoFrame
Definition: mythframe.h:87
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:223
CC608Text::m_y
int m_y
Definition: cc608reader.h:25
CC608Reader::TranscodeWriteText
void TranscodeWriteText(CC608WriteFn func, void *ptr)
Definition: cc608reader.cpp:327
CC608Reader::m_inputBuffers
std::array< TextContainer, MAXTBUFFER+1 > m_inputBuffers
Definition: cc608reader.h:111