MythTV master
cc608reader.cpp
Go to the documentation of this file.
1#include <algorithm>
2
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
54CC608Buffer *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;
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
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
163int 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;
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
450void 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
479int 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
497void 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()));
537 m_inputBuffers[prev_readpos].timecode + 500ms;
538 }
539
540 m_inputBuffers[m_readPosition].timecode = timecode;
543 memset(m_inputBuffers[m_readPosition].buffer, 0, m_maxTextSize);
544 memcpy(m_inputBuffers[m_readPosition].buffer, buffer, len);
545
547}
static constexpr uint8_t CC_LINE_CONT
Definition: cc608decoder.h:45
@ CC_CC4
Definition: cc608decoder.h:54
@ CC_CC3
Definition: cc608decoder.h:53
@ CC_CC2
Definition: cc608decoder.h:50
@ CC_CC1
Definition: cc608decoder.h:49
static constexpr uint8_t CC_MODE_MASK
Definition: cc608decoder.h:46
static constexpr uint8_t CC_TXT_MASK
Definition: cc608decoder.h:47
static constexpr int8_t MAXTBUFFER
Definition: cc608reader.h:14
static constexpr int8_t MAXOUTBUFFERS
Definition: cc608reader.h:15
void(*)(void *, unsigned char *, int, std::chrono::milliseconds, int) CC608WriteFn
Definition: cc608reader.h:76
CC608Buffer * GetOutputText(bool &changed, int &streamIdx)
Definition: cc608reader.cpp:54
void TranscodeWriteText(CC608WriteFn func, void *ptr)
void FlushTxtBuffers(void)
Definition: cc608reader.cpp:30
int NumInputBuffers(bool need_to_lock=true)
QMutex m_inputBufLock
Definition: cc608reader.h:109
std::array< TextContainer, MAXTBUFFER+1 > m_inputBuffers
Definition: cc608reader.h:111
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)
std::array< CC608StateTracker, MAXOUTBUFFERS > m_state
Definition: cc608reader.h:115
CC608Reader(MythPlayer *parent)
Definition: cc608reader.cpp:9
bool m_enabled
Definition: cc608reader.h:105
int Update(unsigned char *inpos)
int m_readPosition
Definition: cc608reader.h:107
void SetMode(int mode)
~CC608Reader() override
Definition: cc608reader.cpp:17
int m_writePosition
Definition: cc608reader.h:108
void AddTextData(unsigned char *buf, int len, std::chrono::milliseconds timecode, char type) override
int m_maxTextSize
Definition: cc608reader.h:110
void ClearBuffers(bool input, bool output, int outputStreamIdx=-1)
MythPlayer * m_parent
Definition: cc608reader.h:104
MythVideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:164
void WrapTimecode(std::chrono::milliseconds &timecode, TCTypes tc_type)
virtual MythVideoFrame * GetLastShownFrame()
Returns frame from the head of the ready to be displayed queue, if StartDisplayingFrame has been call...
static guint32 * tmp
Definition: goom_core.cpp:26
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ TC_CC
Definition: mythplayer.h:59
unsigned char resumetext
Definition: cc608decoder.h:32
unsigned char row
Definition: cc608decoder.h:29
unsigned char rowcount
Definition: cc608decoder.h:30
unsigned char len
Definition: cc608decoder.h:34
unsigned char clr
Definition: cc608decoder.h:33
unsigned char len
Definition: cc608decoder.h:24
unsigned char row
Definition: cc608decoder.h:19
unsigned char col
Definition: cc608decoder.h:20
#define output