Import new http://tivo-vlc.sf.net tivo demuxer version. (The author
authorLaurent Aimar <fenrir@videolan.org>
Fri, 12 Oct 2007 22:55:19 +0000 (22:55 +0000)
committerLaurent Aimar <fenrir@videolan.org>
Fri, 12 Oct 2007 22:55:19 +0000 (22:55 +0000)
is Neal Symms, patch made by Dave Still)
Changelog is
* v2.0.0 - 21-Mar-2005 - Series 2 support!  No AC-3 on S2 DTivo yet.
* v2.1.0 - 22-Mar-2005 - Support for AC-3 on S2 DTivo (long ac3 packets)
* v3.0.0 - 14-Jul-2005 - Support for skipping fwd/back via VLC hotkeys
Please test and report any regression. (The patch is too big to be
correctly reviewed)

modules/demux/ty.c

index 5ac2c5b..22f1d4f 100644 (file)
@@ -26,6 +26,9 @@
  * v1.0.0 - 24-Feb-2005 - Initial release - Series 1 support ONLY!
  * v1.0.1 - 25-Feb-2005 - Added fix for bad GOP headers - Neal
  * v1.0.2 - 26-Feb-2005 - No longer require "seekable" input stream - Neal
+ * v2.0.0 - 21-Mar-2005 - Series 2 support!  No AC-3 on S2 DTivo yet.
+ * v2.1.0 - 22-Mar-2005 - Support for AC-3 on S2 DTivo (long ac3 packets)
+ * v3.0.0 - 14-Jul-2005 - Support for skipping fwd/back via VLC hotkeys
  *****************************************************************************/
 
 /*****************************************************************************
 #include <vlc_demux.h>
 #include "vlc_codec.h"
 
-#define SERIES1_PES_LENGTH  (11)
-#define SERIES2_PES_LENGTH  (16)
-#define AC3_PES_LENGTH      (14)
-#define DTIVO_PTS_OFFSET    (6)
-#define SA_PTS_OFFSET       (9)
-#define AC3_PTS_OFFSET      (9)
-static const unsigned char ty_VideoPacket[] = { 0x00, 0x00, 0x01, 0xe0 };
-static const unsigned char ty_MPEGAudioPacket[] = { 0x00, 0x00, 0x01, 0xc0 };
-static const unsigned char ty_AC3AudioPacket[] = { 0x00, 0x00, 0x01, 0xbd };
-
-/*****************************************************************************
- * Local prototypes
- *****************************************************************************/
-static int get_chunk_header(demux_t *);
-static void setup_audio_streams(char, demux_t *);
-static mtime_t get_pts( unsigned char *buf );
-static int find_es_header( unsigned const char *header,
-   unsigned char *buffer, int bufferSize, int *esOffset1 );
-static int ty_stream_seek(demux_t *p_demux, double seek_pct);
-
-static int TyOpen (vlc_object_t *);
-static void TyClose(vlc_object_t *);
-static int TyDemux(demux_t *);
-static int Control(demux_t *, int, va_list);
-
 /*****************************************************************************
  * Module descriptor
  *****************************************************************************/
+static int  Open ( vlc_object_t * );
+static void Close( vlc_object_t * );
+
 vlc_module_begin();
-    set_shortname( "TY" );
+    set_shortname( _("TY") );
     set_description(_("TY Stream audio/video demux"));
     set_category( CAT_INPUT );
     set_subcategory( SUBCAT_INPUT_DEMUX );
@@ -74,11 +55,32 @@ vlc_module_begin();
      * and TY demux has a bigger priority than PS
      * Something must be wrong.
      */
-    set_callbacks(TyOpen, TyClose);
+    set_callbacks( Open, Close );
     add_shortcut("ty");
     add_shortcut("tivo");
 vlc_module_end();
 
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+static int Demux  ( demux_t * );
+static int Control( demux_t *, int, va_list );
+
+#define SERIES1_PES_LENGTH  (11)    /* length of audio PES hdr on S1 */
+#define SERIES2_PES_LENGTH  (16)    /* length of audio PES hdr on S2 */
+#define AC3_PES_LENGTH      (14)    /* length of audio PES hdr for AC3 */
+#define VIDEO_PES_LENGTH    (16)    /* length of video PES header */
+#define DTIVO_PTS_OFFSET    (6)     /* offs into PES for MPEG PTS on DTivo */
+#define SA_PTS_OFFSET       (9)     /* offset into PES for MPEG PTS on SA */
+#define AC3_PTS_OFFSET      (9)     /* offset into PES for AC3 PTS on DTivo */
+#define VIDEO_PTS_OFFSET    (9)     /* offset into PES for video PTS on all */
+#define AC3_PKT_LENGTH      (1536)  /* size of TiVo AC3 pkts (w/o PES hdr) */
+static const uint8_t ty_VideoPacket[] = { 0x00, 0x00, 0x01, 0xe0 };
+static const uint8_t ty_MPEGAudioPacket[] = { 0x00, 0x00, 0x01, 0xc0 };
+static const uint8_t ty_AC3AudioPacket[] = { 0x00, 0x00, 0x01, 0xbd };
+
+#define CHUNK_PEEK_COUNT    (3)         /* number of chunks to probe */
+
 /* packet types for reference:
  2/c0: audio data continued
  3/c0: audio packet header (PES header)
@@ -92,8 +94,9 @@ vlc_module_end();
  b/e0: video B-frame header start
  c/e0: video GOP header start
  e/01: closed-caption data
- e/02: Extended data services data
+ e/02: Extended data services data 
  e/03: ipreview data ("thumbs up to record" signal)
+ e/05: UK Teletext
 */
 
 #define TIVO_PES_FILEID   ( 0xf5467abd )
@@ -103,25 +106,65 @@ vlc_module_end();
 typedef struct
 {
   long l_rec_size;
-  unsigned char ex1, ex2;
-  unsigned char rec_type;
-  unsigned char subrec_type;
-  char b_ext;
+  uint8_t ex1, ex2;
+  uint8_t rec_type;
+  uint8_t subrec_type;
+  vlc_bool_t b_ext;
+  uint64_t l_ty_pts;            /* TY PTS in the record header */
 } ty_rec_hdr_t;
 
+typedef struct
+{
+    uint64_t l_timestamp;
+    uint8_t chunk_bitmask[8];
+} ty_seq_table_t;
+
+typedef enum
+{
+    TIVO_TYPE_UNKNOWN,
+    TIVO_TYPE_SA,
+    TIVO_TYPE_DTIVO
+} tivo_type_t;
+
+typedef enum
+{
+    TIVO_SERIES_UNKNOWN,
+    TIVO_SERIES1,
+    TIVO_SERIES2
+} tivo_series_t;
+
+typedef enum
+{
+    TIVO_AUDIO_UNKNOWN,
+    TIVO_AUDIO_AC3,
+    TIVO_AUDIO_MPEG
+} tivo_audio_t;
+
 struct demux_sys_t
 {
   es_out_id_t *p_video;               /* ptr to video codec */
   es_out_id_t *p_audio;               /* holds either ac3 or mpeg codec ptr */
 
-  int             i_chunk_count;
+  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? */
-  int             tivoType;           /* 1 = SA, 2 = DTiVo */
-  vlc_bool_t      b_mpeg_audio;       /* true if we're using MPEG audio */
+  vlc_bool_t      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 */
+  int             i_Pes_Length;       /* Length of Audio PES header */
+  int             i_Pts_Offset;       /* offset into audio PES of PTS */
   uint8_t         pes_buffer[20];     /* holds incomplete pes headers */
   int             i_pes_buf_cnt;      /* how many bytes in our buffer */
+  size_t          l_ac3_pkt_size;     /* len of ac3 pkt we've seen so far */
+  uint64_t        l_last_ty_pts;      /* last TY timestamp we've seen */
+  //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 */
 
   mtime_t         firstAudioPTS;
   mtime_t         lastAudioPTS;
@@ -131,13 +174,26 @@ struct demux_sys_t
   int             i_cur_rec;          /* current record in this chunk */
   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;
 };
 
