MythTV  0.26-pre
cc608reader.cpp
Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends