All pastes #1880650 Raw Edit

Mine

public text v1 · immutable
#1880650 ·published 2010-06-10 19:55 UTC
rendered paste body
Index: libs/libmythtv/vbi608extractor.h
===================================================================
--- libs/libmythtv/vbi608extractor.h	(revision 0)
+++ libs/libmythtv/vbi608extractor.h	(revision 0)
@@ -0,0 +1,57 @@
+/*
+  VBI 608 Extractor, extracts CEA-608 VBI from a line of raw data.
+  Copyright (C) 2010  Digital Nirvana, Inc.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License
+  as published by the Free Software Foundation; either version 2
+  of the License, or (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+
+#ifndef _VBI_608_EXTRACTOR_H_
+#define _VBI_608_EXTRACTOR_H_
+
+#include <stdint.h>
+
+#include <QList>
+
+#include "frame.h"
+
+class VBI608Extractor
+{
+  public:
+    VBI608Extractor();
+
+    uint16_t GetCode1(void) const { return code[0]; }
+    uint16_t GetCode2(void) const { return code[1]; }
+
+    bool ExtractCC(const VideoFrame*, uint max_lines = 4);
+    bool ExtractCC12(const unsigned char *buf, uint width);
+    bool ExtractCC34(const unsigned char *buf, uint width);
+
+    uint FillCCData(uint8_t cc_data[8]);
+
+  private:
+    float    GetClockStart(void) const { return start; }
+    float    GetClockRate(void)  const { return rate;  }
+    bool     FindClocks(const unsigned char *buf, uint width);
+
+    QList<uint>  raw_minimas;
+    QList<uint>  raw_maximas;
+    QList<float> maximas;
+    QList<float> minimas;
+    float        start;
+    float        rate;
+    uint16_t     code[2];
+};
+
+#endif // _VBI_608_EXTRACTOR_H_
Index: libs/libmythtv/NuppelVideoRecorder.cpp
===================================================================
--- libs/libmythtv/NuppelVideoRecorder.cpp	(revision 25079)
+++ libs/libmythtv/NuppelVideoRecorder.cpp	(working copy)
@@ -24,13 +24,13 @@
 #include "mythcontext.h"
 #include "mythverbose.h"
 #include "NuppelVideoRecorder.h"
-#include "vbitext/cc.h"
 #include "channelbase.h"
 #include "filtermanager.h"
 #include "recordingprofile.h"
 #include "tv_rec.h"
 #include "tv_play.h"
 #include "audioinput.h"
+#include "vbi608extractor.h"
 
 #ifdef WORDS_BIGENDIAN
 extern "C" {
@@ -181,6 +181,7 @@
     volume = 100;
 
     ccr.SetEnabled(true);
+    vbi608 = new VBI608Extractor();
     ccd = new CC608Decoder(&ccr);
 
     go7007 = false;
@@ -253,6 +254,8 @@
         delete videoFilters;
     if (FiltMan)
         delete FiltMan;
+    if (vbi608)
+        delete vbi608;
     if (ccd)
         delete ccd;
 }
@@ -2573,7 +2576,7 @@
 void NuppelVideoRecorder::FormatTeletextSubtitles(struct VBIData *vbidata) {}
 #endif // USING_V4L
 
-void NuppelVideoRecorder::FormatCC(struct cc *cc)
+void NuppelVideoRecorder::FormatCC(uint code1, uint code2)
 {
     struct timeval tnow;
     gettimeofday (&tnow, &tzone);
@@ -2583,7 +2586,7 @@
     int tc = (tnow.tv_sec - stm.tv_sec) * 1000 +
              tnow.tv_usec / 1000 - stm.tv_usec / 1000;
 
-    ccd->FormatCC(tc, cc->code1, cc->code2);
+    ccd->FormatCC(tc, code1, code2);
 }
 
 void NuppelVideoRecorder::AddTextData(unsigned char *buf, int len,
@@ -2651,10 +2654,9 @@
     //VERBOSE(VB_IMPORTANT, LOC + "vbi begin");
     struct VBIData vbicallbackdata;
     struct vbi *pal_tt = NULL;
-    struct cc *ntsc_cc = NULL;
     int vbifd = -1;
-    char *ptr = NULL;
-    char *ptr_end = NULL;
+    unsigned char *buf = NULL, *ptr = NULL, *ptr_end = NULL;
+    uint width = 0, start_line = 0, line_count = 0;
 
     QByteArray vbidev = vbidevice.toAscii();
     if (VBIMode::PAL_TT == vbimode)
@@ -2669,17 +2671,7 @@
     }
     else if (VBIMode::NTSC_CC == vbimode)
     {
-        ntsc_cc = new struct cc;
-        bzero(ntsc_cc, sizeof(struct cc));
-        ntsc_cc->fd = open(vbidev.constData(), O_RDONLY|O_NONBLOCK);
-        ntsc_cc->code1 = -1;
-        ntsc_cc->code2 = -1;
-
-        vbifd   = ntsc_cc->fd;
-        ptr     = ntsc_cc->buffer;
-
-        if (vbifd < 0)
-            delete ntsc_cc;
+        vbifd   = open(vbidev.constData(), O_RDONLY|O_NONBLOCK);
     }
     else
     {
@@ -2703,7 +2695,7 @@
         {
             VERBOSE(VB_IMPORTANT, LOC_ERR +
                     "Failed to query vbi capabilities (v4l1)");
-            cc_close(ntsc_cc);
+            close(vbifd);
             return;
         }
         VERBOSE(VB_RECORD, LOC + "vbi_format  rate: "<<vfmt.sampling_rate
@@ -2715,19 +2707,28 @@
                 <<"\n\t\t\t           flags: "
                 <<QString("0x%1").arg(vfmt.flags));
         uint sz = vfmt.samples_per_line * (vfmt.count[0] + vfmt.count[1]);
-        ntsc_cc->samples_per_line = vfmt.samples_per_line;
-        ntsc_cc->start_line       = vfmt.start[0];
-        ntsc_cc->line_count       = vfmt.count[0];
-        ntsc_cc->scale0           = (vfmt.sampling_rate + 503488 / 2) / 503488;
-        ntsc_cc->scale1           = (ntsc_cc->scale0 * 2 + 3) / 5; /* 40% */
-        ptr_end = ntsc_cc->buffer + sz;
-        if (sz > CC_VBIBUFSIZE)
+        buf = ptr = new unsigned char[sz];
+        ptr_end    = buf + sz;
+        width      = vfmt.samples_per_line;
+        start_line = vfmt.start[0];
+        line_count = vfmt.count[0];
+        if (line_count != vfmt.count[1])
         {
             VERBOSE(VB_IMPORTANT, LOC_ERR +
-                    "VBI format has too many samples per frame");
-            cc_close(ntsc_cc);
+                    "VBI must have the same number of "
+                    "odd and even fields for our decoder");
+            delete [] buf;
+            close(vbifd);
             return;
         }