+static int get_chunk_header(demux_t *);
+static mtime_t get_pts( const uint8_t *buf );
+static int find_es_header( const uint8_t *header,
+                           const uint8_t *buffer, int i_search_len );
+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,
+                                          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);
+static void parse_master(demux_t *p_demux);
 
 /*
- * TyOpen: check file and initialize demux structures
+ * Open: check file and initialize demux structures
  *
  * here's what we do:
  * 1. peek at the first 12 bytes of the stream for the
@@ -146,7 +202,7 @@ struct demux_sys_t
  * 3. set up video (mpgv) codec
  * 4. return VLC_SUCCESS
  */
-static int TyOpen(vlc_object_t *p_this)
+static int Open(vlc_object_t *p_this)
 {
     demux_t *p_demux = (demux_t *)p_this;
     demux_sys_t *p_sys;
@@ -162,18 +218,17 @@ static int TyOpen(vlc_object_t *p_this)
          U32_AT(&p_peek[4]) != 0x02 ||
          U32_AT(&p_peek[8]) != CHUNK_SIZE )
     {
-        /* doesn't look like a TY file... */
-        if( !p_demux->b_force && !demux2_IsPathExtension( p_demux, ".ty" ) )
+        if( !p_demux->b_force )
             return VLC_EGENERIC;
         msg_Warn( p_demux, "this does not look like a TY file, "
-                  "continuing anyway..." );
+                           "continuing anyway..." );
     }
 
-    /* at this point, we assume we have a valid TY stream */
+       /* at this point, we assume we have a valid TY stream */  
     msg_Dbg( p_demux, "valid TY stream detected" );
 
     /* Set exported functions */
-    p_demux->pf_demux = TyDemux;
+    p_demux->pf_demux = Demux;
     p_demux->pf_control = Control;
 
     /* create our structure that will hold all data */
@@ -182,20 +237,34 @@ static int TyOpen(vlc_object_t *p_this)
 
     /* set up our struct (most were zero'd out with the memset above) */
     p_sys->b_first_chunk = VLC_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);
-    p_sys->b_mpeg_audio = VLC_FALSE;
-
+    p_sys->tivo_type = TIVO_TYPE_UNKNOWN;
+    p_sys->audio_type = TIVO_AUDIO_UNKNOWN;
+    p_sys->tivo_series = TIVO_SERIES_UNKNOWN;
+    p_sys->i_Pes_Length = 0;
+    p_sys->i_Pts_Offset = 0;
+    p_sys->l_ac3_pkt_size = 0;
+  
     /* see if this stream is seekable */
     stream_Control( p_demux->s, STREAM_CAN_SEEK, &p_sys->b_seekable );
 
-    /* TODO: read first chunk & parse first audio PTS, then (if seekable)
-     *       seek to last chunk & last record; read its PTS and compute
-     *       overall program time.  Also determine Tivo type.   */
+    if (probe_stream(p_demux) != VLC_SUCCESS) {
+        //TyClose(p_demux);
+        return VLC_EGENERIC;
+    }
 
-    /* NOTE: we wait to create the audio ES until we know what
-     * audio type we have.   */
-    p_sys->p_audio = NULL;
+    if (!p_sys->b_have_master)
+      msg_Warn(p_demux, "No master chunk found; seeking will be limited.");
+
+    /* 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' ) );
+    } else {
+        es_format_Init( &fmt, AUDIO_ES, VLC_FOURCC( 'a', '5', '2', ' ' ) );
+    }
+    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' ) );
@@ -211,31 +280,10 @@ static int TyOpen(vlc_object_t *p_this)
 }
 
 
-/* set up audio codec.
- * this will be called once we determine audio type */
-static void setup_audio_streams(char stream_type, demux_t *p_demux)
-{
-    demux_sys_t *p_sys = p_demux->p_sys;
-    es_format_t  fmt;
-
-    if (stream_type == 'A') {
-        /* AC3 audio detected */
-        es_format_Init( &fmt, AUDIO_ES, VLC_FOURCC( 'a', '5', '2', ' ' ) );
-        p_sys->tivoType = 2;      /* AC3 is only on dtivo */
-    } else {
-        /* assume MPEG */
-        es_format_Init( &fmt, AUDIO_ES, VLC_FOURCC( 'm', 'p', 'g', 'a' ) );
-        p_sys->b_mpeg_audio = VLC_TRUE;
-    }
-    /* register the chosen audio output codec */
-    p_sys->p_audio = es_out_Add( p_demux->out, &fmt );
-}
-
-
 /* =========================================================================== */
 /* Compute Presentation Time Stamp (PTS)
  * Assume buf points to beginning of PTS */
