Commit 8281a155 authored by Laurent Aimar's avatar Laurent Aimar

Import new http://tivo-vlc.sf.net tivo demuxer version. (The author

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)
parent 35289585
...@@ -26,6 +26,9 @@ ...@@ -26,6 +26,9 @@
* v1.0.0 - 24-Feb-2005 - Initial release - Series 1 support ONLY! * 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.1 - 25-Feb-2005 - Added fix for bad GOP headers - Neal
* v1.0.2 - 26-Feb-2005 - No longer require "seekable" input stream - 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
*****************************************************************************/ *****************************************************************************/
/***************************************************************************** /*****************************************************************************
...@@ -36,36 +39,14 @@ ...@@ -36,36 +39,14 @@
#include <vlc_demux.h> #include <vlc_demux.h>
#include "vlc_codec.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 * Module descriptor
*****************************************************************************/ *****************************************************************************/
static int Open ( vlc_object_t * );
static void Close( vlc_object_t * );
vlc_module_begin(); vlc_module_begin();
set_shortname( "TY" ); set_shortname( _("TY") );
set_description(_("TY Stream audio/video demux")); set_description(_("TY Stream audio/video demux"));
set_category( CAT_INPUT ); set_category( CAT_INPUT );
set_subcategory( SUBCAT_INPUT_DEMUX ); set_subcategory( SUBCAT_INPUT_DEMUX );
...@@ -74,11 +55,32 @@ vlc_module_begin(); ...@@ -74,11 +55,32 @@ vlc_module_begin();
* and TY demux has a bigger priority than PS * and TY demux has a bigger priority than PS
* Something must be wrong. * Something must be wrong.
*/ */
set_callbacks(TyOpen, TyClose); set_callbacks( Open, Close );
add_shortcut("ty"); add_shortcut("ty");
add_shortcut("tivo"); add_shortcut("tivo");
vlc_module_end(); 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: /* packet types for reference:
2/c0: audio data continued 2/c0: audio data continued
3/c0: audio packet header (PES header) 3/c0: audio packet header (PES header)
...@@ -92,8 +94,9 @@ vlc_module_end(); ...@@ -92,8 +94,9 @@ vlc_module_end();
b/e0: video B-frame header start b/e0: video B-frame header start
c/e0: video GOP header start c/e0: video GOP header start
e/01: closed-caption data 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/03: ipreview data ("thumbs up to record" signal)
e/05: UK Teletext
*/ */
#define TIVO_PES_FILEID ( 0xf5467abd ) #define TIVO_PES_FILEID ( 0xf5467abd )
...@@ -103,25 +106,65 @@ vlc_module_end(); ...@@ -103,25 +106,65 @@ vlc_module_end();
typedef struct typedef struct
{ {
long l_rec_size; long l_rec_size;
unsigned char ex1, ex2; uint8_t ex1, ex2;
unsigned char rec_type; uint8_t rec_type;
unsigned char subrec_type; uint8_t subrec_type;
char b_ext; vlc_bool_t b_ext;
uint64_t l_ty_pts; /* TY PTS in the record header */
} ty_rec_hdr_t; } 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 struct demux_sys_t
{ {
es_out_id_t *p_video; /* ptr to video codec */ es_out_id_t *p_video; /* ptr to video codec */
es_out_id_t *p_audio; /* holds either ac3 or mpeg codec ptr */ 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; int i_stuff_cnt;
size_t i_stream_size; /* size of input stream (if known) */ 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_seekable; /* is this stream seekable? */
int tivoType; /* 1 = SA, 2 = DTiVo */ vlc_bool_t b_have_master; /* are master chunks present? */
vlc_bool_t b_mpeg_audio; /* true if we're using MPEG audio */ 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 */ uint8_t pes_buffer[20]; /* holds incomplete pes headers */
int i_pes_buf_cnt; /* how many bytes in our buffer */ 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 firstAudioPTS;
mtime_t lastAudioPTS; mtime_t lastAudioPTS;
...@@ -131,13 +174,26 @@ struct demux_sys_t ...@@ -131,13 +174,26 @@ struct demux_sys_t
int i_cur_rec; /* current record in this chunk */ int i_cur_rec; /* current record in this chunk */
int i_num_recs; /* number of recs in this chunk */ int i_num_recs; /* number of recs in this chunk */
int i_seq_rec; /* record number where seq start is */ 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 eof;
vlc_bool_t b_first_chunk; 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: * here's what we do:
* 1. peek at the first 12 bytes of the stream for the * 1. peek at the first 12 bytes of the stream for the
...@@ -146,7 +202,7 @@ struct demux_sys_t ...@@ -146,7 +202,7 @@ struct demux_sys_t
* 3. set up video (mpgv) codec * 3. set up video (mpgv) codec
* 4. return VLC_SUCCESS * 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_t *p_demux = (demux_t *)p_this;
demux_sys_t *p_sys; demux_sys_t *p_sys;
...@@ -162,18 +218,17 @@ static int TyOpen(vlc_object_t *p_this) ...@@ -162,18 +218,17 @@ static int TyOpen(vlc_object_t *p_this)
U32_AT(&p_peek[4]) != 0x02 || U32_AT(&p_peek[4]) != 0x02 ||
U32_AT(&p_peek[8]) != CHUNK_SIZE ) U32_AT(&p_peek[8]) != CHUNK_SIZE )
{ {
/* doesn't look like a TY file... */ if( !p_demux->b_force )
if( !p_demux->b_force && !demux2_IsPathExtension( p_demux, ".ty" ) )
return VLC_EGENERIC; return VLC_EGENERIC;
msg_Warn( p_demux, "this does not look like a TY file, " 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" ); msg_Dbg( p_demux, "valid TY stream detected" );
/* Set exported functions */ /* Set exported functions */
p_demux->pf_demux = TyDemux; p_demux->pf_demux = Demux;
p_demux->pf_control = Control; p_demux->pf_control = Control;
/* create our structure that will hold all data */ /* create our structure that will hold all data */
...@@ -182,20 +237,34 @@ static int TyOpen(vlc_object_t *p_this) ...@@ -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) */ /* 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 = VLC_TRUE;
p_sys->b_have_master = (U32_AT(p_peek) == TIVO_PES_FILEID);
p_sys->firstAudioPTS = -1; p_sys->firstAudioPTS = -1;
p_sys->i_stream_size = stream_Size(p_demux->s); 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 */ /* see if this stream is seekable */
stream_Control( p_demux->s, STREAM_CAN_SEEK, &p_sys->b_seekable ); stream_Control( p_demux->s, STREAM_CAN_SEEK, &p_sys->b_seekable );
/* TODO: read first chunk & parse first audio PTS, then (if seekable) if (probe_stream(p_demux) != VLC_SUCCESS) {
* seek to last chunk & last record; read its PTS and compute //TyClose(p_demux);
* overall program time. Also determine Tivo type. */ return VLC_EGENERIC;
}
/* NOTE: we wait to create the audio ES until we know what if (!p_sys->b_have_master)
* audio type we have. */ msg_Warn(p_demux, "No master chunk found; seeking will be limited.");
p_sys->p_audio = NULL;
/* 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 */ /* register the video stream */
es_format_Init( &fmt, VIDEO_ES, VLC_FOURCC( 'm', 'p', 'g', 'v' ) ); es_format_Init( &fmt, VIDEO_ES, VLC_FOURCC( 'm', 'p', 'g', 'v' ) );
...@@ -211,31 +280,10 @@ static int TyOpen(vlc_object_t *p_this) ...@@ -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) /* Compute Presentation Time Stamp (PTS)
* Assume buf points to beginning of 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; mtime_t i_pts;
...@@ -250,24 +298,17 @@ static mtime_t get_pts( unsigned char *buf ) ...@@ -250,24 +298,17 @@ static mtime_t get_pts( unsigned char *buf )
/* =========================================================================== */ /* =========================================================================== */
static int find_es_header( unsigned const char *header, static int find_es_header( const uint8_t *header,
unsigned char *buffer, int bufferSize, int *esOffset1 ) const uint8_t *buffer, int i_search_len )
{ {
int count; int count;
*esOffset1 = -1; for( count = 0; count < i_search_len; count++ )
for( count = 0 ; count < bufferSize ; count++ )
{ {
if ( ( buffer[ count + 0 ] == header[ 0 ] ) && if( !memcmp( &buffer[count], header, 4 ) )
( buffer[ count + 1 ] == header[ 1 ] ) && return count;
( buffer[ count + 2 ] == header[ 2 ] ) &&
( buffer[ count + 3 ] == header[ 3 ] ) )
{
*esOffset1 = count;
return 1;
}
} }
return( -1 ); return -1;
} }
...@@ -278,25 +319,13 @@ static int find_es_header( unsigned const char *header, ...@@ -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, some audio data found (buffer adjusted),
* -1 partial PES hdr found, no audio data found * -1 partial PES hdr found, no audio data found
* 0 otherwise (complete PES found, pts extracted, pts set, buffer adjusted) */ * 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, static int check_sync_pes( demux_t *p_demux, block_t *p_block,
int32_t offset, int32_t rec_len ) int32_t offset, int32_t rec_len )
{ {
demux_sys_t *p_sys = p_demux->p_sys; 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 ) if ( offset < 0 || offset + p_sys->i_Pes_Length > rec_len )
{
/* 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 )
{ {
/* entire PES header not present */ /* entire PES header not present */
msg_Dbg( p_demux, "PES header at %d not complete in record. storing.", 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, ...@@ -305,7 +334,7 @@ static int check_sync_pes( demux_t *p_demux, block_t *p_block,
if( offset < 0 ) if( offset < 0 )
{ {
/* no header found, fake some 00's (this works, believe me) */ /* 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; p_sys->i_pes_buf_cnt = 4;
if( rec_len > 4 ) if( rec_len > 4 )
msg_Err( p_demux, "PES header not found in record of %d bytes!", 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, ...@@ -326,21 +355,35 @@ static int check_sync_pes( demux_t *p_demux, block_t *p_block,
return -1; /* partial PES, no audio data */ return -1; /* partial PES, no audio data */
} }
/* full PES header present, extract PTS */ /* 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) if (p_sys->firstAudioPTS < 0)
p_sys->firstAudioPTS = p_sys->lastAudioPTS; p_sys->firstAudioPTS = p_sys->lastAudioPTS;
p_block->i_pts = p_sys->lastAudioPTS; p_block->i_pts = p_sys->lastAudioPTS;
/*msg_Dbg(p_demux, "Audio PTS %lld", p_sys->lastAudioPTS );*/ /*msg_Dbg(p_demux, "Audio PTS %lld", p_sys->lastAudioPTS );*/
/* adjust audio record to remove PES header */ /* adjust audio record to remove PES header */
memmove(p_block->p_buffer + offset, p_block->p_buffer + offset + pes_length, memmove(p_block->p_buffer + offset, p_block->p_buffer + offset +
rec_len - pes_length); p_sys->i_Pes_Length, rec_len - p_sys->i_Pes_Length);
p_block->i_buffer -= 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; 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 * 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, ...@@ -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) * that means we can demux an entire chunk and shoot it back (may be more efficient)
* -- should try that some day :) -- * -- 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 invalidType = 0;
int recordsDecoded = 0; int recordsDecoded = 0;
...@@ -362,13 +407,11 @@ int TyDemux(demux_t *p_demux) ...@@ -362,13 +407,11 @@ int TyDemux(demux_t *p_demux)
block_t *p_block_in = NULL; block_t *p_block_in = NULL;
int esOffset1; int esOffset1;
unsigned char lastCC[ 16 ]; uint8_t lastCC[ 16 ];
unsigned char lastXDS[ 16 ]; uint8_t lastXDS[ 16 ];
demux_sys_t *p_sys = p_demux->p_sys;
/*msg_Dbg(p_demux, "ty demux processing" );*/ /*msg_Dbg(p_demux, "ty demux processing" );*/
/* did we hit EOF earlier? */ /* did we hit EOF earlier? */
if (p_sys->eof) return 0; if (p_sys->eof) return 0;
...@@ -383,7 +426,7 @@ int TyDemux(demux_t *p_demux) ...@@ -383,7 +426,7 @@ int TyDemux(demux_t *p_demux)
* - set PTS for data packets * - set PTS for data packets
* - pass the data on to the proper codec via es_out_Send() * - 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 * if we're at the end of this chunk, start a new one
*/ */
/* parse the next chunk's record headers */ /* parse the next chunk's record headers */
...@@ -405,7 +448,7 @@ int TyDemux(demux_t *p_demux) ...@@ -405,7 +448,7 @@ int TyDemux(demux_t *p_demux)
{ {
/*msg_Dbg(p_demux, "Record Type 0x%x/%02x %ld bytes", /*msg_Dbg(p_demux, "Record Type 0x%x/%02x %ld bytes",
subrec_type, rec_type, l_rec_size );*/ subrec_type, rec_type, l_rec_size );*/
/* some normal records are 0 length, so check for that... */ /* some normal records are 0 length, so check for that... */
if (l_rec_size > 0) if (l_rec_size > 0)
{ {
...@@ -439,36 +482,58 @@ int TyDemux(demux_t *p_demux) ...@@ -439,36 +482,58 @@ int TyDemux(demux_t *p_demux)
*================================================================*/ *================================================================*/
if ( rec_type == 0xe0 ) 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. /* get the PTS from this packet if it has one.
* Do NOT Pass this packet (a PES Header) on to the MPEG2 codec */ * on S1, only 0x06 has PES. On S2, however, most all do.
find_es_header( ty_VideoPacket, p_block_in->p_buffer, * Do NOT Pass the PES Header to the MPEG2 codec */
l_rec_size, &esOffset1 ); esOffset1 = find_es_header( ty_VideoPacket, p_block_in->p_buffer,
5 );
if ( esOffset1 != -1 ) if ( esOffset1 != -1 )
{ {
/* msg_Dbg(p_demux, "Video PES hdr at offset %d", esOffset1); */ //msg_Dbg(p_demux, "Video PES hdr in pkt type 0x%02x at offset %d",
p_sys->lastVideoPTS = get_pts( &p_block_in->p_buffer[ esOffset1 + 9 ] ); //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 "I64Fd, p_sys->i_cur_rec,
p_sys->lastVideoPTS );*/ p_sys->lastVideoPTS );*/
} if (subrec_type != 0x06) {
block_Release(p_block_in); /* 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 (subrec_type == 0x06) {
{ /* type 6 (S1 DTivo) has no data, so we're done */
#if 0 block_Release(p_block_in);
msg_Dbg(p_demux, "packet buffer has " } else {
"%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 it's not a continue blk, then set PTS */ /* if it's not a continue blk, then set PTS */
if (subrec_type != 0x02) if (subrec_type != 0x02)
{ {
...@@ -480,16 +545,33 @@ int TyDemux(demux_t *p_demux) ...@@ -480,16 +545,33 @@ int TyDemux(demux_t *p_demux)
* and it causes problems */ * and it causes problems */
if (subrec_type == 0x0c && l_rec_size >= 6) if (subrec_type == 0x0c && l_rec_size >= 6)
p_block_in->p_buffer[5] |= 0x08; 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 */ /* set PTS for this block before we send */
if (p_sys->lastVideoPTS > 0) if (p_sys->lastVideoPTS > 0)
{ {
p_block_in->i_pts = p_sys->lastVideoPTS; p_block_in->i_pts = p_sys->lastVideoPTS;
/* PTS gets used ONCE. /* PTS gets used ONCE.
* Any subsequent frames we get BEFORE next PES * Any subsequent frames we get BEFORE next PES
* header will have their PTS computed in the codec */ * header will have their PTS computed in the codec */
p_sys->lastVideoPTS = 0; 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); es_out_Send(p_demux->out, p_sys->p_video, p_block_in);
} }
} /* end if video rec type */ } /* end if video rec type */
...@@ -507,22 +589,6 @@ int TyDemux(demux_t *p_demux) ...@@ -507,22 +589,6 @@ int TyDemux(demux_t *p_demux)
printf( "%2.2x ", p_block_in->p_buffer[i] ); printf( "%2.2x ", p_block_in->p_buffer[i] );
printf( "\n" ); printf( "\n" );
#endif #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) /* SA or DTiVo Audio Data, no PES (continued block)
* ================================================ * ================================================
...@@ -530,10 +596,9 @@ int TyDemux(demux_t *p_demux) ...@@ -530,10 +596,9 @@ int TyDemux(demux_t *p_demux)
if ( subrec_type == 2 ) if ( subrec_type == 2 )
{ {
/* continue PES if previous was incomplete */ /* continue PES if previous was incomplete */
/* TODO: Make this work for all series & types of tivos */
if (p_sys->i_pes_buf_cnt > 0) 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"); msg_Dbg(p_demux, "continuing PES header");
/* do we have enough data to complete? */ /* do we have enough data to complete? */
...@@ -546,12 +611,12 @@ int TyDemux(demux_t *p_demux) ...@@ -546,12 +611,12 @@ int TyDemux(demux_t *p_demux)
p_block_in->p_buffer += i_need; p_block_in->p_buffer += i_need;
p_block_in->i_buffer -= i_need; p_block_in->i_buffer -= i_need;
/* get the PTS out of this PES header (MPEG or AC3) */ /* get the PTS out of this PES header (MPEG or AC3) */
if (p_sys->b_mpeg_audio) if (p_sys->audio_type == TIVO_AUDIO_MPEG)
find_es_header(ty_MPEGAudioPacket, p_sys->pes_buffer, esOffset1 = find_es_header(ty_MPEGAudioPacket,
10, &esOffset1); p_sys->pes_buffer, 5);
else else
find_es_header(ty_AC3AudioPacket, p_sys->pes_buffer, esOffset1 = find_es_header(ty_AC3AudioPacket,
10, &esOffset1); p_sys->pes_buffer, 5);
if (esOffset1 < 0) if (esOffset1 < 0)
{ {
/* god help us; something's really wrong */ /* god help us; something's really wrong */
...@@ -559,8 +624,8 @@ int TyDemux(demux_t *p_demux) ...@@ -559,8 +624,8 @@ int TyDemux(demux_t *p_demux)
} }
else else
{ {
p_sys->lastAudioPTS = get_pts( p_sys->lastAudioPTS = get_pts(
&p_sys->pes_buffer[ esOffset1 + DTIVO_PTS_OFFSET ] ); &p_sys->pes_buffer[ esOffset1 + p_sys->i_Pts_Offset ] );
p_block_in->i_pts = p_sys->lastAudioPTS; p_block_in->i_pts = p_sys->lastAudioPTS;
} }
p_sys->i_pes_buf_cnt = 0; p_sys->i_pes_buf_cnt = 0;
...@@ -576,9 +641,22 @@ int TyDemux(demux_t *p_demux) ...@@ -576,9 +641,22 @@ int TyDemux(demux_t *p_demux)
return 1; return 1;
} }
} }
/* set PCR before we send */ /* S2 DTivo has AC3 packets with 2 padding bytes at end. This is
/*es_out_Control( p_demux->out, ES_OUT_SET_PCR, * not allowed in the AC3 spec and will cause problems. So here
p_block_in->i_pts );*/ * 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 ); es_out_Send( p_demux->out, p_sys->p_audio, p_block_in );
} /* subrec == 2 */ } /* subrec == 2 */
...@@ -586,8 +664,8 @@ int TyDemux(demux_t *p_demux) ...@@ -586,8 +664,8 @@ int TyDemux(demux_t *p_demux)
/* ================================================ */ /* ================================================ */
if ( subrec_type == 0x03 ) if ( subrec_type == 0x03 )
{ {
find_es_header( ty_MPEGAudioPacket, p_block_in->p_buffer, esOffset1 = find_es_header( ty_MPEGAudioPacket,
l_rec_size, &esOffset1 ); p_block_in->p_buffer, 5 );
/*msg_Dbg(p_demux, "buffer has %#02x %#02x %#02x %#02x", /*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[0], p_block_in->p_buffer[1],
...@@ -598,7 +676,6 @@ int TyDemux(demux_t *p_demux) ...@@ -598,7 +676,6 @@ int TyDemux(demux_t *p_demux)
/* ================================================ */ /* ================================================ */
if ( ( esOffset1 == 0 ) && ( l_rec_size == 16 ) ) if ( ( esOffset1 == 0 ) && ( l_rec_size == 16 ) )
{ {
p_sys->tivoType = 1;
p_sys->lastAudioPTS = get_pts( &p_block_in->p_buffer[ p_sys->lastAudioPTS = get_pts( &p_block_in->p_buffer[
SA_PTS_OFFSET ] ); SA_PTS_OFFSET ] );
if (p_sys->firstAudioPTS < 0) if (p_sys->firstAudioPTS < 0)
...@@ -611,14 +688,11 @@ int TyDemux(demux_t *p_demux) ...@@ -611,14 +688,11 @@ int TyDemux(demux_t *p_demux)
/* DTiVo Audio with PES Header */ /* DTiVo Audio with PES Header */
/* ================================================ */ /* ================================================ */
{ {
p_sys->tivoType = 2; /* Check for complete PES */
/* Check for complete PES
* (TODO: handle proper size for tivo version) */
if (check_sync_pes(p_demux, p_block_in, esOffset1, if (check_sync_pes(p_demux, p_block_in, esOffset1,
l_rec_size) == -1) l_rec_size) == -1)
{ {
/* partial PES header found, nothing else. /* partial PES header found, nothing else.
* we're done. */ * we're done. */
p_sys->i_cur_rec++; p_sys->i_cur_rec++;
block_Release(p_block_in); block_Release(p_block_in);
...@@ -666,13 +740,23 @@ int TyDemux(demux_t *p_demux) ...@@ -666,13 +740,23 @@ int TyDemux(demux_t *p_demux)
/* ================================================ */ /* ================================================ */
if ( subrec_type == 0x09 ) if ( subrec_type == 0x09 )
{ {
find_es_header( ty_AC3AudioPacket, p_block_in->p_buffer, esOffset1 = find_es_header( ty_AC3AudioPacket,
l_rec_size, &esOffset1 ); p_block_in->p_buffer, 5 );
/*msg_Dbg(p_demux, "buffer has %#02x %#02x %#02x %#02x", #if 0
p_block_in->p_buffer[0], p_block_in->p_buffer[1], msg_Dbg(p_demux, "buffer has "
p_block_in->p_buffer[2], p_block_in->p_buffer[3]); "%02x %02x %02x %02x %02x %02x %02x %02x "
msg_Dbg(p_demux, "audio ES AC3 hdr at offset %d", esOffset1);*/ "%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 */ /* Check for complete PES */
if (check_sync_pes(p_demux, p_block_in, esOffset1, if (check_sync_pes(p_demux, p_block_in, esOffset1,
...@@ -688,6 +772,15 @@ int TyDemux(demux_t *p_demux) ...@@ -688,6 +772,15 @@ int TyDemux(demux_t *p_demux)
es_out_Control( p_demux->out, ES_OUT_SET_PCR, es_out_Control( p_demux->out, ES_OUT_SET_PCR,
p_block_in->i_pts ); 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 ); es_out_Send( p_demux->out, p_sys->p_audio, p_block_in );
} }
} /* end "if audio" */ } /* end "if audio" */
...@@ -756,41 +849,52 @@ int TyDemux(demux_t *p_demux) ...@@ -756,41 +849,52 @@ int TyDemux(demux_t *p_demux)
/* seek to a position within the stream, if possible */ /* 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; demux_sys_t *p_sys = p_demux->p_sys;
int64_t seek_pos = p_sys->i_stream_size * seek_pct; int64_t seek_pos = p_sys->i_stream_size * seek_pct;
int i; int i, i_cur_part;
long l_skip_amt; long l_skip_amt;
/* if we're not seekable, there's nothing to do */ /* if we're not seekable, there's nothing to do */
if (!p_sys->b_seekable) if (!p_sys->b_seekable)
return VLC_EGENERIC; return VLC_EGENERIC;
/* figure out which chunk we want & go there */ /* figure out which part & chunk we want & go there */
p_sys->i_chunk_count = seek_pos / CHUNK_SIZE; 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 */ /* can't seek stream */
return VLC_EGENERIC; return VLC_EGENERIC;
} }
/* load the chunk */ /* load the chunk */
p_sys->i_stuff_cnt = 0;
get_chunk_header(p_demux); get_chunk_header(p_demux);
/* seek within the chunk to get roughly to where we want */ /* seek within the chunk to get roughly to where we want */
p_sys->i_cur_rec = (int) p_sys->i_cur_rec = (int)
((double) ((seek_pos % CHUNK_SIZE) / (double) (CHUNK_SIZE)) * p_sys->i_num_recs); ((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)", 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. /* seek to the start of this record's data.
* to do that, we have to skip past all prior records */ * to do that, we have to skip past all prior records */
l_skip_amt = 0; l_skip_amt = 0;
for (i=0; i<p_sys->i_cur_rec; i++) for (i=0; i<p_sys->i_cur_rec; i++)
l_skip_amt += p_sys->rec_hdrs[i].l_rec_size; 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); (p_sys->i_num_recs * 16) + l_skip_amt + 4);
/* to hell with syncing any audio or video, just start reading records... :) */ /* 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) ...@@ -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) static int Control(demux_t *p_demux, int i_query, va_list args)
{ {
demux_sys_t *p_sys = p_demux->p_sys; demux_sys_t *p_sys = p_demux->p_sys;
double f, *pf; double f, *pf;
int64_t i64, *p_i64; int64_t i64, *p_i64;
/*msg_Info(p_demux, "control cmd %d", i_query);*/ /*msg_Info(p_demux, "control cmd %d", i_query);*/
switch( i_query ) switch( i_query )
{ {
...@@ -822,17 +1128,26 @@ static int Control(demux_t *p_demux, int i_query, va_list args) ...@@ -822,17 +1128,26 @@ static int Control(demux_t *p_demux, int i_query, va_list args)
case DEMUX_SET_POSITION: case DEMUX_SET_POSITION:
/* arg is 0.0 - 1.0 percent of overall file position */ /* arg is 0.0 - 1.0 percent of overall file position */
f = (double) va_arg( args, double ); 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) 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; return VLC_EGENERIC;
case DEMUX_GET_TIME: case DEMUX_GET_TIME:
/* return latest PTS - start PTS */ /* return TiVo timestamp */
p_i64 = (int64_t *) va_arg(args, int64_t *); 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; return VLC_SUCCESS;
case DEMUX_SET_TIME: /* arg is time in microsecs */
case DEMUX_GET_LENGTH: /* length of program in microseconds, 0 if unk */ 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: case DEMUX_GET_FPS:
default: default:
return VLC_EGENERIC; return VLC_EGENERIC;
...@@ -840,8 +1155,8 @@ static int Control(demux_t *p_demux, int i_query, va_list args) ...@@ -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; demux_sys_t *p_sys = ((demux_t *) p_this)->p_sys;
...@@ -850,48 +1165,208 @@ static void TyClose( vlc_object_t *p_this ) ...@@ -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) static int get_chunk_header(demux_t *p_demux)
{ {
int i_readSize, i_num_recs, i; int i_readSize, i_num_recs;
uint8_t packet_header[4]; uint8_t *p_hdr_buf;
uint8_t record_header[16]; const uint8_t *p_peek;
ty_rec_hdr_t *p_rec_hdr;
demux_sys_t *p_sys = p_demux->p_sys; 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 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); stream_Read( p_demux->s, NULL, p_sys->i_stuff_cnt);
p_sys->i_stuff_cnt = 0;
}
/* read the TY packet header */ /* read the TY packet header */
i_readSize = stream_Read( p_demux->s, packet_header, 4 ); i_readSize = stream_Peek( p_demux->s, &p_peek, 4 );
p_sys->i_chunk_count++; p_sys->i_cur_chunk++;
if ( i_readSize < 4 ) if ( (i_readSize < 4) || ( U32_AT(&p_peek[ 0 ] ) == 0 ))
{ {
/* EOF */ /* EOF */
p_sys->eof = 1; p_sys->eof = 1;
return 0; return 0;
} }
/* if it's a PART Header, then try again. */ /* check if it's a PART Header */
if( U32_AT( &packet_header[ 0 ] ) == TIVO_PES_FILEID ) if( U32_AT( &p_peek[ 0 ] ) == TIVO_PES_FILEID )
{ {
msg_Dbg( p_demux, "skipping TY PART Header" ); /* parse master chunk */
/* TODO: if stream is seekable, should we seek() instead of read() ?? */ parse_master(p_demux);
stream_Read( p_demux->s, NULL, CHUNK_SIZE - 4 );
return get_chunk_header(p_demux); return get_chunk_header(p_demux);
} }
/* number of records in chunk (8- or 16-bit number) */ /* number of records in chunk (8- or 16-bit number) */
if (packet_header[3] & 0x80) if (p_peek[3] & 0x80)
{ {
/* 16 bit rec cnt */ /* 16 bit rec cnt */
p_sys->i_num_recs = i_num_recs = (packet_header[1] << 8) + packet_header[0]; p_sys->i_num_recs = i_num_recs = (p_peek[1] << 8) + p_peek[0];
p_sys->i_seq_rec = (packet_header[3] << 8) + packet_header[2]; p_sys->i_seq_rec = (p_peek[3] << 8) + p_peek[2];
if (p_sys->i_seq_rec != 0xffff) if (p_sys->i_seq_rec != 0xffff)
{ {
p_sys->i_seq_rec &= ~0x8000; p_sys->i_seq_rec &= ~0x8000;
...@@ -900,58 +1375,80 @@ static int get_chunk_header(demux_t *p_demux) ...@@ -900,58 +1375,80 @@ static int get_chunk_header(demux_t *p_demux)
else else
{ {
/* 8 bit reclen - tivo 1.3 format */ /* 8 bit reclen - tivo 1.3 format */
p_sys->i_num_recs = i_num_recs = packet_header[0]; p_sys->i_num_recs = i_num_recs = p_peek[0];
p_sys->i_seq_rec = packet_header[1]; p_sys->i_seq_rec = p_peek[1];
} }
p_sys->i_cur_rec = 0; p_sys->i_cur_rec = 0;
p_sys->b_first_chunk = VLC_FALSE; p_sys->b_first_chunk = VLC_FALSE;
/*msg_Dbg( p_demux, "chunk has %d records", i_num_recs );*/ /*msg_Dbg( p_demux, "chunk has %d records", i_num_recs );*/
/* parse headers into array */
if (p_sys->rec_hdrs) if (p_sys->rec_hdrs)
free(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++) for (i = 0; i < i_num_recs; i++)
{ {
i_readSize = stream_Read( p_demux->s, record_header, 16 ); const uint8_t *record_header = p_buf + (i * 16);
if (i_readSize < 16) p_rec_hdr = &p_hdrs[i]; /* for brevity */
{
/* EOF */
p_sys->eof = VLC_TRUE;
return 0;
}
p_rec_hdr = &p_sys->rec_hdrs[i]; /* for brevity */
p_rec_hdr->rec_type = record_header[3]; p_rec_hdr->rec_type = record_header[3];
p_rec_hdr->subrec_type = record_header[2] & 0x0f; p_rec_hdr->subrec_type = record_header[2] & 0x0f;
if ((record_header[ 0 ] & 0x80) == 0x80) if ((record_header[ 0 ] & 0x80) == 0x80)
{ {
unsigned char b1, b2; uint8_t b1, b2;
/* marker bit 2 set, so read extended data */ /* 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 ) ); ( ( record_header[ 1 ] & 0xf0 ) >> 4 ) );
b1 &= 0x7f; b1 &= 0x7f;
b2 = ( ( ( record_header[ 1 ] & 0x0f ) << 4 ) | b2 = ( ( ( record_header[ 1 ] & 0x0f ) << 4 ) |
( ( record_header[ 2 ] & 0xf0 ) >> 4 ) ); ( ( record_header[ 2 ] & 0xf0 ) >> 4 ) );
b2 &= 0x7f; b2 &= 0x7f;
p_rec_hdr->ex1 = b1; p_rec_hdr->ex1 = b1;
p_rec_hdr->ex2 = b2; p_rec_hdr->ex2 = b2;
p_rec_hdr->l_rec_size = 0; 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 = VLC_TRUE;
} }
else else
{ {
p_rec_hdr->l_rec_size = ( record_header[ 0 ] << 8 | p_rec_hdr->l_rec_size = ( record_header[ 0 ] << 8 |
record_header[ 1 ] ) << 4 | ( record_header[ 2 ] >> 4 ); 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->b_ext = VLC_FALSE;
p_rec_hdr->l_ty_pts = U64_AT( &record_header[ 8 ] );
} }
} /* end of record-header loop */ } /* end of record-header loop */
p_sys->i_stuff_cnt = CHUNK_SIZE - 4 - return p_hdrs;
(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;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment