]> git.sesse.net Git - vlc/blobdiff - modules/demux/ty.c
Use Brackets for global headers.
[vlc] / modules / demux / ty.c
index f9e0514b6de647c78e3fcfd8ef868dea670a0171..9cd712af506f2bff056f3d76f09128471ea9f0cb 100644 (file)
  * Preamble
  *****************************************************************************/
 
-#include <vlc/vlc.h>
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
 #include <vlc_demux.h>
-#include "vlc_codec.h"
+#include <vlc_codec.h>
+#include <vlc_meta.h>
+#include <vlc_input.h>
 #include "../codec/cc.h"
 
+#include <assert.h>
+
 /*****************************************************************************
  * Module descriptor
  *****************************************************************************/
 static int  Open ( vlc_object_t * );
 static void Close( vlc_object_t * );
 
-vlc_module_begin();
-    set_shortname( _("TY") );
-    set_description(_("TY Stream audio/video demux"));
-    set_category( CAT_INPUT );
-    set_subcategory( SUBCAT_INPUT_DEMUX );
-    set_capability("demux2", 6);
+vlc_module_begin ()
+    set_shortname( N_("TY") )
+    set_description(N_("TY Stream audio/video demux"))
+    set_category( CAT_INPUT )
+    set_subcategory( SUBCAT_INPUT_DEMUX )
+    set_capability("demux", 6)
     /* FIXME: there seems to be a segfault when using PVR access
      * and TY demux has a bigger priority than PS
      * Something must be wrong.
      */
-    set_callbacks( Open, Close );
-    add_shortcut("ty");
-    add_shortcut("tivo");
-vlc_module_end();
+    set_callbacks( Open, Close )
+    add_shortcut("ty")
+    add_shortcut("tivo")
+vlc_module_end ()
 
 /*****************************************************************************
  * Local prototypes
@@ -107,10 +116,10 @@ static const uint8_t ty_AC3AudioPacket[] = { 0x00, 0x00, 0x01, 0xbd };
 typedef struct
 {
   long l_rec_size;
-  uint8_t ex1, ex2;
+  uint8_t ex[2];
   uint8_t rec_type;
   uint8_t subrec_type;
-  vlc_bool_t b_ext;
+  bool b_ext;
   uint64_t l_ty_pts;            /* TY PTS in the record header */
 } ty_rec_hdr_t;
 
@@ -141,6 +150,69 @@ typedef enum
     TIVO_AUDIO_MPEG
 } tivo_audio_t;
 
+#define XDS_MAX_DATA_SIZE (32)
+typedef enum
+{
+    XDS_CLASS_CURRENT        = 0,
+    XDS_CLASS_FUTURE         = 1,
+    XDS_CLASS_CHANNEL        = 2,
+    XDS_CLASS_MISCELLANEOUS  = 3,
+    XDS_CLASS_PUBLIC_SERVICE = 4,
+    XDS_CLASS_RESERVED       = 5,
+    XDS_CLASS_UNDEFINED      = 6,
+    XDS_CLASS_OTHER          = 7,
+
+    XDS_MAX_CLASS_COUNT
+} xds_class_t;
+typedef struct
+{
+    bool b_started;
+    int        i_data;
+    uint8_t    p_data[XDS_MAX_DATA_SIZE];
+    int        i_sum;
+} xds_packet_t;
+typedef enum
+{
+    XDS_META_PROGRAM_RATING_NONE,
+    XDS_META_PROGRAM_RATING_MPAA,
+    XDS_META_PROGRAM_RATING_TPG,
+    /* TODO add CA/CE rating */
+} xds_meta_program_rating_t;
+typedef struct
+{
+    char *psz_name;
+    xds_meta_program_rating_t rating;
+    char *psz_rating;
+    /* Add the other fields once I have the samples */
+} xds_meta_program_t;
+typedef struct
+{
+    char *psz_channel_name;
+    char *psz_channel_call_letter;
+    char *psz_channel_number;
+
+    xds_meta_program_t  current;
+    xds_meta_program_t  future;
+} xds_meta_t;
+typedef struct
+{
+    /* Are we in XDS mode */
+    bool b_xds;
+
+    /* Current class type */
+    xds_class_t i_class;
+    int         i_type;
+    bool  b_future;
+
+    /* */
+    xds_packet_t pkt[XDS_MAX_CLASS_COUNT][128]; /* XXX it is way too much, but simpler */
+
+    /* */
+    bool  b_meta_changed;
+    xds_meta_t  meta;
+
+} xds_t;
+
 struct demux_sys_t
 {
   es_out_id_t *p_video;               /* ptr to video codec */
@@ -149,12 +221,14 @@ struct demux_sys_t
   cc_data_t   cc;
   es_out_id_t *p_cc[4];
 
+  xds_t       xds;
+
   int             i_cur_chunk;
   int             i_stuff_cnt;
   size_t          i_stream_size;      /* size of input stream (if known) */
   //uint64_t        l_program_len;      /* length of this stream in msec */
-  vlc_bool_t      b_seekable;         /* is this stream seekable? */
-  vlc_bool_t      b_have_master;      /* are master chunks present? */
+  bool      b_seekable;         /* is this stream seekable? */
+  bool      b_have_master;      /* are master chunks present? */
   tivo_type_t     tivo_type;          /* tivo type (SA / DTiVo) */
   tivo_series_t   tivo_series;        /* Series1 or Series2 */
   tivo_audio_t    audio_type;         /* AC3 or MPEG */
@@ -167,8 +241,8 @@ struct demux_sys_t
   //mtime_t         l_last_ty_pts_sync; /* audio PTS at time of last TY PTS */
   uint64_t        l_first_ty_pts;     /* first TY PTS in this master chunk */
   uint64_t        l_final_ty_pts;     /* final TY PTS in this master chunk */
-  int             i_seq_table_size;   /* number of entries in SEQ table */
-  int             i_bits_per_seq_entry; /* # of bits in SEQ table bitmask */
+  unsigned        i_seq_table_size;   /* number of entries in SEQ table */
+  unsigned        i_bits_per_seq_entry; /* # of bits in SEQ table bitmask */
 
   mtime_t         firstAudioPTS;
   mtime_t         lastAudioPTS;
@@ -179,8 +253,8 @@ struct demux_sys_t
   int             i_num_recs;         /* number of recs in this chunk */
   int             i_seq_rec;          /* record number where seq start is */
   ty_seq_table_t  *seq_table;         /* table of SEQ entries from mstr chk */
-  vlc_bool_t      eof;
-  vlc_bool_t      b_first_chunk;
+  bool      eof;
+  bool      b_first_chunk;
 };
 
 static int get_chunk_header(demux_t *);