-static mtime_t get_pts( unsigned char *buf )
+static mtime_t get_pts( const uint8_t *buf )
 {
     mtime_t i_pts;
 
@@ -250,24 +298,17 @@ static mtime_t get_pts( unsigned char *buf )
 
 
 /* =========================================================================== */
-static int find_es_header( unsigned const char *header,
-   unsigned char *buffer, int bufferSize, int *esOffset1 )
+static int find_es_header( const uint8_t *header,
+                           const uint8_t *buffer, int i_search_len )
 {
     int count;
 
-    *esOffset1 = -1;
-    for( count = 0 ; count < bufferSize ; count++ )
+    for( count = 0; count < i_search_len; count++ )
     {
-        if ( ( buffer[ count + 0 ] == header[ 0 ] ) &&
-             ( buffer[ count + 1 ] == header[ 1 ] ) &&
-             ( buffer[ count + 2 ] == header[ 2 ] ) &&
-             ( buffer[ count + 3 ] == header[ 3 ] ) )
-        {
-            *esOffset1 = count;
-            return 1;
-        }
+        if( !memcmp( &buffer[count], header, 4 ) )
+            return count;
     }
-    return( -1 );
+    return -1;
 }
 
 
@@ -278,25 +319,13 @@ static int find_es_header( unsigned const char *header,
  *     1 partial PES hdr found, some audio data found (buffer adjusted),
  *    -1 partial PES hdr found, no audio data found
  *     0 otherwise (complete PES found, pts extracted, pts set, buffer adjusted) */
-/* TODO: fix it so it works with S2 / SA / DTivo / HD etc... */
+/* TODO: HD support -- nothing known about those streams */
 static int check_sync_pes( demux_t *p_demux, block_t *p_block,
                            int32_t offset, int32_t rec_len )
 {
     demux_sys_t *p_sys = p_demux->p_sys;
-    int pts_offset;
-    int pes_length = p_sys->b_mpeg_audio?SERIES1_PES_LENGTH:AC3_PES_LENGTH;
 
-    if( p_sys->tivoType == 1 )
-    {
-        /* SA tivo */
-        pts_offset = SA_PTS_OFFSET;
-    }
-    else
-    {
-        /* DTivo */
-        pts_offset = p_sys->b_mpeg_audio?DTIVO_PTS_OFFSET:AC3_PTS_OFFSET;
-    }
-    if ( offset < 0 || offset + pes_length > rec_len )
+    if ( offset < 0 || offset + p_sys->i_Pes_Length > rec_len )
     {
         /* entire PES header not present */
         msg_Dbg( p_demux, "PES header at %d not complete in record. storing.",
@@ -305,7 +334,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, 0, 4 );
+            memset( p_sys->pes_buffer, 4, 0 );
             p_sys->i_pes_buf_cnt = 4;
             if( rec_len > 4 )
                 msg_Err( p_demux, "PES header not found in record of %d bytes!",
@@ -326,21 +355,35 @@ static int check_sync_pes( demux_t *p_demux, block_t *p_block,
         return -1;    /* partial PES, no audio data */
     }
     /* full PES header present, extract PTS */
-    p_sys->lastAudioPTS = get_pts( &p_block->p_buffer[ offset + pts_offset ] );
+    p_sys->lastAudioPTS = get_pts( &p_block->p_buffer[ offset +
+            p_sys->i_Pts_Offset ] );
     if (p_sys->firstAudioPTS < 0)
         p_sys->firstAudioPTS = p_sys->lastAudioPTS;
     p_block->i_pts = p_sys->lastAudioPTS;
     /*msg_Dbg(p_demux, "Audio PTS %lld", p_sys->lastAudioPTS );*/
     /* adjust audio record to remove PES header */
-    memmove(p_block->p_buffer + offset, p_block->p_buffer + offset + pes_length,
-            rec_len - pes_length);
-    p_block->i_buffer -= pes_length;
+    memmove(p_block->p_buffer + offset, p_block->p_buffer + offset +
+            p_sys->i_Pes_Length, rec_len - p_sys->i_Pes_Length);
+    p_block->i_buffer -= p_sys->i_Pes_Length;
+#if 0
+    msg_Dbg(p_demux, "pes hdr removed; buffer len=%d and has "
+             "%02x %02x %02x %02x %02x %02x %02x %02x "
+             "%02x %02x %02x %02x %02x %02x %02x %02x", p_block->i_buffer,
+             p_block->p_buffer[0], p_block->p_buffer[1],
+             p_block->p_buffer[2], p_block->p_buffer[3],
+             p_block->p_buffer[4], p_block->p_buffer[5],
+             p_block->p_buffer[6], p_block->p_buffer[7],
+             p_block->p_buffer[8], p_block->p_buffer[9],
+             p_block->p_buffer[10], p_block->p_buffer[11],
+             p_block->p_buffer[12], p_block->p_buffer[13],
+             p_block->p_buffer[14], p_block->p_buffer[15]);
+#endif
     return 0;
 }
 
 
 /* =========================================================================== */
-/* TyDemux: Read & Demux one record from the chunk
+/* Demux: Read & Demux one record from the chunk
  *
  * Returns -1 in case of error, 0 in case of EOF, 1 otherwise
  *
@@ -348,8 +391,10 @@ static int check_sync_pes( demux_t *p_demux, block_t *p_block,
  * that means we can demux an entire chunk and shoot it back (may be more efficient)
  * -- should try that some day :) --
  */
-int TyDemux(demux_t *p_demux)
+static int Demux( demux_t *p_demux )
 {
+    demux_sys_t      *p_sys = p_demux->p_sys;
+
     int              invalidType = 0;
     int              recordsDecoded = 0;
 
@@ -362,13 +407,11 @@ int TyDemux(demux_t *p_demux)
     block_t          *p_block_in = NULL;
     int              esOffset1;
 
-    unsigned char    lastCC[ 16 ];
-    unsigned char    lastXDS[ 16 ];
-
-    demux_sys_t      *p_sys = p_demux->p_sys;
+    uint8_t          lastCC[ 16 ];
+    uint8_t          lastXDS[ 16 ];
 
     /*msg_Dbg(p_demux, "ty demux processing" );*/
-
+   
     /* did we hit EOF earlier? */
     if (p_sys->eof) return 0;
 
@@ -383,7 +426,7 @@ int TyDemux(demux_t *p_demux)
     * - set PTS for data packets
     * - pass the data on to the proper codec via es_out_Send()
 
-    * if this is the first time or
+    * if this is the first time or  
     * if we're at the end of this chunk, start a new one
     */
     /* parse the next chunk's record headers */
@@ -405,7 +448,7 @@ int TyDemux(demux_t *p_demux)
     {
         /*msg_Dbg(p_demux, "Record Type 0x%x/%02x %ld bytes",
                     subrec_type, rec_type, l_rec_size );*/
+  
         /* some normal records are 0 length, so check for that... */
         if (l_rec_size > 0)
         {
@@ -439,36 +482,58 @@ int TyDemux(demux_t *p_demux)
      *================================================================*/
     if ( rec_type == 0xe0 )
     {
-        if( subrec_type == 0x06 )
+#if 0
+        msg_Dbg(p_demux, "packet buffer has "
+                "%02x %02x %02x %02x %02x %02x %02x %02x "
+                "%02x %02x %02x %02x %02x %02x %02x %02x",
+                p_block_in->p_buffer[0], p_block_in->p_buffer[1],
+                p_block_in->p_buffer[2], p_block_in->p_buffer[3],
+                p_block_in->p_buffer[4], p_block_in->p_buffer[5],
+                p_block_in->p_buffer[6], p_block_in->p_buffer[7],
+                p_block_in->p_buffer[8], p_block_in->p_buffer[9],
+                p_block_in->p_buffer[10], p_block_in->p_buffer[11],
+                p_block_in->p_buffer[12], p_block_in->p_buffer[13],
+                p_block_in->p_buffer[14], p_block_in->p_buffer[15]);
+#endif
+        //if( subrec_type == 0x06 || subrec_type == 0x07 )
+        if( subrec_type != 0x02 && subrec_type != 0x0c
+            && subrec_type != 0x08 && l_rec_size > 4 )
         {
-            /* get the PTS from this packet.
-             * Do NOT Pass this packet (a PES Header) on to the MPEG2 codec */
-            find_es_header( ty_VideoPacket, p_block_in->p_buffer,
-                            l_rec_size, &esOffset1 );
+            /* get the PTS from this packet if it has one.
+             * on S1, only 0x06 has PES.  On S2, however, most all do.
+             * Do NOT Pass the PES Header to the MPEG2 codec */
+            esOffset1 = find_es_header( ty_VideoPacket, p_block_in->p_buffer,
+                    5 );
             if ( esOffset1 != -1 )
             {
-                /* msg_Dbg(p_demux, "Video PES hdr at offset %d", esOffset1); */
-                p_sys->lastVideoPTS = get_pts( &p_block_in->p_buffer[ esOffset1 + 9 ] );
+                //msg_Dbg(p_demux, "Video PES hdr in pkt type 0x%02x at offset %d",
+                    //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,
                             p_sys->lastVideoPTS );*/
-            }
-            block_Release(p_block_in);
+                if (subrec_type != 0x06) {
+                    /* if we found a PES, and it's not type 6, then we're S2 */
+                    /* The packet will have video data (& other headers) so we
+                     * chop out the PES header and send the rest */
+                    if (l_rec_size >= VIDEO_PES_LENGTH) {
+                        p_block_in->p_buffer += VIDEO_PES_LENGTH + esOffset1;
+                        p_block_in->i_buffer -= VIDEO_PES_LENGTH + esOffset1;
+                    } else {
+                        msg_Dbg(p_demux, "video rec type 0x%02x has short PES"
+                            " (%ld bytes)", subrec_type, l_rec_size);
+                        /* nuke this block; it's too short, but has PES marker */
+                        p_block_in->i_buffer = 0;
+                    }
+                }
+            }/* else
+                msg_Dbg(p_demux, "No Video PES hdr in pkt type 0x%02x",
+                    subrec_type); */
         }
-        else
-        {
-#if 0
-            msg_Dbg(p_demux, "packet buffer has "
-                    "%02x %02x %02x %02x %02x %02x %02x %02x "
-                    "%02x %02x %02x %02x %02x %02x %02x %02x",
-                    p_block_in->p_buffer[0], p_block_in->p_buffer[1],
-                    p_block_in->p_buffer[2], p_block_in->p_buffer[3],
-                    p_block_in->p_buffer[4], p_block_in->p_buffer[5],
-                    p_block_in->p_buffer[6], p_block_in->p_buffer[7],
-                    p_block_in->p_buffer[8], p_block_in->p_buffer[9],
-                    p_block_in->p_buffer[10], p_block_in->p_buffer[11],
-                    p_block_in->p_buffer[12], p_block_in->p_buffer[13],
-                    p_block_in->p_buffer[14], p_block_in->p_buffer[15]);
-#endif
+        if (subrec_type == 0x06) {
+            /* type 6 (S1 DTivo) has no data, so we're done */
+            block_Release(p_block_in);
+        } else {
             /* if it's not a continue blk, then set PTS */
             if (subrec_type != 0x02)
             {
@@ -480,16 +545,33 @@ int TyDemux(demux_t *p_demux)
                  * and it causes problems */
                 if (subrec_type == 0x0c && l_rec_size >= 6)
                     p_block_in->p_buffer[5] |= 0x08;
+                /* store the TY PTS if there is one */
+                if (subrec_type == 0x07) {
+                    p_sys->l_last_ty_pts = rec_hdr->l_ty_pts;
+                    /* should we use audio or video PTS? */
+                    //p_sys->l_last_ty_pts_sync = p_sys->lastAudioPTS;
+                } else {
+                    /* yes I know this is a cheap hack.  It's the timestamp
+                       used for display and skipping fwd/back, so it
+                       doesn't have to be accurate to the millisecond.
+                       I adjust it here by roughly one 1/30 sec.  Yes it
+                       will be slightly off for UK streams, but it's OK.
+                     */
+                    p_sys->l_last_ty_pts += 35000000;
+                    //p_sys->l_last_ty_pts += 33366667;
+                }
                 /* set PTS for this block before we send */
                 if (p_sys->lastVideoPTS > 0)
                 {
                     p_block_in->i_pts = p_sys->lastVideoPTS;
-                    /* PTS gets used ONCE.
+                    /* PTS gets used ONCE. 
                      * Any subsequent frames we get BEFORE next PES
                      * header will have their PTS computed in the codec */
                     p_sys->lastVideoPTS = 0;
                 }
             }
+            //msg_Dbg(p_demux, "sending rec %d as video type 0x%02x",
+                    //p_sys->i_cur_rec, subrec_type);
             es_out_Send(p_demux->out, p_sys->p_video, p_block_in);
         }
     } /* end if video rec type */
@@ -507,22 +589,6 @@ int TyDemux(demux_t *p_demux)
             printf( "%2.2x ", p_block_in->p_buffer[i] );
         printf( "\n" );
 #endif
-        /* load a codec if we haven't yet */
-        if ( p_sys->p_audio == NULL )
-        {
-            if ( subrec_type == 0x09 )
-            {
-                /* set up for AC-3 audio */
-                msg_Dbg(p_demux, "detected AC-3 Audio" );
-                        setup_audio_streams('A', p_demux);
-            }
-            else
-            {
-                /* set up for MPEG audio */
-                msg_Dbg(p_demux, "detected MPEG Audio" );
-                setup_audio_streams('M', p_demux);
-            }
-        }
 
         /* SA or DTiVo Audio Data, no PES (continued block)
          * ================================================
@@ -530,10 +596,9 @@ int TyDemux(demux_t *p_demux)
         if ( subrec_type == 2 )
         {
             /* continue PES if previous was incomplete */
-            /* TODO: Make this work for all series & types of tivos */
             if (p_sys->i_pes_buf_cnt > 0)
             {
-                int i_need = SERIES1_PES_LENGTH - p_sys->i_pes_buf_cnt;
+                int i_need = p_sys->i_Pes_Length - p_sys->i_pes_buf_cnt;
 
                 msg_Dbg(p_demux, "continuing PES header");
                 /* do we have enough data to complete? */
@@ -546,12 +611,12 @@ int TyDemux(demux_t *p_demux)
                     p_block_in->p_buffer += i_need;
                     p_block_in->i_buffer -= i_need;
                     /* get the PTS out of this PES header (MPEG or AC3) */
-                    if (p_sys->b_mpeg_audio)
-                        find_es_header(ty_MPEGAudioPacket, p_sys->pes_buffer,
-                                        10, &esOffset1);
+                    if (p_sys->audio_type == TIVO_AUDIO_MPEG)
+                        esOffset1 = find_es_header(ty_MPEGAudioPacket,
+                                p_sys->pes_buffer, 5);
                     else
-                        find_es_header(ty_AC3AudioPacket, p_sys->pes_buffer,
-                                        10, &esOffset1);
+                        esOffset1 = find_es_header(ty_AC3AudioPacket,
+                                p_sys->pes_buffer, 5);
                     if (esOffset1 < 0)
                     {
                         /* god help us; something's really wrong */
@@ -559,8 +624,8 @@ int TyDemux(demux_t *p_demux)
                     }
                     else
                     {
-                        p_sys->lastAudioPTS = get_pts(
-                            &p_sys->pes_buffer[ esOffset1 + DTIVO_PTS_OFFSET ] );
+                        p_sys->lastAudioPTS = get_pts( 
+                            &p_sys->pes_buffer[ esOffset1 + p_sys->i_Pts_Offset ] );
                         p_block_in->i_pts = p_sys->lastAudioPTS;
                     }
                     p_sys->i_pes_buf_cnt = 0;
@@ -576,9 +641,22 @@ int TyDemux(demux_t *p_demux)
                     return 1;
                 }
             }
-            /* set PCR before we send */
-            /*es_out_Control( p_demux->out, ES_OUT_SET_PCR,
-                              p_block_in->i_pts );*/
+            /* S2 DTivo has AC3 packets with 2 padding bytes at end.  This is
+             * not allowed in the AC3 spec and will cause problems.  So here
+             * we try to trim things. */
+            /* Also, S1 DTivo has alternating short / long AC3 packets.  That
+             * is, one packet is short (incomplete) and the next packet has
+             * the first one's missing data, plus all of its own.  Strange. */
+            if (p_sys->audio_type == TIVO_AUDIO_AC3 &&
+                    p_sys->tivo_series == TIVO_SERIES2) {
+                if (p_sys->l_ac3_pkt_size + p_block_in->i_buffer >
+                        AC3_PKT_LENGTH) {
+                    p_block_in->i_buffer -= 2;
+                    p_sys->l_ac3_pkt_size = 0;
+                } else {
+                    p_sys->l_ac3_pkt_size += p_block_in->i_buffer;
+                }
+            }
             es_out_Send( p_demux->out, p_sys->p_audio, p_block_in );
         } /* subrec == 2 */
 
@@ -586,8 +664,8 @@ int TyDemux(demux_t *p_demux)
         /* ================================================ */
         if ( subrec_type == 0x03 )
         {
-            find_es_header( ty_MPEGAudioPacket, p_block_in->p_buffer,
-            l_rec_size, &esOffset1 );
+            esOffset1 = find_es_header( ty_MPEGAudioPacket,
+                    p_block_in->p_buffer, 5 );
 
             /*msg_Dbg(p_demux, "buffer has %#02x %#02x %#02x %#02x",
                p_block_in->p_buffer[0], p_block_in->p_buffer[1],
@@ -598,7 +676,6 @@ int TyDemux(demux_t *p_demux)
             /* ================================================ */
             if ( ( esOffset1 == 0 ) && ( l_rec_size == 16 ) )
             {
-                p_sys->tivoType = 1;
                 p_sys->lastAudioPTS = get_pts( &p_block_in->p_buffer[
                             SA_PTS_OFFSET ] );
                 if (p_sys->firstAudioPTS < 0)
@@ -611,14 +688,11 @@ int TyDemux(demux_t *p_demux)
             /* DTiVo Audio with PES Header                      */
             /* ================================================ */
             {
-                p_sys->tivoType = 2;
-
-                /* Check for complete PES
-                 * (TODO: handle proper size for tivo version) */
+                /* Check for complete PES */
                 if (check_sync_pes(p_demux, p_block_in, esOffset1,
                                     l_rec_size) == -1)
                 {
-                    /* partial PES header found, nothing else.
+                    /* partial PES header found, nothing else. 
                      * we're done. */
                     p_sys->i_cur_rec++;
                     block_Release(p_block_in);
@@ -666,13 +740,23 @@ int TyDemux(demux_t *p_demux)
         /* ================================================ */
         if ( subrec_type == 0x09 )
         {
-            find_es_header( ty_AC3AudioPacket, p_block_in->p_buffer,
-                            l_rec_size, &esOffset1 );
+            esOffset1 = find_es_header( ty_AC3AudioPacket,
+                    p_block_in->p_buffer, 5 );
 
-            /*msg_Dbg(p_demux, "buffer has %#02x %#02x %#02x %#02x",
-                       p_block_in->p_buffer[0], p_block_in->p_buffer[1],
-                       p_block_in->p_buffer[2], p_block_in->p_buffer[3]);
-            msg_Dbg(p_demux, "audio ES AC3 hdr at offset %d", esOffset1);*/
+#if 0
+            msg_Dbg(p_demux, "buffer has "
+                     "%02x %02x %02x %02x %02x %02x %02x %02x "
+                     "%02x %02x %02x %02x %02x %02x %02x %02x",
+                     p_block_in->p_buffer[0], p_block_in->p_buffer[1],
+                     p_block_in->p_buffer[2], p_block_in->p_buffer[3],
+                     p_block_in->p_buffer[4], p_block_in->p_buffer[5],
+                     p_block_in->p_buffer[6], p_block_in->p_buffer[7],
+                     p_block_in->p_buffer[8], p_block_in->p_buffer[9],
+                     p_block_in->p_buffer[10], p_block_in->p_buffer[11],
+                     p_block_in->p_buffer[12], p_block_in->p_buffer[13],
+                     p_block_in->p_buffer[14], p_block_in->p_buffer[15]);
+            msg_Dbg(p_demux, "audio ES AC3 hdr at offset %d", esOffset1);
+#endif
 
             /* Check for complete PES */
             if (check_sync_pes(p_demux, p_block_in, esOffset1,
@@ -688,6 +772,15 @@ int TyDemux(demux_t *p_demux)
                 es_out_Control( p_demux->out, ES_OUT_SET_PCR,
                                 p_block_in->i_pts );
             }
+            /* S2 DTivo has invalid long AC3 packets */
+            if (p_sys->tivo_series == TIVO_SERIES2) {
+                if (p_block_in->i_buffer > AC3_PKT_LENGTH) {
+                    p_block_in->i_buffer -= 2;
+                    p_sys->l_ac3_pkt_size = 0;
+                } else {
+                    p_sys->l_ac3_pkt_size = p_block_in->i_buffer;
+                }
+            }
             es_out_Send( p_demux->out, p_sys->p_audio, p_block_in );
         }
     } /* end "if audio" */
@@ -756,41 +849,52 @@ int TyDemux(demux_t *p_demux)
 
 
 /* seek to a position within the stream, if possible */
-static int ty_stream_seek(demux_t *p_demux, double seek_pct)
+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;
+    int i, i_cur_part;
     long l_skip_amt;
 
     /* if we're not seekable, there's nothing to do */
     if (!p_sys->b_seekable)
         return VLC_EGENERIC;
 
-    /* figure out which chunk we want & go there */
-    p_sys->i_chunk_count = seek_pos / CHUNK_SIZE;
+    /* figure out which part & chunk we want & go there */
+    i_cur_part = seek_pos / TIVO_PART_LENGTH;
+    p_sys->i_cur_chunk = seek_pos / CHUNK_SIZE;
+    
+    /* try to read the part header (master chunk) if it's there */
+    if ( stream_Seek( p_demux->s, i_cur_part * TIVO_PART_LENGTH ))
+    {
+        /* can't seek stream */
+        return VLC_EGENERIC;
+    }
+    parse_master(p_demux);
 
-    if ( stream_Seek( p_demux->s, p_sys->i_chunk_count * CHUNK_SIZE))
+    /* now for the actual chunk */
+    if ( stream_Seek( p_demux->s, p_sys->i_cur_chunk * CHUNK_SIZE))
     {
         /* can't seek stream */
         return VLC_EGENERIC;
     }
     /* load the chunk */
+    p_sys->i_stuff_cnt = 0;
     get_chunk_header(p_demux);
+  
     /* 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 " I64Fd, seek_pos);
     msg_Dbg(p_demux, " (chunk %d, record %d)",
-             p_sys->i_chunk_count - 1, p_sys->i_cur_rec);
+             p_sys->i_cur_chunk - 1, p_sys->i_cur_rec);
 
     /* seek to the start of this record's data.
      * to do that, we have to skip past all prior records */
     l_skip_amt = 0;
     for (i=0; i<p_sys->i_cur_rec; i++)
         l_skip_amt += p_sys->rec_hdrs[i].l_rec_size;
-    stream_Seek(p_demux->s, ((p_sys->i_chunk_count-1) * CHUNK_SIZE) +
+    stream_Seek(p_demux->s, ((p_sys->i_cur_chunk-1) * CHUNK_SIZE) +
                  (p_sys->i_num_recs * 16) + l_skip_amt + 4);
 
     /* to hell with syncing any audio or video, just start reading records... :) */
@@ -800,12 +904,214 @@ static int ty_stream_seek(demux_t *p_demux, double seek_pct)
 }
 
 
+/* seek to an exact time position within the stream, if possible.
+ * l_seek_time is in nanoseconds, the TIVO time standard.
+ */
+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_skip_cnt;
+    long l_cur_pos = stream_Tell(p_demux->s);
+    int i_cur_part = l_cur_pos / TIVO_PART_LENGTH;
+    long l_seek_secs = l_seek_time / 1000000000;
+    uint64_t l_fwd_stamp = 1;
+
+    /* if we're not seekable, there's nothing to do */
+    if (!p_sys->b_seekable || !p_sys->b_have_master)
+        return VLC_EGENERIC;
+
+    msg_Dbg(p_demux, "Skipping to time %02ld:%02ld:%02ld",
+            l_seek_secs / 3600, (l_seek_secs / 60) % 60, l_seek_secs % 60);
+
+    /* seek to the proper segment if necessary */
+    /* first see if we need to go back */
+    while (l_seek_time < p_sys->l_first_ty_pts) {
+        msg_Dbg(p_demux, "skipping to prior segment.");
+        /* load previous part */
+        if (i_cur_part == 0) {
+            stream_Seek(p_demux->s, l_cur_pos);
+            msg_Err(p_demux, "Attempt to seek past BOF");
+            return VLC_EGENERIC;
+        }
+        stream_Seek(p_demux->s, (i_cur_part - 1) * TIVO_PART_LENGTH);
+        i_cur_part--;
+        parse_master(p_demux);
+    }
+    /* maybe we need to go forward */
+    while (l_seek_time > p_sys->l_final_ty_pts) {
+        msg_Dbg(p_demux, "skipping to next segment.");
+        /* load next part */
+        if ((i_cur_part + 1) * TIVO_PART_LENGTH > p_sys->i_stream_size) {
+            /* error; restore previous file position */
+            stream_Seek(p_demux->s, l_cur_pos);
+            msg_Err(p_demux, "seek error");
+            return VLC_EGENERIC;
+        }
+        stream_Seek(p_demux->s, (i_cur_part + 1) * TIVO_PART_LENGTH);
+        i_cur_part++;
+        parse_master(p_demux);
+    }
+
+    /* our target is somewhere within this part;
+       find the proper chunk using seq_table */
+    for (i=1; i<p_sys->i_seq_table_size; i++) {
+        if (p_sys->seq_table[i].l_timestamp > l_seek_time) {
+            /* i-1 is the section we want; remember the next timestamp in case
+               we have to use it (this section may not have a proper SEQ hdr
+               for the time we're seeking) */
+            msg_Dbg(p_demux, "stopping at seq entry %d.", i);
+            l_fwd_stamp = p_sys->seq_table[i].l_timestamp;
+            i_seq_entry = i-1;
+            break;
+        }
+    }
+    
+    /* if we went through the entire last loop and didn't find our target,
+       then we skip to the next part.  What has happened is that the actual
+       time we're seeking is within this part, but there isn't a SEQ hdr
+       for it here.  So we skip to the next part */
+    if (i == p_sys->i_seq_table_size) {
+        if ((i_cur_part + 1) * TIVO_PART_LENGTH > p_sys->i_stream_size) {
+            /* error; restore previous file position */
+            stream_Seek(p_demux->s, l_cur_pos);
+            msg_Err(p_demux, "seek error");
+            return VLC_EGENERIC;
+        }
+        stream_Seek(p_demux->s, (i_cur_part + 1) * TIVO_PART_LENGTH);
+        i_cur_part++;
+        parse_master(p_demux);
+        i_seq_entry = 0;
+    }     
+     
+    /* determine which chunk has our seek_time */
+    for (i=0; i<p_sys->i_bits_per_seq_entry; i++) {
+        long l_chunk_nr = i_seq_entry * p_sys->i_bits_per_seq_entry + i;
+        long l_chunk_offset = (l_chunk_nr + 1) * CHUNK_SIZE;
+        msg_Dbg(p_demux, "testing part %d chunk %ld mask 0x%02X bit %d",
+            i_cur_part, l_chunk_nr,
+            p_sys->seq_table[i_seq_entry].chunk_bitmask[i/8], i%8);
+        if (p_sys->seq_table[i_seq_entry].chunk_bitmask[i/8] & (1 << (i%8))) {
+            /* check this chunk's SEQ header timestamp */
+            msg_Dbg(p_demux, "has SEQ. seeking to chunk at 0x%lX",
+                (i_cur_part * TIVO_PART_LENGTH) + l_chunk_offset);
+            stream_Seek(p_demux->s, (i_cur_part * TIVO_PART_LENGTH) +
+                l_chunk_offset);
+            // TODO: we don't have to parse the full header set;
+            // just test the seq_rec entry for its timestamp
+            p_sys->i_stuff_cnt = 0;
+            get_chunk_header(p_demux);
+            // check ty PTS for the SEQ entry in this chunk
+            if (p_sys->i_seq_rec < 0 || p_sys->i_seq_rec > p_sys->i_num_recs) {
+                msg_Err(p_demux, "no SEQ hdr in chunk; table had one.");
+                /* Seek to beginning of original chunk & reload it */
+                stream_Seek(p_demux->s, (l_cur_pos / CHUNK_SIZE) * CHUNK_SIZE);
+                p_sys->i_stuff_cnt = 0;
+                get_chunk_header(p_demux);
+                return VLC_EGENERIC;
+            }
+            l_seek_secs = p_sys->rec_hdrs[p_sys->i_seq_rec].l_ty_pts /
+                1000000000;
+            msg_Dbg(p_demux, "found SEQ hdr for timestamp %02ld:%02ld:%02ld",
+                l_seek_secs / 3600,
+                (l_seek_secs / 60) % 60, l_seek_secs % 60);
+            if (p_sys->rec_hdrs[p_sys->i_seq_rec].l_ty_pts >= l_seek_time) {
+                // keep this one?  go back?
+                /* for now, we take this one.  it's the first SEQ hdr AFTER
+                   the time we were searching for. */
+                msg_Dbg(p_demux, "seek target found.");
+                break;
+            }
+            msg_Dbg(p_demux, "timestamp too early. still scanning.");
+        }
+    }
+    /* if we made it through this entire loop without finding our target,
+       then we skip to the next section.  What has happened is that the actual
+       time we're seeking is within this section, but there isn't a SEQ hdr
+       for it here.  So we skip to the next closest one (l_fwd_stamp) */
+    if (i == p_sys->i_bits_per_seq_entry)
+        return ty_stream_seek_time(p_demux, l_fwd_stamp);
+
+    /* current stream ptr is at beginning of data for this chunk,
+       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;
+    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;
+    //p_sys->l_last_ty_pts_sync = p_sys->lastAudioPTS;
+
+    return VLC_SUCCESS;
+}
+
+
+/* parse a master chunk, filling the SEQ table and other variables.
+ * We assume the stream is currently pointing to it.
+ */
+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;
+    int64_t i_save_pos = stream_Tell(p_demux->s);
+    int64_t i_pts_secs;
+
+    /* Note that the entries in the SEQ table in the stream may have
+       different sizes depending on the bits per entry.  We store them
+       all in the same size structure, so we have to parse them out one
+       by one.  If we had a dynamic structure, we could simply read the
+       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);
+    
+    /* parse header info */
+    stream_Read(p_demux->s, mst_buf, 32);
+    i_map_size = U32_AT(&mst_buf[20]);  /* size of bitmask, in bytes */
+    p_sys->i_bits_per_seq_entry = i_map_size * 8;
+    i = U32_AT(&mst_buf[28]);   /* size of SEQ table, in bytes */
+    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[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);
+        } else {
+            memcpy(p_sys->seq_table[i].chunk_bitmask, &mst_buf[8], i_map_size);
+        }
+    }
+
+    /* set up a few of our variables */
+    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;
+
+    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) );
+    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) );
+
+    /* seek past this chunk */
+    stream_Seek(p_demux->s, i_save_pos + CHUNK_SIZE);
+}
+
+
 static int Control(demux_t *p_demux, int i_query, va_list args)
 {
     demux_sys_t *p_sys = p_demux->p_sys;
     double f, *pf;
     int64_t i64, *p_i64;
+
     /*msg_Info(p_demux, "control cmd %d", i_query);*/
     switch( i_query )
     {
@@ -822,17 +1128,26 @@ static int Control(demux_t *p_demux, int i_query, va_list args)
     case DEMUX_SET_POSITION:
         /* arg is 0.0 - 1.0 percent of overall file position */
         f = (double) va_arg( args, double );
-        //msg_Dbg(p_demux, "Control - set position to %2.3f", f);
+        /* msg_Dbg(p_demux, "Control - set position to %2.3f", f); */
         if ((i64 = p_sys->i_stream_size) > 0)
-            return ty_stream_seek(p_demux, f);
+            return ty_stream_seek_pct(p_demux, f);
         return VLC_EGENERIC;
     case DEMUX_GET_TIME:
-        /* return latest PTS - start PTS */
+        /* return TiVo timestamp */
         p_i64 = (int64_t *) va_arg(args, int64_t *);
-        *p_i64 = p_sys->lastAudioPTS - p_sys->firstAudioPTS;
+        //*p_i64 = p_sys->lastAudioPTS - p_sys->firstAudioPTS;
+        //*p_i64 = (p_sys->l_last_ty_pts / 1000) + (p_sys->lastAudioPTS -
+        //    p_sys->l_last_ty_pts_sync);
+        *p_i64 = (p_sys->l_last_ty_pts / 1000);
         return VLC_SUCCESS;
-    case DEMUX_SET_TIME:      /* arg is time in microsecs */
     case DEMUX_GET_LENGTH:    /* length of program in microseconds, 0 if unk */
+        /* size / bitrate */
+        p_i64 = (int64_t *) va_arg(args, int64_t *);
+        *p_i64 = 0;
+        return VLC_SUCCESS;
+    case DEMUX_SET_TIME:      /* arg is time in microsecs */
+        i64 = (int64_t) va_arg( args, int64_t );
+        return ty_stream_seek_time(p_demux, i64 * 1000);
     case DEMUX_GET_FPS:
     default:
         return VLC_EGENERIC;
@@ -840,8 +1155,8 @@ static int Control(demux_t *p_demux, int i_query, va_list args)
 }
 
 
-/* =========================================================================== */
-static void TyClose( vlc_object_t *p_this )
+/* ======================================================================== */
+static void Close( vlc_object_t *p_this )
 {
     demux_sys_t *p_sys = ((demux_t *) p_this)->p_sys;
 
@@ -850,48 +1165,208 @@ static void TyClose( vlc_object_t *p_this )
 }
 
 
+/* ======================================================================== */
+/* "Peek" at some chunks.  Skip over the Part header if we find it.
+ * We parse the peeked data and determine audio type,
+ * SA vs. DTivo, & Tivo Series.
+ * Set global vars i_Pes_Length, i_Pts_Offset,
+ * p_sys->tivo_series, p_sys->tivo_type, p_sys->audio_type */
+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;
+
+    /* 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 ) <
+            CHUNK_PEEK_COUNT * CHUNK_SIZE) {
+        msg_Err(p_demux, "Can't peek %d chunks", CHUNK_PEEK_COUNT);
+        /* TODO: if seekable, then loop reading chunks into a temp buffer */
+        return VLC_EGENERIC;
+    }
+    
+    /* the real work: analyze this chunk */
+    for (i = 0; i < CHUNK_PEEK_COUNT; i++) {
+        analyze_chunk(p_demux, p_buf);
+        if (p_sys->tivo_series != TIVO_SERIES_UNKNOWN &&
+            p_sys->audio_type  != TIVO_AUDIO_UNKNOWN &&
+            p_sys->tivo_type   != TIVO_TYPE_UNKNOWN)
+            break;
+        p_buf += CHUNK_SIZE;
+    }
+    
+    /* 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;
+    }
+    if (p_sys->audio_type == TIVO_AUDIO_UNKNOWN) {
+        msg_Err(p_demux, "Can't determine Tivo Audio Type.");
+        b_probe_error = VLC_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;
+    }
+    return b_probe_error?VLC_EGENERIC:VLC_SUCCESS;
+}
+
+
+/* ======================================================================== */
+/* gather statistics for this chunk & set our tivo-type vars accordingly */
+static void analyze_chunk(demux_t *p_demux, const uint8_t *p_chunk)
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    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;
+
+    /* skip if it's a Part header */
+    if( U32_AT( &p_chunk[ 0 ] ) == TIVO_PES_FILEID )
+        return;
+
+    /* number of records in chunk (we ignore high order byte;
+     * rarely are there > 256 chunks & we don't need that many anyway) */
+    i_num_recs = p_chunk[0];
+    if (i_num_recs < 5) {
+        /* try again with the next chunk.  Sometimes there are dead ones */
+        return;
+    }
+    
+    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);
+    /* scan headers.
+     * 1. check video packets.  Presence of 0x6e0 means S1.
+     *    No 6e0 but have be0 means S2.
+     * 2. probe for audio 0x9c0 vs 0x3c0 (AC3 vs Mpeg)
+     *    If AC-3, then we have DTivo.
+     *    If MPEG, search for PTS offset.  This will determine SA vs. DTivo.
+     */
+    i_num_6e0 = i_num_be0 = i_num_9c0 = i_num_3c0 = 0;
+    for (i=0; i<i_num_recs; i++) {
+        //msg_Dbg(p_demux, "probe: rec is %d/%d = 0x%04x", p_hdrs[i].subrec_type,
+            //p_hdrs[i].rec_type,
+            //p_hdrs[i].subrec_type << 8 | p_hdrs[i].rec_type);
+        switch (p_hdrs[i].subrec_type << 8 | p_hdrs[i].rec_type) {
+            case 0x6e0:
+                i_num_6e0++;
+                break;
+            case 0xbe0:
+                i_num_be0++;
+                break;
+            case 0x3c0:
+                i_num_3c0++;
+                break;
+            case 0x9c0:
+                i_num_9c0++;
+                break;
+        }
+    }
+    msg_Dbg(p_demux, "probe: chunk has %d 0x6e0 recs, %d 0xbe0 recs.",
+        i_num_6e0, i_num_be0);
+
+    /* set up our variables */
+    if (i_num_6e0 > 0) {
+        msg_Dbg(p_demux, "detected Series 1 Tivo");
+        p_sys->tivo_series = TIVO_SERIES1;
+        p_sys->i_Pes_Length = SERIES1_PES_LENGTH;
+    } else if (i_num_be0 > 0) {
+        msg_Dbg(p_demux, "detected Series 2 Tivo");
+        p_sys->tivo_series = TIVO_SERIES2;
+        p_sys->i_Pes_Length = SERIES2_PES_LENGTH;
+    }
+    if (i_num_9c0 > 0) {
+        msg_Dbg(p_demux, "detected AC-3 Audio (DTivo)" );
+        p_sys->audio_type = TIVO_AUDIO_AC3;
+        p_sys->tivo_type = TIVO_TYPE_DTIVO;
+        p_sys->i_Pts_Offset = AC3_PTS_OFFSET;
+        p_sys->i_Pes_Length = AC3_PES_LENGTH;
+    } else if (i_num_3c0 > 0) {
+        p_sys->audio_type = TIVO_AUDIO_MPEG;
+        msg_Dbg(p_demux, "detected MPEG Audio" );
+    }
+
+    /* if tivo_type still unknown, we can check PTS location
+     * in MPEG packets to determine tivo_type */
+    if (p_sys->tivo_type == TIVO_TYPE_UNKNOWN) {
+        uint32_t i_data_offset = (16 * i_num_recs);
+        for (i=0; i<i_num_recs; i++) {
+            if ((p_hdrs[i].subrec_type << 0x08 | p_hdrs[i].rec_type) == 0x3c0 &&
+                    p_hdrs[i].l_rec_size > 15) {
+                /* first make sure we're aligned */
+                int i_pes_offset = find_es_header(ty_MPEGAudioPacket,
+                        &p_chunk[i_data_offset], 5);
+                if (i_pes_offset >= 0) {
+                    /* pes found. on SA, PES has hdr data at offset 6, not PTS. */
+                    //msg_Dbg(p_demux, "probe: mpeg es header found in rec %d at offset %d",
+                            //i, i_pes_offset);
+                    if ((p_chunk[i_data_offset + 6 + i_pes_offset] & 0x80) == 0x80) {
+                        /* S1SA or S2(any) Mpeg Audio (PES hdr, not a PTS start) */
+                        if (p_sys->tivo_series == TIVO_SERIES1)
+                            msg_Dbg(p_demux, "detected Stand-Alone Tivo" );
+                        p_sys->tivo_type = TIVO_TYPE_SA;
+                        p_sys->i_Pts_Offset = SA_PTS_OFFSET;
+                    } else {
+                        if (p_sys->tivo_series == TIVO_SERIES1)
+                            msg_Dbg(p_demux, "detected DirecTV Tivo" );
+                        p_sys->tivo_type = TIVO_TYPE_DTIVO;
+                        p_sys->i_Pts_Offset = DTIVO_PTS_OFFSET;
+                    }
+                    break;
+                }
+            }
+            i_data_offset += p_hdrs[i].l_rec_size;
+        }
+    }
+    free(p_hdrs);
+}
+
+
 /* =========================================================================== */
 static int get_chunk_header(demux_t *p_demux)
 {
-    int i_readSize, i_num_recs, i;
-    uint8_t packet_header[4];
-    uint8_t record_header[16];
-    ty_rec_hdr_t *p_rec_hdr;
+    int i_readSize, i_num_recs;
+    uint8_t *p_hdr_buf;
+    const uint8_t *p_peek;
     demux_sys_t *p_sys = p_demux->p_sys;
-    int i_payload_size = 0;         /* sum of all records */
+    int i_payload_size;             /* sum of all records' sizes */
 
-    msg_Dbg(p_demux, "parsing ty chunk #%d", p_sys->i_chunk_count );
+    msg_Dbg(p_demux, "parsing ty chunk #%d", p_sys->i_cur_chunk );
 
     /* if we have left-over filler space from the last chunk, get that */
-    if (p_sys->i_stuff_cnt > 0)
+    if (p_sys->i_stuff_cnt > 0) {
         stream_Read( p_demux->s, NULL, p_sys->i_stuff_cnt);
+        p_sys->i_stuff_cnt = 0;
+    }
 
     /* read the TY packet header */
-    i_readSize = stream_Read( p_demux->s, packet_header, 4 );
-    p_sys->i_chunk_count++;
-    if ( i_readSize < 4 )
+    i_readSize = stream_Peek( p_demux->s, &p_peek, 4 );
+    p_sys->i_cur_chunk++;
+  
+    if ( (i_readSize < 4) || ( U32_AT(&p_peek[ 0 ] ) == 0 ))
     {
         /* EOF */
         p_sys->eof = 1;
         return 0;
     }
-    /* if it's a PART Header, then try again. */
-    if( U32_AT( &packet_header[ 0 ] ) == TIVO_PES_FILEID )
+  
+    /* check if it's a PART Header */
+    if( U32_AT( &p_peek[ 0 ] ) == TIVO_PES_FILEID )
     {
-        msg_Dbg( p_demux, "skipping TY PART Header" );
-        /* TODO: if stream is seekable, should we seek() instead of read() ?? */
-        stream_Read( p_demux->s, NULL, CHUNK_SIZE - 4 );
+        /* parse master chunk */
+        parse_master(p_demux);
         return get_chunk_header(p_demux);
     }
-
+    
     /* number of records in chunk (8- or 16-bit number) */
-    if (packet_header[3] & 0x80)
+    if (p_peek[3] & 0x80)
     {
         /* 16 bit rec cnt */
-        p_sys->i_num_recs = i_num_recs = (packet_header[1] << 8) + packet_header[0];
-        p_sys->i_seq_rec = (packet_header[3] << 8) + packet_header[2];
+        p_sys->i_num_recs = i_num_recs = (p_peek[1] << 8) + p_peek[0];
+        p_sys->i_seq_rec = (p_peek[3] << 8) + p_peek[2];
         if (p_sys->i_seq_rec != 0xffff)
         {
             p_sys->i_seq_rec &= ~0x8000;
@@ -900,58 +1375,80 @@ static int get_chunk_header(demux_t *p_demux)
     else
     {
         /* 8 bit reclen - tivo 1.3 format */
-        p_sys->i_num_recs = i_num_recs = packet_header[0];
-        p_sys->i_seq_rec = packet_header[1];
+        p_sys->i_num_recs = i_num_recs = p_peek[0];
+        p_sys->i_seq_rec = p_peek[1];
     }
     p_sys->i_cur_rec = 0;
     p_sys->b_first_chunk = VLC_FALSE;
+  
     /*msg_Dbg( p_demux, "chunk has %d records", i_num_recs );*/
 
-    /* parse headers into array */
     if (p_sys->rec_hdrs)
         free(p_sys->rec_hdrs);
-    p_sys->rec_hdrs = malloc(i_num_recs * sizeof(ty_rec_hdr_t));
+
+    /* skip past the 4 bytes we "peeked" earlier */
+    stream_Read( p_demux->s, NULL, 4 );
+
+    /* read the record headers into a temp buffer */
+    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) {
+        p_sys->eof = VLC_TRUE;
+        return 0;
+    }
+    /* parse them */
+    p_sys->rec_hdrs = parse_chunk_headers(p_demux, p_hdr_buf, i_num_recs,
+            &i_payload_size);
+    free(p_hdr_buf);
+
+    p_sys->i_stuff_cnt = CHUNK_SIZE - 4 -
+        (p_sys->i_num_recs * 16) - i_payload_size;
+    if (p_sys->i_stuff_cnt > 0)
+        msg_Dbg( p_demux, "chunk has %d stuff bytes at end",
+                 p_sys->i_stuff_cnt );
+    return 1;
+}
+
+
+static ty_rec_hdr_t *parse_chunk_headers( demux_t *p_demux, const uint8_t *p_buf,
+                                          int i_num_recs, int *pi_payload_size)
+{
+    int i;
+    ty_rec_hdr_t *p_hdrs, *p_rec_hdr;
+
+    *pi_payload_size = 0;
+    p_hdrs = malloc(i_num_recs * sizeof(ty_rec_hdr_t));
+
     for (i = 0; i < i_num_recs; i++)
     {
-        i_readSize = stream_Read( p_demux->s, record_header, 16 );
-        if (i_readSize < 16)
-        {
-            /* EOF */
-            p_sys->eof = VLC_TRUE;
-            return 0;
-        }
-        p_rec_hdr = &p_sys->rec_hdrs[i];     /* for brevity */
+        const uint8_t *record_header = p_buf + (i * 16);
+        p_rec_hdr = &p_hdrs[i];     /* for brevity */
         p_rec_hdr->rec_type = record_header[3];
         p_rec_hdr->subrec_type = record_header[2] & 0x0f;
         if ((record_header[ 0 ] & 0x80) == 0x80)
         {
-            unsigned char b1, b2;
+            uint8_t b1, b2;
             /* marker bit 2 set, so read extended data */
-            b1 = ( ( ( record_header[ 0 ] & 0x0f ) << 4 ) |
+            b1 = ( ( ( record_header[ 0 ] & 0x0f ) << 4 ) | 
                    ( ( record_header[ 1 ] & 0xf0 ) >> 4 ) );
             b1 &= 0x7f;
-            b2 = ( ( ( record_header[ 1 ] & 0x0f ) << 4 ) |
+            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->l_rec_size = 0;
+            p_rec_hdr->l_ty_pts = 0;
             p_rec_hdr->b_ext = VLC_TRUE;
         }
         else
         {
             p_rec_hdr->l_rec_size = ( record_header[ 0 ] << 8 |
                 record_header[ 1 ] ) << 4 | ( record_header[ 2 ] >> 4 );
-            i_payload_size += p_rec_hdr->l_rec_size;
+            *pi_payload_size += p_rec_hdr->l_rec_size;
             p_rec_hdr->b_ext = VLC_FALSE;
+            p_rec_hdr->l_ty_pts = U64_AT( &record_header[ 8 ] );
         }
     } /* end of record-header loop */
-    p_sys->i_stuff_cnt = CHUNK_SIZE - 4 -
-        (p_sys->i_num_recs * 16) - i_payload_size;
-    if (p_sys->i_stuff_cnt > 0)
-        msg_Dbg( p_demux, "chunk has %d stuff bytes at end",
-                 p_sys->i_stuff_cnt );
-    return 1;
+    return p_hdrs;
 }