MythTV  master
cc608reader.cpp
Go to the documentation of this file.
1 #include "vbitext/vbi.h"
2 
3 #include <algorithm>
4 
5 #include "mythplayer.h"
6 #include "captions/cc608reader.h"
7 
9  : m_parent(parent)
10 {
11  m_maxTextSize = 8 * (sizeof(teletextsubtitle) + VT_WIDTH);
12  for (int i = 0; i < MAXTBUFFER; i++)
13  m_inputBuffers[i].buffer = new unsigned char[m_maxTextSize + 1];
14 }
15 
17 {
18  ClearBuffers(true, true);
19  for (int i = 0; i < MAXTBUFFER; i++)
20  {
21  if (m_inputBuffers[i].buffer)
22  {
23  delete [] m_inputBuffers[i].buffer;
24  m_inputBuffers[i].buffer = nullptr;
25  }
26  }
27 }
28 
30 {
31  QMutexLocker locker(&m_inputBufLock);
33 }
34 
36 {
37  bool last_changed = true;
38  while (last_changed)
39  {
40  last_changed = false;
41  int streamIdx = -1;
42  CC608Buffer *tmp = GetOutputText(last_changed, streamIdx);
43  if (last_changed && (((streamIdx << 4) & CC_MODE_MASK) == m_ccMode))
44  {
45  changed = true;
46  return tmp;
47  }
48  }
49 
50  return nullptr;
51 }
52 
53 CC608Buffer *CC608Reader::GetOutputText(bool &changed, int &streamIdx)
54 {
55  streamIdx = -1;
56 
57  if (!m_enabled)
58  return nullptr;
59 
60  if (!m_parent)
61  {
62  if (NumInputBuffers())
63  {
64  streamIdx = Update(m_inputBuffers[m_writePosition].buffer);
65  changed = true;
66 
67  QMutexLocker locker(&m_inputBufLock);
70  }
71 
72  if (streamIdx >= 0)
73  {
74  m_state[streamIdx].m_changed = false;
75  return &m_state[streamIdx].m_output;
76  }
77  return &m_state[MAXOUTBUFFERS - 1].m_output;
78  }
79 
80  MythVideoFrame *last = nullptr;
81  if (m_parent->GetVideoOutput())
83 
84  if (NumInputBuffers() && (m_inputBuffers[m_writePosition].timecode > 0ms) &&
85  (last && m_inputBuffers[m_writePosition].timecode <= last->m_timecode))
86  {
88  {
89  streamIdx = MAXOUTBUFFERS - 1;
90 
91  // display full page of teletext
92  //
93  // all formatting is always defined in the page itself,
94  // if scrolling is needed for live closed captions this
95  // is handled by the broadcaster:
96  // the pages are then very often transmitted (sometimes as often as
97  // every 2 frames) with small differences between them
98  unsigned char *inpos = m_inputBuffers[m_writePosition].buffer;
99  int pagenr = 0;
100  memcpy(&pagenr, inpos, sizeof(int));
101  inpos += sizeof(int);
102 
103  if (pagenr == (m_ccPageNum<<16))
104  {
105  // show teletext subtitles
106  ClearBuffers(false, true, streamIdx);
107  (*inpos)++;
108  while (*inpos)
109  {
110  struct teletextsubtitle st {};
111  memcpy(&st, inpos, sizeof(st));
112  inpos += sizeof(st);
113 
114 #if 0
115  // With the removal of support for cc608 teletext,
116  // we still want to skip over any teletext packets
117  // that might inadvertently be present.
118  CC608Text *cc = new CC608Text(
119  QString((const char*) inpos), st.row, st.col);
120 
121  m_state[streamIdx].m_output.lock.lock();
122  m_state[streamIdx].m_output.buffers.push_back(cc);
123  m_state[streamIdx].m_output.lock.unlock();
124 #endif // 0
125 
126  inpos += st.len;
127  }
128  //changed = true;
129  }
130  }
131  else if (m_inputBuffers[m_writePosition].type == 'C')
132  {
133  streamIdx = Update(m_inputBuffers[m_writePosition].buffer);
134  changed = true;
135  }
136 
137  QMutexLocker locker(&m_inputBufLock);
140  }
141 
142  if (streamIdx >= 0)
143  {
144  m_state[streamIdx].m_changed = false;
145  return &m_state[streamIdx].m_output;
146  }
147  return &m_state[MAXOUTBUFFERS - 1].m_output;
148 }
149 
150 void CC608Reader::SetMode(int mode)
151 {
152  // TODO why was the clearing code removed?
153  //int oldmode = m_ccMode;
154  m_ccMode = (mode <= 2) ? ((mode == 1) ? CC_CC1 : CC_CC2) :
155  ((mode == 3) ? CC_CC3 : CC_CC4);
156  //if (oldmode != m_ccMode)
157  // ClearBuffers(true, true);
158 }
159 
160 int CC608Reader::Update(unsigned char *inpos)
161 {
162  struct ccsubtitle subtitle {};
163 
164  memcpy(&subtitle, inpos, sizeof(subtitle));
165  inpos += sizeof(ccsubtitle);
166 
167  const int streamIdx = (subtitle.resumetext & CC_MODE_MASK) >> 4;
168 
169  if (subtitle.row == 0)
170  subtitle.row = 1;
171 
172  if (subtitle.clr)
173  {
174 #if 0
175  LOG(VB_VBI, LOG_DEBUG, "erase displayed memory");
176 #endif
177  ClearBuffers(false, true, streamIdx);
178  if (!subtitle.len)
179  return streamIdx;
180  }
181 
182 // if (subtitle.len || !subtitle.clr)
183  {
184  unsigned char *end = inpos + subtitle.len;
185  int row = 0;
186  int linecont = (subtitle.resumetext & CC_LINE_CONT);
187 
188  auto *ccbuf = new vector<CC608Text*>;
189  CC608Text *tmpcc = nullptr;
190  int replace = linecont;
191  int scroll = 0;
192  bool scroll_prsv = false;
193  int scroll_yoff = 0;
194  int scroll_ymax = 15;
195 
196  do
197  {
198  if (linecont)
199  {
200  // append to last line; needs to be redrawn
201  replace = 1;
202  // backspace into existing line if needed
203  int bscnt = 0;
204  while ((inpos < end) && *inpos != 0 && (char)*inpos == '\b')
205  {
206  bscnt++;
207  inpos++;
208  }
209  if (bscnt)
210  {
211  m_state[streamIdx].m_outputText.remove(
212  m_state[streamIdx].m_outputText.length() - bscnt,
213  bscnt);
214  }
215  }
216  else
217  {
218  // new line: count spaces to calculate column position
219  row++;
220  m_state[streamIdx].m_outputCol = 0;
221  m_state[streamIdx].m_outputText = "";
222  while ((inpos < end) && *inpos != 0 && (char)*inpos == ' ')
223  {
224  inpos++;
225  m_state[streamIdx].m_outputCol++;
226  }
227  }
228 
229  m_state[streamIdx].m_outputRow = subtitle.row;
230  unsigned char *cur = inpos;
231 
232  //null terminate at EOL
233  while (cur < end && *cur != '\n' && *cur != 0)
234  cur++;
235  *cur = 0;
236 
237  if (*inpos != 0 || linecont)
238  {
239  if (linecont)
240  {
241  m_state[streamIdx].m_outputText +=
242  QString::fromUtf8((const char *)inpos, -1);
243  }
244  else
245  {
246  m_state[streamIdx].m_outputText =
247  QString::fromUtf8((const char *)inpos, -1);
248  }
249  tmpcc = new CC608Text(
250  m_state[streamIdx].m_outputText,
251  m_state[streamIdx].m_outputCol,
252  m_state[streamIdx].m_outputRow);
253  ccbuf->push_back(tmpcc);
254 #if 0
255  if (ccbuf->size() > 4)
256  LOG(VB_VBI, LOG_DEBUG, QString("CC overflow: %1 %2 %3")
257  .arg(m_state[streamIdx].m_outputCol)
258  .arg(m_state[streamIdx].m_outputRow)
259  .arg(m_state[streamIdx].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 {
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  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  if (len > m_maxTextSize)
511  len = m_maxTextSize;
512 
513  QMutexLocker locker(&m_inputBufLock);
514  int prev_readpos = (m_readPosition - 1 + MAXTBUFFER) % MAXTBUFFER;
515  /* Check whether the reader appears to be waiting on a caption
516  whose timestamp is too large. We can guess this is the case
517  if we are adding a timestamp that is smaller than timestamp
518  being waited on but larger than the timestamp before that.
519  Note that even if the text buffer is full, the entry at index
520  m_readPosition-1 should still be valid.
521  */
522  if (NumInputBuffers(false) > 0 &&
523  m_inputBuffers[m_readPosition].timecode > timecode &&
524  timecode > m_inputBuffers[prev_readpos].timecode)
525  {
526  /* If so, reset the timestamp that the reader is waiting on
527  to a value reasonably close to the previously read
528  timestamp. This will probably cause one or more captions
529  to appear rapidly, but at least the captions won't
530  appear to be stuck.
531  */
532  LOG(VB_VBI, LOG_INFO,
533  QString("Writing caption timecode %1 but waiting on %2")
534  .arg(timecode.count()).arg(m_inputBuffers[m_readPosition].timecode.count()));
535  m_inputBuffers[m_readPosition].timecode =
536  m_inputBuffers[prev_readpos].timecode + 500ms;
537  }
538 
539  m_inputBuffers[m_readPosition].timecode = timecode;
541  m_inputBuffers[m_readPosition].len = len;
542  memset(m_inputBuffers[m_readPosition].buffer, 0, m_maxTextSize);
543  memcpy(m_inputBuffers[m_readPosition].buffer, buffer, len);
544 
546 }
CC_CC4
#define CC_CC4
Definition: format.h:207
CC608Reader::m_enabled
bool m_enabled
Definition: cc608reader.h:106
CC608Text
Definition: cc608reader.h:18
vbi.h
teletextsubtitle::row
unsigned char row
Definition: format.h:175
MythPlayer::GetVideoOutput
MythVideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:168
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:110
CC608Reader::CC608Reader
CC608Reader(MythPlayer *parent)
Definition: cc608reader.cpp:8
cc
Definition: cc.h:9
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
cc608reader.h
mythplayer.h
MythPlayer
Definition: mythplayer.h:86
CC608WriteFn
void(*)(void *, unsigned char *, int, std::chrono::milliseconds, int) CC608WriteFn
Definition: cc608reader.h:77
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:31
CC608Reader::GetOutputText
CC608Buffer * GetOutputText(bool &changed, int &streamIdx)
Definition: cc608reader.cpp:53
VT_WIDTH
#define VT_WIDTH
Definition: vt.h:4
ccsubtitle::resumetext
unsigned char resumetext
Definition: format.h:188
MAXOUTBUFFERS
#define MAXOUTBUFFERS
Definition: cc608reader.h:16
CC608Reader::SetMode
void SetMode(int mode)
Definition: cc608reader.cpp:150
CC608Reader::m_writePosition
int m_writePosition
Definition: cc608reader.h:109
CC608Reader::FlushTxtBuffers
void FlushTxtBuffers(void)
Definition: cc608reader.cpp:29
CC_CC3
#define CC_CC3
Definition: format.h:206
teletextsubtitle
Definition: format.h:173
CC608Buffer
Definition: cc608reader.h:37
MythPlayer::WrapTimecode
void WrapTimecode(std::chrono::milliseconds &timecode, TCTypes tc_type)
Definition: mythplayer.cpp:1297
CC608Reader::m_state
std::array< CC608StateTracker, MAXOUTBUFFERS > m_state
Definition: cc608reader.h:116
CC608Reader::~CC608Reader
~CC608Reader() override
Definition: cc608reader.cpp:16
CC_LINE_CONT
#define CC_LINE_CONT
Definition: format.h:199
CC608Reader::m_readPosition
int m_readPosition
Definition: cc608reader.h:108
CC608Reader::Update
int Update(unsigned char *inpos)
Definition: cc608reader.cpp:160
CC608Reader::m_maxTextSize
int m_maxTextSize
Definition: cc608reader.h:111
TC_CC
@ TC_CC
Definition: mythplayer.h:60
CC_CC1
#define CC_CC1
Definition: format.h:202
CC608Reader::NumInputBuffers
int NumInputBuffers(bool need_to_lock=true)
Definition: cc608reader.cpp:477
CC608Reader::m_ccPageNum
int m_ccPageNum
Definition: cc608reader.h:114
teletextsubtitle::len
unsigned char len
Definition: format.h:180
MAXTBUFFER
#define MAXTBUFFER
Definition: cc608reader.h:15
CC608Reader::m_ccMode
int m_ccMode
Definition: cc608reader.h:113
ccsubtitle::rowcount
unsigned char rowcount
Definition: format.h:186
teletextsubtitle::col
unsigned char col
Definition: format.h:176
CC_MODE_MASK
#define CC_MODE_MASK
Definition: format.h:200
CC_CC2
#define CC_CC2
Definition: format.h:203
ccsubtitle::row
unsigned char row
Definition: format.h:185
CC_TXT_MASK
#define CC_TXT_MASK
Definition: format.h:201
CC608Reader::ClearBuffers
void ClearBuffers(bool input, bool output, int outputStreamIdx=-1)
Definition: cc608reader.cpp:448
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:315
ccsubtitle
Definition: format.h:183
CC608Reader::m_parent
MythPlayer * m_parent
Definition: cc608reader.h:105
output
#define output
Definition: synaesthesia.cpp:220
CC608Text::m_y
int m_y
Definition: cc608reader.h:26
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:112