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 @@
* 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
*****************************************************************************/
/*****************************************************************************
......@@ -36,36 +39,14 @@
#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;
}
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