@@ -190,7 +264,7 @@ static int find_es_header( const uint8_t *header,
 static int ty_stream_seek_pct(demux_t *p_demux, double seek_pct);
 static int ty_stream_seek_time(demux_t *, uint64_t);
 
-static ty_rec_hdr_t *parse_chunk_headers( demux_t *p_demux, const uint8_t *p_buf,
+static ty_rec_hdr_t *parse_chunk_headers( const uint8_t *p_buf,
                                           int i_num_recs, int *pi_payload_size);
 static int probe_stream(demux_t *p_demux);
 static void analyze_chunk(demux_t *p_demux, const uint8_t *p_chunk);
@@ -202,6 +276,11 @@ static int DemuxRecCc( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_block
 
 static void DemuxDecodeXds( demux_t *p_demux, uint8_t d1, uint8_t d2 );
 
+static void XdsInit( xds_t * );
+static void XdsExit( xds_t * );
+
+#define TY_ES_GROUP (1)
+
 /*
  * Open: check file and initialize demux structures
  *
@@ -230,8 +309,8 @@ static int Open(vlc_object_t *p_this)
          U32_AT(&p_peek[8]) != CHUNK_SIZE )
     {
         if( !p_demux->b_force &&
-            !demux2_IsPathExtension( p_demux, ".ty" ) &&
-            !demux2_IsPathExtension( p_demux, ".ty+" ) )
+            !demux_IsPathExtension( p_demux, ".ty" ) &&
+            !demux_IsPathExtension( p_demux, ".ty+" ) )
             return VLC_EGENERIC;
         msg_Warn( p_demux, "this does not look like a TY file, "
                            "continuing anyway..." );
@@ -249,7 +328,7 @@ static int Open(vlc_object_t *p_this)
     memset(p_sys, 0, sizeof(demux_sys_t));
 
     /* set up our struct (most were zero'd out with the memset above) */
-    p_sys->b_first_chunk = VLC_TRUE;
+    p_sys->b_first_chunk = true;
     p_sys->b_have_master = (U32_AT(p_peek) == TIVO_PES_FILEID);
     p_sys->firstAudioPTS = -1;
     p_sys->i_stream_size = stream_Size(p_demux->s);
@@ -273,14 +352,16 @@ static int Open(vlc_object_t *p_this)
 
     /* register the proper audio codec */
     if (p_sys->audio_type == TIVO_AUDIO_MPEG) {
-        es_format_Init( &fmt, AUDIO_ES, VLC_FOURCC( 'm', 'p', 'g', 'a' ) );
+        es_format_Init( &fmt, AUDIO_ES, VLC_CODEC_MPGA );
     } else {
-        es_format_Init( &fmt, AUDIO_ES, VLC_FOURCC( 'a', '5', '2', ' ' ) );
+        es_format_Init( &fmt, AUDIO_ES, VLC_CODEC_A52 );
     }
+    fmt.i_group = TY_ES_GROUP;
     p_sys->p_audio = es_out_Add( p_demux->out, &fmt );
 
     /* register the video stream */
-    es_format_Init( &fmt, VIDEO_ES, VLC_FOURCC( 'm', 'p', 'g', 'v' ) );
+    es_format_Init( &fmt, VIDEO_ES, VLC_CODEC_MPGV );
+    fmt.i_group = TY_ES_GROUP;
     p_sys->p_video = es_out_Add( p_demux->out, &fmt );
 
     /* */
@@ -288,6 +369,8 @@ static int Open(vlc_object_t *p_this)
         p_sys->p_cc[i] = NULL;
     cc_Init( &p_sys->cc );
 
+    XdsInit( &p_sys->xds );
+
     return VLC_SUCCESS;
 }
 
@@ -422,7 +505,7 @@ static int Control(demux_t *p_demux, int i_query, va_list args)
         if( ( i64 = p_sys->i_stream_size ) > 0 )
         {
             pf = (double*) va_arg( args, double* );
-            *pf = (double)stream_Tell( p_demux->s ) / (double) i64;
+            *pf = ((double)1.0) * stream_Tell( p_demux->s ) / (double) i64;
             return VLC_SUCCESS;
         }
         return VLC_EGENERIC;
@@ -462,10 +545,10 @@ static void Close( vlc_object_t *p_this )
     demux_t *p_demux = (demux_t*)p_this;
     demux_sys_t *p_sys = p_demux->p_sys;
 
+    XdsExit( &p_sys->xds );
     cc_Exit( &p_sys->cc );
     free( p_sys->rec_hdrs );
-    if( p_sys->seq_table )
-        free( p_sys->seq_table );
+    free( p_sys->seq_table );
     free(p_sys);
 }
 
@@ -524,7 +607,7 @@ static int check_sync_pes( demux_t *p_demux, block_t *p_block,
         if( offset < 0 )
         {
             /* no header found, fake some 00's (this works, believe me) */
-            memset( p_sys->pes_buffer, 4, 0 );
+            memset( p_sys->pes_buffer, 0, 4 );
             p_sys->i_pes_buf_cnt = 4;
             if( rec_len > 4 )
                 msg_Err( p_demux, "PES header not found in record of %d bytes!",
@@ -610,7 +693,7 @@ static int DemuxRecVideo( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_bl
                 //subrec_type, esOffset1);
             p_sys->lastVideoPTS = get_pts(
                     &p_block_in->p_buffer[ esOffset1 + VIDEO_PTS_OFFSET ] );
-            /*msg_Dbg(p_demux, "Video rec %d PTS "I64Fd, p_sys->i_cur_rec,
+            /*msg_Dbg(p_demux, "Video rec %d PTS %"PRId64, p_sys->i_cur_rec,
                         p_sys->lastVideoPTS );*/
             if (subrec_type != 0x06) {
                 /* if we found a PES, and it's not type 6, then we're S2 */
@@ -697,7 +780,8 @@ static int DemuxRecVideo( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_bl
             continue;
 
         es_format_Init( &fmt, SPU_ES, fcc[i] );
-        fmt.psz_description = strdup( _(ppsz_description[i]) );
+        fmt.psz_description = strdup( vlc_gettext(ppsz_description[i]) );
+        fmt.i_group = TY_ES_GROUP;
         p_sys->p_cc[i] = es_out_Add( p_demux->out, &fmt );
         es_format_Clean( &fmt );
 
@@ -705,24 +789,17 @@ static int DemuxRecVideo( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_bl
     /* Send the CC data */
     if( p_block_in->i_pts > 0 && p_sys->cc.i_data > 0 )
     {
-        int i_cc_count;
-
-        block_t *p_cc = block_New( p_demux, p_sys->cc.i_data );
-        p_cc->i_flags |= BLOCK_FLAG_TYPE_I;
-        p_cc->i_pts = p_block_in->i_pts;
-        memcpy( p_cc->p_buffer, p_sys->cc.p_data, p_sys->cc.i_data );
-
-        for( i = 0, i_cc_count = 0; i < 4; i++ )
-            i_cc_count += p_sys->p_cc[i] ? 1 : 0;
-
         for( i = 0; i < 4; i++ )
         {
-            if( !p_sys->p_cc[i] )
-                continue;
-            if( i_cc_count > 1 )
-                es_out_Send( p_demux->out, p_sys->p_cc[i], block_Duplicate( p_cc ) );
-            else
+            if( p_sys->p_cc[i] )
+            {
+                block_t *p_cc = block_New( p_demux, p_sys->cc.i_data );
+                p_cc->i_flags |= BLOCK_FLAG_TYPE_I;
+                p_cc->i_pts = p_block_in->i_pts;
+                memcpy( p_cc->p_buffer, p_sys->cc.p_data, p_sys->cc.i_data );
+
                 es_out_Send( p_demux->out, p_sys->p_cc[i], p_cc );
+            }
         }
         cc_Flush( &p_sys->cc );
     }
@@ -744,10 +821,10 @@ static int DemuxRecAudio( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_bl
         return -1;
 #if 0
         int i;
-        printf( "Audio Packet Header " );
+        fprintf( stderr, "Audio Packet Header " );
         for( i = 0 ; i < 24 ; i++ )
-            printf( "%2.2x ", p_block_in->p_buffer[i] );
-        printf( "\n" );
+            fprintf( stderr, "%2.2x ", p_block_in->p_buffer[i] );
+        fprintf( stderr, "\n" );
 #endif
 
     if( subrec_type == 2 )
@@ -939,7 +1016,6 @@ static int DemuxRecCc( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_block
 {
     demux_sys_t *p_sys = p_demux->p_sys;
     int i_field;
-    int i_channel;
 
     if( p_block_in )
         block_Release(p_block_in);
@@ -953,19 +1029,12 @@ static int DemuxRecCc( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_block
 
     /* XDS data (extract programs infos) transmitted on field 2 only */
     if( i_field == 1 )
-        DemuxDecodeXds( p_demux, rec_hdr->ex1, rec_hdr->ex2 );
+        DemuxDecodeXds( p_demux, rec_hdr->ex[0], rec_hdr->ex[1] );
 
     if( p_sys->cc.i_data + 3 > CC_MAX_DATA_SIZE )
         return 0;
 
-    p_sys->cc.p_data[p_sys->cc.i_data+0] = i_field;
-    p_sys->cc.p_data[p_sys->cc.i_data+1] = rec_hdr->ex1;
-    p_sys->cc.p_data[p_sys->cc.i_data+2] = rec_hdr->ex2;
-    p_sys->cc.i_data += 3;
-
-    i_channel = cc_Channel( i_field, &p_sys->cc.p_data[p_sys->cc.i_data+1] );
-    if( i_channel >= 0 && i_channel < 4 )
-        p_sys->cc.pb_present[i_channel] = VLC_TRUE;
+    cc_AppendData( &p_sys->cc, i_field, rec_hdr->ex );
     return 0;
 }
 
@@ -974,8 +1043,9 @@ static int ty_stream_seek_pct(demux_t *p_demux, double seek_pct)
 {
     demux_sys_t *p_sys = p_demux->p_sys;
     int64_t seek_pos = p_sys->i_stream_size * seek_pct;
-    int i, i_cur_part;
     long l_skip_amt;
+    int i;
+    unsigned i_cur_part;
 
     /* if we're not seekable, there's nothing to do */
     if (!p_sys->b_seekable)
@@ -1006,7 +1076,7 @@ static int ty_stream_seek_pct(demux_t *p_demux, double seek_pct)
     /* seek within the chunk to get roughly to where we want */
     p_sys->i_cur_rec = (int)
       ((double) ((seek_pos % CHUNK_SIZE) / (double) (CHUNK_SIZE)) * p_sys->i_num_recs);
-    msg_Dbg(p_demux, "Seeked to file pos " I64Fd, seek_pos);
+    msg_Dbg(p_demux, "Seeked to file pos %"PRId64, seek_pos);
     msg_Dbg(p_demux, " (chunk %d, record %d)",
              p_sys->i_cur_chunk - 1, p_sys->i_cur_rec);
 
@@ -1020,13 +1090,373 @@ static int ty_stream_seek_pct(demux_t *p_demux, double seek_pct)
 
     /* to hell with syncing any audio or video, just start reading records... :) */
     /*p_sys->lastAudioPTS = p_sys->lastVideoPTS = 0;*/
-    es_out_Control( p_demux->out, ES_OUT_RESET_PCR );
     return VLC_SUCCESS;
 }
 
+/* XDS decoder */
+//#define TY_XDS_DEBUG
+static void XdsInit( xds_t *h )
+{
+    int i, j;
+
+    h->b_xds = false;
+    h->i_class = XDS_MAX_CLASS_COUNT;
+    h->i_type = 0;
+    h->b_future = false;
+    for( i = 0; i < XDS_MAX_CLASS_COUNT; i++ )
+    {
+        for( j = 0; j < 128; j++ )
+            h->pkt[i][j].b_started = false;
+    }
+    h->b_meta_changed = false;
+    memset( &h->meta, 0, sizeof(h->meta) );
+}
+static void XdsExit( xds_t *h )
+{
+    /* */
+    free( h->meta.psz_channel_name );
+    free( h->meta.psz_channel_call_letter );
+    free( h->meta.psz_channel_number );
+
+    /* */
+    free( h->meta.current.psz_name );
+    free( h->meta.current.psz_rating );
+    /* */
+    free( h->meta.future.psz_name );
+    free( h->meta.future.psz_rating );
+}
+static void XdsStringUtf8( char dst[2*32+1], const uint8_t *p_src, int i_src )
+{
+    int i;
+    int i_dst;
+
+    for( i = 0, i_dst = 0; i < i_src; i++ )
+    {
+        switch( p_src[i] )
+        {
+#define E2( c, u1, u2 ) case c: dst[i_dst++] = u1; dst[i_dst++] = u2; break
+        E2( 0x2a, 0xc3,0xa1); // lowercase a, acute accent
+        E2( 0x5c, 0xc3,0xa9); // lowercase e, acute accent
+        E2( 0x5e, 0xc3,0xad); // lowercase i, acute accent
+        E2( 0x5f, 0xc3,0xb3); // lowercase o, acute accent
+        E2( 0x60, 0xc3,0xba); // lowercase u, acute accent
+        E2( 0x7b, 0xc3,0xa7); // lowercase c with cedilla
+        E2( 0x7c, 0xc3,0xb7); // division symbol
+        E2( 0x7d, 0xc3,0x91); // uppercase N tilde
+        E2( 0x7e, 0xc3,0xb1); // lowercase n tilde
+#undef E2
+        default:
+            dst[i_dst++] = p_src[i];
+            break;
+        }
+    }
+    dst[i_dst++] = '\0';
+}
+static bool XdsChangeString( xds_t *h, char **ppsz_dst, const char *psz_new )
+{
+    if( *ppsz_dst && psz_new && !strcmp( *ppsz_dst, psz_new ) )
+        return false;
+    if( *ppsz_dst == NULL && psz_new == NULL )
+        return false;
+
+    free( *ppsz_dst );
+    if( psz_new )
+        *ppsz_dst = strdup( psz_new );
+    else
+        *ppsz_dst = NULL;
+
+    h->b_meta_changed = true;
+    return true;
+}
+
+static void XdsDecodeCurrentFuture( xds_t *h, xds_packet_t *pk )
+{
+    xds_meta_program_t *p_prg = h->b_future ? &h->meta.future : &h->meta.current;
+    char name[2*32+1];
+    int i_rating;
+
+    switch( h->i_type )
+    {
+    case 0x03:
+        XdsStringUtf8( name, pk->p_data, pk->i_data );
+        if( XdsChangeString( h, &p_prg->psz_name, name ) )
+        {
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Program Name) %d'\n", pk->i_data );
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> program name %s\n", name );
+        }
+        break;
+    case 0x05:
+        i_rating = (pk->p_data[0] & 0x18);
+        if( i_rating == 0x08 )
+        {
+            /* TPG */
+            static const char *pppsz_ratings[8][2] = {
+                { "None",   "No rating (no content advisory)" },
+                { "TV-Y",   "All Children (no content advisory)" },
+                { "TV-Y7",  "Directed to Older Children (V = Fantasy Violence)" },
+                { "TV-G",   "General Audience (no content advisory)" },
+                { "TV-PG",  "Parental Guidance Suggested" },
+                { "TV-14",  "Parents Strongly Cautioned" },
+                { "TV-MA",  "Mature Audience Only" },
+                { "None",   "No rating (no content advisory)" }
+            };
+            p_prg->rating = XDS_META_PROGRAM_RATING_TPG;
+            if( XdsChangeString( h, &p_prg->psz_rating, pppsz_ratings[pk->p_data[1]&0x07][0] ) )
+            {
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Rating) %d'\n", pk->i_data );
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> TPG Rating %s (%s)\n",
+                //         pppsz_ratings[pk->p_data[1]&0x07][0], pppsz_ratings[pk->p_data[1]&0x07][1] );
+            }
+        }
+        else if( i_rating == 0x00 || i_rating == 0x10 )
+        {
+            /* MPAA */
+            static const char *pppsz_ratings[8][2] = {
+                { "N/A",    "N/A" },
+                { "G",      "General Audiences" },
+                { "PG",     "Parental Guidance Suggested" },
+                { "PG-13",  "Parents Strongly Cautioned" },
+                { "R",      "Restricted" },
+                { "NC-17",  "No one 17 and under admitted" },
+                { "X",      "No one under 17 admitted" },
+                { "NR",     "Not Rated" },
+            };
+            p_prg->rating = XDS_META_PROGRAM_RATING_MPAA;
+            if( XdsChangeString( h, &p_prg->psz_rating, pppsz_ratings[pk->p_data[0]&0x07][0] ) )
+            {
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Rating) %d'\n", pk->i_data );
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> TPG Rating %s (%s)\n",
+                //         pppsz_ratings[pk->p_data[0]&0x07][0], pppsz_ratings[pk->p_data[0]&0x07][1] );
+            }
+        }
+        else
+        {
+            /* Non US Rating TODO */
+            assert( i_rating == 0x18 ); // only left value possible */
+            p_prg->rating = XDS_META_PROGRAM_RATING_NONE;
+            if( XdsChangeString( h, &p_prg->psz_rating, NULL ) )
+            {
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Rating) %d'\n", pk->i_data );
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> 0x%2.2x 0x%2.2x\n", pk->p_data[0], pk->p_data[1] );
+            }
+        }
+        break;
+
+    default:
+#ifdef TY_XDS_DEBUG
+        fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Unknown 0x%x)'\n", h->i_type );
+#endif
+        break;
+    }
+}
+
+static void XdsDecodeChannel( xds_t *h, xds_packet_t *pk )
+{
+    char name[2*32+1];
+    char chan[2*32+1];
+
+    switch( h->i_type )
+    {
+    case 0x01:
+        if( pk->i_data < 2 )
+            return;
+        XdsStringUtf8( name, pk->p_data, pk->i_data );
+        if( XdsChangeString( h, &h->meta.psz_channel_name, name ) )
+        {
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Network Name) %d'\n", pk->i_data );
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> %s\n", name );
+        }
+        break;
+
+    case 0x02:
+        if( pk->i_data < 4 )
+            return;
+
+        XdsStringUtf8( name, pk->p_data, 4 );
+        if( XdsChangeString( h, &h->meta.psz_channel_call_letter, name ) )
+        {
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Network Call Letter)' %d\n", pk->i_data );
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> call letter %s\n", name );
+        }
+        if( pk->i_data >= 6 )
+        {
+            XdsStringUtf8( chan, &pk->p_data[4], 2 );
+            if( XdsChangeString( h, &h->meta.psz_channel_number, chan ) )
+            {
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Network Call Letter)' %d\n", pk->i_data );
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> channel number %s\n", chan );
+            }
+        }
+        else
+        {
+            if( XdsChangeString( h, &h->meta.psz_channel_number, NULL ) )
+            {
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Network Call Letter)' %d\n", pk->i_data );
+                //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> no channel number letter anymore\n" );
+            }
+        }
+        break;
+    case 0x03:
+        //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Channel Tape Delay)'\n" );
+        break;
+    case 0x04:
+        //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Transmission Signal Identifier)'\n" );
+        break;
+    default:
+#ifdef TY_XDS_DEBUG
+        fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Unknown 0x%x)'\n", h->i_type );
+#endif
+        break;
+    }
+}
+
+static void XdsDecode( xds_t *h, xds_packet_t *pk )
+{
+    switch( h->i_class )
+    {
+    case XDS_CLASS_CURRENT:
+    case XDS_CLASS_FUTURE:
+        XdsDecodeCurrentFuture( h, pk );
+        break;
+    case XDS_CLASS_CHANNEL:
+        XdsDecodeChannel( h, pk );
+        break;
+    case XDS_CLASS_MISCELLANEOUS:
+#ifdef TY_XDS_DEBUG
+        fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Miscellaneous'\n" );
+#endif
+        break;
+    case XDS_CLASS_PUBLIC_SERVICE:
+#ifdef TY_XDS_DEBUG
+        fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Public Service'\n" );
+#endif
+        break;
+    default:
+        //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: unknown class\n" );
+        break;
+    }
+}
+
+static void XdsParse( xds_t *h, uint8_t d1, uint8_t d2 )
+{
+    /* TODO check parity */
+    d1 &= 0x7f;
+    d2 &= 0x7f;
+
+    /* */
+    if( d1 >= 0x01 && d1 <= 0x0e )
+    {
+        const xds_class_t i_class = ( d1 - 1 ) >> 1;
+        const int i_type = d2;
+        const bool b_start = d1 & 0x01;
+        xds_packet_t *pk = &h->pkt[i_class][i_type];
+
+        if( !b_start && !pk->b_started )
+        {
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS Continuying a non started packet, ignoring\n" );
+            h->b_xds = false;
+            return;
+        }
+
+        h->b_xds = true;
+        h->i_class = i_class;
+        h->i_type  = i_type;
+        h->b_future = !b_start;
+        pk->b_started = true;
+        if( b_start )
+        {
+            pk->i_data = 0;
+            pk->i_sum = d1 + d2;
+        }
+    }
+    else if( d1 == 0x0f && h->b_xds )
+    {
+        xds_packet_t *pk = &h->pkt[h->i_class][h->i_type];
+
+        /* TODO checksum and decode */
+        pk->i_sum += d1 + d2;
+        if( pk->i_sum & 0x7f )
+        {
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS invalid checksum, ignoring ---------------------------------\n" );
+            pk->b_started = false;
+            return;
+        }
+        if( pk->i_data <= 0 )
+        {
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS empty packet, ignoring ---------------------------------\n" );
+            pk->b_started = false;
+            return;
+        }
+
+        //if( pk->p_data[pk->i_data-1] == 0x40 ) /* Padding byte */
+        //    pk->i_data--;
+        XdsDecode( h, pk );
+
+        /* Reset it */
+        pk->b_started = false;
+    }
+    else if( d1 >= 0x20 && h->b_xds )
+    {
+        xds_packet_t *pk = &h->pkt[h->i_class][h->i_type];
+
+        if( pk->i_data+2 > XDS_MAX_DATA_SIZE )
+        {
+            /* Broken -> reinit */
+            //fprintf( stderr, "xxxxxxxxxxxxxxxXDS broken, reset\n" );
+            h->b_xds = false;
+            pk->b_started = false;
+            return;
+        }
+        /* TODO check parity bit */
+        pk->p_data[pk->i_data++] = d1 & 0x7f;
+        pk->p_data[pk->i_data++] = d2 & 0x7f;
+        pk->i_sum += d1+d2;
+    }
+    else
+    {
+        h->b_xds = false;
+    }
+}
+
 static void DemuxDecodeXds( demux_t *p_demux, uint8_t d1, uint8_t d2 )
 {
-    /* TODO */
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    XdsParse( &p_demux->p_sys->xds, d1, d2 );
+    if( p_demux->p_sys->xds.b_meta_changed )
+    {
+        xds_meta_t *m = &p_sys->xds.meta;
+        vlc_meta_t *p_meta;
+        vlc_epg_t *p_epg;
+
+        /* Channel meta data */
+        p_meta = vlc_meta_New();
+        if( m->psz_channel_name )
+            vlc_meta_SetPublisher( p_meta, m->psz_channel_name );
+        if( m->psz_channel_call_letter )
+            vlc_meta_SetTitle( p_meta, m->psz_channel_call_letter );
+        if( m->psz_channel_number )
+            vlc_meta_AddExtra( p_meta, "Channel number", m->psz_channel_number );
+        es_out_Control( p_demux->out, ES_OUT_SET_GROUP_META, TY_ES_GROUP, p_meta );
+        vlc_meta_Delete( p_meta );
+
+        /* Event meta data (current/future) */
+        p_epg = vlc_epg_New( NULL );
+        if( m->current.psz_name )
+        {
+            vlc_epg_AddEvent( p_epg, 0, 0, m->current.psz_name, NULL, NULL );
+            //if( m->current.psz_rating )
+            //  TODO but VLC cannot yet handle rating per epg event
+            vlc_epg_SetCurrent( p_epg, 0 );
+        }
+        if( m->future.psz_name )
+        {
+        }
+        if( p_epg->i_event > 0 )
+            es_out_Control( p_demux->out, ES_OUT_SET_GROUP_EPG, TY_ES_GROUP, p_epg );
+        vlc_epg_Delete( p_epg );
+    }
+    p_demux->p_sys->xds.b_meta_changed = false;
 }
 
 /* seek to an exact time position within the stream, if possible.
@@ -1035,10 +1465,11 @@ static void DemuxDecodeXds( demux_t *p_demux, uint8_t d1, uint8_t d2 )
 static int ty_stream_seek_time(demux_t *p_demux, uint64_t l_seek_time)
 {
     demux_sys_t *p_sys = p_demux->p_sys;
-    int i, i_seq_entry = 0;
+    int i_seq_entry = 0;
     int i_skip_cnt;
+    unsigned i;
     long l_cur_pos = stream_Tell(p_demux->s);
-    int i_cur_part = l_cur_pos / TIVO_PART_LENGTH;
+    unsigned i_cur_part = l_cur_pos / TIVO_PART_LENGTH;
     long l_seek_secs = l_seek_time / 1000000000;
     uint64_t l_fwd_stamp = 1;
 
@@ -1161,8 +1592,8 @@ static int ty_stream_seek_time(demux_t *p_demux, uint64_t l_seek_time)
        so we need to skip past any stream data prior to the seq_rec
        in this chunk */
     i_skip_cnt = 0;
-    for (i=0; i<p_sys->i_seq_rec; i++)
-        i_skip_cnt += p_sys->rec_hdrs[i].l_rec_size;
+    for (int j=0; j<p_sys->i_seq_rec; j++)
+        i_skip_cnt += p_sys->rec_hdrs[j].l_rec_size;
     stream_Read(p_demux->s, NULL, i_skip_cnt);
     p_sys->i_cur_rec = p_sys->i_seq_rec;
     //p_sys->l_last_ty_pts = p_sys->rec_hdrs[p_sys->i_seq_rec].l_ty_pts;
@@ -1179,7 +1610,7 @@ static void parse_master(demux_t *p_demux)
 {
     demux_sys_t *p_sys = p_demux->p_sys;
     uint8_t mst_buf[32];
-    int i, i_map_size;
+    uint32_t i, i_map_size;
     int64_t i_save_pos = stream_Tell(p_demux->s);
     int64_t i_pts_secs;
 
@@ -1190,8 +1621,7 @@ static void parse_master(demux_t *p_demux)
        entire table directly from the stream into memory in place. */
 
     /* clear the SEQ table */
-    if (p_sys->seq_table != NULL)
-        free(p_sys->seq_table);
+    free(p_sys->seq_table);
     
     /* parse header info */
     stream_Read(p_demux->s, mst_buf, 32);
@@ -1201,14 +1631,20 @@ static void parse_master(demux_t *p_demux)
     p_sys->i_seq_table_size = i / (8 + i_map_size);
 
     /* parse all the entries */
-    p_sys->seq_table = malloc(p_sys->i_seq_table_size * sizeof(ty_seq_table_t));
-    for (i=0; i<p_sys->i_seq_table_size; i++) {
-        stream_Read(p_demux->s, mst_buf, 8 + i_map_size);
+    p_sys->seq_table = calloc(p_sys->i_seq_table_size, sizeof(ty_seq_table_t));
+    if (p_sys->seq_table == NULL)
+    {
+        p_sys->i_seq_table_size = 0;
+        return;
+    }
+    for (unsigned i=0; i<p_sys->i_seq_table_size; i++) {
+        stream_Read(p_demux->s, mst_buf, 8);
         p_sys->seq_table[i].l_timestamp = U64_AT(&mst_buf[0]);
         if (i_map_size > 8) {
             msg_Err(p_demux, "Unsupported SEQ bitmap size in master chunk");
-            memset(p_sys->seq_table[i].chunk_bitmask, i_map_size, 0);
+            stream_Read(p_demux->s, NULL, i_map_size);
         } else {
+            stream_Read(p_demux->s, mst_buf + 8, i_map_size);
             memcpy(p_sys->seq_table[i].chunk_bitmask, &mst_buf[8], i_map_size);
         }
     }
@@ -1217,14 +1653,16 @@ static void parse_master(demux_t *p_demux)
     p_sys->l_first_ty_pts = p_sys->seq_table[0].l_timestamp;
     p_sys->l_final_ty_pts =
         p_sys->seq_table[p_sys->i_seq_table_size - 1].l_timestamp;
-    p_sys->b_have_master = VLC_TRUE;
+    p_sys->b_have_master = true;
 
     i_pts_secs = p_sys->l_first_ty_pts / 1000000000;
-    msg_Dbg( p_demux, "first TY pts in master is %02d:%02d:%02d",
-             (int)(i_pts_secs / 3600), (int)((i_pts_secs / 60) % 60), (int)(i_pts_secs % 60) );
+    msg_Dbg( p_demux,
+             "first TY pts in master is %02"PRId64":%02"PRId64":%02"PRId64,
+             i_pts_secs / 3600, (i_pts_secs / 60) % 60, i_pts_secs % 60 );
     i_pts_secs = p_sys->l_final_ty_pts / 1000000000;
-    msg_Dbg( p_demux, "final TY pts in master is %02d:%02d:%02d",
-             (int)(i_pts_secs / 3600), (int)((i_pts_secs / 60) % 60), (int)(i_pts_secs % 60) );
+    msg_Dbg( p_demux,
+             "final TY pts in master is %02"PRId64":%02"PRId64":%02"PRId64,
+             i_pts_secs / 3600, (i_pts_secs / 60) % 60, i_pts_secs % 60 );
 
     /* seek past this chunk */
     stream_Seek(p_demux->s, i_save_pos + CHUNK_SIZE);
@@ -1242,7 +1680,7 @@ static int probe_stream(demux_t *p_demux)
     demux_sys_t *p_sys = p_demux->p_sys;
     const uint8_t *p_buf;
     int i;
-    vlc_bool_t b_probe_error = VLC_FALSE;
+    bool b_probe_error = false;
 
     /* we need CHUNK_PEEK_COUNT chunks of data, first one might be a Part header, so ... */
     if (stream_Peek( p_demux->s, &p_buf, CHUNK_PEEK_COUNT * CHUNK_SIZE ) <
@@ -1265,15 +1703,15 @@ static int probe_stream(demux_t *p_demux)
     /* the final tally */
     if (p_sys->tivo_series == TIVO_SERIES_UNKNOWN) {
         msg_Err(p_demux, "Can't determine Tivo Series.");
-        b_probe_error = VLC_TRUE;
+        b_probe_error = true;
     }
     if (p_sys->audio_type == TIVO_AUDIO_UNKNOWN) {
         msg_Err(p_demux, "Can't determine Tivo Audio Type.");
-        b_probe_error = VLC_TRUE;
+        b_probe_error = true;
     }
     if (p_sys->tivo_type == TIVO_TYPE_UNKNOWN) {
         msg_Err(p_demux, "Can't determine Tivo Type (SA/DTivo).");
-        b_probe_error = VLC_TRUE;
+        b_probe_error = true;
     }
     return b_probe_error?VLC_EGENERIC:VLC_SUCCESS;
 }
@@ -1287,7 +1725,7 @@ static void analyze_chunk(demux_t *p_demux, const uint8_t *p_chunk)
     int i_num_recs, i;
     ty_rec_hdr_t *p_hdrs;
     int i_num_6e0, i_num_be0, i_num_9c0, i_num_3c0;
-    uint32_t i_payload_size;
+    int i_payload_size;
 
     /* skip if it's a Part header */
     if( U32_AT( &p_chunk[ 0 ] ) == TIVO_PES_FILEID )
@@ -1303,7 +1741,7 @@ static void analyze_chunk(demux_t *p_demux, const uint8_t *p_chunk)
     
     p_chunk += 4;       /* skip past rec count & SEQ bytes */
     //msg_Dbg(p_demux, "probe: chunk has %d recs", i_num_recs);
-    p_hdrs = parse_chunk_headers(p_demux, p_chunk, i_num_recs, &i_payload_size);
+    p_hdrs = parse_chunk_headers(p_chunk, i_num_recs, &i_payload_size);
     /* scan headers.
      * 1. check video packets.  Presence of 0x6e0 means S1.
      *    No 6e0 but have be0 means S2.
@@ -1445,12 +1883,11 @@ static int get_chunk_header(demux_t *p_demux)
         p_sys->i_seq_rec = p_peek[1];
     }
     p_sys->i_cur_rec = 0;
-    p_sys->b_first_chunk = VLC_FALSE;
+    p_sys->b_first_chunk = false;
   
     /*msg_Dbg( p_demux, "chunk has %d records", i_num_recs );*/
 
-    if (p_sys->rec_hdrs)
-        free(p_sys->rec_hdrs);
+    free(p_sys->rec_hdrs);
 
     /* skip past the 4 bytes we "peeked" earlier */
     stream_Read( p_demux->s, NULL, 4 );
@@ -1459,11 +1896,11 @@ static int get_chunk_header(demux_t *p_demux)
     p_hdr_buf = malloc(i_num_recs * 16);
     if (stream_Read(p_demux->s, p_hdr_buf, i_num_recs * 16) < i_num_recs * 16) {
         free( p_hdr_buf );
-        p_sys->eof = VLC_TRUE;
+        p_sys->eof = true;
         return 0;
     }
     /* parse them */
-    p_sys->rec_hdrs = parse_chunk_headers(p_demux, p_hdr_buf, i_num_recs,
+    p_sys->rec_hdrs = parse_chunk_headers(p_hdr_buf, i_num_recs,
             &i_payload_size);
     free(p_hdr_buf);
 
@@ -1476,7 +1913,7 @@ static int get_chunk_header(demux_t *p_demux)
 }
 
 
-static ty_rec_hdr_t *parse_chunk_headers( demux_t *p_demux, const uint8_t *p_buf,
+static ty_rec_hdr_t *parse_chunk_headers( const uint8_t *p_buf,
                                           int i_num_recs, int *pi_payload_size)
 {
     int i;
@@ -1497,23 +1934,21 @@ static ty_rec_hdr_t *parse_chunk_headers( demux_t *p_demux, const uint8_t *p_buf
             /* marker bit 2 set, so read extended data */
             b1 = ( ( ( record_header[ 0 ] & 0x0f ) << 4 ) | 
                    ( ( record_header[ 1 ] & 0xf0 ) >> 4 ) );
-            b1 &= 0x7f;
             b2 = ( ( ( record_header[ 1 ] & 0x0f ) << 4 ) | 
                    ( ( record_header[ 2 ] & 0xf0 ) >> 4 ) );
-            b2 &= 0x7f;
 
-            p_rec_hdr->ex1 = b1;
-            p_rec_hdr->ex2 = b2;
+            p_rec_hdr->ex[0] = b1;
+            p_rec_hdr->ex[1] = b2;
             p_rec_hdr->l_rec_size = 0;
             p_rec_hdr->l_ty_pts = 0;
-            p_rec_hdr->b_ext = VLC_TRUE;
+            p_rec_hdr->b_ext = true;
         }
         else
         {
             p_rec_hdr->l_rec_size = ( record_header[ 0 ] << 8 |
                 record_header[ 1 ] ) << 4 | ( record_header[ 2 ] >> 4 );
             *pi_payload_size += p_rec_hdr->l_rec_size;
-            p_rec_hdr->b_ext = VLC_FALSE;
+            p_rec_hdr->b_ext = false;
             p_rec_hdr->l_ty_pts = U64_AT( &record_header[ 8 ] );
         }
         //fprintf( stderr, "parse_chunk_headers[%d] t=0x%x s=%d\n", i, p_rec_hdr->rec_type, p_rec_hdr->subrec_type );