|
MythTV
0.26-pre
|
00001 extern "C" { 00002 #include "vbitext/vbi.h" 00003 } 00004 00005 #include <algorithm> 00006 using namespace std; 00007 00008 #include "mythplayer.h" 00009 #include "cc608reader.h" 00010 00011 CC608Reader::CC608Reader(MythPlayer *parent) 00012 : m_parent(parent), m_enabled(false), m_readPosition(0), 00013 m_writePosition(0), m_maxTextSize(0), m_ccMode(CC_CC1), 00014 m_ccPageNum(0x888) 00015 { 00016 memset(&m_inputBuffers, 0, sizeof(m_inputBuffers)); 00017 m_maxTextSize = 8 * (sizeof(teletextsubtitle) + VT_WIDTH); 00018 for (int i = 0; i < MAXTBUFFER; i++) 00019 m_inputBuffers[i].buffer = new unsigned char[m_maxTextSize + 1]; 00020 } 00021 00022 CC608Reader::~CC608Reader() 00023 { 00024 ClearBuffers(true, true); 00025 for (int i = 0; i < MAXTBUFFER; i++) 00026 { 00027 if (m_inputBuffers[i].buffer) 00028 { 00029 delete [] m_inputBuffers[i].buffer; 00030 m_inputBuffers[i].buffer = NULL; 00031 } 00032 } 00033 } 00034 00035 void CC608Reader::FlushTxtBuffers(void) 00036 { 00037 QMutexLocker locker(&m_inputBufLock); 00038 m_readPosition = m_writePosition; 00039 } 00040 00041 CC608Buffer *CC608Reader::GetOutputText(bool &changed) 00042 { 00043 bool last_changed = true; 00044 while (last_changed) 00045 { 00046 last_changed = false; 00047 int streamIdx = -1; 00048 CC608Buffer *tmp = GetOutputText(last_changed, streamIdx); 00049 if (last_changed && (streamIdx == m_ccMode)) 00050 { 00051 changed = true; 00052 return tmp; 00053 } 00054 } 00055 00056 return NULL; 00057 } 00058 00059 CC608Buffer *CC608Reader::GetOutputText(bool &changed, int &streamIdx) 00060 { 00061 streamIdx = -1; 00062 00063 if (!m_enabled) 00064 return NULL; 00065 00066 if (!m_parent) 00067 { 00068 if (NumInputBuffers()) 00069 { 00070 streamIdx = Update(m_inputBuffers[m_writePosition].buffer); 00071 changed = true; 00072 00073 QMutexLocker locker(&m_inputBufLock); 00074 if (m_writePosition != m_readPosition) 00075 m_writePosition = (m_writePosition + 1) % MAXTBUFFER; 00076 } 00077 00078 if (streamIdx >= 0) 00079 { 00080 m_state[streamIdx].m_changed = false; 00081 return &m_state[streamIdx].m_output; 00082 } 00083 else 00084 return &m_state[MAXOUTBUFFERS - 1].m_output; 00085 } 00086 00087 VideoFrame *last = NULL; 00088 if (m_parent->GetVideoOutput()) 00089 last = m_parent->GetVideoOutput()->GetLastShownFrame(); 00090 00091 if (NumInputBuffers() && m_inputBuffers[m_writePosition].timecode && 00092 (last && m_inputBuffers[m_writePosition].timecode <= last->timecode)) 00093 { 00094 if (m_inputBuffers[m_writePosition].type == 'T') 00095 { 00096 streamIdx = MAXOUTBUFFERS - 1; 00097 00098 // display full page of teletext 00099 // 00100 // all formatting is always defined in the page itself, 00101 // if scrolling is needed for live closed captions this 00102 // is handled by the broadcaster: 00103 // the pages are then very often transmitted (sometimes as often as 00104 // every 2 frames) with small differences between them 00105 unsigned char *inpos = m_inputBuffers[m_writePosition].buffer; 00106 int pagenr; 00107 memcpy(&pagenr, inpos, sizeof(int)); 00108 inpos += sizeof(int); 00109 00110 if (pagenr == (m_ccPageNum<<16)) 00111 { 00112 // show teletext subtitles 00113 ClearBuffers(false, true, streamIdx); 00114 (*inpos)++; 00115 while (*inpos) 00116 { 00117 struct teletextsubtitle st; 00118 memcpy(&st, inpos, sizeof(st)); 00119 inpos += sizeof(st); 00120 00121 CC608Text *cc = new CC608Text( 00122 QString((const char*) inpos), st.row, st.col, true); 00123 00124 m_state[streamIdx].m_output.lock.lock(); 00125 m_state[streamIdx].m_output.buffers.push_back(cc); 00126 m_state[streamIdx].m_output.lock.unlock(); 00127 00128 inpos += st.len; 00129 } 00130 changed = true; 00131 } 00132 } 00133 else if (m_inputBuffers[m_writePosition].type == 'C') 00134 { 00135 streamIdx = Update(m_inputBuffers[m_writePosition].buffer); 00136 changed = true; 00137 } 00138 00139 QMutexLocker locker(&m_inputBufLock); 00140 if (m_writePosition != m_readPosition) 00141 m_writePosition = (m_writePosition + 1) % MAXTBUFFER; 00142 } 00143 00144 if (streamIdx >= 0) 00145 { 00146 m_state[streamIdx].m_changed = false; 00147 return &m_state[streamIdx].m_output; 00148 } 00149 else 00150 { 00151 return &m_state[MAXOUTBUFFERS - 1].m_output; 00152 } 00153 } 00154 00155 void CC608Reader::SetMode(int mode) 00156 { 00157 // TODO why was the clearing code removed? 00158 //int oldmode = m_ccMode; 00159 m_ccMode = (mode <= 2) ? ((mode == 1) ? CC_CC1 : CC_CC2) : 00160 ((mode == 3) ? CC_CC3 : CC_CC4); 00161 //if (oldmode != m_ccMode) 00162 // ClearBuffers(true, true); 00163 } 00164 00165 int CC608Reader::Update(unsigned char *inpos) 00166 { 00167 struct ccsubtitle subtitle; 00168 00169 memcpy(&subtitle, inpos, sizeof(subtitle)); 00170 inpos += sizeof(ccsubtitle); 00171 00172 const int streamIdx = (subtitle.resumetext & CC_MODE_MASK) >> 4; 00173 00174 if (subtitle.row == 0) 00175 subtitle.row = 1; 00176 00177 if (subtitle.clr) 00178 { 00179 #if 0 00180 LOG(VB_VBI, LOG_DEBUG, "erase displayed memory"); 00181 #endif 00182 ClearBuffers(false, true, streamIdx); 00183 if (!subtitle.len) 00184 return streamIdx; 00185 } 00186 00187 // if (subtitle.len || !subtitle.clr) 00188 { 00189 unsigned char *end = inpos + subtitle.len; 00190 int row = 0; 00191 int linecont = (subtitle.resumetext & CC_LINE_CONT); 00192 00193 vector<CC608Text*> *ccbuf = new vector<CC608Text*>; 00194 vector<CC608Text*>::iterator ccp; 00195 CC608Text *tmpcc = NULL; 00196 int replace = linecont; 00197 int scroll = 0; 00198 bool scroll_prsv = false; 00199 int scroll_yoff = 0; 00200 int scroll_ymax = 15; 00201 00202 do 00203 { 00204 if (linecont) 00205 { 00206 // append to last line; needs to be redrawn 00207 replace = 1; 00208 // backspace into existing line if needed 00209 int bscnt = 0; 00210 while ((inpos < end) && *inpos != 0 && (char)*inpos == '\b') 00211 { 00212 bscnt++; 00213 inpos++; 00214 } 00215 if (bscnt) 00216 { 00217 m_state[streamIdx].m_outputText.remove( 00218 m_state[streamIdx].m_outputText.length() - bscnt, 00219 bscnt); 00220 } 00221 } 00222 else 00223 { 00224 // new line: count spaces to calculate column position 00225 row++; 00226 m_state[streamIdx].m_outputCol = 0; 00227 m_state[streamIdx].m_outputText = ""; 00228 while ((inpos < end) && *inpos != 0 && (char)*inpos == ' ') 00229 { 00230 inpos++; 00231 m_state[streamIdx].m_outputCol++; 00232 } 00233 } 00234 00235 m_state[streamIdx].m_outputRow = subtitle.row; 00236 unsigned char *cur = inpos; 00237 00238 //null terminate at EOL 00239 while (cur < end && *cur != '\n' && *cur != 0) 00240 cur++; 00241 *cur = 0; 00242 00243 if (*inpos != 0 || linecont) 00244 { 00245 if (linecont) 00246 { 00247 m_state[streamIdx].m_outputText += 00248 QString::fromUtf8((const char *)inpos, -1); 00249 } 00250 else 00251 { 00252 m_state[streamIdx].m_outputText = 00253 QString::fromUtf8((const char *)inpos, -1); 00254 } 00255 tmpcc = new CC608Text( 00256 m_state[streamIdx].m_outputText, 00257 m_state[streamIdx].m_outputCol, 00258 m_state[streamIdx].m_outputRow, false); 00259 ccbuf->push_back(tmpcc); 00260 #if 0 00261 if (ccbuf->size() > 4) 00262 LOG(VB_VBI, LOG_DEBUG, QString("CC overflow: %1 %2 %3") 00263 .arg(m_outputCol) .arg(m_outputRow) 00264 .arg(m_outputText)); 00265 #endif 00266 } 00267 subtitle.row++; 00268 inpos = cur + 1; 00269 linecont = 0; 00270 } while (inpos < end); 00271 00272 // adjust row position 00273 if (subtitle.resumetext & CC_TXT_MASK) 00274 { 00275 // TXT mode 00276 // - can use entire 15 rows 00277 // - scroll up when reaching bottom 00278 if (m_state[streamIdx].m_outputRow > 15) 00279 { 00280 if (row) 00281 scroll = m_state[streamIdx].m_outputRow - 15; 00282 if (tmpcc) 00283 tmpcc->y = 15; 00284 } 00285 } 00286 else if (subtitle.rowcount == 0 || row > 1) 00287 { 00288 // multi-line text 00289 // - fix display of old (badly-encoded) files 00290 if (m_state[streamIdx].m_outputRow > 15) 00291 { 00292 ccp = ccbuf->begin(); 00293 for (; ccp != ccbuf->end(); ++ccp) 00294 { 00295 tmpcc = *ccp; 00296 tmpcc->y -= (m_state[streamIdx].m_outputRow - 15); 00297 } 00298 } 00299 } 00300 else 00301 { 00302 // scrolling text 00303 // - scroll up previous lines if adding new line 00304 // - if caption is at bottom, row address is for last 00305 // row 00306 // - if caption is at top, row address is for first row (?) 00307 if (subtitle.rowcount > 4) 00308 subtitle.rowcount = 4; 00309 if (m_state[streamIdx].m_outputRow < subtitle.rowcount) 00310 { 00311 m_state[streamIdx].m_outputRow = subtitle.rowcount; 00312 if (tmpcc) 00313 tmpcc->y = m_state[streamIdx].m_outputRow; 00314 } 00315 if (row) 00316 { 00317 scroll = row; 00318 scroll_prsv = true; 00319 scroll_yoff = 00320 m_state[streamIdx].m_outputRow - subtitle.rowcount; 00321 scroll_ymax = m_state[streamIdx].m_outputRow; 00322 } 00323 } 00324 00325 Update608Text(ccbuf, replace, scroll, 00326 scroll_prsv, scroll_yoff, scroll_ymax, streamIdx); 00327 delete ccbuf; 00328 } 00329 00330 return streamIdx; 00331 } 00332 00333 void CC608Reader::TranscodeWriteText(void (*func) 00334 (void *, unsigned char *, int, int, int), 00335 void * ptr) 00336 { 00337 QMutexLocker locker(&m_inputBufLock); 00338 while (NumInputBuffers(false)) 00339 { 00340 locker.unlock(); 00341 int pagenr = 0; 00342 unsigned char *inpos = m_inputBuffers[m_readPosition].buffer; 00343 if (m_inputBuffers[m_readPosition].type == 'T') 00344 { 00345 memcpy(&pagenr, inpos, sizeof(int)); 00346 inpos += sizeof(int); 00347 m_inputBuffers[m_readPosition].len -= sizeof(int); 00348 } 00349 func(ptr, inpos, m_inputBuffers[m_readPosition].len, 00350 m_inputBuffers[m_readPosition].timecode, pagenr); 00351 00352 locker.relock(); 00353 m_readPosition = (m_readPosition + 1) % MAXTBUFFER; 00354 } 00355 } 00356 00357 void CC608Reader::Update608Text( 00358 vector<CC608Text*> *ccbuf, int replace, int scroll, bool scroll_prsv, 00359 int scroll_yoff, int scroll_ymax, int streamIdx) 00360 // ccbuf : new text 00361 // replace : replace last lines 00362 // scroll : scroll amount 00363 // scroll_prsv: preserve last lines and move into scroll window 00364 // scroll_yoff: yoff < scroll window <= ymax 00365 // scroll_ymax: 00366 { 00367 vector<CC608Text*>::iterator i; 00368 int visible = 0; 00369 00370 m_state[streamIdx].m_output.lock.lock(); 00371 if (m_state[streamIdx].m_output.buffers.size() && (scroll || replace)) 00372 { 00373 CC608Text *cc; 00374 00375 // get last row 00376 int ylast = 0; 00377 i = m_state[streamIdx].m_output.buffers.end() - 1; 00378 cc = *i; 00379 if (cc) 00380 ylast = cc->y; 00381 00382 // calculate row positions to delete, keep 00383 int ydel = scroll_yoff + scroll; 00384 int ykeep = scroll_ymax; 00385 int ymove = 0; 00386 if (scroll_prsv && ylast) 00387 { 00388 ymove = ylast - scroll_ymax; 00389 ydel += ymove; 00390 ykeep += ymove; 00391 } 00392 00393 i = m_state[streamIdx].m_output.buffers.begin(); 00394 while (i < m_state[streamIdx].m_output.buffers.end()) 00395 { 00396 cc = (*i); 00397 if (!cc) 00398 { 00399 i = m_state[streamIdx].m_output.buffers.erase(i); 00400 continue; 00401 } 00402 00403 if (cc->y > (ylast - replace)) 00404 { 00405 // delete last lines 00406 delete cc; 00407 i = m_state[streamIdx].m_output.buffers.erase(i); 00408 } 00409 else if (scroll) 00410 { 00411 if (cc->y > ydel && cc->y <= ykeep) 00412 { 00413 // scroll up 00414 cc->y -= (scroll + ymove); 00415 ++i; 00416 } 00417 else 00418 { 00419 // delete lines outside scroll window 00420 i = m_state[streamIdx].m_output.buffers.erase(i); 00421 delete cc; 00422 } 00423 } 00424 else 00425 { 00426 ++i; 00427 } 00428 } 00429 } 00430 00431 visible += m_state[streamIdx].m_output.buffers.size(); 00432 00433 if (ccbuf) 00434 { 00435 // add new text 00436 for (i = ccbuf->begin(); i != ccbuf->end(); ++i) 00437 { 00438 if (*i) 00439 { 00440 visible++; 00441 m_state[streamIdx].m_output.buffers.push_back(*i); 00442 } 00443 } 00444 } 00445 m_state[streamIdx].m_changed = visible; 00446 m_state[streamIdx].m_output.lock.unlock(); 00447 } 00448 00449 void CC608Reader::ClearBuffers(bool input, bool output, int outputStreamIdx) 00450 { 00451 if (input) 00452 { 00453 for (int i = 0; i < MAXTBUFFER; i++) 00454 { 00455 m_inputBuffers[i].timecode = 0; 00456 if (m_inputBuffers[i].buffer) 00457 memset(m_inputBuffers[i].buffer, 0, m_maxTextSize); 00458 } 00459 00460 QMutexLocker locker(&m_inputBufLock); 00461 m_readPosition = 0; 00462 m_writePosition = 0; 00463 } 00464 00465 if (output && outputStreamIdx < 0) 00466 { 00467 for (int i = 0; i < MAXOUTBUFFERS; ++i) 00468 m_state[i].Clear(); 00469 } 00470 00471 if (output && outputStreamIdx >= 0) 00472 { 00473 outputStreamIdx = min(outputStreamIdx, MAXOUTBUFFERS - 1); 00474 m_state[outputStreamIdx].Clear(); 00475 } 00476 } 00477 00478 int CC608Reader::NumInputBuffers(bool need_to_lock) 00479 { 00480 int ret; 00481 00482 if (need_to_lock) 00483 m_inputBufLock.lock(); 00484 00485 if (m_readPosition >= m_writePosition) 00486 ret = m_readPosition - m_writePosition; 00487 else 00488 ret = MAXTBUFFER - (m_writePosition - m_readPosition); 00489 00490 if (need_to_lock) 00491 m_inputBufLock.unlock(); 00492 00493 return ret; 00494 } 00495 00496 void CC608Reader::AddTextData(unsigned char *buffer, int len, 00497 int64_t timecode, char type) 00498 { 00499 if (m_parent) 00500 m_parent->WrapTimecode(timecode, TC_CC); 00501 00502 if (!m_enabled) 00503 return; 00504 00505 if (NumInputBuffers() >= MAXTBUFFER - 1) 00506 { 00507 LOG(VB_VBI, LOG_ERR, "AddTextData(): Text buffer overflow"); 00508 return; 00509 } 00510 00511 if (len > m_maxTextSize) 00512 len = m_maxTextSize; 00513 00514 QMutexLocker locker(&m_inputBufLock); 00515 int prev_readpos = (m_readPosition - 1 + MAXTBUFFER) % MAXTBUFFER; 00516 /* Check whether the reader appears to be waiting on a caption 00517 whose timestamp is too large. We can guess this is the case 00518 if we are adding a timestamp that is smaller than timestamp 00519 being waited on but larger than the timestamp before that. 00520 Note that even if the text buffer is full, the entry at index 00521 m_readPosition-1 should still be valid. 00522 */ 00523 if (NumInputBuffers(false) > 0 && 00524 m_inputBuffers[m_readPosition].timecode > timecode && 00525 timecode > m_inputBuffers[prev_readpos].timecode) 00526 { 00527 /* If so, reset the timestamp that the reader is waiting on 00528 to a value reasonably close to the previously read 00529 timestamp. This will probably cause one or more captions 00530 to appear rapidly, but at least the captions won't 00531 appear to be stuck. 00532 */ 00533 LOG(VB_VBI, LOG_INFO, 00534 QString("Writing caption timecode %1 but waiting on %2") 00535 .arg(timecode).arg(m_inputBuffers[m_readPosition].timecode)); 00536 m_inputBuffers[m_readPosition].timecode = 00537 m_inputBuffers[prev_readpos].timecode + 500; 00538 } 00539 00540 m_inputBuffers[m_readPosition].timecode = timecode; 00541 m_inputBuffers[m_readPosition].type = type; 00542 m_inputBuffers[m_readPosition].len = len; 00543 memset(m_inputBuffers[m_readPosition].buffer, 0, m_maxTextSize); 00544 memcpy(m_inputBuffers[m_readPosition].buffer, buffer, len); 00545 00546 m_readPosition = (m_readPosition+1) % MAXTBUFFER; 00547 }
1.7.6.1