+        if (start_line > 21 || start_line + line_count < 22)
+        {
+            VERBOSE(VB_IMPORTANT, LOC_ERR +
+                    "VBI does not include line 21");
+            delete [] buf;
+            close(vbifd);
+            return;
+        }
     }
 
     // Qt4 requires a QMutex as a parameter...
@@ -2774,9 +2775,21 @@
             ptr = (ret > 0) ? ptr + ret : ptr;
             if ((ptr_end - ptr) == 0)
             {
-                cc_decode(ntsc_cc);
-                FormatCC(ntsc_cc);
-                ptr = ntsc_cc->buffer;
+                unsigned char *line21_field1 =
+                    ptr + (21 - start_line) * width;
+                unsigned char *line21_field2 =
+                    ptr + (line_count + 21 - start_line) * width;
+                bool cc1 = vbi608->ExtractCC12(line21_field1, width);
+                bool cc2 = vbi608->ExtractCC34(line21_field2, width);
+                if (cc1 || cc2)
+                {
+                    int code1 = vbi608->GetCode1();
+                    int code2 = vbi608->GetCode2();
+                    code1 = (0xFFFF==code1) ? -1 : code1;
+                    code2 = (0xFFFF==code2) ? -1 : code2;
+                    FormatCC(code1, code2);
+                }
+                ptr = buf;
             }
             else if (ret < 0)
             {
@@ -2790,10 +2803,14 @@
     {
         vbi_del_handler(pal_tt, (void*) vbi_event, &vbicallbackdata);
         vbi_close(pal_tt);
+        vbifd = -1;
     }
 
-    if (ntsc_cc)
-        cc_close(ntsc_cc);
+    if (vbifd >= 0)
+    {
+        close(vbifd);
+        delete [] buf;
+    }
 
     //VERBOSE(VB_RECORD, LOC + "vbi end");
 }
