MythTV  master
cc608reader.cpp
Go to the documentation of this file.
1 #include <algorithm>
2 
3 #include "captions/cc608reader.h"
4 
6 
7 #include "mythplayer.h"
8 
10  : m_parent(parent),
11  m_maxTextSize(8 * (sizeof(teletextsubtitle) + 40))
12 {
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 << 4) & CC_MODE_MASK) == 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  MythVideoFrame *last = nullptr;
82  if (m_parent->GetVideoOutput())
84 
85  if (NumInputBuffers() && (m_inputBuffers[m_writePosition].timecode > 0ms) &&
86  (last && m_inputBuffers[m_writePosition].timecode <= last->m_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  if (mode <= 2)
156  m_ccMode = (mode == 1) ? CC_CC1 : CC_CC2;
157  else
158  m_ccMode = (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  auto *ccbuf = new std::vector<CC608Text*>;
192  CC608Text *tmpcc = nullptr;
193  int replace = linecont;
194  int scroll = 0;
195  bool scroll_prsv = false;
196  int scroll_yoff = 0;
197  int scroll_ymax = 15;
198 
199  do
200  {
201  if (linecont)
202  {
203  // append to last line; needs to be redrawn
204  replace = 1;
205  // backspace into existing line if needed
206  int bscnt = 0;
207  while ((inpos < end) && *inpos != 0 && (char)*inpos == '\b')
208  {
209  bscnt++;
210  inpos++;
211  }
212  if (bscnt)
213  {
214  m_state[streamIdx].m_outputText.remove(
215  m_state[streamIdx].m_outputText.length() - bscnt,
216  bscnt);
217  }
218  }
219  else
220  {
221  // new line: count spaces to calculate column position
222  row++;
223  m_state[streamIdx].m_outputCol = 0;
224  m_state[streamIdx].m_outputText = "";
225  while ((inpos < end) && *inpos != 0 && (char)*inpos == ' ')
226  {
227  inpos++;
228  m_state[streamIdx].m_outputCol++;
229  }
230  }
231 
232  m_state[streamIdx].m_outputRow = subtitle.row;
233  unsigned char *cur = inpos;
234 
235  //null terminate at EOL
236  while (cur < end && *cur != '\n' && *cur != 0)
237  cur++;
238  *cur = 0;
239 
240  if (*inpos != 0 || linecont)
241  {
242  if (linecont)
243  {
244  m_state[streamIdx].m_outputText +=
245  QString::fromUtf8((const char *)inpos, -1);
246  }
247  else
248  {
249  m_state[streamIdx].m_outputText =
250  QString::fromUtf8((const char *)inpos, -1);
251  }
252  tmpcc = new CC608Text(
253  m_state[streamIdx].m_outputText,
254  m_state[streamIdx].m_outputCol,
255  m_state[streamIdx].m_outputRow);
256  ccbuf->push_back(tmpcc);
257 #if 0
258  if (ccbuf->size() > 4)
259  LOG(VB_VBI, LOG_DEBUG, QString("CC overflow: %1 %2 %3")
260  .arg(m_state[streamIdx].m_outputCol)
261  .arg(m_state[streamIdx].m_outputRow)
262  .arg(m_state[streamIdx].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->m_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  for (auto & ccp : *ccbuf)
291  {
292  tmpcc = ccp;
293  tmpcc->m_y -= (m_state[streamIdx].m_outputRow - 15);
294  }
295  }
296  }
297  else
298  {
299  // scrolling text
300  // - scroll up previous lines if adding new line
301  // - if caption is at bottom, row address is for last
302  // row
303  // - if caption is at top, row address is for first row (?)
304  subtitle.rowcount = std::min<int>(subtitle.rowcount, 4);
305  if (m_state[streamIdx].m_outputRow < subtitle.rowcount)
306  {
307  m_state[streamIdx].m_outputRow = subtitle.rowcount;
308  if (tmpcc)
309  tmpcc->m_y = m_state[streamIdx].m_outputRow;
310  }
311  if (row)
312  {
313  scroll = row;
314  scroll_prsv = true;
315  scroll_yoff =
316  m_state[streamIdx].m_outputRow - subtitle.rowcount;
317  scroll_ymax = m_state[streamIdx].m_outputRow;
318  }
319  }
320 
321  Update608Text(ccbuf, replace, scroll,
322  scroll_prsv, scroll_yoff, scroll_ymax, streamIdx);
323  delete ccbuf;
324  }
325 
326  return streamIdx;
327 }
328 
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  std::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 #if 0
362  LOG(VB_VBI, LOG_DEBUG, QString("%1:%2 ").arg(__FUNCTION__).arg(__LINE__) +
363  QString("replace:%1 ").arg(replace) +
364  QString("scroll:%1 ").arg(scroll) +
365  QString("scroll_prsv:%1 ").arg(scroll_prsv) +
366  QString("scroll_yoff:%1 ").arg(scroll_yoff) +
367  QString("scroll_ymax:%1 ").arg(scroll_ymax) +
368  QString("streamIdx:%1 ").arg(streamIdx));
369 #endif
370  std::vector<CC608Text*>::iterator i;
371  int visible = 0;
372 
373  m_state[streamIdx].m_output.m_lock.lock();
374  if (!m_state[streamIdx].m_output.m_buffers.empty() && (scroll || replace))
375  {
376  // get last row
377  int ylast = 0;
378  i = m_state[streamIdx].m_output.m_buffers.end() - 1;
379  CC608Text *cc = *i;
380  if (cc)
381  ylast = cc->m_y;
382 
383  // calculate row positions to delete, keep
384  int ydel = scroll_yoff + scroll;
385  int ykeep = scroll_ymax;
386  int ymove = 0;
387  if (scroll_prsv && ylast)
388  {
389  ymove = ylast - scroll_ymax;
390  ydel += ymove;
391  ykeep += ymove;
392  }
393 
394  i = m_state[streamIdx].m_output.m_buffers.begin();
395  while (i < m_state[streamIdx].m_output.m_buffers.end())
396  {
397  cc = (*i);
398  if (!cc)
399  {
400  i = m_state[streamIdx].m_output.m_buffers.erase(i);
401  continue;
402  }
403 
404  if (cc->m_y > (ylast - replace))
405  {
406  // delete last lines
407  delete cc;
408  i = m_state[streamIdx].m_output.m_buffers.erase(i);
409  }
410  else if (scroll)
411  {
412  if (cc->m_y > ydel && cc->m_y <= ykeep)
413  {
414  // scroll up
415  cc->m_y -= (scroll + ymove);
416  ++i;
417  }
418  else
419  {
420  // delete lines outside scroll window
421  i = m_state[streamIdx].m_output.m_buffers.erase(i);
422  delete cc;
423  }
424  }
425  else
426  {
427  ++i;
428  }
429  }
430  }
431 
432  visible += m_state[streamIdx].m_output.m_buffers.size();
433 
434  if (ccbuf)
435  {
436  // add new text
437  for (i = ccbuf->begin(); i != ccbuf->end(); ++i)
438  {
439  if (*i)
440  {
441  visible++;
442  m_state[streamIdx].m_output.m_buffers.push_back(*i);
443  }
444  }
445  }
446  m_state[streamIdx].m_changed = (visible != 0);
447  m_state[streamIdx].m_output.m_lock.unlock();
448 }
449 
450 void CC608Reader::ClearBuffers(bool input, bool output, int outputStreamIdx)
451 {
452  if (input)
453  {
454  for (int i = 0; i < MAXTBUFFER; i++)
455  {
456  m_inputBuffers[i].timecode = 0ms;
457  if (m_inputBuffers[i].buffer)
458  memset(m_inputBuffers[i].buffer, 0, m_maxTextSize);
459  }
460 
461  QMutexLocker locker(&m_inputBufLock);
462  m_readPosition = 0;
463  m_writePosition = 0;
464  }
465 
466  if (output && outputStreamIdx < 0)
467  {
468  for (auto & state : m_state)
469  state.Clear();
470  }
471 
472  if (output && outputStreamIdx >= 0)
473  {
474  outputStreamIdx = std::min(outputStreamIdx, MAXOUTBUFFERS - 1);
475  m_state[outputStreamIdx].Clear();
476  }
477 }
478 
479 int CC608Reader::NumInputBuffers(bool need_to_lock)
480 {
481  int ret = 0;
482 
483  if (need_to_lock)
484  m_inputBufLock.lock();
485 
488  else
490 
491  if (need_to_lock)
492  m_inputBufLock.unlock();
493 
494  return ret;
495 }
496 
497 void CC608Reader::AddTextData(unsigned char *buffer, int len,
498  std::chrono::milliseconds timecode, char type)
499 {
500  if (m_parent)
501  m_parent->WrapTimecode(timecode, TC_CC);
502 
503  if (!m_enabled)
504  return;
505 
506  if (NumInputBuffers() >= MAXTBUFFER - 1)
507  {
508  LOG(VB_VBI, LOG_ERR, "AddTextData(): Text buffer overflow");
509  return;
510  }
511 
512  len = std::min(len, m_maxTextSize);
513 
514  QMutexLocker locker(&m_inputBufLock);
515  int prev_readpos = (m_readPosition - 1 + MAXTBUFFER) % MAXTBUFFER;
516  /* Check whether the reader appears to be waiting on a caption
517  whose timestamp is too large. We can guess this is the case
518  if we are adding a timestamp that is smaller than timestamp
519  being waited on but larger than the timestamp before that.
520  Note that even if the text buffer is full, the entry at index
521  m_readPosition-1 should still be valid.
522  */
523  if (NumInputBuffers(false) > 0 &&
524  m_inputBuffers[m_readPosition].timecode > timecode &&
525  timecode > m_inputBuffers[prev_readpos].timecode)
526  {
527  /* If so, reset the timestamp that the reader is waiting on
528  to a value reasonably close to the previously read
529  timestamp. This will probably cause one or more captions
530  to appear rapidly, but at least the captions won't
531  appear to be stuck.
532  */
533  LOG(VB_VBI, LOG_INFO,
534  QString("Writing caption timecode %1 but waiting on %2")
535  .arg(timecode.count()).arg(m_inputBuffers[m_readPosition].timecode.count()));
536  m_inputBuffers[m_readPosition].timecode =
537  m_inputBuffers[prev_readpos].timecode + 500ms;
538  }
539 
540  m_inputBuffers[m_readPosition].timecode = timecode;
542  m_inputBuffers[m_readPosition].len = len;
543  memset(m_inputBuffers[m_readPosition].buffer, 0, m_maxTextSize);
544  memcpy(m_inputBuffers[m_readPosition].buffer, buffer, len);
545 
547 }
CC608Reader::m_enabled
bool m_enabled
Definition: cc608reader.h:105
CC608Text
Definition: cc608reader.h:17
teletextsubtitle::row
unsigned char row
Definition: cc608decoder.h:20
MythPlayer::GetVideoOutput
MythVideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:164
CC608Reader::AddTextData
void AddTextData(unsigned char *buf, int len, std::chrono::milliseconds timecode, char type) override
Definition: cc608reader.cpp:497
CC_CC3
@ CC_CC3
Definition: cc608decoder.h:54
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:351
CC608Reader::m_inputBufLock
QMutex m_inputBufLock
Definition: cc608reader.h:109
CC608Reader::CC608Reader
CC608Reader(MythPlayer *parent)
Definition: cc608reader.cpp:9
CC_CC4
@ CC_CC4
Definition: cc608decoder.h:55
CC_LINE_CONT
static constexpr uint8_t CC_LINE_CONT
Definition: cc608decoder.h:46
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
cc608reader.h
mythplayer.h
MythPlayer
Definition: mythplayer.h:83
CC_CC2
@ CC_CC2
Definition: cc608decoder.h:51
CC608WriteFn
void(*)(void *, unsigned char *, int, std::chrono::milliseconds, int) CC608WriteFn
Definition: cc608reader.h:76
ccsubtitle::len
unsigned char len
Definition: cc608decoder.h:35
ccsubtitle::clr
unsigned char clr
Definition: cc608decoder.h:34
tmp
static guint32 * tmp
Definition: goom_core.cpp:26
CC608Reader::GetOutputText
CC608Buffer * GetOutputText(bool &changed, int &streamIdx)
Definition: cc608reader.cpp:54
ccsubtitle::resumetext
unsigned char resumetext
Definition: cc608decoder.h:33
CC608Reader::SetMode
void SetMode(int mode)
Definition: cc608reader.cpp:151
mythlogging.h
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:30
teletextsubtitle
Definition: cc608decoder.h:18
CC608Buffer
Definition: cc608reader.h:36
MythPlayer::WrapTimecode
void WrapTimecode(std::chrono::milliseconds &timecode, TCTypes tc_type)
Definition: mythplayer.cpp:1298
CC608Reader::m_state
std::array< CC608StateTracker, MAXOUTBUFFERS > m_state
Definition: cc608reader.h:115
CC608Reader::~CC608Reader
~CC608Reader() override
Definition: cc608reader.cpp:17
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:163
CC608Reader::m_maxTextSize
int m_maxTextSize
Definition: cc608reader.h:110
CC_CC1
@ CC_CC1
Definition: cc608decoder.h:50
MAXTBUFFER
static constexpr int8_t MAXTBUFFER
Definition: cc608reader.h:14
CC608Reader::NumInputBuffers
int NumInputBuffers(bool need_to_lock=true)
Definition: cc608reader.cpp:479
CC608Reader::m_ccPageNum
int m_ccPageNum
Definition: cc608reader.h:113
CC_MODE_MASK
static constexpr uint8_t CC_MODE_MASK
Definition: cc608decoder.h:47
teletextsubtitle::len
unsigned char len
Definition: cc608decoder.h:25
CC608Reader::m_ccMode
int m_ccMode
Definition: cc608reader.h:112
ccsubtitle::rowcount
unsigned char rowcount
Definition: cc608decoder.h:31
teletextsubtitle::col
unsigned char col
Definition: cc608decoder.h:21
ccsubtitle::row
unsigned char row
Definition: cc608decoder.h:30
CC608Reader::ClearBuffers
void ClearBuffers(bool input, bool output, int outputStreamIdx=-1)
Definition: cc608reader.cpp:450
CC_TXT_MASK
static constexpr uint8_t CC_TXT_MASK
Definition: cc608decoder.h:48
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: cc608decoder.h:28
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:329
CC608Reader::m_inputBuffers
std::array< TextContainer, MAXTBUFFER+1 > m_inputBuffers
Definition: cc608reader.h:111