>From c99057478a1339fe9da967b8e2035598252d405f Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Kempf <jb@videolan.org>
Date: Mon, 26 Apr 2010 00:34:39 +0200
Subject: [PATCH] New SDI input, made by Meuuh, adapted by /me to master

---
 configure.ac              |    1 +
 modules/access/Modules.am |    1 +
 modules/access/sdi.c      | 1758 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1760 insertions(+), 0 deletions(-)
 create mode 100644 modules/access/sdi.c

diff --git a/configure.ac b/configure.ac
index 80ff9df..41241dd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1852,6 +1852,7 @@ dnl - dv module: digital video module check for libraw1394
 dnl
 PKG_ENABLE_MODULES_VLC([DC1394], [], [libraw1394 >= 2.0.1 libdc1394-2 >= 2.1.0], [dc1394 access module], [auto])
 PKG_ENABLE_MODULES_VLC([DV], [access_dv], [libraw1394 >= 2.0.1 libavc1394 >= 0.5.3], [DV input module], [auto])
+PKG_ENABLE_MODULES_VLC([SDI], [access_sdi], [zvbi-0.2 >= 0.2.28], [SDI input module], [disabled])
 
 dnl
 dnl dvdread module: check for libdvdread
diff --git a/modules/access/Modules.am b/modules/access/Modules.am
index 8cf53ea..629d286 100644
--- a/modules/access/Modules.am
+++ b/modules/access/Modules.am
@@ -53,6 +53,7 @@ SOURCES_access_sftp = sftp.c
 SOURCES_access_imem = imem.c
 SOURCES_access_avio = avio.c avio.h
 SOURCES_access_attachment = attachment.c
+SOURCES_access_sdi = sdi.c
 
 libaccess_rtmp_plugin_la_SOURCES = \
         rtmp/access.c \
