[vlc-devel] [PATCH 1/2] codec: add CEA708 decoder

Francois Cartegnie fcvlcdev at free.fr
Tue Sep 26 22:59:50 CEST 2017


CEA708 closed captions decoder.
Still will have features to add after some sputext fixes.

---
 include/vlc_fourcc.h      |    3 +-
 modules/codec/Makefile.am |    3 +-
 modules/codec/cc.c        |  142 +++-
 modules/codec/cc.h        |    2 +
 modules/codec/cea708.c    | 1576 +++++++++++++++++++++++++++++++++++++++++++++
 modules/codec/cea708.h    |   44 ++
 6 files changed, 1733 insertions(+), 37 deletions(-)
 create mode 100644 modules/codec/cea708.c
 create mode 100644 modules/codec/cea708.h

diff --git a/include/vlc_fourcc.h b/include/vlc_fourcc.h
index 26dd46a..c15b106 100644
--- a/include/vlc_fourcc.h
+++ b/include/vlc_fourcc.h
@@ -539,8 +539,9 @@
 #define VLC_CODEC_EBU_STL   VLC_FOURCC('S','T','L',' ')
 #define VLC_CODEC_SCTE_18   VLC_FOURCC('S','C','1','8')
 #define VLC_CODEC_SCTE_27   VLC_FOURCC('S','C','2','7')
-/* EIA/CEA-608 */
+/* EIA/CEA-608/708 */
 #define VLC_CODEC_CEA608    VLC_FOURCC('c','6','0','8')
+#define VLC_CODEC_CEA708    VLC_FOURCC('c','7','0','8')
 #define VLC_CODEC_TTML      VLC_FOURCC('T','T','M','L')
 
 /* XYZ colorspace 12 bits packed in 16 bits, organisation |XXX0|YYY0|ZZZ0| */
diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am
index da49a07..77ce5e9 100644
--- a/modules/codec/Makefile.am
+++ b/modules/codec/Makefile.am
@@ -180,7 +180,8 @@ endif
 EXTRA_LTLIBRARIES += liblibass_plugin.la
 codec_LTLIBRARIES += $(LTLIBlibass)
 
-libcc_plugin_la_SOURCES = codec/cc.c codec/cc.h codec/substext.h
+libcc_plugin_la_SOURCES = codec/cc.c codec/cc.h codec/substext.h \
+                          codec/cea708.h codec/cea708.c
 codec_LTLIBRARIES += libcc_plugin.la
 
 libcvdsub_plugin_la_SOURCES = codec/cvdsub.c
diff --git a/modules/codec/cc.c b/modules/codec/cc.c
index 2ed58cc..f8fb283 100644
--- a/modules/codec/cc.c
+++ b/modules/codec/cc.c
@@ -28,7 +28,6 @@
 
 /* TODO:
  *  Check parity
- *  708 decoding
  */
 
 #ifdef HAVE_CONFIG_H
@@ -43,6 +42,7 @@
 #include <vlc_charset.h>
 
 #include "substext.h"
+#include "cea708.h"
 
 /*****************************************************************************
  * Module descriptor.
@@ -219,13 +219,26 @@ struct decoder_sys_t
 
     int i_reorder_depth;
 
-    eia608_t eia608;
+    cea708_demux_t *p_dtvcc;
+
+    cea708_t *p_cea708;
+    eia608_t *p_eia608;
     bool b_opaque;
 };
 
 static int Decode( decoder_t *, block_t * );
 static void Flush( decoder_t * );
 
+static void DTVCC_ServiceData_Handler( void *priv, uint8_t i_sid, mtime_t i_time,
+                                       const uint8_t *p_data, size_t i_data )
+{
+    decoder_t *p_dec = priv;
+    decoder_sys_t *p_sys = p_dec->p_sys;
+    //msg_Err( p_dec, "DTVCC_ServiceData_Handler sid %d bytes %ld", i_sid, i_data );
+    if( i_sid == 1 )
+        CEA708_Decoder_Push( p_sys->p_cea708, i_time, p_data, i_data );
+}
+
 /*****************************************************************************
  * Open: probe the decoder and return score
  *****************************************************************************
@@ -237,18 +250,12 @@ static int Open( vlc_object_t *p_this )
     decoder_t     *p_dec = (decoder_t*)p_this;
     decoder_sys_t *p_sys;
 
-    if( p_dec->fmt_in.i_codec != VLC_CODEC_CEA608 ||
-        p_dec->fmt_in.subs.cc.i_channel > 3 )
+    if( ( p_dec->fmt_in.i_codec != VLC_CODEC_CEA608 ||
+          p_dec->fmt_in.subs.cc.i_channel > 3 ) &&
+        ( p_dec->fmt_in.i_codec != VLC_CODEC_CEA708 ||
+          p_dec->fmt_in.subs.cc.i_channel > 63 ) )
         return VLC_EGENERIC;
 
-    /*  0 -> i_field = 0; i_channel = 1;
-        1 -> i_field = 0; i_channel = 2;
-        2 -> i_field = 1; i_channel = 1;
-        3 -> i_field = 1; i_channel = 2; */
-
-    const int i_field = p_dec->fmt_in.subs.cc.i_channel >> 1;
-    const int i_channel = 1 + (p_dec->fmt_in.subs.cc.i_channel & 1);
-
     p_dec->pf_decode = Decode;
     p_dec->pf_flush  = Flush;
 
@@ -257,11 +264,43 @@ static int Open( vlc_object_t *p_this )
     if( p_sys == NULL )
         return VLC_ENOMEM;
 
-    /* init of p_sys */
-    p_sys->i_field = i_field;
-    p_sys->i_channel = i_channel;
+    if( p_dec->fmt_in.i_codec == VLC_CODEC_CEA608 )
+    {
+        /*  0 -> i_field = 0; i_channel = 1;
+            1 -> i_field = 0; i_channel = 2;
+            2 -> i_field = 1; i_channel = 1;
+            3 -> i_field = 1; i_channel = 2; */
+        p_sys->i_field = p_dec->fmt_in.subs.cc.i_channel >> 1;
+        p_sys->i_channel = 1 + (p_dec->fmt_in.subs.cc.i_channel & 1);
+
+        p_sys->p_eia608 = malloc(sizeof(*p_sys->p_eia608));
+        if( !p_sys->p_eia608 )
+        {
+            free( p_sys );
+            return VLC_ENOMEM;
+        }
+        Eia608Init( p_sys->p_eia608 );
+    }
+    else
+    {
+        p_sys->p_dtvcc = CEA708_DTVCC_Demuxer_New( p_dec, DTVCC_ServiceData_Handler );
+        if( !p_sys->p_dtvcc )
+        {
+            free( p_sys );
+            return VLC_ENOMEM;
+        }
+
+        p_sys->p_cea708 = CEA708_Decoder_New( p_dec );
+        if( !p_sys->p_cea708 )
+        {
+            CEA708_DTVCC_Demuxer_Release( p_sys->p_dtvcc );
+            free( p_sys );
+            return VLC_ENOMEM;
+        }
+
+         p_sys->i_channel = p_dec->fmt_in.subs.cc.i_channel;
+    }
 
-    Eia608Init( &p_sys->eia608 );
     p_sys->b_opaque = var_InheritBool( p_dec, "cc-opaque" );
     p_sys->i_reorder_depth = p_dec->fmt_in.subs.cc.i_reorder_depth;
 