Index: libs/libmythtv/avformatdecoder.cpp
===================================================================
--- libs/libmythtv/avformatdecoder.cpp	(revision 25079)
+++ libs/libmythtv/avformatdecoder.cpp	(working copy)
@@ -22,6 +22,7 @@
 #include "mpegtables.h"
 #include "atscdescriptors.h"
 #include "dvbdescriptors.h"
+#include "vbi608extractor.h"
 #include "cc608decoder.h"
 #include "cc708decoder.h"
 #include "subtitlereader.h"
@@ -488,6 +489,8 @@
       no_hardware_decoders(no_hardware_decode),
       maxkeyframedist(-1),
       // Closed Caption & Teletext decoders
+      use_vbi(true),
+      vbi608(new VBI608Extractor()),
       ccd608(new CC608Decoder(parent->GetCC608Reader())),
       ccd708(new CC708Decoder(parent->GetCC708Reader())),
       ttd(new TeletextDecoder()),   subReader(parent->GetSubReader()),
@@ -552,6 +555,7 @@
     }
 
     CloseContext();
+    delete vbi608;
     delete ccd608;
     delete ccd708;
     delete ttd;
@@ -3166,10 +3170,11 @@
 
     // Decode CEA-608 and CEA-708 captions
     for (uint i = 0; i < (uint)mpa_pic.atsc_cc_len;
-    i += ((mpa_pic.atsc_cc_buf[i] & 0x1f) * 3) + 2)
+         i += ((mpa_pic.atsc_cc_buf[i] & 0x1f) * 3) + 2)
     {
         DecodeDTVCC(mpa_pic.atsc_cc_buf + i,
                     mpa_pic.atsc_cc_len - i);
+        use_vbi = false;
     }
 
     VideoFrame *picframe = (VideoFrame *)(mpa_pic.opaque);
@@ -3268,6 +3273,13 @@
     picframe->repeat_pict = mpa_pic.repeat_pict;
 
     picframe->frameNumber = framesPlayed;
+
+    if (use_vbi && vbi608->ExtractCC(picframe))
+    {
+        uint8_t cc_data[8];
+        DecodeDTVCC(cc_data, vbi608->FillCCData(cc_data));
+    }
+
     GetNVP()->ReleaseNextVideoFrame(picframe, temppts);
     if (d->HasMPEG2Dec() && mpa_pic.data[3])
         context->release_buffer(context, &mpa_pic);
Index: libs/libmythtv/libmythtv.pro
===================================================================
--- libs/libmythtv/libmythtv.pro	(revision 25079)
+++ libs/libmythtv/libmythtv.pro	(working copy)
@@ -147,6 +147,7 @@
 HEADERS += remoteencoder.h          videosource.h
 HEADERS += cardutil.h               sourceutil.h
 HEADERS += videometadatautil.h
+HEADERS += vbi608extractor.h
 HEADERS += cc608decoder.h           cc608reader.h
 HEADERS += cc708decoder.h           cc708reader.h
 HEADERS += cc708window.h            subtitlereader.h
@@ -171,6 +172,7 @@
 SOURCES += remoteencoder.cpp        videosource.cpp
 SOURCES += cardutil.cpp             sourceutil.cpp
 SOURCES += videometadatautil.cpp
+SOURCES += vbi608extractor.cpp
 SOURCES += cc608decoder.cpp         cc608reader.cpp
 SOURCES += cc708decoder.cpp         cc708reader.cpp
 SOURCES += cc708window.cpp          subtitlereader.cpp
