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 == 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 &&
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_outputCol) .arg(m_outputRow)
258  .arg(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  (void *, unsigned char *, int, int, int),
328  void * ptr)
329 {
330  QMutexLocker locker(&m_inputBufLock);
331  while (NumInputBuffers(false))
332  {
333  locker.unlock();
334  int pagenr = 0;
335  unsigned char *inpos = m_inputBuffers[m_readPosition].buffer;
336  if (m_inputBuffers[m_readPosition].type == 'T')
337  {
338  memcpy(&pagenr, inpos, sizeof(int));
339  inpos += sizeof(int);
340  m_inputBuffers[m_readPosition].len -= sizeof(int);
341  }
342  func(ptr, inpos, m_inputBuffers[m_readPosition].len,
343  m_inputBuffers[m_readPosition].timecode, pagenr);
344 
345  locker.relock();
347  }
348 }
349 
351  std::vector<CC608Text*> *ccbuf, int replace, int scroll, bool scroll_prsv,
352  int scroll_yoff, int scroll_ymax, int streamIdx)
353 // ccbuf : new text
354 // replace : replace last lines
355 // scroll : scroll amount
356 // scroll_prsv: preserve last lines and move into scroll window
357 // scroll_yoff: yoff < scroll window <= ymax
358 // scroll_ymax:
359 {
360  vector<CC608Text*>::iterator i;
361  int visible = 0;
362 
363  m_state[streamIdx].m_output.m_lock.lock();
364  if (!m_state[streamIdx].m_output.m_buffers.empty() && (scroll || replace))
365  {
366  // get last row
367  int ylast = 0;
368  i = m_state[streamIdx].m_output.m_buffers.end() - 1;
369  CC608Text *cc = *i;
370  if (cc)
371  ylast = cc->m_y;
372 
373  // calculate row positions to delete, keep
374  int ydel = scroll_yoff + scroll;
375  int ykeep = scroll_ymax;
376  int ymove = 0;
377  if (scroll_prsv && ylast)
378  {
379  ymove = ylast - scroll_ymax;
380  ydel += ymove;
381  ykeep += ymove;
382  }
383 
384  i = m_state[streamIdx].m_output.m_buffers.begin();
385  while (i < m_state[streamIdx].m_output.m_buffers.end())
386  {
387  cc = (*i);
388  if (!cc)
389  {
390  i = m_state[streamIdx].m_output.m_buffers.erase(i);
391  continue;
392  }
393 
394  if (cc->m_y > (ylast - replace))
395  {
396  // delete last lines
397  delete cc;
398  i = m_state[streamIdx].m_output.m_buffers.erase(i);
399  }
400  else if (scroll)
401  {
402  if (cc->m_y > ydel && cc->m_y <= ykeep)
403  {
404  // scroll up
405  cc->m_y -= (scroll + ymove);
406  ++i;
407  }
408  else
409  {
410  // delete lines outside scroll window
411  i = m_state[streamIdx].m_output.m_buffers.erase(i);
412  delete cc;
413  }
414  }
415  else
416  {
417  ++i;
418  }
419  }
420  }
421 
422  visible += m_state[streamIdx].m_output.m_buffers.size();
423 
424  if (ccbuf)
425  {
426  // add new text
427  for (i = ccbuf->begin(); i != ccbuf->end(); ++i)
428  {
429  if (*i)
430  {
431  visible++;
432  m_state[streamIdx].m_output.m_buffers.push_back(*i);
433  }
434  }
435  }
436  m_state[streamIdx].m_changed = (visible != 0);
437  m_state[streamIdx].m_output.m_lock.unlock();
438 }
439 
440 void CC608Reader::ClearBuffers(bool input, bool output, int outputStreamIdx)
441 {
442  if (input)
443  {
444  for (int i = 0; i < MAXTBUFFER; i++)
445  {
446  m_inputBuffers[i].timecode = 0;
447  if (m_inputBuffers[i].buffer)
448  memset(m_inputBuffers[i].buffer, 0, m_maxTextSize);
449  }
450 
451  QMutexLocker locker(&m_inputBufLock);
452  m_readPosition = 0;
453  m_writePosition = 0;
454  }
455 
456  if (output && outputStreamIdx < 0)
457  {
458  for (auto & state : m_state)
459  state.Clear();
460  }
461 
462  if (output && outputStreamIdx >= 0)
463  {
464  outputStreamIdx = std::min(outputStreamIdx, MAXOUTBUFFERS - 1);
465  m_state[outputStreamIdx].Clear();
466  }
467 }
468 
469 int CC608Reader::NumInputBuffers(bool need_to_lock)
470 {
471  int ret = 0;
472 
473  if (need_to_lock)
474  m_inputBufLock.lock();
475 
478  else
480 
481  if (need_to_lock)
482  m_inputBufLock.unlock();
483 
484  return ret;
485 }
486 
487 void CC608Reader::AddTextData(unsigned char *buffer, int len,
488  int64_t timecode, char type)
489 {
490  if (m_parent)
491  m_parent->WrapTimecode(timecode, TC_CC);
492 
493  if (!m_enabled)
494  return;
495 
496  if (NumInputBuffers() >= MAXTBUFFER - 1)
497  {
498  LOG(VB_VBI, LOG_ERR, "AddTextData(): Text buffer overflow");
499  return;
500  }
501 
502  if (len > m_maxTextSize)
503  len = m_maxTextSize;
504 
505  QMutexLocker locker(&m_inputBufLock);
506  int prev_readpos = (m_readPosition - 1 + MAXTBUFFER) % MAXTBUFFER;
507  /* Check whether the reader appears to be waiting on a caption
508  whose timestamp is too large. We can guess this is the case
509  if we are adding a timestamp that is smaller than timestamp
510  being waited on but larger than the timestamp before that.
511  Note that even if the text buffer is full, the entry at index
512  m_readPosition-1 should still be valid.
513  */
514  if (NumInputBuffers(false) > 0 &&
515  m_inputBuffers[m_readPosition].timecode > timecode &&
516  timecode > m_inputBuffers[prev_readpos].timecode)
517  {
518  /* If so, reset the timestamp that the reader is waiting on
519  to a value reasonably close to the previously read
520  timestamp. This will probably cause one or more captions
521  to appear rapidly, but at least the captions won't
522  appear to be stuck.
523  */
524  LOG(VB_VBI, LOG_INFO,
525  QString("Writing caption timecode %1 but waiting on %2")
526  .arg(timecode).arg(m_inputBuffers[m_readPosition].timecode));
527  m_inputBuffers[m_readPosition].timecode =
528  m_inputBuffers[prev_readpos].timecode + 500;
529  }
530 
531  m_inputBuffers[m_readPosition].timecode = timecode;
533  m_inputBuffers[m_readPosition].len = len;
534  memset(m_inputBuffers[m_readPosition].buffer, 0, m_maxTextSize);
535  memcpy(m_inputBuffers[m_readPosition].buffer, buffer, len);
536 
538 }
CC_CC4
#define CC_CC4
Definition: format.h:207
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:175
MythPlayer::GetVideoOutput
MythVideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:163
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:350
CC608Reader::m_inputBufLock
QMutex m_inputBufLock
Definition: cc608reader.h:110
CC608Reader::CC608Reader
CC608Reader(MythPlayer *parent)
Definition: cc608reader.cpp:8
cc
Definition: cc.h:10
arg
arg(title).arg(filename).arg(doDelete))
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
cc608reader.h
mythplayer.h
MythPlayer
Definition: mythplayer.h:83
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
MythVideoFrame::m_timecode
long long m_timecode
Definition: mythframe.h:122
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:174
CC608Buffer
Definition: cc608reader.h:38
CC608Reader::m_state
std::array< CC608StateTracker, MAXOUTBUFFERS > m_state
Definition: cc608reader.h:116
CC608Reader::~CC608Reader
~CC608Reader() override
Definition: cc608reader.cpp:16
CC608Reader::AddTextData
void AddTextData(unsigned char *buf, int len, int64_t timecode, char type) override
Definition: cc608reader.cpp:487
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:56
CC608Reader::TranscodeWriteText
void TranscodeWriteText(void(*func)(void *, unsigned char *, int, int, int), void *ptr)
Definition: cc608reader.cpp:326
CC_CC1
#define CC_CC1
Definition: format.h:202
CC608Reader::NumInputBuffers
int NumInputBuffers(bool need_to_lock=true)
Definition: cc608reader.cpp:469
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:440
MythVideoFrame
Definition: mythframe.h:83
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:351
ccsubtitle
Definition: format.h:184
MythPlayer::WrapTimecode
void WrapTimecode(int64_t &timecode, TCTypes tc_type)
Definition: mythplayer.cpp:1276
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::m_inputBuffers
std::array< TextContainer, MAXTBUFFER+1 > m_inputBuffers
Definition: cc608reader.h:112