diff --git a/modules/access/sdi.c b/modules/access/sdi.c
new file mode 100644
index 0000000..927c748
--- /dev/null
+++ b/modules/access/sdi.c
@@ -0,0 +1,1758 @@
+/*****************************************************************************
+ * sdi.c: SDI capture module for Computer Modules cards
+ *****************************************************************************
+ * Copyright (C) 2009 VideoLAN
+ * $Id$
+ *
+ * Authors: Christophe Massiot <massiot@via.ecp.fr>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#include <stdlib.h>
+#include <poll.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+
+#include <vlc/vlc.h>
+#include <vlc_input.h>
+#include <vlc_access.h>
+#include <vlc_demux.h>
+
+#include "sdi.h"
+
+#define ZVBI_DEBUG
+#include <libzvbi.h>
+
+#define SDI_DEVICE       "/dev/sdirx%u"
+#define SDI_BUFFERS_FILE "/sys/class/sdi/sdirx%u/buffers"
+#define SDI_BUFSIZE_FILE "/sys/class/sdi/sdirx%u/bufsize"
+#define SDI_MODE_FILE    "/sys/class/sdi/sdirx%u/mode"
+#define READ_TIMEOUT     80000
+#define RESYNC_TIMEOUT   500000
+
+#define DEMUX_BUFFER_SIZE 1350000
+#define MAX_AUDIOS 4
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+#define CACHING_TEXT N_("Caching value in ms")
+#define CACHING_LONGTEXT N_( \
+    "Allows you to modify the default caching value for sdi capture " \
+    "streams. This value should be set in millisecond units." )
+#define LINK_TEXT N_("Link #")
+#define LINK_LONGTEXT N_( \
+    "Allows you to set the desired link of the board for the capture (starting at 0)." )
+#define VIDEO_TEXT N_("Video ID")
+#define VIDEO_LONGTEXT N_( \
+    "Allows you to set the ES ID of the video." )
+#define VIDEO_ASPECT_TEXT N_("Aspect ratio")
+#define VIDEO_ASPECT_LONGTEXT N_( \
+    "Allows you to force the aspect ratio of the video." )
+#define AUDIO_TEXT N_("Audio configuration")
+#define AUDIO_LONGTEXT N_( \
+    "Allows you to set audio configuration (id=group,pair:id=group,pair...)." )
+#define TELX_TEXT N_("Teletext configuration")
+#define TELX_LONGTEXT N_( \
+    "Allows you to set Teletext configuration (id=line1-lineN with both fields)." )
+#define TELX_LANG_TEXT N_("Teletext language")
+#define TELX_LANG_LONGTEXT N_( \
+    "Allows you to set Teletext language (page=lang/type,...)." )
+
+static int  Open ( vlc_object_t * );
+static void Close( vlc_object_t * );
+static int  DemuxOpen ( vlc_object_t * );
+static void DemuxClose( vlc_object_t * );
+
+vlc_module_begin()
+    set_description( N_("SDI Input") )
+    set_shortname( N_("sdi" ))
+    set_category( CAT_INPUT )
+    set_subcategory( SUBCAT_INPUT_ACCESS )
+
+    add_integer( "sdi-caching", DEFAULT_PTS_DELAY / 1000, NULL,
+        CACHING_TEXT, CACHING_LONGTEXT, true )
+    add_integer( "sdi-link", 0, NULL,
+        LINK_TEXT, LINK_LONGTEXT, true )
+
+    add_integer( "sdi-id-video", 0, NULL,
+        VIDEO_TEXT, VIDEO_LONGTEXT, true )
+    add_string( "sdi-aspect-ratio", "", NULL,
+        VIDEO_ASPECT_TEXT, VIDEO_ASPECT_LONGTEXT, true )
+    add_string( "sdi-audio", "0=1,1", NULL,
+        AUDIO_TEXT, AUDIO_LONGTEXT, true )
+    add_string( "sdi-telx", "", NULL,
+        TELX_TEXT, TELX_LONGTEXT, true )
+    add_string( "sdi-telx-lang", "", NULL,
+        TELX_LANG_TEXT, TELX_LANG_LONGTEXT, true )
+
+    set_capability( "access_demux", 0 )
+    add_shortcut( "sdi" )
+    set_callbacks( Open, Close )
+
+    add_submodule()
+        set_description( N_("SDI Demux") )
+        set_capability( "demux", 0 )
+        set_callbacks( DemuxOpen, DemuxClose )
+vlc_module_end()
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+typedef struct sdi_audio_t
+{
+    unsigned int i_group, i_pair;
+
+    /* SDI parser */
+    int32_t      i_delay;
+    unsigned int i_rate;
+    block_t      *p_block;
+    int16_t      *p_buffer;
+    unsigned int i_left_samples, i_right_samples, i_max_samples;
+
+    /* ES stuff */
+    int          i_id;
+    es_out_id_t *p_es;
+} sdi_audio_t;
+
+enum {
+    STATE_NOSYNC,
+    STATE_STARTSYNC,
+    STATE_ANCSYNC,
+    STATE_LINESYNC,
+    STATE_ACTIVESYNC,
+    STATE_VBLANKSYNC,
+    STATE_PICSYNC,
+    STATE_SYNC,
+};
+
+struct demux_sys_t
+{
+    /* device reader */
+    int          i_fd;
+    unsigned int i_link;
+    uint8_t      **pp_buffers;
+    unsigned int i_buffers, i_current_buffer;
+    unsigned int i_buffer_size;
+
+    /* SDI sync */
+    int          i_state;
+    mtime_t      i_last_state_change;
+    unsigned int i_anc_size, i_active_size, i_picture_size;
+    unsigned int i_line_offset, i_nb_lines;
+
+    /* SDI parser */
+    unsigned int i_line_buffer;
+    unsigned int i_current_line;
+    uint8_t      *p_line_buffer;
+    block_t      *p_current_picture;
+    uint8_t      *p_y, *p_u, *p_v;
+    uint8_t      *p_wss_buffer;
+    uint8_t      *p_telx_buffer;
+
+    /* picture decoding */
+    unsigned int i_width, i_height, i_aspect, i_forced_aspect;
+    unsigned int i_block_size;
+    float        f_fps;
+    unsigned int i_telx_line, i_telx_count;
+    char         *psz_telx, *psz_telx_lang;
+    bool         b_hd, b_vbi;
+    vbi_raw_decoder rd_wss, rd_telx;
+    mtime_t i_next_date;
+    int          i_incr;
+
+    /* ES stuff */
+    int          i_id_video;
+    es_out_id_t *p_es_video;
+    sdi_audio_t p_audios[MAX_AUDIOS];
+    es_out_id_t *p_es_telx;
+};
+
+static int Control( demux_t *, int, va_list );
+static int DemuxControl( demux_t *, int, va_list );
+static int Demux( demux_t * );
+static int DemuxDemux( demux_t * );
+
+static int InitWSS( demux_t *p_demux );
+static int InitTelx( demux_t *p_demux );
+
+static int HandleSDBuffer( demux_t *p_demux, uint8_t *p_buffer,
+                           unsigned int i_buffer_size );
+
+static int InitCapture( demux_t *p_demux );
+static void CloseCapture( demux_t *p_demux );
+static int Capture( demux_t *p_demux );
+
+/*****************************************************************************
+ * DemuxOpen:
+ *****************************************************************************/
+static int DemuxOpen( vlc_object_t *p_this )
+{
+    demux_t     *p_demux = (demux_t *)p_this;
+    demux_sys_t *p_sys;
+    vlc_value_t val;
+    char        *psz_parser;
+    int         i;
+
+    /* Fill p_demux field */
+    p_demux->pf_demux = DemuxDemux;
+    p_demux->pf_control = DemuxControl;
+    p_demux->p_sys = p_sys = malloc( sizeof( demux_sys_t ) );
+    memset( p_sys, 0, sizeof( demux_sys_t ) );
+
+    p_sys->i_state = STATE_NOSYNC;
+    p_sys->i_last_state_change = mdate();
+
+    var_Create( p_demux, "sdi-aspect-ratio",
+                VLC_VAR_STRING|VLC_VAR_DOINHERIT );
+    var_Get( p_demux, "sdi-aspect-ratio", &val );
+    psz_parser = strchr( val.psz_string, ':' );
+    if ( psz_parser )
+    {
+        *psz_parser++ = '\0';
+        p_sys->i_forced_aspect = p_sys->i_aspect =
+            strtol( val.psz_string, NULL, 0 ) * VOUT_ASPECT_FACTOR
+             / strtol( psz_parser, NULL, 0 );
+    }
+    else
+        p_sys->i_forced_aspect = 0;
+    free( val.psz_string );
+
+    var_Create( p_demux, "sdi-id-video",
+                VLC_VAR_INTEGER|VLC_VAR_DOINHERIT );
+    var_Get( p_demux, "sdi-id-video", &val );
+    p_sys->i_id_video = val.i_int;
+
+    /* Audio ES */
+    var_Create( p_demux, "sdi-audio",
+                VLC_VAR_STRING|VLC_VAR_DOINHERIT );
+    var_Get( p_demux, "sdi-audio", &val );
+    psz_parser = val.psz_string;
+    i = 0;
+
+    while ( psz_parser != NULL && *psz_parser )
+    {
+        int i_id, i_group, i_pair;
+        char *psz_next = strchr( psz_parser, '=' );
+        if ( psz_next != NULL )
+        {
+            *psz_next = '\0';
+            i_id = strtol( psz_parser, NULL, 0 );
+            psz_parser = psz_next + 1;
+        }
+        else
+            i_id = 0;
+
+        psz_next = strchr( psz_parser, ':' );
+        if ( psz_next != NULL )
+        {
+            *psz_next = '\0';
+            psz_next++;
+        }
+
+        if ( sscanf( psz_parser, "%d,%d", &i_group, &i_pair ) == 2 )
+        {
+            p_sys->p_audios[i].i_group = i_group;
+            p_sys->p_audios[i].i_pair = i_pair;
+            p_sys->p_audios[i].i_id = i_id;
+            i++;
+        }
+        else
+            msg_Warn( p_demux, "malformed audio configuration (%s)",
+                      psz_parser );
+
+        psz_parser = psz_next;
+    }
+    free( val.psz_string );
+
+    /* Teletext ES */
+    var_Create( p_demux, "sdi-telx",
+                VLC_VAR_STRING|VLC_VAR_DOINHERIT );
+    var_Get( p_demux, "sdi-telx", &val );
+    p_sys->psz_telx = val.psz_string;
+
+    var_Create( p_demux, "sdi-telx-lang",
+                VLC_VAR_STRING|VLC_VAR_DOINHERIT );
+    var_Get( p_demux, "sdi-telx-lang", &val );
+    p_sys->psz_telx_lang = val.psz_string;
+
+    return VLC_SUCCESS;
+}
+
+static int Open( vlc_object_t *p_this )
+{
+    demux_t     *p_demux = (demux_t *)p_this;
+    demux_sys_t *p_sys;
+    vlc_value_t val;
+    int         i_ret;
+
+    if ( (i_ret = DemuxOpen( p_this )) != VLC_SUCCESS )
+        return i_ret;
+
+    /* Fill p_demux field */
+    p_demux->pf_demux = Demux;
+    p_demux->pf_control = Control;
+    p_sys = p_demux->p_sys;
+
+    /* Update default_pts to a suitable value for sdi access */
+    var_Create( p_demux, "sdi-caching", VLC_VAR_INTEGER|VLC_VAR_DOINHERIT );
+
+    var_Create( p_demux, "sdi-link", VLC_VAR_INTEGER|VLC_VAR_DOINHERIT );
+    var_Get( p_demux, "sdi-link", &val );
+    p_sys->i_link = val.i_int;
+
+    if( InitCapture( p_demux ) != VLC_SUCCESS )
+    {
+        free( p_sys );
+        return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * DemuxClose:
+ *****************************************************************************/
+static void DemuxClose( vlc_object_t *p_this )
+{
+    demux_t     *p_demux = (demux_t *)p_this;
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    free( p_sys->psz_telx );
+    free( p_sys->psz_telx_lang );
+    free( p_sys );
+}
+
+static void Close( vlc_object_t *p_this )
+{
+    demux_t     *p_demux = (demux_t *)p_this;
+
+    CloseCapture( p_demux );
+    DemuxClose( p_this );
+}
+
+/*****************************************************************************
+ * DemuxDemux:
+ *****************************************************************************/
+static int DemuxDemux( demux_t *p_demux )
+{
+    block_t *p_block = stream_Block( p_demux->s, DEMUX_BUFFER_SIZE );
+    int i_ret;
+
+    if ( p_block == NULL )
+        return 0;
+
+    i_ret = HandleSDBuffer( p_demux, p_block->p_buffer, p_block->i_buffer );
+    block_Release( p_block );
+
+    return ( i_ret == VLC_SUCCESS );
+}
+
+static int Demux( demux_t *p_demux )
+{
+    return ( Capture( p_demux ) == VLC_SUCCESS );
+}
+
+/*****************************************************************************
+ * Control:
+ *****************************************************************************/
+static int DemuxControl( demux_t *p_demux, int i_query, va_list args )
+{
+    /* FIXME for HD */
+    return demux_vaControlHelper( p_demux->s, -1, -1, 270000000, 1, i_query,
+                                   args );
+}
+
+static int Control( demux_t *p_demux, int i_query, va_list args )
+{
+    bool *pb;
+    int64_t *pi64;
+
+    switch( i_query )
+    {
+        /* Special for access_demux */
+        case DEMUX_CAN_PAUSE:
+        case DEMUX_CAN_CONTROL_PACE:
+            /* TODO */
+            pb = (bool*)va_arg( args, bool * );
+            *pb = false;
+            return VLC_SUCCESS;
+
+        case DEMUX_GET_PTS_DELAY:
+            pi64 = (int64_t*)va_arg( args, int64_t * );
+            *pi64 = (int64_t)var_GetInteger( p_demux, "sdi-caching" ) *1000;
+            return VLC_SUCCESS;
+
+        /* TODO implement others */
+        default:
+            return VLC_EGENERIC;
+    }
+}
+
+/*****************************************************************************
+ * Video, audio & VBI decoding
+ *****************************************************************************/
+#define WSS_LINE        23
+
+struct block_extension_t
+{
+    bool      b_progressive;          /**< is it a progressive frame ? */
+    unsigned int    i_nb_fields;                  /**< # of displayed fields */
+    bool      b_top_field_first;             /**< which field is first */
+    unsigned int    i_aspect;                     /**< aspect ratio of frame */
+};
+
+static void NewFrame( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    int i;
+
+    p_sys->p_current_picture = block_New( p_demux, p_sys->i_block_size );
+    p_sys->p_y = p_sys->p_current_picture->p_buffer;
+    p_sys->p_u = p_sys->p_y + p_sys->i_width * p_sys->i_height;
+    p_sys->p_v = p_sys->p_u + p_sys->i_width * p_sys->i_height / 4;
+
+    for ( i = 0; i < MAX_AUDIOS; i++ )
+    {
+        sdi_audio_t *p_audio = &p_sys->p_audios[i];
+        if ( p_audio->p_es != NULL )
+        {
+            p_audio->p_block = block_New( p_demux,
+                               p_audio->i_max_samples * sizeof(int16_t) * 2 );
+            p_audio->p_buffer = (int16_t *)p_audio->p_block->p_buffer;
+            p_audio->i_left_samples = p_audio->i_right_samples = 0;
+        }
+    }
+}
+
+static void StartDecode( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    es_format_t fmt;
+    char *psz_parser;
+
+    p_sys->i_next_date = 1;
+    p_sys->i_incr = 1000000. / p_sys->f_fps;
+    p_sys->i_block_size = p_sys->i_width * p_sys->i_height * 3 / 2
+                           + sizeof(struct block_extension_t);
+    NewFrame( p_demux );
+
+    /* Video ES */
+    es_format_Init( &fmt, VIDEO_ES, VLC_FOURCC('I','4','2','0') );
+    fmt.i_id = p_sys->i_id_video;
+    fmt.video.i_frame_rate = p_sys->f_fps * 1000. + 0.5;
+    fmt.video.i_frame_rate_base = 1000;
+    fmt.video.i_width = p_sys->i_width;
+    fmt.video.i_height = p_sys->i_height;
+    int i_aspect = p_sys->i_forced_aspect ? p_sys->i_forced_aspect :
+                         p_sys->i_aspect;
+    fmt.video.i_sar_num = i_aspect * fmt.video.i_height
+                           / fmt.video.i_width;
+    fmt.video.i_sar_den = VOUT_ASPECT_FACTOR;
+    p_sys->p_es_video = es_out_Add( p_demux->out, &fmt );
+
+    if ( p_sys->b_vbi && InitWSS( p_demux ) != VLC_SUCCESS )
+        p_sys->b_vbi = 0;
+
+    /* Teletext ES */
+    psz_parser = p_sys->psz_telx;
+    if ( psz_parser != NULL && *psz_parser )
+    {
+        if ( !p_sys->b_vbi )
+        {
+            msg_Warn( p_demux, "VBI is unsupported on this input stream" );
+        }
+        else
+        {
+            int i_id;
+            char *psz_next = strchr( psz_parser, '=' );
+            if ( psz_next != NULL )
+            {
+                *psz_next = '\0';
+                i_id = strtol( psz_parser, NULL, 0 );
+                psz_parser = psz_next + 1;
+            }
+            else
+                i_id = 0;
+
+            psz_next = strchr( psz_parser, '-' );
+            if ( psz_next != NULL )
+                *psz_next++ = '\0';
+
+            p_sys->i_telx_line = strtol( psz_parser, NULL, 0 ) - 1;
+            if ( psz_next != NULL )
+                p_sys->i_telx_count = strtol( psz_next, NULL, 0 )
+                                       - p_sys->i_telx_line - 1 + 1;
+            else
+                p_sys->i_telx_count = 1;
+
+            if ( InitTelx( p_demux ) == VLC_SUCCESS )
+            {
+                int i_dr_size = 0;
+                uint8_t *p_dr = NULL;
+
+                msg_Dbg( p_demux, "capturing VBI lines %d-%d and %d-%d",
+                         p_sys->i_telx_line + 1,
+                         p_sys->i_telx_line + 1 + p_sys->i_telx_count - 1,
+                         p_sys->i_telx_line + 1 + 313,
+                         p_sys->i_telx_line + 1 + 313
+                                                + p_sys->i_telx_count - 1 );
+
+                es_format_Init( &fmt, SPU_ES, VLC_FOURCC('t','e','l','x') );
+                fmt.i_id = i_id;
+
+                /* Teletext language & type */
+                psz_parser = p_sys->psz_telx_lang;
+
+                while ( (psz_next = strchr( psz_parser, '=' )) != NULL )
+                {
+                    int i_page;
+                    *psz_next++ = '\0';
+                    if ( !psz_next[0] || !psz_next[1] || !psz_next[2] )
+                        break;
+                    i_page = strtol( psz_parser, NULL, 0 );
+                    i_dr_size += 5;
+                    p_dr = realloc( p_dr, i_dr_size );
+                    p_dr[i_dr_size - 5] = *psz_next++;
+                    p_dr[i_dr_size - 4] = *psz_next++;
+                    p_dr[i_dr_size - 3] = *psz_next++;
+                    if ( *psz_next == '/' )
+                    {
+                        psz_next++;
+                        p_dr[i_dr_size - 2] = strtol( psz_next, &psz_next, 0 )
+                                               << 3;
+                    }
+                    else  /* subtitle for hearing impaired */
+                        p_dr[i_dr_size - 2] = 0x5 << 3;
+                    p_dr[i_dr_size - 2] |= (i_page / 100) & 0x7;
+                    p_dr[i_dr_size - 1] = i_page % 100;
+
+                    if ( *psz_next == ',' )
+                        psz_next++;
+                    psz_parser = psz_next;
+                }
+
+                fmt.i_extra = i_dr_size;
+                fmt.p_extra = p_dr;
+                p_sys->p_es_telx = es_out_Add( p_demux->out, &fmt );
+            }
+            else
+                p_sys->i_telx_count = 0;
+        }
+    }
+}
+
+static void StopDecode( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    int i;
+
+    if ( p_sys->i_state != STATE_SYNC )
+        return;
+
+    free( p_sys->p_line_buffer );
+
+    block_Release( p_sys->p_current_picture );
+    p_sys->p_current_picture = NULL;
+    es_out_Del( p_demux->out, p_sys->p_es_video );
+
+    if ( p_sys->b_vbi )
+    {
+        free( p_sys->p_wss_buffer );
+        p_sys->p_wss_buffer = NULL;
+        vbi_raw_decoder_destroy( &p_sys->rd_wss );
+
+        if ( p_sys->p_es_telx )
+        {
+            es_out_Del( p_demux->out, p_sys->p_es_telx );
+            free( p_sys->p_telx_buffer );
+            p_sys->p_telx_buffer = NULL;
+            vbi_raw_decoder_destroy( &p_sys->rd_telx );
+        }
+    }
+
+    for ( i = 0; i < MAX_AUDIOS; i++ )
+    {
+        sdi_audio_t *p_audio = &p_sys->p_audios[i];
+        if ( p_audio->i_group && p_audio->p_es != NULL )
+        {
+            es_out_Del( p_demux->out, p_audio->p_es );
+            p_audio->p_es = NULL;
+            block_Release( p_audio->p_block );
+        }
+    }
+}
+
+static void InitVideo( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    int i_total_width = (p_sys->i_anc_size + p_sys->i_active_size) * 4 / 5;
+    p_sys->i_width = (p_sys->i_active_size - 5) * 4 / 10;
+    if ( p_sys->i_nb_lines == 625 )
+    {
+        /* PAL */
+        p_sys->i_height = 576;
+        p_sys->i_aspect = 4 * VOUT_ASPECT_FACTOR / 3;
+        p_sys->f_fps = 25.0;
+        p_sys->b_hd = false;
+    }
+    else if ( p_sys->i_nb_lines == 525 )
+    {
+        /* NTSC */
+        p_sys->i_height = 480;
+        p_sys->i_aspect = 4 * VOUT_ASPECT_FACTOR / 3;
+        p_sys->f_fps = 29.97;
+        p_sys->b_hd = false;
+    }
+    else if ( p_sys->i_nb_lines == 1125 && i_total_width == 2640 )
+    {
+        /* 1080i50 or 1080p25 */
+        p_sys->i_height = 1080;
+        p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9;
+        p_sys->f_fps = 25.0;
+        p_sys->b_hd = true;
+    }
+    else if ( p_sys->i_nb_lines == 1125 && i_total_width == 2200 )
+    {
+        /* 1080i60 or 1080p30 */
+        p_sys->i_height = 1080;
+        p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9;
+        p_sys->f_fps = 29.97;
+        p_sys->b_hd = true;
+    }
+    else if ( p_sys->i_nb_lines == 750 && i_total_width == 1980 )
+    {
+        /* 720p50 */
+        p_sys->i_height = 720;
+        p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9;
+        p_sys->f_fps = 50.0;
+        p_sys->b_hd = true;
+    }
+    else if ( p_sys->i_nb_lines == 750 && i_total_width == 1650 )
+    {
+        /* 720p60 */
+        p_sys->i_height = 720;
+        p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9;
+        p_sys->f_fps = 59.94;
+        p_sys->b_hd = true;
+    }
+    else
+    {
+        msg_Warn( p_demux, "unable to determine video type" );
+        /* Put sensitive defaults */
+        p_sys->i_height = p_sys->i_nb_lines;
+        p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9;
+        p_sys->f_fps = 25.0;
+        p_sys->b_hd = true;
+    }
+    p_sys->b_vbi = !p_sys->b_hd;
+}
+
+static void DecodeVideo( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    struct block_extension_t ext;
+
+    /* FIXME: progressive formats ? */
+    ext.b_progressive = false;
+    ext.i_nb_fields = 2;
+    ext.b_top_field_first = true;
+    ext.i_aspect = p_sys->i_forced_aspect ? p_sys->i_forced_aspect :
+                   p_sys->i_aspect;
+
+    memcpy( &p_sys->p_current_picture->p_buffer[p_sys->i_block_size
+                                     - sizeof(struct block_extension_t)],
+            &ext, sizeof(struct block_extension_t) );
+
+    p_sys->p_current_picture->i_dts = p_sys->p_current_picture->i_pts
+        = p_sys->i_next_date;
+    es_out_Send( p_demux->out, p_sys->p_es_video, p_sys->p_current_picture );
+}
+
+static int InitWSS( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    vbi_raw_decoder_init( &p_sys->rd_wss );
+
+    p_sys->rd_wss.scanning = 625;
+    p_sys->rd_wss.sampling_format = VBI_PIXFMT_UYVY;
+    p_sys->rd_wss.sampling_rate = 13.5e6;
+    p_sys->rd_wss.bytes_per_line = 720 * 2;
+    p_sys->rd_wss.offset = 9.5e-6 * 13.5e6;
+
+    p_sys->rd_wss.start[0] = 23;
+    p_sys->rd_wss.count[0] = 1;
+    p_sys->rd_wss.start[1] = 0;
+    p_sys->rd_wss.count[1] = 0;
+
+    p_sys->rd_wss.interlaced = FALSE;
+    p_sys->rd_wss.synchronous = TRUE;
+
+    if ( vbi_raw_decoder_add_services( &p_sys->rd_wss,
+                                       VBI_SLICED_WSS_625,
+                                       /* strict */ 2 ) == 0 )
+    {
+        msg_Warn( p_demux, "cannot initialize zvbi for WSS" );
+        return VLC_EGENERIC;
+    }
+
+    p_sys->p_wss_buffer = malloc( p_sys->i_width * 2 );
+    return VLC_SUCCESS;
+}
+
+static void DecodeWSS( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    vbi_sliced p_sliced[1];
+
+    if ( vbi_raw_decode( &p_sys->rd_wss, p_sys->p_wss_buffer, p_sliced ) == 0 )
+    {
+        p_sys->i_aspect = 4 * VOUT_ASPECT_FACTOR / 3;
+    }
+    else
+    {
+        unsigned int i_old_aspect = p_sys->i_aspect;
+        uint8_t *p = p_sliced[0].data;
+        int i_aspect, i_parity;
+
+        i_aspect = p[0] & 15;
+        i_parity = i_aspect;
+        i_parity ^= i_parity >> 2;
+        i_parity ^= i_parity >> 1;
+        i_aspect &= 7;
+
+        if ( !(i_parity & 1) )
+            msg_Warn( p_demux, "WSS parity error" );
+        else if ( i_aspect == 7 )
+            p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9;
+        else
+            p_sys->i_aspect = 4 * VOUT_ASPECT_FACTOR / 3;
+
+        if ( p_sys->i_aspect != i_old_aspect )
+            msg_Dbg( p_demux, "new WSS information (ra=%x md=%x cod=%x hlp=%x rvd=%x sub=%x pos=%x srd=%x c=%x cp=%x)",
+                     i_aspect, (p[0] & 0x10) >> 4, (p[0] & 0x20) >> 5,
+                     (p[0] & 0x40) >> 6, (p[0] & 0x80) >> 7, p[1] & 0x01,
+                     (p[1] >> 1) & 3, (p[1] & 0x08) >> 3, (p[1] & 0x10) >> 4,
+                     (p[1] & 0x20) >> 5 );
+    }
+}
+
+static int InitTelx( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    vbi_raw_decoder_init( &p_sys->rd_telx );
+
+    p_sys->rd_telx.scanning = 625;
+    p_sys->rd_telx.sampling_format = VBI_PIXFMT_UYVY;
+    p_sys->rd_telx.sampling_rate = 13.5e6;
+    p_sys->rd_telx.bytes_per_line = 720 * 2;
+    p_sys->rd_telx.offset = 9.5e-6 * 13.5e6;
+
+    p_sys->rd_telx.start[0] = p_sys->i_telx_line + 1;
+    p_sys->rd_telx.count[0] = p_sys->i_telx_count;
+    p_sys->rd_telx.start[1] = p_sys->i_telx_line + 1 + 313;
+    p_sys->rd_telx.count[1] = p_sys->i_telx_count;
+
+    p_sys->rd_telx.interlaced = FALSE;
+    p_sys->rd_telx.synchronous = TRUE;
+
+    if ( vbi_raw_decoder_add_services( &p_sys->rd_telx, VBI_SLICED_TELETEXT_B,
+                                       0 ) == 0 )
+    {
+        msg_Warn( p_demux, "cannot initialize zvbi for Teletext" );
+        return VLC_EGENERIC;
+    }
+
+    p_sys->p_telx_buffer = malloc( p_sys->i_telx_count * p_sys->i_width * 2 );
+    return VLC_SUCCESS;
+}
+
+static void DecodeTelx( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    vbi_sliced p_sliced[p_sys->i_telx_count * 2];
+    int i_nb_slices = vbi_raw_decode( &p_sys->rd_telx, p_sys->p_telx_buffer,
+                                      p_sliced );
+
+    if ( i_nb_slices )
+    {
+        /* 3, 7, 11, 15, etc. */
+        int i_nb_slices_rounded = 3 + (i_nb_slices / 4) * 4;
+        int i;
+        uint8_t *p;
+        msg_Err( p_demux, "Got %d slices !!!!", i_nb_slices );
+        block_t *p_block = block_New( p_demux,
+                                      1 + i_nb_slices_rounded * 46 );
+        p_block->p_buffer[0] = 0x10; /* FIXME ? data_identifier */
+        p = p_block->p_buffer + 1;
+
+        for ( i = 0; i < i_nb_slices; i++ )
+        {
+            int i_line = p_sliced[i].line;
+            p[0] = 0x3; /* FIXME data_unit_id == subtitles */
+            p[1] = 0x2c; /* data_unit_length */
+            /* reserved | field_parity (kind of inverted) | line */
+            p[2] = 0xc0 | (i_line > 313 ? 0 : 0x20) | (i_line % 313);
+            p[3] = 0xe4; /* framing code */
+            vlc_memcpy( p + 4, p_sliced[i].data, 42 );
+            p += 46;
+        }
+
+        /* Let's stuff */
+        for ( ; i < i_nb_slices_rounded; i++ )
+        {
+            p[0] = 0xff;
+            p[1] = 0x2c;
+            memset( p + 2, 0xff, 44 );
+            p += 46;
+        }
+
+        p_block->i_dts = p_block->i_pts = p_sys->i_next_date;
+        es_out_Send( p_demux->out, p_sys->p_es_telx, p_block );
+    }
+}
+
+static void InitAudio( demux_t *p_demux, sdi_audio_t *p_audio )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    es_format_t fmt;
+
+    msg_Dbg( p_demux, "starting audio %u/%u rate:%u delay:%d", p_audio->i_group,
+             p_audio->i_pair, p_audio->i_rate, p_audio->i_delay );
+
+    es_format_Init( &fmt, AUDIO_ES, VLC_FOURCC('s','1','6','l') );
+    fmt.i_id = p_audio->i_id;
+    fmt.audio.i_channels = 2;
+    fmt.audio.i_rate = p_audio->i_rate;
+    fmt.audio.i_bitspersample = 16;
+    fmt.audio.i_blockalign = fmt.audio.i_channels *
+        fmt.audio.i_bitspersample / 8;
+    fmt.i_bitrate = fmt.audio.i_channels * fmt.audio.i_rate *
+        fmt.audio.i_bitspersample;
+    p_audio->p_es = es_out_Add( p_demux->out, &fmt );
+
+    p_audio->i_max_samples = p_audio->i_rate / p_sys->f_fps;
+
+    p_audio->p_block = block_New( p_demux,
+                           p_audio->i_max_samples * sizeof(int16_t) * 2 );
+    p_audio->p_buffer = (int16_t *)p_audio->p_block->p_buffer;
+    p_audio->i_left_samples = p_audio->i_right_samples = 0;
+}
+
+static void DecodeAudio( demux_t *p_demux, sdi_audio_t *p_audio )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    if ( p_audio->p_buffer == NULL )
+        return;
+    if ( !p_audio->i_left_samples && !p_audio->i_right_samples )
+    {
+        block_Release( p_audio->p_block );
+        return;
+    }
+    if ( p_audio->i_left_samples != p_audio->i_right_samples )
+    {
+        msg_Warn( p_demux,
+            "left (%u) and right (%u) channels do not match for audio %u/%u",
+            p_audio->i_left_samples, p_audio->i_right_samples,
+            p_audio->i_group, p_audio->i_pair );
+        block_Release( p_audio->p_block );
+        return;
+    }
+    if ( p_audio->i_left_samples != p_audio->i_max_samples )
+    {
+        msg_Warn( p_demux, "not enough samples for audio %u/%u",
+                  p_audio->i_group, p_audio->i_pair );
+        block_Release( p_audio->p_block );
+        return;
+    }
+
+    p_audio->p_block->i_dts = p_audio->p_block->i_pts
+        = p_sys->i_next_date + (mtime_t)p_audio->i_delay
+                                 * INT64_C(1000000) / p_audio->i_rate;
+    es_out_Send( p_demux->out, p_audio->p_es, p_audio->p_block );
+}
+
+static void DecodeFrame( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    int i;
+
+    if ( p_sys->b_vbi )
+    {
+        DecodeWSS( p_demux );
+
+        if ( p_sys->i_height == 576 )
+        {
+            /* For PAL, erase first half of line 23, last half of line 623,
+             * and line 624 ; no need to erase chrominance */
+            memset( p_sys->p_y, 0, p_sys->i_width / 2 );
+            memset( p_sys->p_y + p_sys->i_width * 574 + p_sys->i_width / 2,
+                    0, p_sys->i_width * 3 / 2 );
+        }
+    }
+
+    if ( p_sys->i_telx_count )
+        DecodeTelx( p_demux );
+
+    for ( i = 0; i < MAX_AUDIOS; i++ )
+    {
+        if ( p_sys->p_audios[i].i_group && p_sys->p_audios[i].p_es != NULL )
+            DecodeAudio( p_demux, &p_sys->p_audios[i] );
+    }
+
+    DecodeVideo( p_demux );
+
+    es_out_Control( p_demux->out, ES_OUT_SET_PCR, p_sys->i_next_date );
+    p_sys->i_next_date += p_sys->i_incr;
+
+    NewFrame( p_demux );
+}
+
+/*****************************************************************************
+ * SDI syntax parsing stuff
+ *****************************************************************************/
+#define FIELD_1_VBLANK_EAV  0xB6
+#define FIELD_1_VBLANK_SAV  0xAB
+#define FIELD_1_ACTIVE_EAV  0x9D
+#define FIELD_1_ACTIVE_SAV  0x80
+#define FIELD_2_VBLANK_EAV  0xF1
+#define FIELD_2_VBLANK_SAV  0xEC
+#define FIELD_2_ACTIVE_EAV  0xDA
+#define FIELD_2_ACTIVE_SAV  0xC7
+
+static const uint8_t *FindReferenceCode( uint8_t i_code,
+                                         const uint8_t *p_parser,
+                                         const uint8_t *p_end )
+{
+    while ( p_parser <= p_end - 5 )
+    {
+        if ( p_parser[0] == 0xff && p_parser[1] == 0x3 && p_parser[2] == 0x0
+              && p_parser[3] == 0x0 && p_parser[4] == i_code )
+            return p_parser;
+        p_parser += 5;
+    }
+
+    return NULL;
+}
+
+static const uint8_t *CountReference( unsigned int *pi_count, uint8_t i_code,
+                                      const uint8_t *p_parser,
+                                      const uint8_t *p_end )
+{
+    const uint8_t *p_tmp = FindReferenceCode( i_code, p_parser, p_end );
+    if ( p_tmp == NULL )
+    {
+        *pi_count += p_end - p_parser;
+        return NULL;
+    }
+    *pi_count += p_tmp - p_parser;
+    return p_tmp;
+}
+
+static const uint8_t *GetLine( demux_t *p_demux, const uint8_t **pp_parser,
+                               const uint8_t *p_end )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    unsigned int i_total_size = p_sys->i_anc_size + p_sys->i_active_size;
+    const uint8_t *p_tmp;
+
+    if ( p_sys->i_line_buffer )
+    {
+        unsigned int i_remaining = i_total_size - p_sys->i_line_buffer;
+        vlc_memcpy( p_sys->p_line_buffer + p_sys->i_line_buffer,
+                                   *pp_parser, i_remaining );
+        *pp_parser += i_remaining;
+        p_sys->i_line_buffer = 0;
+
+        return p_sys->p_line_buffer;
+    }
+
+    if ( p_end - *pp_parser < i_total_size )
+    {
+        vlc_memcpy( p_sys->p_line_buffer, *pp_parser,
+                                   p_end - *pp_parser );
+        p_sys->i_line_buffer = p_end - *pp_parser;
+        return NULL;
+    }
+
+    p_tmp = *pp_parser;
+    *pp_parser += i_total_size;
+    return p_tmp;
+}
+
+#define U   (uint16_t)((p_line[0]) | ((p_line[1] & 0x3) << 8))
+#define Y1  (uint16_t)((p_line[1] >> 2) | ((p_line[2] & 0xf) << 6))
+#define V   (uint16_t)((p_line[2] >> 4) | ((p_line[3] & 0x3f) << 4))
+#define Y2  (uint16_t)((p_line[3] >> 6) | (p_line[4] << 2))
+
+static void UnpackVBI( const uint8_t *p_line, unsigned int i_size,
+                       uint8_t *p_dest )
+{
+    const uint8_t *p_end = p_line + i_size;
+
+    while ( p_line < p_end )
+    {
+        *p_dest++ = (U + 2) / 4;
+        *p_dest++ = (Y1 + 2) / 4;
+        *p_dest++ = (V + 2) / 4;
+        *p_dest++ = (Y2 + 2) / 4;
+        p_line += 5;
+    }
+}
+
+/* For lines 0 [4] or 1 [4] */
+static void Unpack01( const uint8_t *p_line, unsigned int i_size,
+                      uint8_t *p_y, uint8_t *p_u, uint8_t *p_v )
+{
+    const uint8_t *p_end = p_line + i_size;
+
+    while ( p_line < p_end )
+    {
+        *p_u++ = (U + 2) / 4;
+        *p_y++ = (Y1 + 2) / 4;
+        *p_v++ = (V + 2) / 4;
+        *p_y++ = (Y2 + 2) / 4;
+        p_line += 5;
+    }
+}
+
+/* For lines 2 [4] */
+static void Unpack2( const uint8_t *p_line, unsigned int i_size,
+                     uint8_t *p_y, uint8_t *p_u, uint8_t *p_v )
+{
+    const uint8_t *p_end = p_line + i_size;
+
+    while ( p_line < p_end )
+    {
+        uint16_t tmp;
+        tmp = 3 * *p_u;
+        tmp += (U + 2) / 4;
+        *p_u++ = tmp / 4;
+        *p_y++ = (Y1 + 2) / 4;
+        tmp = 3 * *p_v;
+        tmp += (V + 2) / 4;
+        *p_v++ = tmp / 4;
+        *p_y++ = (Y2 + 2) / 4;
+        p_line += 5;
+    }
+}
+
+/* For lines 3 [4] */
+static void Unpack3( const uint8_t *p_line, unsigned int i_size,
+                     uint8_t *p_y, uint8_t *p_u, uint8_t *p_v )
+{
+    const uint8_t *p_end = p_line + i_size;
+
+    while ( p_line < p_end )
+    {
+        uint16_t tmp;
+        tmp = *p_u;
+        tmp += 3 * (U + 2) / 4;
+        *p_u++ = tmp / 4;
+        *p_y++ = (Y1 + 2) / 4;
+        tmp = *p_v;
+        tmp += 3 * (V + 2) / 4;
+        *p_v++ = tmp / 4;
+        *p_y++ = (Y2 + 2) / 4;
+        p_line += 5;
+    }
+}
+
+#undef U
+#undef Y1
+#undef V
+#undef Y2
+
+#define A0  (uint16_t)((p_anc[0]) | ((p_anc[1] & 0x3) << 8))
+#define A1  (uint16_t)((p_anc[1] >> 2) | ((p_anc[2] & 0xf) << 6))
+#define A2  (uint16_t)((p_anc[2] >> 4) | ((p_anc[3] & 0x3f) << 4))
+#define A3  (uint16_t)((p_anc[3] >> 6) | (p_anc[4] << 2))
+
+static void UnpackAnc( const uint8_t *p_anc, unsigned int i_size,
+                       uint16_t *p_dest )
+{
+    const uint8_t *p_end = p_anc + i_size;
+
+    while ( p_anc <= p_end - 5 )
+    {
+        *p_dest++ = A0;
+        *p_dest++ = A1;
+        *p_dest++ = A2;
+        *p_dest++ = A3;
+        p_anc += 5;
+    }
+}
+
+#undef A0
+#undef A1
+#undef A2
+#undef A3
+
+static int HasAncillary( const uint8_t *p_anc )
+{
+    return ( (p_anc[0] == 0x0 && p_anc[1] == 0xfc && p_anc[2] == 0xff
+               && (p_anc[3] & 0x3f) == 0x3f) );
+}
+
+static void HandleAudioData( demux_t *p_demux, const uint16_t *p_anc,
+                             uint8_t i_data_count, uint8_t i_group )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    int i;
+
+    if ( i_data_count % 3 )
+    {
+        msg_Warn( p_demux, "malformed audio data for group %u", i_group );
+        return;
+    }
+
+    for ( i = 0; i < MAX_AUDIOS; i++ )
+    {
+        sdi_audio_t *p_audio = &p_sys->p_audios[i];
+        if ( p_audio->i_group == i_group )
+        {
+            const uint16_t *x = p_anc;
+
+            /* SMPTE 272M says that when parsing a frame, if an audio config
+             * structure is present we will encounter it first. Otherwise
+             * it is assumed to be 48 kHz. */
+            if ( p_audio->p_es == NULL )
+            {
+                p_audio->i_rate = 48000;
+                p_audio->i_delay = 0;
+                InitAudio( p_demux, p_audio );
+            }
+
+            while ( x < p_anc + i_data_count )
+            {
+                if ( ((*x & 0x4) && p_audio->i_pair == 2)
+                      || (!(*x & 0x4) && p_audio->i_pair == 1) )
+                {
+                    uint32_t i_tmp = (uint32_t)((x[0] & 0x1f1) >> 3)
+                                                  | ((x[1] & 0x1ff) << 6)
+                                                  | ((x[2] & 0x1f) << 15);
+                    int32_t i_sample;
+                    if ( x[2] & 0x10 )
+                        i_sample = i_tmp | 0xfff00000;
+                    else
+                        i_sample = i_tmp;
+
+                    if ( x[0] & 0x2 )
+                    {
+                        if ( p_audio->i_right_samples >=
+                                p_audio->i_max_samples )
+                            msg_Warn( p_demux,
+                                      "too many left samples for audio %u/%u",
+                                      p_audio->i_group, p_audio->i_pair );
+                        else
+                            p_audio->p_buffer[2 * p_audio->i_right_samples++
+                                               + 1] = (i_sample + 8) / 16;
+                    }
+                    else
+                    {
+                        if ( p_audio->i_left_samples >= p_audio->i_max_samples )
+                            msg_Warn( p_demux,
+                                      "too many right samples for audio %u/%u",
+                                      p_audio->i_group, p_audio->i_pair );
+                        else
+                            p_audio->p_buffer[2 * p_audio->i_left_samples++]
+                                = (i_sample + 8) / 16;
+                    }
+                }
+                x += 3;
+            }
+        }
+    }
+}
+
+static void HandleAudioConfig( demux_t *p_demux, const uint16_t *p_anc,
+                               uint8_t i_data_count, uint8_t i_group )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    int i;
+
+    if ( i_data_count != 18 )
+    {
+        msg_Warn( p_demux, "malformed audio config for group %u", i_group );
+        return;
+    }
+
+    for ( i = 0; i < MAX_AUDIOS; i++ )
+    {
+        sdi_audio_t *p_audio = &p_sys->p_audios[i];
+        if ( p_audio->i_group == i_group && p_audio->p_es == NULL )
+        {
+            unsigned int i_rate;
+
+            if ( p_audio->i_pair == 2 )
+            {
+                i_rate = (p_anc[2] & 0xe0) >> 5;
+                if ( p_anc[7] & 0x1 )
+                {
+                    uint32_t i_tmp = ((p_anc[7] & 0x1fe) >> 1)
+                                       | ((p_anc[8] & 0x1ff) << 8)
+                                       | ((p_anc[9] & 0x1ff) << 17);
+                    if ( p_anc[9] & 0x80 )
+                        p_audio->i_delay = i_tmp | 0xfc000000;
+                    else
+                        p_audio->i_delay = i_tmp;
+                }
+                if ( p_anc[13] & 0x1 )
+                    msg_Warn( p_demux, "asymetric audio is not supported" );
+            }
+            else
+            {
+                i_rate = (p_anc[2] & 0xe) >> 1;
+                if ( p_anc[4] & 0x1 )
+                {
+                    uint32_t i_tmp = ((p_anc[4] & 0x1fe) >> 1)
+                                       | ((p_anc[5] & 0x1ff) << 8)
+                                       | ((p_anc[6] & 0x1ff) << 17);
+                    if ( p_anc[6] & 0x80 )
+                        p_audio->i_delay = i_tmp | 0xfc000000;
+                    else
+                        p_audio->i_delay = i_tmp;
+                }
+                if ( p_anc[10] & 0x1 )
+                    msg_Warn( p_demux, "asymetric audio is not supported" );
+            }
+
+            switch ( i_rate )
+            {
+            case 0: p_audio->i_rate = 48000; break;
+            case 1: p_audio->i_rate = 44100; break;
+            case 2: p_audio->i_rate = 32000; break;
+            default:
+                msg_Warn( p_demux, "unknown rate for audio %u/%u (%u)",
+                          i_group, p_sys->p_audios[i].i_pair, i_rate );
+                continue;
+            }
+
+            InitAudio( p_demux, p_audio );
+        }
+    }
+}
+
+static void HandleAncillary( demux_t *p_demux, const uint16_t *p_anc,
+                             unsigned int i_size )
+{
+    uint8_t i_data_count;
+
+    if ( i_size < 7
+          || p_anc[0] != 0x0 || p_anc[1] != 0x3ff || p_anc[2] != 0x3ff )
+        return;
+
+    i_data_count = p_anc[5] & 0xff;
+    if ( i_size - 6 < i_data_count )
+    {
+        msg_Warn( p_demux, "malformed ancillary packet (size %u > %u)",
+                  i_data_count, i_size - 6 );
+        return;
+    }
+
+    switch ( p_anc[3] ) /* Data ID */
+    {
+    case 0x2ff:
+        HandleAudioData( p_demux, p_anc + 6, i_data_count, 1 );
+        break;
+    case 0x1fd:
+        HandleAudioData( p_demux, p_anc + 6, i_data_count, 2 );
+        break;
+    case 0x1fb:
+        HandleAudioData( p_demux, p_anc + 6, i_data_count, 3 );
+        break;
+    case 0x2f9:
+        HandleAudioData( p_demux, p_anc + 6, i_data_count, 4 );
+        break;
+
+    case 0x1ef:
+        HandleAudioConfig( p_demux, p_anc + 6, i_data_count, 1 );
+        break;
+    case 0x2ee:
+        HandleAudioConfig( p_demux, p_anc + 6, i_data_count, 2 );
+        break;
+    case 0x2ed:
+        HandleAudioConfig( p_demux, p_anc + 6, i_data_count, 3 );
+        break;
+    case 0x1ec:
+        HandleAudioConfig( p_demux, p_anc + 6, i_data_count, 4 );
+        break;
+
+    /* Extended data packets, same order */
+    case 0x1fe:
+    case 0x2fc:
+    case 0x2fa:
+    case 0x1f8:
+
+    default:
+        break;
+
+    case 0x88: /* non-conforming ANC packet */
+        p_anc += 7;
+        i_size -= 7;
+        while ( i_size >= 7 && (p_anc[0] != 0x0 || p_anc[1] != 0x3ff
+                                 || p_anc[2] != 0x3ff) )
+        {
+            p_anc++;
+            i_size--;
+        }
+        if ( i_size >= 7 )
+            HandleAncillary( p_demux, p_anc, i_size );
+        return;
+    }
+
+    return HandleAncillary( p_demux, p_anc + i_data_count + 6,
+                            i_size - i_data_count - 6 );
+
+}
+
+static int HandleSDBuffer( demux_t *p_demux, uint8_t *p_buffer,
+                           unsigned int i_buffer_size )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    const uint8_t *p_parser = p_buffer;
+    const uint8_t *p_end = p_parser + i_buffer_size;
+    const uint8_t *p_line;
+
+    if ( p_sys->i_state != STATE_SYNC
+          && p_sys->i_last_state_change < mdate() - RESYNC_TIMEOUT )
+    {
+        p_sys->i_state = STATE_NOSYNC;
+        p_sys->i_last_state_change = mdate();
+        return VLC_EGENERIC;
+    }
+
+    switch ( p_sys->i_state )
+    {
+    case STATE_NOSYNC:
+    default:
+        p_parser = FindReferenceCode( FIELD_2_VBLANK_SAV, p_parser, p_end );
+        if ( p_parser == NULL )
+            break;
+        p_sys->i_state = STATE_STARTSYNC;
+        p_sys->i_last_state_change = mdate();
+
+    case STATE_STARTSYNC:
+        p_parser = FindReferenceCode( FIELD_1_VBLANK_EAV, p_parser, p_end );
+        if ( p_parser == NULL )
+            break;
+        p_sys->i_anc_size = 0;
+        p_sys->i_state = STATE_ANCSYNC;
+        p_sys->i_last_state_change = mdate();
+
+    case STATE_ANCSYNC:
+        p_parser = CountReference( &p_sys->i_anc_size,
+                                   FIELD_1_VBLANK_SAV, p_parser, p_end );
+        if ( p_parser == NULL )
+            break;
+        p_sys->i_active_size = 0;
+        p_sys->i_state = STATE_LINESYNC;
+        p_sys->i_last_state_change = mdate();
+
+    case STATE_LINESYNC:
+        p_parser = CountReference( &p_sys->i_active_size,
+                                   FIELD_1_VBLANK_EAV, p_parser, p_end );
+        if ( p_parser == NULL )
+            break;
+        p_sys->i_picture_size = p_sys->i_anc_size + p_sys->i_active_size;
+        p_sys->i_state = STATE_ACTIVESYNC;
+        p_sys->i_last_state_change = mdate();
+
+    case STATE_ACTIVESYNC:
+        p_parser = CountReference( &p_sys->i_picture_size,
+                                   FIELD_1_ACTIVE_EAV, p_parser, p_end );
+        if ( p_parser == NULL )
+            break;
+        p_sys->i_line_offset = p_sys->i_picture_size
+                             / (p_sys->i_anc_size + p_sys->i_active_size);
+        p_sys->i_state = STATE_VBLANKSYNC;
+        p_sys->i_last_state_change = mdate();
+
+    case STATE_VBLANKSYNC:
+        p_parser = CountReference( &p_sys->i_picture_size,
+                                   FIELD_2_ACTIVE_EAV, p_parser, p_end );
+        if ( p_parser == NULL )
+            break;
+        p_sys->i_state = STATE_PICSYNC;
+        p_sys->i_last_state_change = mdate();
+
+    case STATE_PICSYNC:
+        p_parser = CountReference( &p_sys->i_picture_size,
+                                   FIELD_1_VBLANK_EAV, p_parser, p_end );
+        if ( p_parser == NULL )
+            break;
+
+        if ( p_sys->i_picture_size
+              % (p_sys->i_anc_size + p_sys->i_active_size) )
+        {
+            msg_Warn( p_demux, "wrong picture size (anc=%d active=%d total=%d offset=%d), syncing",
+                 p_sys->i_anc_size, p_sys->i_active_size,
+                 p_sys->i_picture_size, p_sys->i_line_offset + 1 );
+            p_sys->i_state = STATE_NOSYNC;
+            p_sys->i_last_state_change = mdate();
+            break;
+        }
+
+        p_sys->i_nb_lines = p_sys->i_picture_size
+                             / (p_sys->i_anc_size + p_sys->i_active_size);
+        InitVideo( p_demux );
+        msg_Dbg( p_demux,
+                 "acquired sync, anc=%d active=%d lines=%d offset=%d",
+                 p_sys->i_anc_size, p_sys->i_active_size,
+                 p_sys->i_nb_lines, p_sys->i_line_offset + 1 );
+        p_sys->i_state = STATE_SYNC;
+        StartDecode( p_demux );
+        p_sys->i_current_line = 0;
+        p_sys->p_line_buffer = malloc( p_sys->i_anc_size
+                                        + p_sys->i_active_size );
+        p_sys->i_line_buffer = 0;
+
+    case STATE_SYNC:
+        while ( (p_line = GetLine( p_demux, &p_parser, p_end )) != NULL )
+        {
+            bool b_field = p_sys->b_hd ? false :
+                (p_sys->i_current_line >= p_sys->i_nb_lines / 2);
+            unsigned int i_field_height = p_sys->b_hd ? p_sys->i_height :
+                p_sys->i_height / 2;
+            unsigned int i_field_line = b_field ?
+                p_sys->i_current_line - (p_sys->i_nb_lines + 1) / 2 :
+                p_sys->i_current_line;
+            bool b_vbi = i_field_line < p_sys->i_line_offset ||
+                i_field_line >= p_sys->i_line_offset + i_field_height;
+            unsigned int anc = p_sys->i_anc_size;
+
+            if ( p_line[0] != 0xff || p_line[1] != 0x3
+                  || p_line[2] != 0x0 || p_line[3] != 0x0
+                  || p_line[anc+0] != 0xff || p_line[anc+1] != 0x3
+                  || p_line[anc+2] != 0x0 || p_line[anc+3] != 0x0
+                  || (!b_field && b_vbi &&
+                      (p_line[4] != FIELD_1_VBLANK_EAV ||
+                       p_line[anc+4] != FIELD_1_VBLANK_SAV))
+                  || (!b_field && !b_vbi &&
+                      (p_line[4] != FIELD_1_ACTIVE_EAV ||
+                       p_line[anc+4] != FIELD_1_ACTIVE_SAV))
+                  || (b_field && b_vbi &&
+                      (p_line[4] != FIELD_2_VBLANK_EAV ||
+                       p_line[anc+4] != FIELD_2_VBLANK_SAV))
+                  || (b_field && !b_vbi &&
+                      (p_line[4] != FIELD_2_ACTIVE_EAV ||
+                       p_line[anc+4] != FIELD_2_ACTIVE_SAV)) )
+            {
+                msg_Warn( p_demux, "lost sync line:%u SAV:%x EAV:%x",
+                          p_sys->i_current_line + 1, p_line[4], p_line[anc+4] );
+                StopDecode( p_demux );
+                p_sys->i_state = STATE_NOSYNC;
+                p_sys->i_last_state_change = mdate();
+                break;
+            }
+
+            if ( HasAncillary( p_line + 5 ) )
+            {
+                /* HANC */
+                unsigned int i_anc_words = (p_sys->i_anc_size - 5) * 4 / 5;
+                uint16_t p_anc[i_anc_words];
+                UnpackAnc( p_line + 5, p_sys->i_anc_size - 5, p_anc );
+                HandleAncillary( p_demux, p_anc, i_anc_words );
+            }
+
+            if ( !b_vbi )
+            {
+                unsigned int i_active_field_line = i_field_line
+                                                    - p_sys->i_line_offset;
+                unsigned int i_active_line = b_field
+                                              + i_active_field_line * 2;
+                if ( !(i_active_field_line % 2) && !b_field )
+                    Unpack01( p_line + anc + 5, p_sys->i_active_size - 5,
+                              p_sys->p_y + p_sys->i_width * i_active_line,
+                              p_sys->p_u + (p_sys->i_width / 2)
+                               * (i_active_line / 2),
+                              p_sys->p_v + (p_sys->i_width / 2)
+                               * (i_active_line / 2) );
+                else if ( !(i_active_field_line % 2) )
+                    Unpack01( p_line + anc + 5, p_sys->i_active_size - 5,
+                              p_sys->p_y + p_sys->i_width * i_active_line,
+                              p_sys->p_u + (p_sys->i_width / 2)
+                               * (i_active_line / 2 + 1),
+                              p_sys->p_v + (p_sys->i_width / 2)
+                               * (i_active_line / 2 + 1) );
+                else if ( !b_field )
+                    Unpack2( p_line + anc + 5, p_sys->i_active_size - 5,
+                             p_sys->p_y + p_sys->i_width * i_active_line,
+                             p_sys->p_u + (p_sys->i_width / 2)
+                              * (i_active_line / 2 - 1),
+                             p_sys->p_v + (p_sys->i_width / 2)
+                              * (i_active_line / 2 - 1) );
+                else
+                    Unpack3( p_line + anc + 5, p_sys->i_active_size - 5,
+                             p_sys->p_y + p_sys->i_width * i_active_line,
+                             p_sys->p_u + (p_sys->i_width / 2)
+                              * (i_active_line / 2),
+                             p_sys->p_v + (p_sys->i_width / 2)
+                              * (i_active_line / 2) );
+
+                if ( p_sys->b_vbi && p_sys->i_height == 576
+                      && p_sys->i_current_line == p_sys->i_line_offset )
+                {
+                    /* Line 23 is half VBI, half active */
+                    UnpackVBI( p_line + anc + 5, p_sys->i_active_size - 5,
+                               p_sys->p_wss_buffer );
+                }
+            }
+            else if ( p_sys->b_vbi && p_sys->i_telx_count &&
+                      i_field_line >= p_sys->i_telx_line &&
+                      i_field_line < p_sys->i_telx_line
+                                      + p_sys->i_telx_count )
+            {
+                UnpackVBI( p_line + anc + 5, p_sys->i_active_size - 5,
+                    &p_sys->p_telx_buffer[i_field_line
+                        - p_sys->i_telx_line
+                        + b_field * p_sys->i_telx_count] );
+            }
+            else if ( b_vbi && HasAncillary( p_line + anc + 5 ) )
+            {
+                /* VANC */
+                unsigned int i_anc_words = (p_sys->i_active_size - 5) * 4 / 5;
+                uint16_t p_anc[i_anc_words];
+                UnpackAnc( p_line + 5, p_sys->i_active_size - 5,
+                           p_anc );
+                HandleAncillary( p_demux, p_anc, i_anc_words );
+            }
+
+            p_sys->i_current_line++;
+            if ( p_sys->i_current_line == p_sys->i_nb_lines )
+            {
+                p_sys->i_current_line %= p_sys->i_nb_lines;
+                DecodeFrame( p_demux );
+            }
+        }
+        break;
+    }
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Low-level device stuff
+ *****************************************************************************/
+#define MAXLEN 256
+
+static int ReadULSysfs( const char *psz_fmt, unsigned int i_link )
+{
+    char psz_file[MAXLEN], psz_data[MAXLEN];
+    char *psz_tmp;
+    int i_fd;
+    ssize_t i_ret;
+    unsigned int i_data;
+
+    snprintf( psz_file, sizeof(psz_file), psz_fmt, i_link );
+    psz_file[sizeof(psz_file) - 1] = '\0';
+
+    if ( (i_fd = open( psz_file, O_RDONLY )) < 0 )
+        return i_fd;
+
+    i_ret = read( i_fd, psz_data, sizeof(psz_data) );
+    close( i_fd );
+
+    if ( i_ret < 0 )
+        return i_ret;
+
+    i_data = strtoul( psz_data, &psz_tmp, 0 );
+    if ( *psz_tmp != '\n' )
+        return -1;
+
+    return i_data;
+}
+
+static ssize_t WriteULSysfs( const char *psz_fmt, unsigned int i_link,
+                             unsigned int i_buf )
+{
+    char psz_file[MAXLEN], psz_data[MAXLEN];
+    int i_fd;
+    ssize_t i_ret;
+
+    snprintf( psz_file, sizeof(psz_file), psz_fmt, i_link );
+    psz_file[sizeof(psz_file) - 1] = '\0';
+
+    snprintf( psz_data, sizeof(psz_data), "%u\n", i_buf );
+    psz_file[sizeof(psz_data) - 1] = '\0';
+
+    if ( (i_fd = open( psz_file, O_WRONLY )) < 0 )
+        return i_fd;
+
+    i_ret = write( i_fd, psz_data, strlen(psz_data) + 1 );
+    close( i_fd );
+    return i_ret;
+}
+
+static int InitCapture( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    const int i_page_size = getpagesize();
+    unsigned int i_bufmemsize;
+    unsigned int i;
+    char psz_dev[MAXLEN];
+
+    /* 10-bit mode or nothing */
+    if ( WriteULSysfs( SDI_MODE_FILE, p_sys->i_link, SDI_CTL_MODE_10BIT ) < 0 )
+    {
+        msg_Err( p_demux, "couldn't write file " SDI_MODE_FILE, p_sys->i_link );
+        return VLC_EGENERIC;
+    }
+
+    if ( (p_sys->i_buffers = ReadULSysfs( SDI_BUFFERS_FILE, p_sys->i_link ))
+          < 0 )
+    {
+        msg_Err( p_demux, "couldn't read file " SDI_BUFFERS_FILE,
+                 p_sys->i_link );
+        return VLC_EGENERIC;
+    }
+    p_sys->i_current_buffer = 0;
+
+    if ( (p_sys->i_buffer_size = ReadULSysfs( SDI_BUFSIZE_FILE, p_sys->i_link ))
+          < 0 )
+    {
+        msg_Err( p_demux, "couldn't read file " SDI_BUFSIZE_FILE,
+                 p_sys->i_link );
+        return VLC_EGENERIC;
+    }
+    if ( p_sys->i_buffer_size % 20 )
+    {
+        msg_Err( p_demux, "buffer size must be a multiple of 20" );
+        return VLC_EGENERIC;
+    }
+
+    snprintf( psz_dev, sizeof(psz_dev), SDI_DEVICE, p_sys->i_link );
+    psz_dev[sizeof(psz_dev) - 1] = '\0';
+    if ( (p_sys->i_fd = open( psz_dev, O_RDONLY ) ) < 0 )
+    {
+        msg_Err( p_demux, "couldn't open device %s", psz_dev );
+        return VLC_EGENERIC;
+    }
+
+    i_bufmemsize = ((p_sys->i_buffer_size + i_page_size - 1) / i_page_size)
+                     * i_page_size;
+    p_sys->pp_buffers = malloc( p_sys->i_buffers * sizeof(uint8_t *) );
+    for ( i = 0; i < p_sys->i_buffers; i++ )
+    {
+        if ( (p_sys->pp_buffers[i] = mmap( NULL, p_sys->i_buffer_size,
+                                           PROT_READ, MAP_SHARED, p_sys->i_fd,
+                                           i * i_bufmemsize )) == MAP_FAILED )
+        {
+            msg_Err( p_demux, "couldn't mmap(%d): %s", i, strerror(errno) );
+            return VLC_EGENERIC;
+        }
+    }
+
+    return VLC_SUCCESS;
+}
+
+static void CloseCapture( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    unsigned int i;
+
+    StopDecode( p_demux );
+    for ( i = 0; i < p_sys->i_buffers; i++ )
+        munmap( p_sys->pp_buffers[i], p_sys->i_buffer_size );
+    close( p_sys->i_fd );
+    free( p_sys->pp_buffers );
+}
+
+static int Capture( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    struct pollfd pfd;
+
+    pfd.fd = p_sys->i_fd;
+    pfd.events = POLLIN | POLLPRI;
+
+    if ( poll( &pfd, 1, READ_TIMEOUT ) < 0 )
+    {
+        msg_Warn( p_demux, "couldn't poll(): %s", strerror(errno) );
+        return VLC_EGENERIC;
+    }
+
+    if ( pfd.revents & POLLPRI )
+    {
+        unsigned int i_val;
+
+        if ( ioctl( p_sys->i_fd, SDI_IOC_RXGETEVENTS, &i_val ) < 0 )
+            msg_Warn( p_demux, "couldn't SDI_IOC_RXGETEVENTS %s", strerror(errno) );
+        else
+        {
+            if ( i_val & SDI_EVENT_RX_BUFFER )
+                msg_Warn( p_demux, "driver receive buffer queue overrun" );
+            if ( i_val & SDI_EVENT_RX_FIFO )
+                msg_Warn( p_demux, "onboard receive FIFO overrun");
+            if ( i_val & SDI_EVENT_RX_CARRIER )
+                msg_Warn( p_demux, "carrier status change");
+        }
+    }
+
+    if ( pfd.revents & POLLIN )
+    {
+        int i_ret;
+
+        if ( ioctl( p_sys->i_fd, SDI_IOC_DQBUF, p_sys->i_current_buffer ) < 0 )
+        {
+            msg_Warn( p_demux, "couldn't SDI_IOC_DQBUF %s", strerror(errno) );
+            return VLC_EGENERIC;
+        }
+
+        i_ret = HandleSDBuffer( p_demux,
+                                p_sys->pp_buffers[p_sys->i_current_buffer],
+                                p_sys->i_buffer_size );
+
+        if ( ioctl( p_sys->i_fd, SDI_IOC_QBUF, p_sys->i_current_buffer ) < 0 )
+        {
+            msg_Warn( p_demux, "couldn't SDI_IOC_QBUF %s", strerror(errno) );
+            return VLC_EGENERIC;
+        }
+
+        if ( i_ret == VLC_SUCCESS )
+        {
+            p_sys->i_current_buffer++;
+            p_sys->i_current_buffer %= p_sys->i_buffers;
+        }
+        else
+        {
+            /* Reference codes do not start on a multiple of 5. This sometimes
+             * happen. We really don't want to allow this. */
+            msg_Warn( p_demux, "resetting board" );
+            CloseCapture( p_demux );
+            InitCapture( p_demux );
+        }
+    }
+
+    return VLC_SUCCESS;
+}
-- 
1.7.0.4