Index: libs/libmythtv/NuppelVideoRecorder.h
===================================================================
--- libs/libmythtv/NuppelVideoRecorder.h	(revision 25079)
+++ libs/libmythtv/NuppelVideoRecorder.h	(working copy)
@@ -37,13 +37,13 @@
 
 struct video_audio;
 struct VBIData;
-struct cc;
 class RTjpeg;
 class RingBuffer;
 class ChannelBase;
 class FilterManager;
 class FilterChain;
 class AudioInput;
+class VBI608Extractor;
 
 class MPUBLIC NuppelVideoRecorder : public RecorderBase
 {
@@ -138,7 +138,7 @@
     void DoMJPEG(void);
 
     void FormatTeletextSubtitles(struct VBIData *vbidata);
-    void FormatCC(struct cc *cc);
+    void FormatCC(uint code1, uint code2);
     void AddTextData(unsigned char *buf, int len,
                      long long timecode, char type);
     
@@ -285,6 +285,7 @@
 
     int volume;
 
+    VBI608Extractor *vbi608;
     CC608Decoder *ccd;
     CC608Reader   ccr;
 
Index: libs/libmythtv/avformatdecoder.h
===================================================================
--- libs/libmythtv/avformatdecoder.h	(revision 25079)
+++ libs/libmythtv/avformatdecoder.h	(working copy)
@@ -22,6 +22,7 @@
 #include "avfringbuffer.h"
 
 class TeletextDecoder;
+class VBI608Extractor;
 class CC608Decoder;
 class CC708Decoder;
 class SubtitleReader;
@@ -268,6 +269,9 @@
     int maxkeyframedist;
 
     // Caption/Subtitle/Teletext decoders
+    /// How often has 608 data been found in the first lines of video
+    bool              use_vbi;
+    VBI608Extractor  *vbi608;
     CC608Decoder     *ccd608;
     CC708Decoder     *ccd708;
     TeletextDecoder  *ttd;
Index: libs/libmythtv/vbi608extractor.cpp
===================================================================
--- libs/libmythtv/vbi608extractor.cpp	(revision 0)
+++ libs/libmythtv/vbi608extractor.cpp	(revision 0)
@@ -0,0 +1,392 @@
+/*
+  VBI 608 Extractor, extracts CEA-608 VBI from a line of raw data.
+  Copyright (C) 2010  Digital Nirvana, Inc.
+
+  This program is free software; you can redistribute it and/or
+  modify it under the terms of the GNU General Public License
+  as published by the Free Software Foundation; either version 2
+  of the License, or (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
+#include <algorithm>
+using namespace std;
+
+#include "vbi608extractor.h"
+#include "mythverbose.h"
+
+static void print(
+    const QList<uint>  &raw_minimas, const QList<uint>  &raw_maximas,
+    const QList<float> &minimas,     const QList<float> &maximas)
+{
+    QString raw_mins, raw_maxs;
+    for (uint i = 0; i < uint(raw_minimas.size()); i++)
+        raw_mins += QString("%1,").arg(raw_minimas[i]);
+    for (uint i = 0; i < uint(raw_maximas.size()); i++)
+        raw_maxs += QString("%1,").arg(raw_maximas[i]);
+    VERBOSE(VB_VBI|VB_EXTRA, QString("raw mins: %1").arg(raw_mins));
+    VERBOSE(VB_VBI|VB_EXTRA, QString("raw maxs: %1").arg(raw_maxs));
+
+    QString mins, maxs;
+    for (uint i = 0; i < uint(minimas.size()); i++)
+        mins += QString("%1,").arg(minimas[i]);
+    for (uint i = 0; i < uint(maximas.size()); i++)
+        maxs += QString("%1,").arg(maximas[i]);
+    VERBOSE(VB_VBI|VB_EXTRA, QString("mins: %1 maxs: %2")
+            .arg(mins).arg(maxs));
+}
+
+static float find_clock_diff(const QList<float> &list)
+{
+    float min_diff = INT32_MAX;
+    float max_diff = 0.0f;
+    float avg_diff = 0.0f;
+    for (uint i = 1; i < uint(list.size()); i++)
+    {
+        float diff = list[i] - list[i-1];
+        min_diff = min(diff, min_diff);
+        max_diff = max(diff, max_diff);
+        avg_diff += diff;
+    }
+    avg_diff /= (list.size() - 1);
+    if (avg_diff * 1.15 < max_diff)
+    {
+        VERBOSE(VB_VBI|VB_EXTRA, "max_diff too big");
+        return 0.0f;
+    }
+    if (avg_diff * 0.85 > max_diff)
+    {
+        VERBOSE(VB_VBI|VB_EXTRA, "min_diff too small");
+        return 0.0f;
+    }
+
+    return avg_diff;
+}
+
+VBI608Extractor::VBI608Extractor() : start(0.0f), rate(0.0f)
+{
+    code[0] = UINT16_MAX;
+    code[1] = UINT16_MAX;
+}
+
+bool VBI608Extractor::FindClocks(const unsigned char *buf, uint width)
+{
+    raw_minimas.clear();
+    raw_maximas.clear();
+    maximas.clear();
+    minimas.clear();
+
+    // find our threshold
+    uint maxv = 0;
+    for (uint j = width / 8; j < width / 4; j++)
+        maxv = max(uint(buf[j]), maxv);
+    uint avgv = maxv / 2;
+
+    // get the raw minima and maxima list
+    uint noise_flr_sm = max(uint(0.003 * width), 2U);
+    uint noise_flr_lg = max(uint(0.007 * width), noise_flr_sm+1);
+    int last_max = -1, last_min = -1;
+    for (uint i = 0; i < (width/3); i++)
+    {
+        if (buf[i] > avgv+10)
+            raw_maximas.push_back(last_max=i);
+        else if (last_max>=0 && (i - last_max) <= noise_flr_sm)
+            raw_maximas.push_back(i);
+        else if (buf[i] < avgv-10)
+            raw_minimas.push_back(last_min=i);
+        else if (last_min>=0 && (i - last_min) <= noise_flr_lg)
+            raw_minimas.push_back(i);
+    }
+
+    for (uint i = 0; i < uint(raw_maximas.size()); i++)
+    {
+        uint start_idx = raw_maximas[i];
+        while ((i+1) < uint(raw_maximas.size()) &&
+               (raw_maximas[i+1] == raw_maximas[i] + 1)) i++;
+        uint end_idx = raw_maximas[i];
+        if (end_idx - start_idx > noise_flr_lg)
+            maximas.push_back((start_idx + end_idx) * 0.5f);
+    }
+
+    if (maximas.size() < 7)
+    {
+        VERBOSE(VB_VBI|VB_EXTRA, QString("%1 < 7").arg(maximas.size()));
+        print(raw_minimas, raw_maximas, minimas, maximas);
+        return false;
+    }
+
+    // drop outliers on edges
+    bool dropped = true;
+    while (maximas.size() > 7 && dropped)
+    {
+        float min_diff = width * 8;
+        float max_diff = 0.0f;
+        float avg_diff = 0.0f;
+        for (uint i = 1; i < uint(maximas.size()); i++)
+        {
+            float diff = maximas[i] - maximas[i-1];
+            min_diff = min(diff, min_diff);
+            max_diff = max(diff, max_diff);
+            avg_diff += diff;
+        }
+        avg_diff -= min_diff;
+        avg_diff -= max_diff;
+        avg_diff /= (maximas.size() - 3);
+
+        dropped = false;
+        if (avg_diff * 1.1f < max_diff)
+        {
+            float last_diff = maximas.back() - maximas[maximas.size()-2];
+            if (last_diff*1.01f >= max_diff || last_diff > avg_diff * 1.2f)
+            {
+                maximas.pop_back();
+                dropped = true;
+            }
+            float first_diff = maximas[1] - maximas[0];
+            if ((maximas.size() > 7) && (first_diff*1.01f >= max_diff))
+            {
+                maximas.pop_front();
+                dropped = true;
+            }
+        }
+
+        if (avg_diff * 0.9f > min_diff)
+        {
+            float last_diff = maximas.back() - maximas[maximas.size()-2];
+            if ((maximas.size() > 7) &&
+                (last_diff*0.99f <= min_diff || last_diff < avg_diff * 0.80f))
+            {
+                maximas.pop_back();
+                dropped = true;
+            } 
+            float first_diff = maximas[1] - maximas[0];
+            if ((maximas.size() > 7) && (first_diff*0.99f <= min_diff))
+            {
+                maximas.pop_front();
+                dropped = true;
+            }
+        }
+    }
+
+    if (maximas.size() != 7)
+    {
+        VERBOSE(VB_VBI|VB_EXTRA, QString("maximas: %1 != 7")
+                .arg(maximas.size()));
+        print(raw_minimas, raw_maximas, minimas, maximas);
+        return false;
+    }
+
+    // find the minimas
+    for (uint i = 0; i < uint(raw_minimas.size()); i++)
+    {
+        uint start_idx = raw_minimas[i];
+        while ((i+1) < uint(raw_minimas.size()) &&
+               (raw_minimas[i+1] == raw_minimas[i] + 1)) i++;
+        uint end_idx = raw_minimas[i];
+        float center = (start_idx + end_idx) * 0.5f;
+        if (end_idx - start_idx > noise_flr_lg &&
+            center > maximas[0] && center < maximas.back())
+        {
+            minimas.push_back(center);
+        }
+    }
+
+    if (minimas.size() != 6)
+    {
+        VERBOSE(VB_VBI|VB_EXTRA, QString("minimas: %1 != 6")
+                .arg(minimas.size()));
+        print(raw_minimas, raw_maximas, minimas, maximas);
+        return false;
+    }
+
+    // get the average clock rate in samples
+    float maxima_avg_diff = find_clock_diff(maximas);
+    float minima_avg_diff = find_clock_diff(minimas);
+    rate = (maxima_avg_diff * 7 + minima_avg_diff * 6) / 13.0f;
+    if (maxima_avg_diff == 0.0f || minima_avg_diff == 0.0f)
+        return false;
+
+    // get the estimated location of the first maxima
+    // based on the rate and location of all maximas
+    start = maximas[0];
+    for (uint i = 1; i < uint(maximas.size()); i++)
+        start += maximas[i] - i * rate;
+    start /= maximas.size();
+    // then move it back by a third to make each sample
+    // more or less in the center of each encoded byte.
+    start -= rate * 0.33f;
+
+    // if the last bit is after the last sample...
+    // 7 clocks + 3 bits run in + 16 bits data
+    if (start+((7+3+8+8-1) * rate) > width)
+    {
+        VERBOSE(VB_VBI|VB_EXTRA, QString("end %1 > width %2")
+                .arg(start+((7+3+8+8-1) * rate)).arg(width));
+
+        return false;
+    }
+
+    //VERBOSE(VB_VBI|VB_EXTRA, QString("Clock start %1, rate %2")
+    //        .arg(start).arg(avg_diff));
+
+    return true;
+}
+
+bool VBI608Extractor::ExtractCC(const VideoFrame *picframe, uint max_lines)
+{
+    int ypitch = picframe->pitches[0];
+    int ywidth = picframe->width;
+
+    code[0] = UINT16_MAX;
+    code[1] = UINT16_MAX;
+
+    // find CC
+    uint found_cnt = 0;
+    for (uint i = 0; i < max_lines; i++)
+    {
+        const unsigned char *y = picframe->buf +
+            picframe->offsets[0] + (i * ypitch);
+        if (FindClocks(y, ywidth))
+        {
+            uint maxv = 0;
+            for (uint j = 0; j < start + 8 * rate; j++)
+                maxv = max(uint((y+(i * ypitch))[j]), maxv);
+            uint avgv = maxv / 2;
+
+            if (y[uint(start + (0+7) * rate)] > avgv ||
+                y[uint(start + (1+7) * rate)] > avgv ||
+                y[uint(start + (2+7) * rate)] < avgv)
+            {
+                continue; // need 001 at run in..
+            }
+
+            code[found_cnt] = 0;
+            for (uint j = 0; j < 8+8; j++)
+            {
+                bool bit = y[uint(start + (j+7+3) * rate)] > avgv;
+                code[found_cnt] =
+                    (code[found_cnt]>>1) | (bit?(1<<15):0);
+            }
+
+            found_cnt++;
+            if (found_cnt>=2)
+                break;
+#if 0
+            unsigned char *Y = const_cast<unsigned char*>(y);
+            unsigned char *u = const_cast<unsigned char*>
+                (picframe->buf + picframe->offsets[1] +
+                 (i*picframe->pitches[1]));
+            unsigned char *v = const_cast<unsigned char*>
+                (picframe->buf + picframe->offsets[2] +
+                 (i*picframe->pitches[2]));
+            unsigned uwidth = picframe->pitches[1];
+            v[uwidth / 3] = 0x40;
+            for (uint j = 0; j < 7+3+8+8; j++)
+            {
+                uint yloc = uint (start + j * rate + 0.5);
+                Y[yloc] = 0xFF;
+                uint uloc = uint (uwidth * (start + j * rate + 0.5) / ywidth);
+                u[uloc] = 0x40;
+            }
+#endif
+        }
+    }
+
+    return found_cnt;
+}
+
+bool VBI608Extractor::ExtractCC12(const unsigned char *buf, uint width)
+{
+    code[0] = UINT16_MAX;
+    if (FindClocks(buf, width))
+    {
+        uint maxv = 0;
+        for (uint j = 0; j < start + 8 * rate; j++)
+            maxv = max(uint(buf[j]), maxv);
+        uint avgv = maxv / 2;
+
+        if (buf[uint(start + (0+7) * rate)] > avgv ||
+            buf[uint(start + (1+7) * rate)] > avgv ||
+            buf[uint(start + (2+7) * rate)] < avgv)
+        {
+            return false;
+        }
+
+        code[0] = 0;
+        for (uint j = 0; j < 8+8; j++)
+        {
+            bool bit = buf[uint(start + (j+7+3) * rate)] > avgv;
+            code[0] = (code[0]>>1) | (bit?(1<<15):0);
+        }
+
+        return true;
+    }
+    return false;
+}
+
+bool VBI608Extractor::ExtractCC34(const unsigned char *buf, uint width)
+{
+    code[1] = UINT16_MAX;
+    if (FindClocks(buf, width))
+    {
+        uint maxv = 0;
+        for (uint j = 0; j < start + 8 * rate; j++)
+            maxv = max(uint(buf[j]), maxv);
+        uint avgv = maxv / 2;
+
+        if (buf[uint(start + (0+7) * rate)] > avgv ||
+            buf[uint(start + (1+7) * rate)] > avgv ||
+            buf[uint(start + (2+7) * rate)] < avgv)
+        {
+            return false;
+        }
+
+        code[1] = 0;
+        for (uint j = 0; j < 8+8; j++)
+        {
+            bool bit = buf[uint(start + (j+7+3) * rate)] > avgv;
+            code[1] = (code[1]>>1) | (bit?(1<<15):0);
+        }
+        return true;
+    }
+    return false;
+}
+
+uint VBI608Extractor::FillCCData(uint8_t cc_data[8])
+{
+    uint cc_count = 0;
+    if (code[0] != UINT16_MAX)
+    {
+        cc_data[2] = 0x04;
+        cc_data[3] = (code[0])    & 0xff;
+        cc_data[4] = (code[0]>>8) & 0xff;
+        cc_count++;
+    }
+
+    if (code[1] != UINT16_MAX)
+    {
+        cc_data[2+3*cc_count] = 0x04|0x01;
+        cc_data[3+3*cc_count] = (code[1])    & 0xff;
+        cc_data[4+3*cc_count] = (code[1]>>8) & 0xff;
+        cc_count++;
+    }
+
+    if (cc_count)
+    {
+        cc_data[0] = 0x40 | cc_count;
+        cc_data[1] = 0x00;
+        return 2+(3*cc_count);
+    }
+    return 0;
+}