MythTV  master
cc608reader.cpp
Go to the documentation of this file.
1 extern "C" {
2 #include "vbitext/vbi.h"
3 }
4 
5 #include <algorithm>
6 using namespace std;
7 
8 #include "mythplayer.h"
9 #include "cc608reader.h"
10 
12  : m_parent(parent)
13 {
14  m_maxTextSize = 8 * (sizeof(teletextsubtitle) + VT_WIDTH);
15  for (int i = 0; i < MAXTBUFFER; i++)
16  m_inputBuffers[i].buffer = new unsigned char[m_maxTextSize + 1];
17 }
18 
20 {
21  ClearBuffers(true, true);
22  for (int i = 0; i < MAXTBUFFER; i++)
23  {
24  if (m_inputBuffers[i].buffer)
25  {
26  delete [] m_inputBuffers[i].buffer;
27  m_inputBuffers[i].buffer = nullptr;
28  }
29  }
30 }
31 
33 {
34  QMutexLocker locker(&m_inputBufLock);
36 }
37 
39 {
40  bool last_changed = true;
41  while (last_changed)
42  {
43  last_changed = false;
44  int streamIdx = -1;
45  CC608Buffer *tmp = GetOutputText(last_changed, streamIdx);
46  if (last_changed && (streamIdx == m_ccMode))
47  {
48  changed = true;
49  return tmp;
50  }
51  }
52 
53  return nullptr;
54 }
55 
56 CC608Buffer *CC608Reader::GetOutputText(bool &changed, int &streamIdx)
57 {
58  streamIdx = -1;
59 
60  if (!m_enabled)
61  return nullptr;
62 
63  if (!m_parent)
64  {
65  if (NumInputBuffers())
66  {
67  streamIdx = Update(m_inputBuffers[m_writePosition].buffer);
68  changed = true;
69 
70  QMutexLocker locker(&m_inputBufLock);
73  }
74 
75  if (streamIdx >= 0)
76  {
77  m_state[streamIdx].m_changed = false;
78  return &m_state[streamIdx].m_output;
79  }
80  return &m_state[MAXOUTBUFFERS - 1].m_output;
81  }
82 
83  VideoFrame *last = nullptr;
84  if (m_parent->GetVideoOutput())
86 
87  if (NumInputBuffers() && m_inputBuffers[m_writePosition].timecode &&
88  (last && m_inputBuffers[m_writePosition].timecode <= last->timecode))
89  {
91  {
92  streamIdx = MAXOUTBUFFERS - 1;
93 
94  // display full page of teletext
95  //
96  // all formatting is always defined in the page itself,
97  // if scrolling is needed for live closed captions this
98  // is handled by the broadcaster:
99  // the pages are then very often transmitted (sometimes as often as
100  // every 2 frames) with small differences between them
101  unsigned char *inpos = m_inputBuffers[m_writePosition].buffer;
102  int pagenr = 0;
103  memcpy(&pagenr, inpos, sizeof(int));
104  inpos += sizeof(int);
105 
106  if (pagenr == (m_ccPageNum<<16))
107  {
108  // show teletext subtitles
109  ClearBuffers(false, true, streamIdx);
110  (*inpos)++;
111  while (*inpos)
112  {
113  struct teletextsubtitle st {};
114  memcpy(&st, inpos, sizeof(st));
115  inpos += sizeof(st);
116 
117 #if 0
118  // With the removal of support for cc608 teletext,
119  // we still want to skip over any teletext packets
120  // that might inadvertently be present.
121  CC608Text *cc = new CC608Text(
122  QString((const char*) inpos), st.row, st.col);
123 
124  m_state[streamIdx].m_output.lock.lock();
125  m_state[streamIdx].m_output.buffers.push_back(cc);
126  m_state[streamIdx].m_output.lock.unlock();
127 #endif // 0
128 
129  inpos += st.len;
130  }
131  //changed = true;
132  }
133  }
134  else if (m_inputBuffers[m_writePosition].type == 'C')
135  {
136  streamIdx = Update(m_inputBuffers[m_writePosition].buffer);
137  changed = true;
138  }
139 
140  QMutexLocker locker(&m_inputBufLock);
143  }
144 
145  if (streamIdx >= 0)
146  {
147  m_state[streamIdx].m_changed = false;
148  return &m_state[streamIdx].m_output;
149  }
150  return &m_state[MAXOUTBUFFERS - 1].m_output;
151 }
152 
153 void CC608Reader::SetMode(int mode)
154 {
155  // TODO why was the clearing code removed?
156  //int oldmode = m_ccMode;
157  m_ccMode = (mode <= 2) ? ((mode == 1) ? CC_CC1 : CC_CC2) :
158  ((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 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_outputCol) .arg(m_outputRow)
261  .arg(m_outputText));
262 #endif
263  }
264  subtitle.row++;
265  inpos = cur + 1;
266  linecont = 0;
267  } while (inpos < end);
268 
269  // adjust row position
270  if (subtitle.resumetext & CC_TXT_MASK)
271  {
272  // TXT mode
273  // - can use entire 15 rows
274  // - scroll up when reaching bottom
275  if (m_state[streamIdx].m_outputRow > 15)
276  {
277  if (row)
278  scroll = m_state[streamIdx].m_outputRow - 15;
279  if (tmpcc)
280  tmpcc->m_y = 15;
281  }
282  }
283  else if (subtitle.rowcount == 0 || row > 1)
284  {
285  // multi-line text
286  // - fix display of old (badly-encoded) files
287  if (m_state[streamIdx].m_outputRow > 15)
288  {
289  for (auto & ccp : *ccbuf)
290  {
291  tmpcc = ccp;
292  tmpcc->m_y -= (m_state[streamIdx].m_outputRow - 15);
293  }
294  }
295  }
296  else
297  {
298  // scrolling text
299  // - scroll up previous lines if adding new line
300  // - if caption is at bottom, row address is for last
301  // row
302  // - if caption is at top, row address is for first row (?)
303  if (subtitle.rowcount > 4)
304  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  (void *, unsigned char *, int, int, int),
331  void * ptr)
332 {
333  QMutexLocker locker(&m_inputBufLock);
334  while (NumInputBuffers(false))
335  {
336  locker.unlock();
337  int pagenr = 0;
338  unsigned char *inpos = m_inputBuffers[m_readPosition].buffer;
339  if (m_inputBuffers[m_readPosition].type == 'T')
340  {
341  memcpy(&pagenr, inpos, sizeof(int));
342  inpos += sizeof(int);
343  m_inputBuffers[m_readPosition].len -= sizeof(int);
344  }
345  func(ptr, inpos, m_inputBuffers[m_readPosition].len,
346  m_inputBuffers[m_readPosition].timecode, pagenr);
347 
348  locker.relock();
350  }
351 }
352 
354  vector<CC608Text*> *ccbuf, int replace, int scroll, bool scroll_prsv,
355  int scroll_yoff, int scroll_ymax, int streamIdx)
356 // ccbuf : new text
357 // replace : replace last lines
358 // scroll : scroll amount
359 // scroll_prsv: preserve last lines and move into scroll window
360 // scroll_yoff: yoff < scroll window <= ymax
361 // scroll_ymax:
362 {
363  vector<CC608Text*>::iterator i;
364  int visible = 0;
365 
366  m_state[streamIdx].m_output.m_lock.lock();
367  if (!m_state[streamIdx].m_output.m_buffers.empty() && (scroll || replace))
368  {
369  // get last row
370  int ylast = 0;
371  i = m_state[streamIdx].m_output.m_buffers.end() - 1;
372  CC608Text *cc = *i;
373  if (cc)
374  ylast = cc->m_y;
375 
376  // calculate row positions to delete, keep
377  int ydel = scroll_yoff + scroll;
378  int ykeep = scroll_ymax;
379  int ymove = 0;
380  if (scroll_prsv && ylast)
381  {
382  ymove = ylast - scroll_ymax;
383  ydel += ymove;
384  ykeep += ymove;
385  }
386 
387  i = m_state[streamIdx].m_output.m_buffers.begin();
388  while (i < m_state[streamIdx].m_output.m_buffers.end())
389  {
390  cc = (*i);
391  if (!cc)
392  {
393  i = m_state[streamIdx].m_output.m_buffers.erase(i);
394  continue;
395  }
396 
397  if (cc->m_y > (ylast - replace))
398  {
399  // delete last lines
400  delete cc;
401  i = m_state[streamIdx].m_output.m_buffers.erase(i);
402  }
403  else if (scroll)
404  {
405  if (cc->m_y > ydel && cc->m_y <= ykeep)
406  {
407  // scroll up
408  cc->m_y -= (scroll + ymove);
409  ++i;
410  }
411  else
412  {
413  // delete lines outside scroll window
414  i = m_state[streamIdx].m_output.m_buffers.erase(i);
415  delete cc;
416  }
417  }
418  else
419  {
420  ++i;
421  }
422  }
423  }
424 
425  visible += m_state[streamIdx].m_output.m_buffers.size();
426 
427  if (ccbuf)
428  {
429  // add new text
430  for (i = ccbuf->begin(); i != ccbuf->end(); ++i)
431  {
432  if (*i)
433  {
434  visible++;
435  m_state[streamIdx].m_output.m_buffers.push_back(*i);
436  }
437  }
438  }
439  m_state[streamIdx].m_changed = visible;
440  m_state[streamIdx].m_output.m_lock.unlock();
441 }
442 
443 void CC608Reader::ClearBuffers(bool input, bool output, int outputStreamIdx)
444 {
445  if (input)
446  {
447  for (int i = 0; i < MAXTBUFFER; i++)
448  {
449  m_inputBuffers[i].timecode = 0;
450  if (m_inputBuffers[i].buffer)
451  memset(m_inputBuffers[i].buffer, 0, m_maxTextSize);
452  }
453 
454  QMutexLocker locker(&m_inputBufLock);
455  m_readPosition = 0;
456  m_writePosition = 0;
457  }
458 
459  if (output && outputStreamIdx < 0)
460  {
461  for (auto & state : m_state)
462  state.Clear();
463  }
464 
465  if (output && outputStreamIdx >= 0)
466  {
467  outputStreamIdx = min(outputStreamIdx, MAXOUTBUFFERS - 1);
468  m_state[outputStreamIdx].Clear();
469  }
470 }
471 
472 int CC608Reader::NumInputBuffers(bool need_to_lock)
473 {
474  int ret = 0;
475 
476  if (need_to_lock)
477  m_inputBufLock.lock();
478 
481  else
483 
484  if (need_to_lock)
485  m_inputBufLock.unlock();
486 
487  return ret;
488 }
489 
490 void CC608Reader::AddTextData(unsigned char *buffer, int len,
491  int64_t timecode, char type)
492 {
493  if (m_parent)
494  m_parent->WrapTimecode(timecode, TC_CC);
495 
496  if (!m_enabled)
497  return;
498 
499  if (NumInputBuffers() >= MAXTBUFFER - 1)
500  {
501  LOG(VB_VBI, LOG_ERR, "AddTextData(): Text buffer overflow");
502  return;
503  }
504 
505  if (len > m_maxTextSize)
506  len = m_maxTextSize;
507 
508  QMutexLocker locker(&m_inputBufLock);
509  int prev_readpos = (m_readPosition - 1 + MAXTBUFFER) % MAXTBUFFER;
510  /* Check whether the reader appears to be waiting on a caption
511  whose timestamp is too large. We can guess this is the case
512  if we are adding a timestamp that is smaller than timestamp
513  being waited on but larger than the timestamp before that.
514  Note that even if the text buffer is full, the entry at index
515  m_readPosition-1 should still be valid.
516  */
517  if (NumInputBuffers(false) > 0 &&
518  m_inputBuffers[m_readPosition].timecode > timecode &&
519  timecode > m_inputBuffers[prev_readpos].timecode)
520  {
521  /* If so, reset the timestamp that the reader is waiting on
522  to a value reasonably close to the previously read
523  timestamp. This will probably cause one or more captions
524  to appear rapidly, but at least the captions won't
525  appear to be stuck.
526  */
527  LOG(VB_VBI, LOG_INFO,
528  QString("Writing caption timecode %1 but waiting on %2")
529  .arg(timecode).arg(m_inputBuffers[m_readPosition].timecode));
531  m_inputBuffers[prev_readpos].timecode + 500;
532  }
533 
537  memset(m_inputBuffers[m_readPosition].buffer, 0, m_maxTextSize);
538  memcpy(m_inputBuffers[m_readPosition].buffer, buffer, len);
539 
541 }
Definition: cc.h:13
#define CC_TXT_MASK
Definition: format.h:197
TextContainer m_inputBuffers[MAXTBUFFER+1]
Definition: cc608reader.h:112
QMutex m_inputBufLock
Definition: cc608reader.h:110
#define CC_CC3
Definition: format.h:202
CC608Buffer * GetOutputText(bool &changed, int &streamIdx)
Definition: cc608reader.cpp:56
#define VT_WIDTH
Definition: vt.h:4
void SetMode(int mode)
int m_writePosition
Definition: cc608reader.h:109
void FlushTxtBuffers(void)
Definition: cc608reader.cpp:32
static guint32 * tmp
Definition: goom_core.c:35
CC608Reader(MythPlayer *parent)
Definition: cc608reader.cpp:11
#define CC_CC4
Definition: format.h:203
~CC608Reader() override
Definition: cc608reader.cpp:19
CC608Buffer m_output
Definition: cc608reader.h:72
unsigned char resumetext
Definition: format.h:184
#define CC_LINE_CONT
Definition: format.h:195
MythVideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:272
unsigned char len
Definition: format.h:186
unsigned char clr
Definition: format.h:185
int m_readPosition
Definition: cc608reader.h:108
int Update(unsigned char *inpos)
#define MAXOUTBUFFERS
Definition: cc608reader.h:16
int m_maxTextSize
Definition: cc608reader.h:111
CC608StateTracker m_state[MAXOUTBUFFERS]
Definition: cc608reader.h:116
#define CC_CC1
Definition: format.h:198
unsigned char len
Definition: format.h:176
void WrapTimecode(int64_t &timecode, TCTypes tc_type)
void AddTextData(unsigned char *buf, int len, int64_t timecode, char type) override
void Clear(void)
Definition: cc608reader.h:59
#define MAXTBUFFER
Definition: cc608reader.h:15
void TranscodeWriteText(void(*func)(void *, unsigned char *, int, int, int), void *ptr)
vector< CC608Text * > m_buffers
Definition: cc608reader.h:51
#define CC_MODE_MASK
Definition: format.h:196
unsigned char * buffer
Definition: cc608reader.h:33
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
int NumInputBuffers(bool need_to_lock=true)
void Update608Text(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)
#define CC_CC2
Definition: format.h:199
virtual VideoFrame * GetLastShownFrame(void)
Returns frame from the head of the ready to be displayed queue, if StartDisplayingFrame has been call...
unsigned char row
Definition: format.h:181
QString m_outputText
Definition: cc608reader.h:68
bool m_enabled
Definition: cc608reader.h:106
void ClearBuffers(bool input, bool output, int outputStreamIdx=-1)
unsigned char rowcount
Definition: format.h:182
unsigned char col
Definition: format.h:172
QMutex m_lock
Definition: cc608reader.h:50
MythPlayer * m_parent
Definition: cc608reader.h:105
#define output
unsigned char row
Definition: format.h:171