@@ -277,7 +316,15 @@ static void Flush( decoder_t *p_dec )
 {
     decoder_sys_t *p_sys = p_dec->p_sys;
 
-    Eia608Init( &p_sys->eia608 );
+    if( p_sys->p_eia608 )
+    {
+        Eia608Init( p_sys->p_eia608 );
+    }
+    else
+    {
+        CEA708_DTVCC_Demuxer_Flush( p_sys->p_dtvcc );
+        CEA708_Decoder_Flush( p_sys->p_cea708 );
+    }
 
     block_ChainRelease( p_sys->p_queue );
     p_sys->p_queue = NULL;
@@ -316,7 +363,15 @@ static int Decode( decoder_t *p_dec, block_t *p_block )
         {
             /* Drain */
             for( ; DoDecode( p_dec, true ) ; );
-            Eia608Init( &p_sys->eia608 );
+            if( p_sys->p_eia608 )
+            {
+                Eia608Init( p_sys->p_eia608 );
+            }
+            else
+            {
+                CEA708_DTVCC_Demuxer_Flush( p_sys->p_dtvcc );
+                CEA708_Decoder_Flush( p_sys->p_cea708 );
+            }
 
             if( (p_block->i_flags & BLOCK_FLAG_CORRUPTED) || p_block->i_buffer < 1 )
             {
@@ -355,6 +410,13 @@ static void Close( vlc_object_t *p_this )
     decoder_t *p_dec = (decoder_t *)p_this;
     decoder_sys_t *p_sys = p_dec->p_sys;
 
+    free( p_sys->p_eia608 );
+    if( p_sys->p_cea708 )
+    {
+        CEA708_Decoder_Release( p_sys->p_cea708 );
+        CEA708_DTVCC_Demuxer_Release( p_sys->p_dtvcc );
+    }
+
     block_ChainRelease( p_sys->p_queue );
     free( p_sys );
 }
@@ -469,29 +531,39 @@ static void Convert( decoder_t *p_dec, mtime_t i_pts,
 {
     decoder_sys_t *p_sys = p_dec->p_sys;
 
-    size_t i_ticks = 0;
+    unsigned i_ticks = 0;
     while( i_buffer >= 3 )
     {
-        /* Mask off the specific i_field bit, else some sequences can be lost. */
-        if ( (p_buffer[0] & 0x03) == p_sys->i_field &&
-             (p_buffer[0] & 0x04) /* Valid bit */ )
+        if( (p_buffer[0] & 0x04) /* Valid bit */ )
         {
-            eia608_status_t i_status =
-                    Eia608Parse( &p_sys->eia608, p_sys->i_channel, &p_buffer[1] );
-
-            /* a caption is ready or removed, process its screen */
-            /*
-             * In case of rollup/painton with 1 packet/frame, we need to update on Changed status.
-             * Batch decoding might be incorrect if those in large number of commands (mp4, ...) then.
-             * see CEAv1.2zero.trp tests
-             */
-            if( i_status & (EIA608_STATUS_DISPLAY | EIA608_STATUS_CHANGED) )
+            const mtime_t i_spupts = i_pts + i_ticks * CLOCK_FREQ / 30;
+            /* Mask off the specific i_field bit, else some sequences can be lost. */
+            if ( p_sys->p_eia608 &&
+                (p_buffer[0] & 0x03) == p_sys->i_field )
             {
-                subpicture_t *p_spu = Subtitle( p_dec, &p_sys->eia608, i_pts + i_ticks * CLOCK_FREQ / 30 );
-                if( p_spu )
-                    decoder_QueueSub( p_dec, p_spu );
+                eia608_status_t i_status = Eia608Parse( p_sys->p_eia608,
+                                                        p_sys->i_channel, &p_buffer[1] );
+
+                /* a caption is ready or removed, process its screen */
+                /*
+                 * In case of rollup/painton with 1 packet/frame, we need
+                 * to update on Changed status.
+                 * Batch decoding might be incorrect if those in
+                 * large number of commands (mp4, ...) then.
+                 * see CEAv1.2zero.trp tests */
+                if( i_status & (EIA608_STATUS_DISPLAY | EIA608_STATUS_CHANGED) )
+                {
+                    subpicture_t *p_spu = Subtitle( p_dec, p_sys->p_eia608, i_spupts );
+                    if( p_spu )
+                        decoder_QueueSub( p_dec, p_spu );
+                }
+            }
+            else if( p_sys->p_cea708 && (p_buffer[0] & 0x03) >= 2 )
+            {
+                CEA708_DTVCC_Demuxer_Push( p_sys->p_dtvcc, i_spupts, p_buffer );
             }
         }
+
         i_ticks++;
 
         i_buffer -= 3;
diff --git a/modules/codec/cc.h b/modules/codec/cc.h
index 2caea83..4562e53 100644
--- a/modules/codec/cc.h
+++ b/modules/codec/cc.h
@@ -43,6 +43,7 @@ typedef struct
 {
     /* Which channel are present */
     bool pb_present[4];
+    int64_t i_708_flags;
 
     /* */
     bool b_reorder;
@@ -64,6 +65,7 @@ static inline void cc_Init( cc_data_t *c )
 {
     for( int i = 0; i < 4; i++ )
         c->pb_present[i] = false;
+    c->i_708_flags = 0;
     c->i_data = 0;
     c->b_reorder = false;
     c->i_payload_type = CC_PAYLOAD_NONE;
diff --git a/modules/codec/cea708.c b/modules/codec/cea708.c
new file mode 100644
index 0000000..4b04cc2
--- /dev/null
+++ b/modules/codec/cea708.c
@@ -0,0 +1,1576 @@
+/*****************************************************************************
+ * cea708.c : CEA708 subtitles decoder
+ *****************************************************************************
+ * Copyright © 2017 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_codec.h>
+#include <vlc_subpicture.h>
+
+#include "cea708.h"
+#include "substext.h"
+
+#include <assert.h>
+
+#if 0
+#define Debug(code) code
+#else
+#define Debug(code)
+#endif
+
+/*****************************************************************************
+ * Demuxing / Agreggation
+ *****************************************************************************/
+struct cea708_demux_t
+{
+   int8_t  i_pkt_sequence;
+   uint8_t i_total_data;
+   uint8_t i_data;
+   uint8_t data[CEA708_DTVCC_MAX_PKT_SIZE];
+   mtime_t i_time;
+   service_data_hdlr_t p_callback;
+   void *priv;
+};
+
+void CEA708_DTVCC_Demuxer_Flush( cea708_demux_t *h )
+{
+    h->i_pkt_sequence = -1;
+    h->i_total_data = h->i_data = 0;
+}
+
+void CEA708_DTVCC_Demuxer_Release( cea708_demux_t *h )
+{
+    free( h );
+}
+
+cea708_demux_t * CEA708_DTVCC_Demuxer_New( void *priv, service_data_hdlr_t hdlr )
+{
+    cea708_demux_t *h = malloc( sizeof(cea708_demux_t) );
+    if( h )
+    {
+        h->priv = priv;
+        h->p_callback = hdlr;
+        CEA708_DTVCC_Demuxer_Flush( h );
+    }
+    return h;
+}
+
+static void CEA708_DTVCC_Demux_ServiceBlocks( cea708_demux_t *h, mtime_t i_start,
+                                              const uint8_t *p_data, size_t i_data )
+{
+    while( i_data >= 2 )
+    {
+        uint8_t i_sid = p_data[0] >> 5;
+        const uint8_t i_block_size = p_data[0] & 0x1F;
+
+        if( i_block_size == 0 || i_block_size > i_data - 1 )
+        {
+            return;
+        }
+        else if( i_sid == 0x07 )
+        {
+            if( i_data < 2 )
+                return;
+            i_sid = p_data[1] & 0x3F;
+            if( i_sid < 0x07 )
+                return;
+            p_data += 1; i_data -= 1;
+        }
+        p_data += 1; i_data -= 1;
+
+        h->p_callback( h->priv, i_sid, i_start, p_data, i_block_size );
+
+        p_data += i_block_size;
+        i_data -= i_block_size;
+    }
+}
+
+void CEA708_DTVCC_Demuxer_Push( cea708_demux_t *h, mtime_t i_start, const uint8_t data[3] )
+{
+    if( (data[0] & 0x03) == 3 ) /* Header packet */
+    {
+        const int8_t i_pkt_sequence = data[1] >> 6;
+
+        /* pkt loss/discontinuity, trash buffer */
+        if( i_pkt_sequence > 0 && ((h->i_pkt_sequence + 1) % 4) != i_pkt_sequence )
+        {
+            h->i_data = h->i_total_data = 0;
+            h->i_pkt_sequence = i_pkt_sequence;
+            return;
+        }
+
+        uint8_t pktsize = data[1] & 63;
+        if( pktsize == 0 )
+            pktsize = 127;
+        else
+            pktsize = pktsize * 2 - 1;
+
+        h->i_pkt_sequence = i_pkt_sequence;
+        h->i_total_data = pktsize;
+        h->i_data = 0;
+        h->i_time = i_start;
+        h->data[h->i_data++] = data[2];
+    }
+    else if( h->i_total_data > 0 ) /* Not synced to pkt header yet */
+    {
+        h->data[h->i_data++] = data[1];
+        h->data[h->i_data++] = data[2];
+    }
+
+    /* pkts assembly finished, we have a service block */
+    if( h->i_data > 0 && h->i_data >= h->i_total_data )
+    {
+        if( h->i_data == h->i_total_data ) /* Only if correct */
+            CEA708_DTVCC_Demux_ServiceBlocks( h, h->i_time, h->data, h->i_data );
+        h->i_total_data = h->i_data = 0;
+    }
+}
+
+/*****************************************************************************
+ * Service Data Decoding
+ *****************************************************************************/
+
+#define CEA708_SERVICE_INPUT_BUFFER    128
+
+#define CEA708_WINDOWS_COUNT            8
+#define CEA708_PREDEFINED_STYLES        8
+
+#define CEA708_SCREEN_ROWS              75
+#define CEA708_SCREEN_COLS_43           160
+#define CEA708_SCREEN_COLS_169          210
+#define CEA708_SAFE_MARGIN_ROWS         15
+#define CEA708_SAFE_AREA_REL            (1.0 - CEA708_SAFE_MARGIN_ROWS / \
+                                               CEA708_SCREEN_ROWS)
+
+#define CEA708_WINDOW_MAX_COLS          42
+#define CEA708_WINDOW_MAX_ROWS          15
+
+#define CEA708_ROW_HEIGHT_STANDARD     (CEA708_SAFE_AREA_REL / \
+                                        CEA708_WINDOW_MAX_ROWS)
+#define CEA708_FONT_TO_LINE_HEIGHT_RATIO 1.06
+
+#define CEA708_FONTRELSIZE_STANDARD    (CEA708_ROW_HEIGHT_STANDARD / \
+                                        CEA708_FONT_TO_LINE_HEIGHT_RATIO)
+#define CEA708_FONTRELSIZE_SMALL       (CEA708_FONTRELSIZE_STANDARD * 0.7)
+#define CEA708_FONTRELSIZE_LARGE       (CEA708_FONTRELSIZE_STANDARD * 1.3)
+
+enum cea708_status_e
+{
+    CEA708_STATUS_OK       = 1 << 0,
+    CEA708_STATUS_STARVING = 1 << 1,
+    CEA708_STATUS_OUTPUT   = 1 << 2,
+};
+
+enum cea708_c0_codes
+{
+    CEA708_C0_NUL   = 0x00,
+    CEA708_C0_ETX   = 0x03,
+    CEA708_C0_BS    = 0x08,
+    CEA708_C0_FF    = 0x0C,
+    CEA708_C0_CR    = 0x0D,
+    CEA708_C0_HCR   = 0x0E,
+    CEA708_C0_EXT1  = 0x10,
+    CEA708_C0_P16   = 0x18,
+};
+
+enum cea708_c1_codes
+{
+    CEA708_C1_CW0   = 0x80,
+    CEA708_C1_CW7   = 0x87,
+    CEA708_C1_CLW,
+    CEA708_C1_DSW,
+    CEA708_C1_HDW,
+    CEA708_C1_TGW,
+    CEA708_C1_DLW,
+    CEA708_C1_DLY,
+    CEA708_C1_DLC,
+    CEA708_C1_RST,
+    CEA708_C1_SPA   = 0x90,
+    CEA708_C1_SPC,
+    CEA708_C1_SPL,
+    CEA708_C1_SWA   = 0x97,
+    CEA708_C1_DF0,
+    CEA708_C1_DF7   = 0x9F,
+};
+
+typedef struct
+{
+    uint8_t ringbuffer[CEA708_SERVICE_INPUT_BUFFER];
+    uint8_t start;
+    uint8_t capacity;
+} cea708_input_buffer_t;
+
+static void cea708_input_buffer_init(cea708_input_buffer_t *ib)
+{
+    ib->capacity = 0;
+    ib->start = 0;
+}
+
+static uint8_t cea708_input_buffer_size(const cea708_input_buffer_t *ib)
+{
+    return ib->capacity;
+}
+
+static uint8_t cea708_input_buffer_remain(const cea708_input_buffer_t *ib)
+{
+    return CEA708_SERVICE_INPUT_BUFFER - ib->capacity;
+}
+
+static void cea708_input_buffer_add(cea708_input_buffer_t *ib, uint8_t a)
+{
+    if( cea708_input_buffer_remain(ib) > 0 )
+        ib->ringbuffer[(ib->start + ib->capacity++) % CEA708_SERVICE_INPUT_BUFFER] = a;
+}
+
+static uint8_t cea708_input_buffer_peek(cea708_input_buffer_t *ib, uint8_t off)
+{
+    if(off + 1 > ib->capacity)
+        return 0;
+    off = (ib->start + off) % CEA708_SERVICE_INPUT_BUFFER;
+    return ib->ringbuffer[off];
+}
+
+static uint8_t cea708_input_buffer_get(cea708_input_buffer_t *ib)
+{
+    uint8_t a = cea708_input_buffer_peek( ib, 0 );
+    ib->start = (ib->start + 1) % CEA708_SERVICE_INPUT_BUFFER;
+    ib->capacity--;
+    return a;
+}
+
+enum cea708_opacity_e
+{
+    CEA708_OPACITY_SOLID = 0,
+    CEA708_OPACITY_FLASH,
+    CEA708_OPACITY_TRANSLUCENT,
+    CEA708_OPACITY_TRANSPARENT,
+};
+
+enum cea708_edge_e
+{
+    CEA708_EDGE_NONE =0,
+    CEA708_EDGE_RAISED,
+    CEA708_EDGE_DEPRESSED,
+    CEA708_EDGE_UNIFORM,
+    CEA708_EDGE_LEFT_DROP_SHADOW,
+    CEA708_EDGE_RIGHT_DROP_SHADOW,
+};
+
+typedef struct
+{
+    enum
+    {
+        CEA708_PEN_SIZE_SMALL = 0,
+        CEA708_PEN_SIZE_STANDARD,
+        CEA708_PEN_SIZE_LARGE,
+    } size;
+    enum
+    {
+        CEA708_FONT_UNDEFINED = 0,
+        CEA708_FONT_MONOSPACED,
+        CEA708_FONT_PROP,
+        CEA708_FONT_MONO_SANS_SERIF,
+        CEA708_FONT_PROP_SANS_SERIF,
+        CEA708_FONT_CASUAL,
+        CEA708_FONT_CURSIVE,
+        CEA708_FONT_SMALL_CAPS,
+    } font;
+    enum
+    {
+        CEA708_TAG_DIALOG = 0,
+        CEA708_TAG_SPEAKER,
+        CEA708_TAG_SYNTHETIC_VOICE,
+        CEA708_TAG_DIALOG_SECONDARY_LANG,
+        CEA708_TAG_VOICEOVER,
+        CEA708_TAG_AUDIBLE_TRANSLATION,
+        CEA708_TAG_SUBTITLE_TRANSLATION,
+        CEA708_TAG_VOICE_QUALITY_DESCRIPTION,
+        CEA708_TAG_SONG_LYRICS,
+        CEA708_TAG_FX_DESCRIPTION,
+        CEA708_TAG_SCORE_DESCRIPTION,
+        CEA708_TAG_EXPLETIVE,
+        CEA708_TAG_NOT_TO_BE_DISPLAYED = 15,
+    } text_tag;
+    enum
+    {
+        CEA708_PEN_OFFSET_SUBSCRIPT = 0,
+        CEA708_PEN_OFFSET_NORMAL,
+        CEA708_PEN_OFFSET_SUPERSCRIPT,
+    } offset;
+    bool b_italics;
+    bool b_underline;
+    struct
+    {
+        uint8_t color;
+        enum cea708_opacity_e opacity;
+    } foreground, background;
+    uint8_t edge_color;
+    enum cea708_edge_e edge_type;
+} cea708_pen_style_t;
+
+typedef struct
+{
+    cea708_pen_style_t style;
+    uint8_t row;
+    uint8_t col;
+} cea708_pen_t;
+
+typedef struct
+{
+    enum
+    {
+        CEA708_WA_JUSTIFY_LEFT = 0,
+        CEA708_WA_JUSTIFY_RIGHT,
+        CEA708_WA_JUSTIFY_CENTER,
+        CEA708_WA_JUSTIFY_FULL,
+    } justify;
+    enum
+    {
+        CEA708_WA_DIRECTION_LTR = 0,
+        CEA708_WA_DIRECTION_RTL,
+        CEA708_WA_DIRECTION_TB,
+        CEA708_WA_DIRECTION_BT,
+    } print_direction, scroll_direction, effect_direction;
+    bool b_word_wrap;
+    enum
+    {
+        CEA708_WA_EFFECT_SNAP = 0,
+        CEA708_WA_EFFECT_FADE,
+        CEA708_WA_EFFECT_WIPE,
+    } display_effect;
+    uint8_t effect_speed;
+    uint8_t fill_color_color;
+    enum cea708_opacity_e fill_opacity;
+    enum cea708_edge_e border_type;
+    uint8_t border_color_color;
+} cea708_window_style_t;
+
+typedef struct cea708_text_row_t cea708_text_row_t;
+
+struct cea708_text_row_t
+{
+    uint8_t characters[CEA708_WINDOW_MAX_COLS * 4];
+    cea708_pen_style_t styles[CEA708_WINDOW_MAX_COLS];
+    uint8_t firstcol;
+    uint8_t lastcol;
+};
+
+static void cea708_text_row_Delete( cea708_text_row_t *p_row )
+{
+    free( p_row );
+}
+
+static cea708_text_row_t * cea708_text_row_New( void )
+{
+    cea708_text_row_t *p_row = malloc( sizeof(*p_row) );
+    if( p_row )
+    {
+        p_row->firstcol = CEA708_WINDOW_MAX_COLS;
+        p_row->lastcol = 0;
+        memset(p_row->characters, 0, 4 * CEA708_WINDOW_MAX_COLS);
+    }
+    return p_row;
+}
+
+typedef struct
+{
+    cea708_text_row_t * rows[CEA708_WINDOW_MAX_ROWS];
+    uint8_t i_firstrow;
+    uint8_t i_lastrow;
+
+    uint8_t i_priority;
+
+    enum
+    {
+        CEA708_ANCHOR_TOP_LEFT = 0,
+        CEA708_ANCHOR_TOP_CENTER,
+        CEA708_ANCHOR_TOP_RIGHT,
+        CEA708_ANCHOR_CENTER_LEFT,
+        CEA708_ANCHOR_CENTER_CENTER,
+        CEA708_ANCHOR_CENTER_RIGHT,
+        CEA708_ANCHOR_BOTTOM_LEFT,
+        CEA708_ANCHOR_BOTTOM_CENTER,
+        CEA708_ANCHOR_BOTTOM_RIGHT,
+    } anchor_point;
+    uint8_t i_anchor_offset_v;
+    uint8_t i_anchor_offset_h;
+
+    /* Extras row for window scroll */
+    uint8_t i_row_count;
+    uint8_t i_col_count;
+
+    /* flags */
+    uint8_t b_relative;
+    uint8_t b_row_lock;
+    uint8_t b_column_lock;
+    uint8_t b_visible;
+
+    cea708_window_style_t style;
+    cea708_pen_style_t    pen;
+
+    uint8_t row;
+    uint8_t col;
+
+    bool b_defined;
+
+} cea708_window_t;
+
+struct cea708_t
+{
+    decoder_t *p_dec;
+
+    /* Defaults */
+    cea708_window_t window[CEA708_WINDOWS_COUNT];
+    cea708_input_buffer_t input_buffer;
+
+    /* Decoding context */
+    cea708_window_t *p_cw; /* current window */
+    mtime_t suspended_deadline; /* > 0 when delay is active */
+    mtime_t i_clock;
+    bool b_text_waiting;
+};
+
+static int CEA708_Decode_G0( uint8_t code, cea708_t *p_cea708 );
+static int CEA708_Decode_C0( uint8_t code, cea708_t *p_cea708 );
+static int CEA708_Decode_G1( uint8_t code, cea708_t *p_cea708 );
+static int CEA708_Decode_C1( uint8_t code, cea708_t *p_cea708 );
+static int CEA708_Decode_G2G3( uint8_t code, cea708_t *p_cea708 );
+static int CEA708_Decode_P16( uint16_t ucs2, cea708_t *p_cea708 );
+
+#define DEFAULT_NTSC_STYLE(font, edge, bgopacity ) \
+    {\
+        CEA708_PEN_SIZE_STANDARD,\
+        font,\
+        CEA708_TAG_DIALOG,\
+        CEA708_PEN_OFFSET_NORMAL,\
+        false,\
+        false,\
+        {   0x2A,   CEA708_OPACITY_SOLID,   },\
+        {   0x00,   bgopacity,              },\
+        0x00,\
+        edge,\
+    }
+static const cea708_pen_style_t cea708_default_pen_styles[CEA708_PREDEFINED_STYLES] =
+{
+    DEFAULT_NTSC_STYLE( CEA708_FONT_UNDEFINED,       CEA708_EDGE_NONE,    CEA708_OPACITY_SOLID ),
+    DEFAULT_NTSC_STYLE( CEA708_FONT_MONOSPACED,      CEA708_EDGE_NONE,    CEA708_OPACITY_SOLID ),
+    DEFAULT_NTSC_STYLE( CEA708_FONT_PROP,            CEA708_EDGE_NONE,    CEA708_OPACITY_SOLID ),
+    DEFAULT_NTSC_STYLE( CEA708_FONT_MONO_SANS_SERIF, CEA708_EDGE_NONE,    CEA708_OPACITY_SOLID ),
+    DEFAULT_NTSC_STYLE( CEA708_FONT_PROP_SANS_SERIF, CEA708_EDGE_NONE,    CEA708_OPACITY_SOLID ),
+    DEFAULT_NTSC_STYLE( CEA708_FONT_MONO_SANS_SERIF, CEA708_EDGE_UNIFORM, CEA708_OPACITY_TRANSPARENT ),
+    DEFAULT_NTSC_STYLE( CEA708_FONT_PROP_SANS_SERIF, CEA708_EDGE_UNIFORM, CEA708_OPACITY_TRANSPARENT ),
+};
+#undef DEFAULT_NTSC_STYLE
+
+#define DEFAULT_NTSC_WA_STYLE(just, pd, scroll, wrap, opacity) \
+    {\
+        just,\
+        pd,\
+        scroll,\
+        CEA708_WA_DIRECTION_LTR,\
+        wrap,\
+        CEA708_WA_EFFECT_SNAP,\
+        1,\
+        0x00,\
+        opacity,\
+        CEA708_EDGE_NONE,\
+        0x00,\
+    }
+static const cea708_window_style_t cea708_default_window_styles[CEA708_PREDEFINED_STYLES] =
+{
+    DEFAULT_NTSC_WA_STYLE(CEA708_WA_JUSTIFY_LEFT,   CEA708_WA_DIRECTION_LTR,
+                          CEA708_WA_DIRECTION_BT,   false, CEA708_OPACITY_SOLID),
+    DEFAULT_NTSC_WA_STYLE(CEA708_WA_JUSTIFY_LEFT,   CEA708_WA_DIRECTION_LTR,
+                          CEA708_WA_DIRECTION_BT,   false, CEA708_OPACITY_TRANSPARENT),
+    DEFAULT_NTSC_WA_STYLE(CEA708_WA_JUSTIFY_CENTER, CEA708_WA_DIRECTION_LTR,
+                          CEA708_WA_DIRECTION_BT,   false, CEA708_OPACITY_SOLID),
+    DEFAULT_NTSC_WA_STYLE(CEA708_WA_JUSTIFY_LEFT,   CEA708_WA_DIRECTION_LTR,
+                          CEA708_WA_DIRECTION_BT,   true,  CEA708_OPACITY_SOLID),
+    DEFAULT_NTSC_WA_STYLE(CEA708_WA_JUSTIFY_LEFT,   CEA708_WA_DIRECTION_LTR,
+                          CEA708_WA_DIRECTION_BT,   true,  CEA708_OPACITY_TRANSPARENT),
+    DEFAULT_NTSC_WA_STYLE(CEA708_WA_JUSTIFY_CENTER, CEA708_WA_DIRECTION_LTR,
+                          CEA708_WA_DIRECTION_BT,   true, CEA708_OPACITY_SOLID),
+    DEFAULT_NTSC_WA_STYLE(CEA708_WA_JUSTIFY_LEFT,   CEA708_WA_DIRECTION_TB,
+                          CEA708_WA_DIRECTION_RTL,  false, CEA708_OPACITY_SOLID),
+};
+#undef DEFAULT_NTSC_WA_STYLE
+
+static void CEA708_Window_Init( cea708_window_t *p_w )
+{
+    memset( p_w, 0, sizeof(*p_w) );
+    p_w->style = cea708_default_window_styles[0];
+    p_w->pen = cea708_default_pen_styles[0];
+    p_w->i_firstrow = CEA708_WINDOW_MAX_ROWS;
+    p_w->b_row_lock = true;
+    p_w->b_column_lock = true;
+}
+
+static void CEA708_Window_ClearText( cea708_window_t *p_w )
+{
+    for( uint8_t i=p_w->i_firstrow; i<=p_w->i_lastrow; i++ )
+    {
+        cea708_text_row_Delete( p_w->rows[i] );
+        p_w->rows[i] = NULL;
+    }
+    p_w->i_lastrow = 0;
+    p_w->i_firstrow = CEA708_WINDOW_MAX_ROWS;
+}
+
+static void CEA708_Window_Reset( cea708_window_t *p_w )
+{
+    CEA708_Window_ClearText( p_w );
+    CEA708_Window_Init( p_w );
+}
+
+static bool CEA708_Window_BreaksSpace( const cea708_window_t *p_w )
+{
+    return true;
+    if( p_w->style.print_direction == CEA708_WA_DIRECTION_LTR &&
+        p_w->style.justify == CEA708_WA_JUSTIFY_LEFT )
+        return true;
+
+    if( p_w->style.print_direction == CEA708_WA_DIRECTION_RTL &&
+        p_w->style.justify == CEA708_WA_JUSTIFY_RIGHT )
+        return true;
+
+    return false;
+}
+
+static uint8_t CEA708_Window_ColCount( const cea708_window_t *p_w )
+{
+    const cea708_text_row_t *p_row = p_w->rows[p_w->row];
+    if( !p_row || p_row->firstcol > p_row->lastcol )
+        return 0;
+    return 1 + p_row->lastcol - p_row->firstcol;
+}
+
+static uint8_t CEA708_Window_RowCount( const cea708_window_t *p_w )
+{
+    if( p_w->i_firstrow > p_w->i_lastrow )
+        return 0;
+    return 1 + p_w->i_lastrow - p_w->i_firstrow;
+}
+
+static void CEA708_Window_Truncate( cea708_window_t *p_w, int i_direction )
+{
+    switch( i_direction )
+    {
+        case CEA708_WA_DIRECTION_LTR:
+            break;
+        case CEA708_WA_DIRECTION_RTL:
+            break;
+        case CEA708_WA_DIRECTION_TB: /* Deletes LAST row */
+            if( CEA708_Window_RowCount( p_w ) > 0 )
+            {
+                cea708_text_row_Delete( p_w->rows[p_w->i_lastrow] );
+                p_w->rows[p_w->i_lastrow--] = NULL;
+            }
+            break;
+        case CEA708_WA_DIRECTION_BT: /* Deletes First row */
+            if( CEA708_Window_RowCount( p_w ) > 0 )
+            {
+                cea708_text_row_Delete( p_w->rows[p_w->i_firstrow] );
+                p_w->rows[p_w->i_firstrow++] = NULL;
+            }
+            break;
+    }
+}
+
+static void CEA708_Window_Scroll( cea708_window_t *p_w )
+{
+    if( CEA708_Window_RowCount( p_w ) == 0 )
+        return;
+    Debug(printf("ROLL ? UP %d %d\n", p_w->i_firstrow, p_w->i_lastrow ) );
+    switch( p_w->style.scroll_direction )
+    {
+        case CEA708_WA_DIRECTION_LTR:
+            break;
+        case CEA708_WA_DIRECTION_RTL:
+            break;
+        case CEA708_WA_DIRECTION_TB:
+            /* Move DOWN */
+            if( p_w->i_firstrow == CEA708_WINDOW_MAX_ROWS - 1 )
+                CEA708_Window_Truncate( p_w, CEA708_WA_DIRECTION_TB );
+            for( int i=p_w->i_lastrow; i > p_w->i_firstrow; i-- )
+                p_w->rows[i+1] = p_w->rows[i];
+            p_w->rows[p_w->i_firstrow] = NULL;
+            p_w->i_firstrow++;
+            p_w->i_lastrow++;
+            break;
+        case CEA708_WA_DIRECTION_BT:
+            /* Move UP */
+            if( p_w->i_firstrow == 0 )
+                CEA708_Window_Truncate( p_w, CEA708_WA_DIRECTION_BT );
+            for( int i=p_w->i_firstrow; i <= p_w->i_lastrow; i++ )
+                p_w->rows[i-1] = p_w->rows[i];
+            p_w->rows[p_w->i_lastrow] = NULL;
+            p_w->i_firstrow--;
+            p_w->i_lastrow--;
+            break;
+    }
+}
+
+static void CEA708_Window_CarriageReturn( cea708_window_t *p_w )
+{
+    switch( p_w->style.scroll_direction )
+    {
+        case CEA708_WA_DIRECTION_LTR:
+            if( p_w->col > 0 &&
+                CEA708_Window_ColCount( p_w ) < p_w->i_col_count )
+                p_w->col--;
+            else
+                CEA708_Window_Scroll( p_w );
+            p_w->row = (p_w->style.print_direction == CEA708_WA_DIRECTION_TB) ?
+                       0 : CEA708_WINDOW_MAX_ROWS - 1;
+            break;
+        case CEA708_WA_DIRECTION_RTL:
+            if( p_w->col + 1 < CEA708_WINDOW_MAX_COLS &&
+                CEA708_Window_ColCount( p_w ) < p_w->i_col_count )
+                p_w->col++;
+            else
+                CEA708_Window_Scroll( p_w );
+            p_w->row = (p_w->style.print_direction == CEA708_WA_DIRECTION_TB) ?
+                       0 : CEA708_WINDOW_MAX_ROWS - 1;
+            break;
+        case CEA708_WA_DIRECTION_TB:
+            if( p_w->row > 0 &&
+                CEA708_Window_RowCount( p_w ) < p_w->i_row_count )
+                p_w->row--;
+            else
+                CEA708_Window_Scroll( p_w );
+            p_w->col = (p_w->style.print_direction == CEA708_WA_DIRECTION_LTR) ?
+                       0 : CEA708_WINDOW_MAX_COLS - 1;
+            break;
+        case CEA708_WA_DIRECTION_BT:
+        printf("SCROLL ROW COUNT %d %d\n", p_w->row, CEA708_Window_RowCount( p_w ));
+            if( p_w->row + 1 < CEA708_WINDOW_MAX_ROWS &&
+                CEA708_Window_RowCount( p_w ) < p_w->i_row_count )
+                p_w->row++;
+            else
+                CEA708_Window_Scroll( p_w );
+            p_w->col = (p_w->style.print_direction == CEA708_WA_DIRECTION_LTR) ?
+                       0 : CEA708_WINDOW_MAX_COLS - 1;
+            break;
+    }
+}
+
+static void CEA708_Window_Forward( cea708_window_t *p_w )
+{
+    switch( p_w->style.print_direction )
+    {
+        case CEA708_WA_DIRECTION_LTR:
+            if( p_w->col + 1 < CEA708_WINDOW_MAX_COLS )
+                p_w->col++;
+            else
+                CEA708_Window_CarriageReturn( p_w );
+            break;
+        case CEA708_WA_DIRECTION_RTL:
+            if( p_w->col > 0 )
+                p_w->col--;
+            else
+                CEA708_Window_CarriageReturn( p_w );
+            break;
+        case CEA708_WA_DIRECTION_TB:
+            if( p_w->row + 1 < CEA708_WINDOW_MAX_ROWS )
+                p_w->row++;
+            else
+                CEA708_Window_CarriageReturn( p_w );
+            break;
+        case CEA708_WA_DIRECTION_BT:
+            if( p_w->row > 0 )
+                p_w->row--;
+            else
+                CEA708_Window_CarriageReturn( p_w );
+            break;
+    }
+}
+
+static void CEA708_Window_Backward( cea708_window_t *p_w )
+{
+    static const int const reverse[] =
+    {
+        [CEA708_WA_DIRECTION_LTR] = CEA708_WA_DIRECTION_RTL,
+        [CEA708_WA_DIRECTION_RTL] = CEA708_WA_DIRECTION_LTR,
+        [CEA708_WA_DIRECTION_TB]  = CEA708_WA_DIRECTION_BT,
+        [CEA708_WA_DIRECTION_BT]  = CEA708_WA_DIRECTION_TB,
+    };
+    int save = p_w->style.print_direction;
+    p_w->style.print_direction = reverse[p_w->style.print_direction];
+    CEA708_Window_Forward( p_w );
+    p_w->style.print_direction = save;
+}
+
+static void CEA708_Window_Write( const uint8_t c[4], cea708_window_t *p_w )
+{
+    if( !p_w->b_defined )
+        return;
+
+    assert( p_w->row < CEA708_WINDOW_MAX_ROWS );
+    assert( p_w->col < CEA708_WINDOW_MAX_COLS );
+    cea708_text_row_t *p_row = p_w->rows[p_w->row];
+    if( !p_row )
+    {
+        p_w->rows[p_w->row] = p_row = cea708_text_row_New();
+        if( !p_row )
+            return;
+        if( p_w->row < p_w->i_firstrow )
+            p_w->i_firstrow = p_w->row;
+        if( p_w->row > p_w->i_lastrow )
+            p_w->i_lastrow = p_w->row;
+    }
+
+    memcpy( &p_row->characters[p_w->col * 4U], c, 4 );
+    p_row->styles[p_w->col] = p_w->pen;
+    if( p_w->col < p_row->firstcol )
+        p_row->firstcol = p_w->col;
+    if( p_w->col > p_row->lastcol )
+        p_row->lastcol = p_w->col;
+
+    CEA708_Window_Forward( p_w );
+
+    Debug(printf("\033[0;33m%s\033[0m", c));
+}
+
+static uint32_t CEA708ColorConvert( uint8_t c )
+{
+    const uint32_t value[4] = {0x00,0x3F,0xF0,0xFF};
+    c = c & 0x3F;
+    return (value[(c >> 4) & 0x03] << 16) |
+           (value[(c >> 2) & 0x03] << 8) |
+           value[c & 0x03];
+}
+
+static uint8_t CEA708AlphaConvert( uint8_t c )
+{
+    if( c == CEA708_OPACITY_TRANSLUCENT )
+        return STYLE_ALPHA_OPAQUE / 2;
+    else if( c == CEA708_OPACITY_TRANSPARENT )
+        return STYLE_ALPHA_TRANSPARENT;
+    else
+        return STYLE_ALPHA_OPAQUE;
+}
+
+static void CEA708PenStyleToSegment( const cea708_pen_style_t *ps, text_style_t *s )
+{
+    if( ps->background.opacity != CEA708_OPACITY_TRANSPARENT )
+    {
+        s->i_background_alpha = CEA708AlphaConvert( ps->background.opacity );
+        s->i_style_flags |= STYLE_BACKGROUND;
+        s->i_background_color = CEA708ColorConvert( ps->background.color );
+        s->i_features |= STYLE_HAS_BACKGROUND_COLOR|STYLE_HAS_BACKGROUND_ALPHA;
+    }
+    s->i_font_color = CEA708ColorConvert( ps->foreground.color );
+    s->i_font_alpha = CEA708AlphaConvert( ps->foreground.opacity );
+    s->i_features |= STYLE_HAS_FONT_ALPHA|STYLE_HAS_FONT_COLOR;
+
+    if( ps->b_italics )
+        s->i_style_flags |= STYLE_ITALIC;
+    if( ps->b_underline )
+        s->i_style_flags |= STYLE_UNDERLINE;
+
+    switch( ps->font )
+    {
+        default:
+        case CEA708_FONT_UNDEFINED:
+        case CEA708_FONT_MONOSPACED:
+        case CEA708_FONT_MONO_SANS_SERIF:
+            s->i_style_flags |= STYLE_MONOSPACED;
+            break;
+        case CEA708_FONT_PROP:
+        case CEA708_FONT_PROP_SANS_SERIF:
+        case CEA708_FONT_CASUAL:
+        case CEA708_FONT_CURSIVE:
+        case CEA708_FONT_SMALL_CAPS:
+            break;
+    }
+
+    switch( ps->size )
+    {
+        case CEA708_PEN_SIZE_SMALL:
+            s->f_font_relsize = CEA708_FONTRELSIZE_SMALL;
+            break;
+        case CEA708_PEN_SIZE_LARGE:
+            s->f_font_relsize = CEA708_FONTRELSIZE_LARGE;
+            break;
+        default:
+            s->f_font_relsize = CEA708_FONTRELSIZE_STANDARD;
+            break;
+    }
+}
+
+static text_segment_t * CEA708CharsToSegment( const cea708_text_row_t *p_row,
+                                              uint8_t i_start, uint8_t i_end,
+                                              bool b_newline )
+{
+    text_segment_t *p_segment = text_segment_New( NULL );
+    if( !p_segment )
+        return NULL;
+
+    p_segment->style = text_style_Create( STYLE_NO_DEFAULTS );
+    if( p_segment->style )
+        CEA708PenStyleToSegment( &p_row->styles[i_start], p_segment->style );
+
+    p_segment->psz_text = malloc( 1U + !!b_newline + (i_end - i_start + 1) * 4U );
+    if( !p_segment->psz_text )
+    {
+        text_segment_Delete( p_segment );
+        return NULL;
+    }
+
+    size_t offsetw = 0;
+    for( uint8_t i=i_start; i<=i_end; i++ )
+    {
+        for( size_t j=0; j<4; j++ )
+        {
+            if( p_row->characters[i * 4 + j] != 0 )
+                p_segment->psz_text[offsetw++] = p_row->characters[i * 4 + j];
+            else if( j == 0 )
+                p_segment->psz_text[offsetw++] = ' ';
+            else
+                break;
+        }
+    }
+
+    if( b_newline )
+        p_segment->psz_text[offsetw++] = '\n';
+    p_segment->psz_text[offsetw] = '\0';
+
+    return p_segment;
+}
+
+static text_segment_t * CEA708RowToSegments( const cea708_text_row_t *p_row,
+                                             bool b_addnewline )
+{
+    text_segment_t *p_segments = NULL;
+    text_segment_t **pp_last = &p_segments;
+
+    uint8_t i_start = p_row->firstcol;
+    for( uint8_t i=i_start; i<=p_row->lastcol; i++ )
+    {
+        if( i == p_row->lastcol ||
+            memcmp( &p_row->styles[i], &p_row->styles[i+1], sizeof(cea708_pen_style_t) ) )
+        {
+            *pp_last = CEA708CharsToSegment( p_row, i_start, i,
+                                             b_addnewline && (i == p_row->lastcol) );
+            if( *pp_last )
+                pp_last  = &((*pp_last)->p_next);
+            i_start = i+1;
+        }
+    }
+
+    return p_segments;
+}
+
+static void CEA708SpuConvert( const cea708_window_t *p_w,
+                              subpicture_updater_sys_region_t *p_region )
+{
+    if( !p_w->b_visible || CEA708_Window_RowCount( p_w ) == 0 )
+        return;
+
+    if( p_region == NULL && !(p_region = SubpictureUpdaterSysRegionNew()) )
+        return;
+
+    text_segment_t **pp_last = &p_region->p_segments;
+    for( uint8_t i=p_w->i_firstrow; i<=p_w->i_lastrow; i++ )
+    {
+        if( !p_w->rows[i] )
+            continue;
+
+        *pp_last = CEA708RowToSegments( p_w->rows[i], i < p_w->i_lastrow );
+        if( *pp_last )
+            pp_last  = &((*pp_last)->p_next);
+    }
+
+    if( p_w->anchor_point <= CEA708_ANCHOR_BOTTOM_RIGHT )
+    {
+        static const int vlc_subpicture_aligns[] =
+        {
+            [CEA708_ANCHOR_TOP_LEFT]        = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT,
+            [CEA708_ANCHOR_TOP_CENTER]      = SUBPICTURE_ALIGN_TOP,
+            [CEA708_ANCHOR_TOP_RIGHT]       = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_RIGHT,
+            [CEA708_ANCHOR_CENTER_LEFT]     = SUBPICTURE_ALIGN_LEFT,
+            [CEA708_ANCHOR_CENTER_CENTER]   = 0,
+            [CEA708_ANCHOR_CENTER_RIGHT]    = SUBPICTURE_ALIGN_RIGHT,
+            [CEA708_ANCHOR_BOTTOM_LEFT]     = SUBPICTURE_ALIGN_BOTTOM|SUBPICTURE_ALIGN_LEFT,
+            [CEA708_ANCHOR_BOTTOM_CENTER]   = SUBPICTURE_ALIGN_BOTTOM,
+            [CEA708_ANCHOR_BOTTOM_RIGHT]    = SUBPICTURE_ALIGN_BOTTOM|SUBPICTURE_ALIGN_RIGHT,
+        };
+        p_region->align = vlc_subpicture_aligns[p_w->anchor_point];
+        p_region->align |= SUBPICTURE_ALIGN_LEAVETEXT;
+    }
+}
+
+static subpicture_t *CEA708_BuildSubtitle( cea708_t *p_cea708 )
+{
+    subpicture_t *p_spu = decoder_NewSubpictureText( p_cea708->p_dec );
+    if( !p_spu )
+        return NULL;
+
+    subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
+    subpicture_updater_sys_region_t *p_region = &p_spu_sys->region;
+
+    for(size_t i=0; i<CEA708_WINDOWS_COUNT; i++)
+    {
+        cea708_window_t *p_w = &p_cea708->window[i];
+        if( p_w->b_defined && p_w->b_visible && CEA708_Window_RowCount( p_w ) )
+        {
+            if( p_region != &p_spu_sys->region )
+            {
+                subpicture_updater_sys_region_t *p_newregion =
+                        SubpictureUpdaterSysRegionNew();
+                if( p_newregion == NULL )
+                    break;
+                SubpictureUpdaterSysRegionAdd( p_region, p_newregion );
+                p_region = p_newregion;
+            }
+            /* Fill region */
+            CEA708SpuConvert( p_w, p_region );
+        }
+    }
+
+    p_spu->i_start    = p_cea708->i_clock;
+    p_spu->i_stop     = p_cea708->i_clock + 10000000;   /* 10s max */
+
+    p_spu->b_ephemer  = true;
+    p_spu->b_absolute = false;
+
+    return p_spu;
+}
+
+static void CEA708_Decoder_Init( cea708_t *p_cea708 )
+{
+    cea708_input_buffer_init( &p_cea708->input_buffer );
+    for(size_t i=0; i<CEA708_WINDOWS_COUNT; i++)
+        CEA708_Window_Init( &p_cea708->window[i] );
+    p_cea708->p_cw = &p_cea708->window[0];
+    p_cea708->suspended_deadline = 0;
+    p_cea708->b_text_waiting = false;
+    p_cea708->i_clock = 0;
+}
+
+static void CEA708_Decoder_Reset( cea708_t *p_cea708 )
+{
+    for(size_t i=0; i<CEA708_WINDOWS_COUNT; i++)
+        CEA708_Window_Reset( &p_cea708->window[i] );
+    CEA708_Decoder_Init( p_cea708 );
+}
+
+void CEA708_Decoder_Flush( cea708_t *p_cea708 )
+{
+    CEA708_Decoder_Reset( p_cea708 );
+}
+
+void CEA708_Decoder_Release( cea708_t *p_cea708 )
+{
+    CEA708_Decoder_Reset( p_cea708 );
+    free( p_cea708 );
+}
+
+cea708_t * CEA708_Decoder_New( decoder_t *p_dec )
+{
+    cea708_t *p_cea708 = malloc( sizeof(cea708_t) );
+    if( p_cea708 )
+    {
+        CEA708_Decoder_Init( p_cea708 );
+        p_cea708->p_dec = p_dec;
+    }
+    return p_cea708;
+}
+
+#define POP_COMMAND() (void) cea708_input_buffer_get( ib )
+#define POP_ARGS(n) for(size_t i=0; i<n;i++) POP_COMMAND()
+#define REQUIRE_ARGS(n) if(cea708_input_buffer_size( ib ) < n + 1)\
+                            return CEA708_STATUS_STARVING
+#define REQUIRE_ARGS_AND_POP_COMMAND(n) REQUIRE_ARGS(n); else POP_COMMAND()
+
+static void CEA708_Output( cea708_t *p_cea708 )
+{
+    Debug(printf("@%ld ms\n", p_cea708->i_clock / 1000));
+    subpicture_t *p_spu = CEA708_BuildSubtitle( p_cea708 );
+    if( p_spu )
+        decoder_QueueSub( p_cea708->p_dec, p_spu );
+}
+
+static int CEA708_Decode_C0( uint8_t code, cea708_t *p_cea708 )
+{
+    uint8_t v, i;
+    uint16_t u16;
+    cea708_input_buffer_t *ib = &p_cea708->input_buffer;
+    int i_ret = CEA708_STATUS_OK;
+
+    switch( code )
+    {
+        case CEA708_C0_NUL:
+            POP_COMMAND();
+            break;
+        case CEA708_C0_ETX:
+            POP_COMMAND();
+            if( p_cea708->b_text_waiting )
+            {
+                i_ret |= CEA708_STATUS_OUTPUT;
+                p_cea708->b_text_waiting = false;
+            }
+            break;
+        case CEA708_C0_BS:
+            POP_COMMAND();
+            if( !p_cea708->p_cw->b_defined )
+                break;
+            CEA708_Window_Backward( p_cea708->p_cw );
+            p_cea708->b_text_waiting = true;
+            break;
+        case CEA708_C0_FF:
+            POP_COMMAND();
+            if( !p_cea708->p_cw->b_defined )
+                break;
+            CEA708_Window_ClearText( p_cea708->p_cw );
+            p_cea708->p_cw->col = 0;
+            p_cea708->p_cw->row = 0;
+            p_cea708->b_text_waiting = true;
+            break;
+        case CEA708_C0_CR:
+            POP_COMMAND();
+            if( !p_cea708->p_cw->b_defined )
+                break;
+            if( p_cea708->p_cw->style.print_direction <= CEA708_WA_DIRECTION_RTL )
+            {
+                CEA708_Window_CarriageReturn( p_cea708->p_cw );
+                if( p_cea708->p_cw->b_visible )
+                    i_ret |= CEA708_STATUS_OUTPUT;
+            }
+            break;
+        case CEA708_C0_HCR:
+            POP_COMMAND();
+            if( !p_cea708->p_cw->b_defined )
+                break;
+            if( p_cea708->p_cw->style.print_direction > CEA708_WA_DIRECTION_RTL )
+            {
+                CEA708_Window_CarriageReturn( p_cea708->p_cw );
+                if( p_cea708->p_cw->b_visible )
+                    i_ret |= CEA708_STATUS_OUTPUT;
+            }
+            break;
+        case CEA708_C0_EXT1: /* Special extended table case */
+            if( cea708_input_buffer_size( ib ) >= 2 )
+            {
+                v = cea708_input_buffer_peek( ib, 1 );
+                /* C2 extended code set */
+                if( v < 0x20 )
+                {
+                    if( v > 0x17 )
+                        i = 3;
+                    else if( v > 0x0f )
+                        i = 2;
+                    else if( v > 0x07 )
+                        i = 1;
+                    else
+                        i = 0;
+                    if( cea708_input_buffer_size( ib ) < 2 + i )
+                        return CEA708_STATUS_STARVING;
+                    POP_COMMAND();
+                    POP_ARGS(1 + i);
+                }
+                /* C3 extended code set */
+                else if( v > 0x7f && v < 0xa0 )
+                {
+                    if( v > 0x87 )
+                        i = 5;
+                    else
+                        i = 4;
+                    if( cea708_input_buffer_size( ib ) < 2 + i )
+                        return CEA708_STATUS_STARVING;
+                    POP_COMMAND();
+                    POP_ARGS(1 + i);
+                }
+                else
+                {
+                    POP_COMMAND();
+                    v = cea708_input_buffer_get( ib );
+                    if( p_cea708->p_cw->b_defined )
+                        i_ret |= CEA708_Decode_G2G3( v, p_cea708 );
+                }
+            }
+            else return CEA708_STATUS_STARVING;
+            break;
+        case CEA708_C0_P16:
+            REQUIRE_ARGS_AND_POP_COMMAND(2);
+            u16 = cea708_input_buffer_get( ib ) << 8;
+            u16 |= cea708_input_buffer_get( ib );
+            i_ret |= CEA708_Decode_P16( u16, p_cea708 );
+            Debug(printf("[P16 %x]", u16));
+            break;
+        default:
+            POP_COMMAND();
+            Debug(printf("[UNK %2.2x]", code));
+            break;
+    }
+    Debug(printf("[C0 %x]", code));
+    return i_ret;
+}
+
+static int CEA708_Decode_G0( uint8_t code, cea708_t *p_cea708 )
+{
+    cea708_input_buffer_t *ib = &p_cea708->input_buffer;
+    POP_COMMAND();
+    int i_ret = CEA708_STATUS_OK;
+
+    if( !p_cea708->p_cw->b_defined )
+        return i_ret;
+
+    uint8_t utf8[4] = {code,0x00,0x00,0x00};
+
+    if(code == 0x7F) // Music note
+    {
+        utf8[0] = 0xe2;
+        utf8[1] = 0x99;
+        utf8[2] = 0xaa;
+    }
+
+    CEA708_Window_Write( utf8, p_cea708->p_cw );
+
+    if( code == 0x20 &&
+        p_cea708->b_text_waiting &&
+        CEA708_Window_BreaksSpace( p_cea708->p_cw ) )
+    {
+        i_ret |= CEA708_STATUS_OUTPUT;
+    }
+
+
+    p_cea708->b_text_waiting |= p_cea708->p_cw->b_visible;
+
+    return i_ret;
+}
+
+static int CEA708_Decode_C1( uint8_t code, cea708_t *p_cea708 )
+{
+    uint8_t v, i;
+    cea708_input_buffer_t *ib = &p_cea708->input_buffer;
+    int i_ret = CEA708_STATUS_OK;
+
+    if( p_cea708->b_text_waiting )
+    {
+        i_ret |= CEA708_STATUS_OUTPUT;
+        p_cea708->b_text_waiting = false;
+    }
+
+    switch( code )
+    {
+        case CEA708_C1_CLW:
+            REQUIRE_ARGS_AND_POP_COMMAND(1);
+            Debug(printf("[CLW]"));
+            if( p_cea708->p_cw->b_defined )
+            {
+                CEA708_Window_ClearText( p_cea708->p_cw );
+                if( p_cea708->p_cw->b_visible )
+                    i_ret |= CEA708_STATUS_OUTPUT;
+            }
+            break;
+        case CEA708_C1_DSW:
+            REQUIRE_ARGS_AND_POP_COMMAND(1);
+            Debug(printf("[DSW"));
+            for( i = 0, v = cea708_input_buffer_get( ib ); v; v = v >> 1, i++ )
+                if( v & 1 )
+                {
+                    if( p_cea708->window[i].b_defined )
+                    {
+                        if( !p_cea708->window[i].b_visible )
+                            i_ret |= CEA708_STATUS_OUTPUT;
+                        p_cea708->window[i].b_visible = true;
+                    }
+                    Debug(printf("%d", i));
+                }
+            Debug(printf("]"));
+            break;
+        case CEA708_C1_HDW:
+            REQUIRE_ARGS_AND_POP_COMMAND(1);
+            Debug(printf("[HDW"));
+            for( i = 0, v = cea708_input_buffer_get( ib ); v; v = v >> 1, i++ )
+                if( v & 1 )
+                {
+                    if( p_cea708->window[i].b_defined )
+                    {
+                        if( p_cea708->window[i].b_visible )
+                            i_ret |= CEA708_STATUS_OUTPUT;
+                        p_cea708->window[i].b_visible = false;
+                    }
+                    Debug(printf("%d", i));
+                }
+            Debug(printf("]"));
+            break;
+        case CEA708_C1_TGW:
+            REQUIRE_ARGS_AND_POP_COMMAND(1);
+            Debug(printf("[TGW"));
+            for( i = 0, v = cea708_input_buffer_get( ib ); v; v = v >> 1, i++ )
+                if( v & 1 )
+                {
+                    if( p_cea708->window[i].b_defined )
+                    {
+                        i_ret |= CEA708_STATUS_OUTPUT;
+                        p_cea708->window[i].b_visible = !p_cea708->window[i].b_visible;
+                    }
+                    Debug(printf("%d", i));
+                }
+            Debug(printf("]"));
+            break;
+        case CEA708_C1_DLW:
+            REQUIRE_ARGS_AND_POP_COMMAND(1);
+            Debug(printf("[DLW"));
+            for( i = 0, v = cea708_input_buffer_get( ib ); v; v = v >> 1, i++ )
+                if( v & 1 )
+                {
+                    if( p_cea708->window[i].b_defined )
+                    {
+                        if( p_cea708->window[i].b_visible )
+                            i_ret |= CEA708_STATUS_OUTPUT;
+                        CEA708_Window_Reset( &p_cea708->window[i] );
+                    }
+                    Debug(printf("%d", i));
+                }
+            Debug(printf("]"));
+            break;
+        case CEA708_C1_DLY:
+            REQUIRE_ARGS_AND_POP_COMMAND(1);
+            p_cea708->suspended_deadline = p_cea708->i_clock +
+                    cea708_input_buffer_get( ib ) * 100 * 1000;
+            Debug(printf("[DLY]"));
+            break;
+        case CEA708_C1_DLC:
+            POP_COMMAND();
+            p_cea708->suspended_deadline = 0;
+            Debug(printf("[DLC]"));
+            break;
+        case CEA708_C1_RST:
+            POP_COMMAND();
+            i_ret |= CEA708_STATUS_OUTPUT;
+            /* FIXME */
+            break;
+        case CEA708_C1_SPA:
+            REQUIRE_ARGS_AND_POP_COMMAND(2);
+            if( !p_cea708->p_cw->b_defined )
+            {
+                POP_ARGS(2);
+                break;
+            }
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->pen.text_tag = v >> 4;
+            p_cea708->p_cw->pen.offset = (v >> 2) & 0x03;
+            p_cea708->p_cw->pen.size = v & 0x03;
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->pen.b_italics = v & 0x80;
+            p_cea708->p_cw->pen.b_underline = v & 0x40;
+            p_cea708->p_cw->pen.edge_type = (v >> 3) & 0x07;
+            p_cea708->p_cw->pen.font = v & 0x07;
+            Debug(printf("[SPA]"));
+            break;
+        case CEA708_C1_SPC:
+            REQUIRE_ARGS_AND_POP_COMMAND(3);
+            if( !p_cea708->p_cw->b_defined )
+            {
+                POP_ARGS(3);
+                break;
+            }
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->pen.foreground.opacity = v >> 6;
+            p_cea708->p_cw->pen.foreground.color = v & 0x3F;
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->pen.background.opacity = v >> 6;
+            p_cea708->p_cw->pen.background.color = v & 0x3F;
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->pen.edge_color = v & 0x3F;
+            Debug(printf("[SPC]"));
+            break;
+        case CEA708_C1_SPL:
+            REQUIRE_ARGS_AND_POP_COMMAND(2);
+            if( !p_cea708->p_cw->b_defined )
+            {
+                POP_ARGS(2);
+                break;
+            }
+            v = cea708_input_buffer_get( ib );/* FIXME: MAX ? */
+            p_cea708->p_cw->row = (v & 0x0F) % CEA708_WINDOW_MAX_ROWS;
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->col = (v & 0x3F) % CEA708_WINDOW_MAX_COLS;
+            Debug(printf("[SPL r%d c%d]", p_cea708->p_cw->row, p_cea708->p_cw->col));
+            break;
+        case CEA708_C1_SWA:
+            REQUIRE_ARGS_AND_POP_COMMAND(4);
+            if( !p_cea708->p_cw->b_defined )
+            {
+                POP_ARGS(4);
+                break;
+            }
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->style.fill_opacity = v >> 6;
+            p_cea708->p_cw->style.fill_color_color = v & 0x3F;
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->style.border_color_color = v & 0x3F;
+            p_cea708->p_cw->style.border_type = v >> 6;
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->style.border_type |= ((v & 0x80) >> 5);
+            p_cea708->p_cw->style.b_word_wrap = v & 0x40;
+            p_cea708->p_cw->style.print_direction = (v >> 4) & 0x03;
+            p_cea708->p_cw->style.scroll_direction = (v >> 2) & 0x03;
+            p_cea708->p_cw->style.justify = v & 0x03;
+            v = cea708_input_buffer_get( ib );
+            p_cea708->p_cw->style.effect_speed = v >> 4;
+            p_cea708->p_cw->style.effect_direction = (v >> 2) & 0x03;
+            p_cea708->p_cw->style.display_effect = v & 0x03;
+            Debug(printf("[SWA]"));
+            break;
+
+        default:
+            if( code >= CEA708_C1_CW0 && code <= CEA708_C1_CW7 )
+            {
+                POP_COMMAND();
+                Debug(printf("[CW%d]", code - CEA708_C1_CW0));
+                if( p_cea708->window[code - CEA708_C1_CW0].b_defined )
+                    p_cea708->p_cw = &p_cea708->window[code - CEA708_C1_CW0];
+            }
+            else if( code >= CEA708_C1_DF0 && code <= CEA708_C1_DF7 )
+            {
+                REQUIRE_ARGS_AND_POP_COMMAND(6);
+                Debug(printf("[DF%d]", code - CEA708_C1_DF0));
+                /* also sets current window */
+                p_cea708->p_cw = &p_cea708->window[code - CEA708_C1_DF0];
+                v = cea708_input_buffer_get( ib );
+                if( p_cea708->p_cw->b_defined &&
+                   !p_cea708->p_cw->b_visible != !(v & 0x20) )
+                    i_ret |= CEA708_STATUS_OUTPUT;
+                p_cea708->p_cw->b_visible = v & 0x20;
+                Debug(printf("vis %d\n", p_cea708->p_cw->b_visible));
+                p_cea708->p_cw->b_row_lock = v & 0x10;
+                p_cea708->p_cw->b_column_lock = v & 0x08;
+                p_cea708->p_cw->i_priority = v & 0x07;
+                v = cea708_input_buffer_get( ib );
+                p_cea708->p_cw->b_relative = v & 0x80;
+                p_cea708->p_cw->i_anchor_offset_v = v & 0x7F;
+                v = cea708_input_buffer_get( ib );
+                p_cea708->p_cw->i_anchor_offset_h = v;
+                v = cea708_input_buffer_get( ib );
+                p_cea708->p_cw->anchor_point = v >> 4;
+                p_cea708->p_cw->i_row_count = v & 0x0F;
+                v = cea708_input_buffer_get( ib );
+                p_cea708->p_cw->i_col_count = v & 0x3F;
+                v = cea708_input_buffer_get( ib );
+                /* zero values style set on init, avoid dealing with updt case */
+                i = (v >> 3) & 0x07; /* Window style id */
+                if( i > 0 )
+                    p_cea708->p_cw->style = cea708_default_window_styles[i];
+                i = v & 0x07; /* Pen style id */
+                if( i > 0 )
+                    p_cea708->p_cw->pen = cea708_default_pen_styles[i];
+                p_cea708->p_cw->b_defined = true;
+            }
+            else
+            {
+                Debug(printf("{%2.2x}", code));
+                POP_COMMAND();
+            }
+    }
+
+    return i_ret;
+}
+
+static int CEA708_Decode_G1( uint8_t code, cea708_t *p_cea708 )
+{
+    cea708_input_buffer_t *ib = &p_cea708->input_buffer;
+    POP_COMMAND();
+
+    if( !p_cea708->p_cw->b_defined )
+        return CEA708_STATUS_OK;
+
+    uint8_t utf8[4] = {0xc0 | (code & 0xc0) >> 6,
+                       0x80 | (code & 0x3f),
+                       0, 0};
+
+    CEA708_Window_Write( utf8, p_cea708->p_cw );
+    p_cea708->b_text_waiting |= p_cea708->p_cw->b_visible;
+
+    return CEA708_STATUS_OK;
+}
+
+static int CEA708_Decode_G2G3( uint8_t code, cea708_t *p_cea708 )
+{
+    if( !p_cea708->p_cw->b_defined )
+        return CEA708_STATUS_OK;
+
+    uint8_t out[4] = { '?', 0, 0, 0 };
+    static const struct {
+        uint8_t c;
+        uint8_t utf8[4];
+    } code2utf8[] = {
+        /* G2 */
+        { 0x20,     { 0x20 } },// transparent space [*** will need special handling]
+        { 0x21,     { 0x20 } },// non breaking transparent space [*** will need special handling]
+        { 0x25,     { 0xe2,0x80,0xa6 } },// HORIZONTAL ELLIPSIS
+        { 0x2a,     { 0xc5,0xa0 } },// LATIN CAPITAL LETTER S WITH CARON
+        { 0x2c,     { 0xc5,0x92 } },// LATIN CAPITAL LIGATURE OE
+        { 0x30,     { 0xe2,0x96,0x88 } },// FULL BLOCK
+        { 0x31,     { 0xe2,0x80,0x98 } },// LEFT SINGLE QUOTATION MARK
+        { 0x32,     { 0xe2,0x80,0x99 } },// RIGHT SINGLE QUOTATION MARK
+        { 0x33,     { 0xe2,0x80,0x9c } },// LEFT DOUBLE QUOTATION MARK
+        { 0x34,     { 0xe2,0x80,0x9d } },// RIGHT DOUBLE QUOTATION MARK
+        { 0x35,     { 0xe2,0x80,0xa2 } },// BULLET
+        { 0x39,     { 0xe2,0x84,0xa2 } },// Trademark symbol (TM)
+        { 0x3a,     { 0xc5,0xa1 } },// LATIN SMALL LETTER S WITH CARON
+        { 0x3c,     { 0xc5,0x93 } },// LATIN SMALL LIGATURE OE
+        { 0x3d,     { 0xe2,0x84,0xa0 } },// SERVICE MARK
+        { 0x3f,     { 0xc5,0xb8 } },// LATIN CAPITAL LETTER Y WITH DIAERESIS
+        { 0x76,     { 0xe2,0x85,0x9b } },// VULGAR FRACTION ONE EIGHTH
+        { 0x77,     { 0xe2,0x85,0x9c } },// VULGAR FRACTION THREE EIGHTHS
+        { 0x78,     { 0xe2,0x85,0x9d } },// VULGAR FRACTION FIVE EIGHTHS
+        { 0x79,     { 0xe2,0x85,0x9e } },// VULGAR FRACTION SEVEN EIGHTHS
+        { 0x7a,     { 0xe2,0x94,0x82 } },// BOX DRAWINGS LIGHT VERTICAL
+        { 0x7b,     { 0xe2,0x94,0x90 } },// BOX DRAWINGS LIGHT DOWN AND LEFT
+        { 0x7c,     { 0xe2,0x94,0x94 } },// BOX DRAWINGS LIGHT UP AND RIGHT
+        { 0x7d,     { 0xe2,0x94,0x80 } },// BOX DRAWINGS LIGHT HORIZONTAL
+        { 0x7e,     { 0xe2,0x94,0x98 } },// BOX DRAWINGS LIGHT UP AND LEFT
+        { 0x7f,     { 0xe2,0x94,0x8c } },// BOX DRAWINGS LIGHT DOWN AND RIGHT
+        /* G3 */
+        { 0xa0,     { 0xf0,0x9f,0x85,0xb2 } },// CC (replaced with NEGATIVE SQUARED LATIN CAPITAL LETTER C)
+    };
+
+    for( size_t i = 0; i < ARRAY_SIZE(code2utf8) ; i++ )
+    {
+        if( code2utf8[i].c == code )
+        {
+            memcpy( out, code2utf8[i].utf8, 4 );
+            if(out[0] < 0xf0)
+            {
+                if(out[0] < 0x80)
+                    out[1] = 0;
+                else if(out[0] < 0xe0)
+                    out[2] = 0;
+                else
+                    out[3] = 0;
+            }
+            break;
+        }
+    }
+
+    CEA708_Window_Write( out, p_cea708->p_cw );
+
+    p_cea708->b_text_waiting |= p_cea708->p_cw->b_visible;
+
+    return CEA708_STATUS_OK;
+}
+
+static int CEA708_Decode_P16( uint16_t ucs2, cea708_t *p_cea708 )
+{
+    if( !p_cea708->p_cw->b_defined )
+        return CEA708_STATUS_OK;
+
+    uint8_t out[4] = { '?', 0, 0, 0 };
+
+    /* adapted from codepoint conversion from strings.h */
+    if( ucs2 <= 0x7F )
+    {
+        out[0] = ucs2;
+    }
+    else if( ucs2 <= 0x7FF )
+    {
+        out[0] = 0xC0 |  (ucs2 >>  6);
+        out[1] = 0x80 |  (ucs2        & 0x3F);
+    }
+    else
+    {
+        out[0] = 0xE0 |  (ucs2 >> 12);
+        out[1] = 0x80 | ((ucs2 >>  6) & 0x3F);
+        out[2] = 0x80 |  (ucs2        & 0x3F);
+    }
+
+    CEA708_Window_Write( out, p_cea708->p_cw );
+
+    p_cea708->b_text_waiting |= p_cea708->p_cw->b_visible;
+
+    return CEA708_STATUS_OK;
+}
+
+static void CEA708_Decode_ServiceBuffer( cea708_t *h )
+{
+    for( ;; )
+    {
+        const uint8_t i_in = cea708_input_buffer_size( &h->input_buffer );
+        if( i_in == 0 )
+            break;
+
+        int i_ret;
+        uint8_t c = cea708_input_buffer_peek( &h->input_buffer, 0 );
+
+        if( c < 0x20 )
+            i_ret = CEA708_Decode_C0( c, h );
+        else if( c >= 0x20 && c <=0x7F )
+            i_ret = CEA708_Decode_G0( c, h );
+        else if( c >= 0x80 && c <= 0x9F )
+            i_ret = CEA708_Decode_C1( c, h );
+        else if( c > 0x9F )
+            i_ret = CEA708_Decode_G1( c, h );
+
+        if( i_ret & CEA708_STATUS_OUTPUT )
+            CEA708_Output( h );
+
+        if( i_ret & CEA708_STATUS_STARVING )
+            break;
+
+        /* Update internal clock */
+        const uint8_t i_consumed = i_in - cea708_input_buffer_size( &h->input_buffer );
+        h->i_clock += CLOCK_FREQ / 1200 * i_consumed;
+    }
+}
+
+void CEA708_Decoder_Push( cea708_t *h, mtime_t i_time,
+                          const uint8_t *p_data, size_t i_data )
+{
+    /* Set new buffer start time */
+    h->i_clock = i_time;
+
+    for( size_t i=0; i<i_data; )
+    {
+        /* Never push more than buffer */
+        size_t i_push = cea708_input_buffer_remain(&h->input_buffer);
+        if( (i_data - i) < i_push )
+            i_push = (i_data - i);
+        else if( h->suspended_deadline > 0 )
+            h->suspended_deadline = 0; /* Full buffer cancels pause */
+
+        for( size_t j=0; j<i_push; j++ )
+        {
+            uint8_t byte = p_data[i+j];
+            cea708_input_buffer_add( &h->input_buffer, byte );
+        }
+
+        if( h->suspended_deadline > 0 )
+        {
+            /* Decoding is paused */
+            if ( h->suspended_deadline > h->i_clock )
+            {
+                /* Increase internal clock */
+                h->i_clock += CLOCK_FREQ / 1200 * i_push;
+                continue;
+            }
+            h->suspended_deadline = 0;
+        }
+
+        /* Decode Buffer */
+        CEA708_Decode_ServiceBuffer( h );
+
+        i += i_push;
+    }
+}
diff --git a/modules/codec/cea708.h b/modules/codec/cea708.h
new file mode 100644
index 0000000..2ba5153
--- /dev/null
+++ b/modules/codec/cea708.h
@@ -0,0 +1,44 @@
+/*****************************************************************************
+ * cea708.h : CEA708 subtitles decoder
+ *****************************************************************************
+ * Copyright © 2017 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 VLC_CEA708_H_
+#define VLC_CEA708_H_
+
+typedef void(*service_data_hdlr_t)(void *, uint8_t i_sid, mtime_t,
+                                   const uint8_t *p_data, size_t i_data);
+
+/* DVTCC Services demuxing */
+#define CEA708_DTVCC_MAX_PKT_SIZE 128
+typedef struct cea708_demux_t cea708_demux_t;
+
+cea708_demux_t * CEA708_DTVCC_Demuxer_New( void *, service_data_hdlr_t );
+void CEA708_DTVCC_Demuxer_Release( cea708_demux_t * );
+void CEA708_DTVCC_Demuxer_Push( cea708_demux_t *h, mtime_t, const uint8_t data[3] );
+void CEA708_DTVCC_Demuxer_Flush( cea708_demux_t *h );
+
+/* DVTCC Services decoding */
+typedef struct cea708_t cea708_t;
+
+cea708_t *CEA708_Decoder_New( decoder_t * );
+void CEA708_Decoder_Release( cea708_t *p_cea708 );
+void CEA708_Decoder_Push( cea708_t *p_cea708, mtime_t,
+                          const uint8_t *p_data, size_t i_data );
+void CEA708_Decoder_Flush( cea708_t *p_cea708 );
+
+#endif
-- 
2.9.5



More information about the vlc-devel mailing list