1 /*****************************************************************************
2 * ty.c - TiVo ty stream video demuxer for VLC
3 *****************************************************************************
4 * Copyright (C) 2005 the VideoLAN team
5 * Copyright (C) 2005 by Neal Symms (tivo@freakinzoo.com) - February 2005
6 * based on code by Christopher Wingert for tivo-mplayer
7 * tivo(at)wingert.org, February 2003
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 * v1.0.0 - 24-Feb-2005 - Initial release - Series 1 support ONLY!
27 * v1.0.1 - 25-Feb-2005 - Added fix for bad GOP headers - Neal
28 * v1.0.2 - 26-Feb-2005 - No longer require "seekable" input stream - Neal
29 * v2.0.0 - 21-Mar-2005 - Series 2 support! No AC-3 on S2 DTivo yet.
30 * v2.1.0 - 22-Mar-2005 - Support for AC-3 on S2 DTivo (long ac3 packets)
31 * v3.0.0 - 14-Jul-2005 - Support for skipping fwd/back via VLC hotkeys
32 *****************************************************************************/
34 /*****************************************************************************
36 *****************************************************************************/
42 #include <vlc_common.h>
43 #include <vlc_plugin.h>
44 #include <vlc_demux.h>
45 #include <vlc_codec.h>
47 #include <vlc_input.h>
48 #include "../codec/cc.h"
52 /*****************************************************************************
54 *****************************************************************************/
55 static int Open ( vlc_object_t * );
56 static void Close( vlc_object_t * );
59 set_shortname( N_("TY") )
60 set_description(N_("TY Stream audio/video demux"))
61 set_category( CAT_INPUT )
62 set_subcategory( SUBCAT_INPUT_DEMUX )
63 set_capability("demux", 6)
64 /* FIXME: there seems to be a segfault when using PVR access
65 * and TY demux has a bigger priority than PS
66 * Something must be wrong.
68 set_callbacks( Open, Close )
73 /*****************************************************************************
75 *****************************************************************************/
76 static int Demux ( demux_t * );
77 static int Control( demux_t *, int, va_list );
79 #define SERIES1_PES_LENGTH (11) /* length of audio PES hdr on S1 */
80 #define SERIES2_PES_LENGTH (16) /* length of audio PES hdr on S2 */
81 #define AC3_PES_LENGTH (14) /* length of audio PES hdr for AC3 */
82 #define VIDEO_PES_LENGTH (16) /* length of video PES header */
83 #define DTIVO_PTS_OFFSET (6) /* offs into PES for MPEG PTS on DTivo */
84 #define SA_PTS_OFFSET (9) /* offset into PES for MPEG PTS on SA */
85 #define AC3_PTS_OFFSET (9) /* offset into PES for AC3 PTS on DTivo */
86 #define VIDEO_PTS_OFFSET (9) /* offset into PES for video PTS on all */
87 #define AC3_PKT_LENGTH (1536) /* size of TiVo AC3 pkts (w/o PES hdr) */
88 static const uint8_t ty_VideoPacket[] = { 0x00, 0x00, 0x01, 0xe0 };
89 static const uint8_t ty_MPEGAudioPacket[] = { 0x00, 0x00, 0x01, 0xc0 };
90 static const uint8_t ty_AC3AudioPacket[] = { 0x00, 0x00, 0x01, 0xbd };
92 #define CHUNK_PEEK_COUNT (3) /* number of chunks to probe */
94 /* packet types for reference:
95 2/c0: audio data continued
96 3/c0: audio packet header (PES header)
97 4/c0: audio data (S/A only?)
98 9/c0: audio packet header, AC-3 audio
99 2/e0: video data continued
100 6/e0: video packet header (PES header)
101 7/e0: video sequence header start
102 8/e0: video I-frame header start
103 a/e0: video P-frame header start
104 b/e0: video B-frame header start
105 c/e0: video GOP header start
106 e/01: closed-caption data
107 e/02: Extended data services data
108 e/03: ipreview data ("thumbs up to record" signal)
112 #define TIVO_PES_FILEID ( 0xf5467abd )
113 #define TIVO_PART_LENGTH ( 0x20000000 ) /* 536,870,912 bytes */
114 #define CHUNK_SIZE ( 128 * 1024 )
123 uint64_t l_ty_pts; /* TY PTS in the record header */
128 uint64_t l_timestamp;
129 uint8_t chunk_bitmask[8];
153 #define XDS_MAX_DATA_SIZE (32)
156 XDS_CLASS_CURRENT = 0,
157 XDS_CLASS_FUTURE = 1,
158 XDS_CLASS_CHANNEL = 2,
159 XDS_CLASS_MISCELLANEOUS = 3,
160 XDS_CLASS_PUBLIC_SERVICE = 4,
161 XDS_CLASS_RESERVED = 5,
162 XDS_CLASS_UNDEFINED = 6,
171 uint8_t p_data[XDS_MAX_DATA_SIZE];
176 XDS_META_PROGRAM_RATING_NONE,
177 XDS_META_PROGRAM_RATING_MPAA,
178 XDS_META_PROGRAM_RATING_TPG,
179 /* TODO add CA/CE rating */
180 } xds_meta_program_rating_t;
184 xds_meta_program_rating_t rating;
186 /* Add the other fields once I have the samples */
187 } xds_meta_program_t;
190 char *psz_channel_name;
191 char *psz_channel_call_letter;
192 char *psz_channel_number;
194 xds_meta_program_t current;
195 xds_meta_program_t future;
199 /* Are we in XDS mode */
202 /* Current class type */
208 xds_packet_t pkt[XDS_MAX_CLASS_COUNT][128]; /* XXX it is way too much, but simpler */
218 es_out_id_t *p_video; /* ptr to video codec */
219 es_out_id_t *p_audio; /* holds either ac3 or mpeg codec ptr */
222 es_out_id_t *p_cc[4];
228 size_t i_stream_size; /* size of input stream (if known) */
229 //uint64_t l_program_len; /* length of this stream in msec */
230 bool b_seekable; /* is this stream seekable? */
231 bool b_have_master; /* are master chunks present? */
232 tivo_type_t tivo_type; /* tivo type (SA / DTiVo) */
233 tivo_series_t tivo_series; /* Series1 or Series2 */
234 tivo_audio_t audio_type; /* AC3 or MPEG */
235 int i_Pes_Length; /* Length of Audio PES header */
236 int i_Pts_Offset; /* offset into audio PES of PTS */
237 uint8_t pes_buffer[20]; /* holds incomplete pes headers */
238 int i_pes_buf_cnt; /* how many bytes in our buffer */
239 size_t l_ac3_pkt_size; /* len of ac3 pkt we've seen so far */
240 uint64_t l_last_ty_pts; /* last TY timestamp we've seen */
241 //mtime_t l_last_ty_pts_sync; /* audio PTS at time of last TY PTS */
242 uint64_t l_first_ty_pts; /* first TY PTS in this master chunk */
243 uint64_t l_final_ty_pts; /* final TY PTS in this master chunk */
244 unsigned i_seq_table_size; /* number of entries in SEQ table */
245 unsigned i_bits_per_seq_entry; /* # of bits in SEQ table bitmask */
247 mtime_t firstAudioPTS;
248 mtime_t lastAudioPTS;
249 mtime_t lastVideoPTS;
251 ty_rec_hdr_t *rec_hdrs; /* record headers array */
252 int i_cur_rec; /* current record in this chunk */
253 int i_num_recs; /* number of recs in this chunk */
254 int i_seq_rec; /* record number where seq start is */
255 ty_seq_table_t *seq_table; /* table of SEQ entries from mstr chk */
260 static int get_chunk_header(demux_t *);
261 static mtime_t get_pts( const uint8_t *buf );
262 static int find_es_header( const uint8_t *header,
263 const uint8_t *buffer, int i_search_len );
264 static int ty_stream_seek_pct(demux_t *p_demux, double seek_pct);
265 static int ty_stream_seek_time(demux_t *, uint64_t);
267 static ty_rec_hdr_t *parse_chunk_headers( const uint8_t *p_buf,
268 int i_num_recs, int *pi_payload_size);
269 static int probe_stream(demux_t *p_demux);
270 static void analyze_chunk(demux_t *p_demux, const uint8_t *p_chunk);
271 static void parse_master(demux_t *p_demux);
273 static int DemuxRecVideo( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_block_in );
274 static int DemuxRecAudio( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_block_in );
275 static int DemuxRecCc( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_block_in );
277 static void DemuxDecodeXds( demux_t *p_demux, uint8_t d1, uint8_t d2 );
279 static void XdsInit( xds_t * );
280 static void XdsExit( xds_t * );
282 #define TY_ES_GROUP (1)
285 * Open: check file and initialize demux structures
288 * 1. peek at the first 12 bytes of the stream for the
289 * magic TiVo PART header & stream type & chunk size
290 * 2. if it's not there, error with VLC_EGENERIC
291 * 3. set up video (mpgv) codec
292 * 4. return VLC_SUCCESS
294 static int Open(vlc_object_t *p_this)
296 demux_t *p_demux = (demux_t *)p_this;
299 const uint8_t *p_peek;
302 /* peek at the first 12 bytes. */
303 /* for TY streams, they're always the same */
304 if( stream_Peek( p_demux->s, &p_peek, 12 ) < 12 )
307 if ( U32_AT(p_peek) != TIVO_PES_FILEID ||
308 U32_AT(&p_peek[4]) != 0x02 ||
309 U32_AT(&p_peek[8]) != CHUNK_SIZE )
311 if( !p_demux->b_force &&
312 !demux_IsPathExtension( p_demux, ".ty" ) &&
313 !demux_IsPathExtension( p_demux, ".ty+" ) )
315 msg_Warn( p_demux, "this does not look like a TY file, "
316 "continuing anyway..." );
319 /* at this point, we assume we have a valid TY stream */
320 msg_Dbg( p_demux, "valid TY stream detected" );
322 /* Set exported functions */
323 p_demux->pf_demux = Demux;
324 p_demux->pf_control = Control;
326 /* create our structure that will hold all data */
327 p_demux->p_sys = p_sys = malloc(sizeof(demux_sys_t));
328 memset(p_sys, 0, sizeof(demux_sys_t));
330 /* set up our struct (most were zero'd out with the memset above) */
331 p_sys->b_first_chunk = true;
332 p_sys->b_have_master = (U32_AT(p_peek) == TIVO_PES_FILEID);
333 p_sys->firstAudioPTS = -1;
334 p_sys->lastAudioPTS = VLC_TS_INVALID;
335 p_sys->lastVideoPTS = VLC_TS_INVALID;
336 p_sys->i_stream_size = stream_Size(p_demux->s);
337 p_sys->tivo_type = TIVO_TYPE_UNKNOWN;
338 p_sys->audio_type = TIVO_AUDIO_UNKNOWN;
339 p_sys->tivo_series = TIVO_SERIES_UNKNOWN;
340 p_sys->i_Pes_Length = 0;
341 p_sys->i_Pts_Offset = 0;
342 p_sys->l_ac3_pkt_size = 0;
344 /* see if this stream is seekable */
345 stream_Control( p_demux->s, STREAM_CAN_SEEK, &p_sys->b_seekable );
347 if (probe_stream(p_demux) != VLC_SUCCESS) {
352 if (!p_sys->b_have_master)
353 msg_Warn(p_demux, "No master chunk found; seeking will be limited.");
355 /* register the proper audio codec */
356 if (p_sys->audio_type == TIVO_AUDIO_MPEG) {
357 es_format_Init( &fmt, AUDIO_ES, VLC_CODEC_MPGA );
359 es_format_Init( &fmt, AUDIO_ES, VLC_CODEC_A52 );
361 fmt.i_group = TY_ES_GROUP;
362 p_sys->p_audio = es_out_Add( p_demux->out, &fmt );
364 /* register the video stream */
365 es_format_Init( &fmt, VIDEO_ES, VLC_CODEC_MPGV );
366 fmt.i_group = TY_ES_GROUP;
367 p_sys->p_video = es_out_Add( p_demux->out, &fmt );
370 for( i = 0; i < 4; i++ )
371 p_sys->p_cc[i] = NULL;
372 cc_Init( &p_sys->cc );
374 XdsInit( &p_sys->xds );
379 /* =========================================================================== */
380 /* Demux: Read & Demux one record from the chunk
382 * Returns -1 in case of error, 0 in case of EOF, 1 otherwise
384 * NOTE: I think we can return the number of packets sent instead of just 1.
385 * that means we can demux an entire chunk and shoot it back (may be more efficient)
386 * -- should try that some day :) --
388 static int Demux( demux_t *p_demux )
390 demux_sys_t *p_sys = p_demux->p_sys;
392 block_t *p_block_in = NULL;
394 /*msg_Dbg(p_demux, "ty demux processing" );*/
396 /* did we hit EOF earlier? */
401 * what we do (1 record now.. maybe more later):
402 * - use stream_Read() to read the chunk header & record headers
403 * - discard entire chunk if it is a PART header chunk
404 * - parse all the headers into record header array
405 * - keep a pointer of which record we're on
406 * - use stream_Block() to fetch each record
407 * - parse out PTS from PES headers
408 * - set PTS for data packets
409 * - pass the data on to the proper codec via es_out_Send()
411 * if this is the first time or
412 * if we're at the end of this chunk, start a new one
414 /* parse the next chunk's record headers */
415 if( p_sys->b_first_chunk || p_sys->i_cur_rec >= p_sys->i_num_recs )
417 if( get_chunk_header(p_demux) == 0 )
421 /*======================================================================
422 * parse & send one record of the chunk
423 *====================================================================== */
424 p_rec = &p_sys->rec_hdrs[p_sys->i_cur_rec];
428 const long l_rec_size = p_rec->l_rec_size;
429 /*msg_Dbg(p_demux, "Record Type 0x%x/%02x %ld bytes",
430 subrec_type, p_rec->rec_type, l_rec_size );*/
432 /* some normal records are 0 length, so check for that... */
433 if( l_rec_size <= 0 )
435 /* no data in payload; we're done */
440 /* read in this record's payload */
441 if( !( p_block_in = stream_Block( p_demux->s, l_rec_size ) ) )
444 /* set these as 'unknown' for now */
446 p_block_in->i_dts = VLC_TS_INVALID;
450 -- don't read any data from the stream, data was in the record header --
452 "Record Type 0x%02x/%02x, ext data = %02x, %02x", subrec_type,
453 p_rec->rec_type, p_rec->ex1, p_rec->ex2);
456 if( p_rec->rec_type == 0xe0 )
459 DemuxRecVideo( p_demux, p_rec, p_block_in );
461 else if ( p_rec->rec_type == 0xc0 )
464 DemuxRecAudio( p_demux, p_rec, p_block_in );
466 else if( p_rec->rec_type == 0x01 || p_rec->rec_type == 0x02 )
468 /* Closed Captions/XDS */
469 DemuxRecCc( p_demux, p_rec, p_block_in );
471 else if ( p_rec->rec_type == 0x03 )
473 /* Tivo data services (e.g. "thumbs-up to record!") useless for us */
475 block_Release(p_block_in);
477 else if ( p_rec->rec_type == 0x05 )
479 /* Unknown, but seen regularly */
481 block_Release(p_block_in);
485 msg_Dbg(p_demux, "Invalid record type 0x%02x", p_rec->rec_type );
487 block_Release(p_block_in);
496 static int Control(demux_t *p_demux, int i_query, va_list args)
498 demux_sys_t *p_sys = p_demux->p_sys;
502 /*msg_Info(p_demux, "control cmd %d", i_query);*/
505 case DEMUX_GET_POSITION:
506 /* arg is 0.0 - 1.0 percent of overall file position */
507 if( ( i64 = p_sys->i_stream_size ) > 0 )
509 pf = (double*) va_arg( args, double* );
510 *pf = ((double)1.0) * stream_Tell( p_demux->s ) / (double) i64;
515 case DEMUX_SET_POSITION:
516 /* arg is 0.0 - 1.0 percent of overall file position */
517 f = (double) va_arg( args, double );
518 /* msg_Dbg(p_demux, "Control - set position to %2.3f", f); */
519 if ((i64 = p_sys->i_stream_size) > 0)
520 return ty_stream_seek_pct(p_demux, f);
523 /* return TiVo timestamp */
524 p_i64 = (int64_t *) va_arg(args, int64_t *);
525 //*p_i64 = p_sys->lastAudioPTS - p_sys->firstAudioPTS;
526 //*p_i64 = (p_sys->l_last_ty_pts / 1000) + (p_sys->lastAudioPTS -
527 // p_sys->l_last_ty_pts_sync);
528 *p_i64 = (p_sys->l_last_ty_pts / 1000);
530 case DEMUX_GET_LENGTH: /* length of program in microseconds, 0 if unk */
532 p_i64 = (int64_t *) va_arg(args, int64_t *);
535 case DEMUX_SET_TIME: /* arg is time in microsecs */
536 i64 = (int64_t) va_arg( args, int64_t );
537 return ty_stream_seek_time(p_demux, i64 * 1000);
545 static void Close( vlc_object_t *p_this )
547 demux_t *p_demux = (demux_t*)p_this;
548 demux_sys_t *p_sys = p_demux->p_sys;
550 XdsExit( &p_sys->xds );
551 cc_Exit( &p_sys->cc );
552 free( p_sys->rec_hdrs );
553 free( p_sys->seq_table );
558 /* =========================================================================== */
559 /* Compute Presentation Time Stamp (PTS)
560 * Assume buf points to beginning of PTS */
561 static mtime_t get_pts( const uint8_t *buf )
565 i_pts = ((mtime_t)(buf[0]&0x0e ) << 29)|
566 (mtime_t)(buf[1] << 22)|
567 ((mtime_t)(buf[2]&0xfe) << 14)|
568 (mtime_t)(buf[3] << 7)|
569 (mtime_t)(buf[4] >> 1);
570 i_pts *= 100 / 9; /* convert PTS (90Khz clock) to microseconds */
575 /* =========================================================================== */
576 static int find_es_header( const uint8_t *header,
577 const uint8_t *buffer, int i_search_len )
581 for( count = 0; count < i_search_len; count++ )
583 if( !memcmp( &buffer[count], header, 4 ) )
590 /* =========================================================================== */
591 /* check if we have a full PES header, if not, then save what we have.
592 * this is called when audio-start packets are encountered.
594 * 1 partial PES hdr found, some audio data found (buffer adjusted),
595 * -1 partial PES hdr found, no audio data found
596 * 0 otherwise (complete PES found, pts extracted, pts set, buffer adjusted) */
597 /* TODO: HD support -- nothing known about those streams */
598 static int check_sync_pes( demux_t *p_demux, block_t *p_block,
599 int32_t offset, int32_t rec_len )
601 demux_sys_t *p_sys = p_demux->p_sys;
603 if ( offset < 0 || offset + p_sys->i_Pes_Length > rec_len )
605 /* entire PES header not present */
606 msg_Dbg( p_demux, "PES header at %d not complete in record. storing.",
608 /* save the partial pes header */
611 /* no header found, fake some 00's (this works, believe me) */
612 memset( p_sys->pes_buffer, 0, 4 );
613 p_sys->i_pes_buf_cnt = 4;
615 msg_Err( p_demux, "PES header not found in record of %d bytes!",
619 /* copy the partial pes header we found */
620 memcpy( p_sys->pes_buffer, p_block->p_buffer + offset,
622 p_sys->i_pes_buf_cnt = rec_len - offset;
626 /* PES Header was found, but not complete, so trim the end of this record */
627 p_block->i_buffer -= rec_len - offset;
630 return -1; /* partial PES, no audio data */
632 /* full PES header present, extract PTS */
633 p_sys->lastAudioPTS = VLC_TS_0 + get_pts( &p_block->p_buffer[ offset +
634 p_sys->i_Pts_Offset ] );
635 if (p_sys->firstAudioPTS < 0)
636 p_sys->firstAudioPTS = p_sys->lastAudioPTS;
637 p_block->i_pts = p_sys->lastAudioPTS;
638 /*msg_Dbg(p_demux, "Audio PTS %lld", p_sys->lastAudioPTS );*/
639 /* adjust audio record to remove PES header */
640 memmove(p_block->p_buffer + offset, p_block->p_buffer + offset +
641 p_sys->i_Pes_Length, rec_len - p_sys->i_Pes_Length);
642 p_block->i_buffer -= p_sys->i_Pes_Length;
644 msg_Dbg(p_demux, "pes hdr removed; buffer len=%d and has "
645 "%02x %02x %02x %02x %02x %02x %02x %02x "
646 "%02x %02x %02x %02x %02x %02x %02x %02x", p_block->i_buffer,
647 p_block->p_buffer[0], p_block->p_buffer[1],
648 p_block->p_buffer[2], p_block->p_buffer[3],
649 p_block->p_buffer[4], p_block->p_buffer[5],
650 p_block->p_buffer[6], p_block->p_buffer[7],
651 p_block->p_buffer[8], p_block->p_buffer[9],
652 p_block->p_buffer[10], p_block->p_buffer[11],
653 p_block->p_buffer[12], p_block->p_buffer[13],
654 p_block->p_buffer[14], p_block->p_buffer[15]);
659 static int DemuxRecVideo( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_block_in )
661 demux_sys_t *p_sys = p_demux->p_sys;
662 const int subrec_type = rec_hdr->subrec_type;
663 const long l_rec_size = rec_hdr->l_rec_size; // p_block_in->i_buffer might be better
667 assert( rec_hdr->rec_type == 0xe0 );
672 msg_Dbg(p_demux, "packet buffer has "
673 "%02x %02x %02x %02x %02x %02x %02x %02x "
674 "%02x %02x %02x %02x %02x %02x %02x %02x",
675 p_block_in->p_buffer[0], p_block_in->p_buffer[1],
676 p_block_in->p_buffer[2], p_block_in->p_buffer[3],
677 p_block_in->p_buffer[4], p_block_in->p_buffer[5],
678 p_block_in->p_buffer[6], p_block_in->p_buffer[7],
679 p_block_in->p_buffer[8], p_block_in->p_buffer[9],
680 p_block_in->p_buffer[10], p_block_in->p_buffer[11],
681 p_block_in->p_buffer[12], p_block_in->p_buffer[13],
682 p_block_in->p_buffer[14], p_block_in->p_buffer[15]);
684 //if( subrec_type == 0x06 || subrec_type == 0x07 )
685 if( subrec_type != 0x02 && subrec_type != 0x0c &&
686 subrec_type != 0x08 && l_rec_size > 4 )
688 /* get the PTS from this packet if it has one.
689 * on S1, only 0x06 has PES. On S2, however, most all do.
690 * Do NOT Pass the PES Header to the MPEG2 codec */
691 esOffset1 = find_es_header( ty_VideoPacket, p_block_in->p_buffer, 5 );
692 if( esOffset1 != -1 )
694 //msg_Dbg(p_demux, "Video PES hdr in pkt type 0x%02x at offset %d",
695 //subrec_type, esOffset1);
696 p_sys->lastVideoPTS = VLC_TS_0 + get_pts(
697 &p_block_in->p_buffer[ esOffset1 + VIDEO_PTS_OFFSET ] );
698 /*msg_Dbg(p_demux, "Video rec %d PTS %"PRId64, p_sys->i_cur_rec,
699 p_sys->lastVideoPTS );*/
700 if (subrec_type != 0x06) {
701 /* if we found a PES, and it's not type 6, then we're S2 */
702 /* The packet will have video data (& other headers) so we
703 * chop out the PES header and send the rest */
704 if (l_rec_size >= VIDEO_PES_LENGTH) {
705 p_block_in->p_buffer += VIDEO_PES_LENGTH + esOffset1;
706 p_block_in->i_buffer -= VIDEO_PES_LENGTH + esOffset1;
708 msg_Dbg(p_demux, "video rec type 0x%02x has short PES"
709 " (%ld bytes)", subrec_type, l_rec_size);
710 /* nuke this block; it's too short, but has PES marker */
711 p_block_in->i_buffer = 0;
715 msg_Dbg(p_demux, "No Video PES hdr in pkt type 0x%02x",
719 if(subrec_type == 0x06 )
721 /* type 6 (S1 DTivo) has no data, so we're done */
722 block_Release(p_block_in);
726 /* if it's not a continue blk, then set PTS */
727 if( subrec_type != 0x02 )
729 /*msg_Dbg(p_demux, "Video rec %d type 0x%02X", p_sys->i_cur_rec,
731 /* if it's a GOP header, make sure it's legal
732 * (if we have enough data) */
733 /* Some ty files don't have this bit set
734 * and it causes problems */
735 if (subrec_type == 0x0c && l_rec_size >= 6)
736 p_block_in->p_buffer[5] |= 0x08;
737 /* store the TY PTS if there is one */
738 if (subrec_type == 0x07) {
739 p_sys->l_last_ty_pts = rec_hdr->l_ty_pts;
740 /* should we use audio or video PTS? */
741 //p_sys->l_last_ty_pts_sync = p_sys->lastAudioPTS;
743 /* yes I know this is a cheap hack. It's the timestamp
744 used for display and skipping fwd/back, so it
745 doesn't have to be accurate to the millisecond.
746 I adjust it here by roughly one 1/30 sec. Yes it
747 will be slightly off for UK streams, but it's OK.
749 p_sys->l_last_ty_pts += 35000000;
750 //p_sys->l_last_ty_pts += 33366667;
752 /* set PTS for this block before we send */
753 if (p_sys->lastVideoPTS > VLC_TS_INVALID)
755 p_block_in->i_pts = p_sys->lastVideoPTS;
756 /* PTS gets used ONCE.
757 * Any subsequent frames we get BEFORE next PES
758 * header will have their PTS computed in the codec */
759 p_sys->lastVideoPTS = VLC_TS_INVALID;
763 /* Register the CC decoders when needed */
764 for( i = 0; i < 4; i++ )
766 static const vlc_fourcc_t fcc[4] = {
767 VLC_FOURCC('c', 'c', '1', ' '),
768 VLC_FOURCC('c', 'c', '2', ' '),
769 VLC_FOURCC('c', 'c', '3', ' '),
770 VLC_FOURCC('c', 'c', '4', ' ')
772 static const char *ppsz_description[4] = {
773 N_("Closed captions 1"),
774 N_("Closed captions 2"),
775 N_("Closed captions 3"),
776 N_("Closed captions 4"),
781 if( !p_sys->cc.pb_present[i] || p_sys->p_cc[i] )
784 es_format_Init( &fmt, SPU_ES, fcc[i] );
785 fmt.psz_description = strdup( vlc_gettext(ppsz_description[i]) );
786 fmt.i_group = TY_ES_GROUP;
787 p_sys->p_cc[i] = es_out_Add( p_demux->out, &fmt );
788 es_format_Clean( &fmt );
791 /* Send the CC data */
792 if( p_block_in->i_pts > VLC_TS_INVALID && p_sys->cc.i_data > 0 )
794 for( i = 0; i < 4; i++ )
798 block_t *p_cc = block_New( p_demux, p_sys->cc.i_data );
799 p_cc->i_flags |= BLOCK_FLAG_TYPE_I;
800 p_cc->i_pts = p_block_in->i_pts;
801 memcpy( p_cc->p_buffer, p_sys->cc.p_data, p_sys->cc.i_data );
803 es_out_Send( p_demux->out, p_sys->p_cc[i], p_cc );
806 cc_Flush( &p_sys->cc );
809 //msg_Dbg(p_demux, "sending rec %d as video type 0x%02x",
810 //p_sys->i_cur_rec, subrec_type);
811 es_out_Send(p_demux->out, p_sys->p_video, p_block_in);
814 static int DemuxRecAudio( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_block_in )
816 demux_sys_t *p_sys = p_demux->p_sys;
817 const int subrec_type = rec_hdr->subrec_type;
818 const long l_rec_size = rec_hdr->l_rec_size;
821 assert( rec_hdr->rec_type == 0xc0 );
826 fprintf( stderr, "Audio Packet Header " );
827 for( i = 0 ; i < 24 ; i++ )
828 fprintf( stderr, "%2.2x ", p_block_in->p_buffer[i] );
829 fprintf( stderr, "\n" );
832 if( subrec_type == 2 )
834 /* SA or DTiVo Audio Data, no PES (continued block)
835 * ================================================
838 /* continue PES if previous was incomplete */
839 if (p_sys->i_pes_buf_cnt > 0)
841 const int i_need = p_sys->i_Pes_Length - p_sys->i_pes_buf_cnt;
843 msg_Dbg(p_demux, "continuing PES header");
844 /* do we have enough data to complete? */
845 if (i_need >= l_rec_size)
847 /* don't have complete PES hdr; save what we have and return */
848 memcpy(&p_sys->pes_buffer[p_sys->i_pes_buf_cnt],
849 p_block_in->p_buffer, l_rec_size);
850 p_sys->i_pes_buf_cnt += l_rec_size;
852 block_Release(p_block_in);
856 /* we have enough; reconstruct this p_frame with the new hdr */
857 memcpy(&p_sys->pes_buffer[p_sys->i_pes_buf_cnt],
858 p_block_in->p_buffer, i_need);
859 /* advance the block past the PES header (don't want to send it) */
860 p_block_in->p_buffer += i_need;
861 p_block_in->i_buffer -= i_need;
862 /* get the PTS out of this PES header (MPEG or AC3) */
863 if (p_sys->audio_type == TIVO_AUDIO_MPEG)
864 esOffset1 = find_es_header(ty_MPEGAudioPacket,
865 p_sys->pes_buffer, 5);
867 esOffset1 = find_es_header(ty_AC3AudioPacket,
868 p_sys->pes_buffer, 5);
871 /* god help us; something's really wrong */
872 msg_Err(p_demux, "can't find audio PES header in packet");
876 p_sys->lastAudioPTS = VLC_TS_0 + get_pts(
877 &p_sys->pes_buffer[ esOffset1 + p_sys->i_Pts_Offset ] );
878 p_block_in->i_pts = p_sys->lastAudioPTS;
880 p_sys->i_pes_buf_cnt = 0;
882 /* S2 DTivo has AC3 packets with 2 padding bytes at end. This is
883 * not allowed in the AC3 spec and will cause problems. So here
884 * we try to trim things. */
885 /* Also, S1 DTivo has alternating short / long AC3 packets. That
886 * is, one packet is short (incomplete) and the next packet has
887 * the first one's missing data, plus all of its own. Strange. */
888 if (p_sys->audio_type == TIVO_AUDIO_AC3 &&
889 p_sys->tivo_series == TIVO_SERIES2) {
890 if (p_sys->l_ac3_pkt_size + p_block_in->i_buffer >
892 p_block_in->i_buffer -= 2;
893 p_sys->l_ac3_pkt_size = 0;
895 p_sys->l_ac3_pkt_size += p_block_in->i_buffer;
899 else if( subrec_type == 0x03 )
901 /* MPEG Audio with PES Header, either SA or DTiVo */
902 /* ================================================ */
903 esOffset1 = find_es_header( ty_MPEGAudioPacket,
904 p_block_in->p_buffer, 5 );
906 /*msg_Dbg(p_demux, "buffer has %#02x %#02x %#02x %#02x",
907 p_block_in->p_buffer[0], p_block_in->p_buffer[1],
908 p_block_in->p_buffer[2], p_block_in->p_buffer[3]);
909 msg_Dbg(p_demux, "audio ES hdr at offset %d", esOffset1);*/
911 /* SA PES Header, No Audio Data */
912 /* ================================================ */
913 if ( ( esOffset1 == 0 ) && ( l_rec_size == 16 ) )
915 p_sys->lastAudioPTS = VLC_TS_0 + get_pts( &p_block_in->p_buffer[
917 if (p_sys->firstAudioPTS < 0)
918 p_sys->firstAudioPTS = p_sys->lastAudioPTS;
920 block_Release(p_block_in);
922 /*msg_Dbg(p_demux, "SA Audio PTS %lld",
923 p_sys->lastAudioPTS );*/
925 /* DTiVo Audio with PES Header */
926 /* ================================================ */
928 /* Check for complete PES */
929 if (check_sync_pes(p_demux, p_block_in, esOffset1,
932 /* partial PES header found, nothing else.
934 block_Release(p_block_in);
938 msg_Dbg(p_demux, "packet buffer has "
939 "%02x %02x %02x %02x %02x %02x %02x %02x "
940 "%02x %02x %02x %02x %02x %02x %02x %02x",
941 p_block_in->p_buffer[0], p_block_in->p_buffer[1],
942 p_block_in->p_buffer[2], p_block_in->p_buffer[3],
943 p_block_in->p_buffer[4], p_block_in->p_buffer[5],
944 p_block_in->p_buffer[6], p_block_in->p_buffer[7],
945 p_block_in->p_buffer[8], p_block_in->p_buffer[9],
946 p_block_in->p_buffer[10], p_block_in->p_buffer[11],
947 p_block_in->p_buffer[12], p_block_in->p_buffer[13],
948 p_block_in->p_buffer[14], p_block_in->p_buffer[15]);
951 else if( subrec_type == 0x04 )
953 /* SA Audio with no PES Header */
954 /* ================================================ */
956 "Adding SA Audio Packet Size %ld", l_rec_size ); */
958 if (p_sys->lastAudioPTS > VLC_TS_INVALID )
959 p_block_in->i_pts = p_sys->lastAudioPTS;
961 else if( subrec_type == 0x09 )
963 /* DTiVo AC3 Audio Data with PES Header */
964 /* ================================================ */
965 esOffset1 = find_es_header( ty_AC3AudioPacket,
966 p_block_in->p_buffer, 5 );
969 msg_Dbg(p_demux, "buffer has "
970 "%02x %02x %02x %02x %02x %02x %02x %02x "
971 "%02x %02x %02x %02x %02x %02x %02x %02x",
972 p_block_in->p_buffer[0], p_block_in->p_buffer[1],
973 p_block_in->p_buffer[2], p_block_in->p_buffer[3],
974 p_block_in->p_buffer[4], p_block_in->p_buffer[5],
975 p_block_in->p_buffer[6], p_block_in->p_buffer[7],
976 p_block_in->p_buffer[8], p_block_in->p_buffer[9],
977 p_block_in->p_buffer[10], p_block_in->p_buffer[11],
978 p_block_in->p_buffer[12], p_block_in->p_buffer[13],
979 p_block_in->p_buffer[14], p_block_in->p_buffer[15]);
980 msg_Dbg(p_demux, "audio ES AC3 hdr at offset %d", esOffset1);
983 /* Check for complete PES */
984 if (check_sync_pes(p_demux, p_block_in, esOffset1,
987 /* partial PES header found, nothing else. we're done. */
988 block_Release(p_block_in);
991 /* S2 DTivo has invalid long AC3 packets */
992 if (p_sys->tivo_series == TIVO_SERIES2) {
993 if (p_block_in->i_buffer > AC3_PKT_LENGTH) {
994 p_block_in->i_buffer -= 2;
995 p_sys->l_ac3_pkt_size = 0;
997 p_sys->l_ac3_pkt_size = p_block_in->i_buffer;
1003 /* Unsupported/Unknown */
1004 block_Release(p_block_in);
1008 /* set PCR before we send (if PTS found) */
1009 if( p_block_in->i_pts > VLC_TS_INVALID )
1010 es_out_Control( p_demux->out, ES_OUT_SET_PCR,
1011 p_block_in->i_pts );
1013 es_out_Send( p_demux->out, p_sys->p_audio, p_block_in );
1017 static int DemuxRecCc( demux_t *p_demux, ty_rec_hdr_t *rec_hdr, block_t *p_block_in )
1019 demux_sys_t *p_sys = p_demux->p_sys;
1023 block_Release(p_block_in);
1025 if( rec_hdr->rec_type == 0x01 )
1027 else if( rec_hdr->rec_type == 0x02 )
1032 /* XDS data (extract programs infos) transmitted on field 2 only */
1034 DemuxDecodeXds( p_demux, rec_hdr->ex[0], rec_hdr->ex[1] );
1036 if( p_sys->cc.i_data + 3 > CC_MAX_DATA_SIZE )
1039 cc_AppendData( &p_sys->cc, i_field, rec_hdr->ex );
1043 /* seek to a position within the stream, if possible */
1044 static int ty_stream_seek_pct(demux_t *p_demux, double seek_pct)
1046 demux_sys_t *p_sys = p_demux->p_sys;
1047 int64_t seek_pos = p_sys->i_stream_size * seek_pct;
1050 unsigned i_cur_part;
1052 /* if we're not seekable, there's nothing to do */
1053 if (!p_sys->b_seekable)
1054 return VLC_EGENERIC;
1056 /* figure out which part & chunk we want & go there */
1057 i_cur_part = seek_pos / TIVO_PART_LENGTH;
1058 p_sys->i_cur_chunk = seek_pos / CHUNK_SIZE;
1060 /* try to read the part header (master chunk) if it's there */
1061 if ( stream_Seek( p_demux->s, i_cur_part * TIVO_PART_LENGTH ))
1063 /* can't seek stream */
1064 return VLC_EGENERIC;
1066 parse_master(p_demux);
1068 /* now for the actual chunk */
1069 if ( stream_Seek( p_demux->s, p_sys->i_cur_chunk * CHUNK_SIZE))
1071 /* can't seek stream */
1072 return VLC_EGENERIC;
1074 /* load the chunk */
1075 p_sys->i_stuff_cnt = 0;
1076 get_chunk_header(p_demux);
1078 /* seek within the chunk to get roughly to where we want */
1079 p_sys->i_cur_rec = (int)
1080 ((double) ((seek_pos % CHUNK_SIZE) / (double) (CHUNK_SIZE)) * p_sys->i_num_recs);
1081 msg_Dbg(p_demux, "Seeked to file pos %"PRId64, seek_pos);
1082 msg_Dbg(p_demux, " (chunk %d, record %d)",
1083 p_sys->i_cur_chunk - 1, p_sys->i_cur_rec);
1085 /* seek to the start of this record's data.
1086 * to do that, we have to skip past all prior records */
1088 for (i=0; i<p_sys->i_cur_rec; i++)
1089 l_skip_amt += p_sys->rec_hdrs[i].l_rec_size;
1090 stream_Seek(p_demux->s, ((p_sys->i_cur_chunk-1) * CHUNK_SIZE) +
1091 (p_sys->i_num_recs * 16) + l_skip_amt + 4);
1093 /* to hell with syncing any audio or video, just start reading records... :) */
1094 /*p_sys->lastAudioPTS = p_sys->lastVideoPTS = VLC_TS_INVALID;*/
1099 //#define TY_XDS_DEBUG
1100 static void XdsInit( xds_t *h )
1105 h->i_class = XDS_MAX_CLASS_COUNT;
1107 h->b_future = false;
1108 for( i = 0; i < XDS_MAX_CLASS_COUNT; i++ )
1110 for( j = 0; j < 128; j++ )
1111 h->pkt[i][j].b_started = false;
1113 h->b_meta_changed = false;
1114 memset( &h->meta, 0, sizeof(h->meta) );
1116 static void XdsExit( xds_t *h )
1119 free( h->meta.psz_channel_name );
1120 free( h->meta.psz_channel_call_letter );
1121 free( h->meta.psz_channel_number );
1124 free( h->meta.current.psz_name );
1125 free( h->meta.current.psz_rating );
1127 free( h->meta.future.psz_name );
1128 free( h->meta.future.psz_rating );
1130 static void XdsStringUtf8( char dst[2*32+1], const uint8_t *p_src, int i_src )
1135 for( i = 0, i_dst = 0; i < i_src; i++ )
1139 #define E2( c, u1, u2 ) case c: dst[i_dst++] = u1; dst[i_dst++] = u2; break
1140 E2( 0x2a, 0xc3,0xa1); // lowercase a, acute accent
1141 E2( 0x5c, 0xc3,0xa9); // lowercase e, acute accent
1142 E2( 0x5e, 0xc3,0xad); // lowercase i, acute accent
1143 E2( 0x5f, 0xc3,0xb3); // lowercase o, acute accent
1144 E2( 0x60, 0xc3,0xba); // lowercase u, acute accent
1145 E2( 0x7b, 0xc3,0xa7); // lowercase c with cedilla
1146 E2( 0x7c, 0xc3,0xb7); // division symbol
1147 E2( 0x7d, 0xc3,0x91); // uppercase N tilde
1148 E2( 0x7e, 0xc3,0xb1); // lowercase n tilde
1151 dst[i_dst++] = p_src[i];
1155 dst[i_dst++] = '\0';
1157 static bool XdsChangeString( xds_t *h, char **ppsz_dst, const char *psz_new )
1159 if( *ppsz_dst && psz_new && !strcmp( *ppsz_dst, psz_new ) )
1161 if( *ppsz_dst == NULL && psz_new == NULL )
1166 *ppsz_dst = strdup( psz_new );
1170 h->b_meta_changed = true;
1174 static void XdsDecodeCurrentFuture( xds_t *h, xds_packet_t *pk )
1176 xds_meta_program_t *p_prg = h->b_future ? &h->meta.future : &h->meta.current;
1183 XdsStringUtf8( name, pk->p_data, pk->i_data );
1184 if( XdsChangeString( h, &p_prg->psz_name, name ) )
1186 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Program Name) %d'\n", pk->i_data );
1187 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> program name %s\n", name );
1191 i_rating = (pk->p_data[0] & 0x18);
1192 if( i_rating == 0x08 )
1195 static const char *pppsz_ratings[8][2] = {
1196 { "None", "No rating (no content advisory)" },
1197 { "TV-Y", "All Children (no content advisory)" },
1198 { "TV-Y7", "Directed to Older Children (V = Fantasy Violence)" },
1199 { "TV-G", "General Audience (no content advisory)" },
1200 { "TV-PG", "Parental Guidance Suggested" },
1201 { "TV-14", "Parents Strongly Cautioned" },
1202 { "TV-MA", "Mature Audience Only" },
1203 { "None", "No rating (no content advisory)" }
1205 p_prg->rating = XDS_META_PROGRAM_RATING_TPG;
1206 if( XdsChangeString( h, &p_prg->psz_rating, pppsz_ratings[pk->p_data[1]&0x07][0] ) )
1208 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Rating) %d'\n", pk->i_data );
1209 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> TPG Rating %s (%s)\n",
1210 // pppsz_ratings[pk->p_data[1]&0x07][0], pppsz_ratings[pk->p_data[1]&0x07][1] );
1213 else if( i_rating == 0x00 || i_rating == 0x10 )
1216 static const char *pppsz_ratings[8][2] = {
1218 { "G", "General Audiences" },
1219 { "PG", "Parental Guidance Suggested" },
1220 { "PG-13", "Parents Strongly Cautioned" },
1221 { "R", "Restricted" },
1222 { "NC-17", "No one 17 and under admitted" },
1223 { "X", "No one under 17 admitted" },
1224 { "NR", "Not Rated" },
1226 p_prg->rating = XDS_META_PROGRAM_RATING_MPAA;
1227 if( XdsChangeString( h, &p_prg->psz_rating, pppsz_ratings[pk->p_data[0]&0x07][0] ) )
1229 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Rating) %d'\n", pk->i_data );
1230 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> TPG Rating %s (%s)\n",
1231 // pppsz_ratings[pk->p_data[0]&0x07][0], pppsz_ratings[pk->p_data[0]&0x07][1] );
1236 /* Non US Rating TODO */
1237 assert( i_rating == 0x18 ); // only left value possible */
1238 p_prg->rating = XDS_META_PROGRAM_RATING_NONE;
1239 if( XdsChangeString( h, &p_prg->psz_rating, NULL ) )
1241 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Rating) %d'\n", pk->i_data );
1242 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> 0x%2.2x 0x%2.2x\n", pk->p_data[0], pk->p_data[1] );
1249 fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Current/Future (Unknown 0x%x)'\n", h->i_type );
1255 static void XdsDecodeChannel( xds_t *h, xds_packet_t *pk )
1263 if( pk->i_data < 2 )
1265 XdsStringUtf8( name, pk->p_data, pk->i_data );
1266 if( XdsChangeString( h, &h->meta.psz_channel_name, name ) )
1268 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Network Name) %d'\n", pk->i_data );
1269 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> %s\n", name );
1274 if( pk->i_data < 4 )
1277 XdsStringUtf8( name, pk->p_data, 4 );
1278 if( XdsChangeString( h, &h->meta.psz_channel_call_letter, name ) )
1280 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Network Call Letter)' %d\n", pk->i_data );
1281 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> call letter %s\n", name );
1283 if( pk->i_data >= 6 )
1285 XdsStringUtf8( chan, &pk->p_data[4], 2 );
1286 if( XdsChangeString( h, &h->meta.psz_channel_number, chan ) )
1288 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Network Call Letter)' %d\n", pk->i_data );
1289 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> channel number %s\n", chan );
1294 if( XdsChangeString( h, &h->meta.psz_channel_number, NULL ) )
1296 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Network Call Letter)' %d\n", pk->i_data );
1297 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: ====> no channel number letter anymore\n" );
1302 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Channel Tape Delay)'\n" );
1305 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Transmission Signal Identifier)'\n" );
1309 fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Channel (Unknown 0x%x)'\n", h->i_type );
1315 static void XdsDecode( xds_t *h, xds_packet_t *pk )
1317 switch( h->i_class )
1319 case XDS_CLASS_CURRENT:
1320 case XDS_CLASS_FUTURE:
1321 XdsDecodeCurrentFuture( h, pk );
1323 case XDS_CLASS_CHANNEL:
1324 XdsDecodeChannel( h, pk );
1326 case XDS_CLASS_MISCELLANEOUS:
1328 fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Miscellaneous'\n" );
1331 case XDS_CLASS_PUBLIC_SERVICE:
1333 fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: class 'Public Service'\n" );
1337 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS XdsDecode: unknown class\n" );
1342 static void XdsParse( xds_t *h, uint8_t d1, uint8_t d2 )
1344 /* TODO check parity */
1349 if( d1 >= 0x01 && d1 <= 0x0e )
1351 const xds_class_t i_class = ( d1 - 1 ) >> 1;
1352 const int i_type = d2;
1353 const bool b_start = d1 & 0x01;
1354 xds_packet_t *pk = &h->pkt[i_class][i_type];
1356 if( !b_start && !pk->b_started )
1358 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS Continuying a non started packet, ignoring\n" );
1364 h->i_class = i_class;
1366 h->b_future = !b_start;
1367 pk->b_started = true;
1371 pk->i_sum = d1 + d2;
1374 else if( d1 == 0x0f && h->b_xds )
1376 xds_packet_t *pk = &h->pkt[h->i_class][h->i_type];
1378 /* TODO checksum and decode */
1379 pk->i_sum += d1 + d2;
1380 if( pk->i_sum & 0x7f )
1382 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS invalid checksum, ignoring ---------------------------------\n" );
1383 pk->b_started = false;
1386 if( pk->i_data <= 0 )
1388 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS empty packet, ignoring ---------------------------------\n" );
1389 pk->b_started = false;
1393 //if( pk->p_data[pk->i_data-1] == 0x40 ) /* Padding byte */
1398 pk->b_started = false;
1400 else if( d1 >= 0x20 && h->b_xds )
1402 xds_packet_t *pk = &h->pkt[h->i_class][h->i_type];
1404 if( pk->i_data+2 > XDS_MAX_DATA_SIZE )
1406 /* Broken -> reinit */
1407 //fprintf( stderr, "xxxxxxxxxxxxxxxXDS broken, reset\n" );
1409 pk->b_started = false;
1412 /* TODO check parity bit */
1413 pk->p_data[pk->i_data++] = d1 & 0x7f;
1414 pk->p_data[pk->i_data++] = d2 & 0x7f;
1423 static void DemuxDecodeXds( demux_t *p_demux, uint8_t d1, uint8_t d2 )
1425 demux_sys_t *p_sys = p_demux->p_sys;
1427 XdsParse( &p_demux->p_sys->xds, d1, d2 );
1428 if( p_demux->p_sys->xds.b_meta_changed )
1430 xds_meta_t *m = &p_sys->xds.meta;
1434 /* Channel meta data */
1435 p_meta = vlc_meta_New();
1436 if( m->psz_channel_name )
1437 vlc_meta_SetPublisher( p_meta, m->psz_channel_name );
1438 if( m->psz_channel_call_letter )
1439 vlc_meta_SetTitle( p_meta, m->psz_channel_call_letter );
1440 if( m->psz_channel_number )
1441 vlc_meta_AddExtra( p_meta, "Channel number", m->psz_channel_number );
1442 es_out_Control( p_demux->out, ES_OUT_SET_GROUP_META, TY_ES_GROUP, p_meta );
1443 vlc_meta_Delete( p_meta );
1445 /* Event meta data (current/future) */
1446 p_epg = vlc_epg_New( NULL );
1447 if( m->current.psz_name )
1449 vlc_epg_AddEvent( p_epg, 0, 0, m->current.psz_name, NULL, NULL );
1450 //if( m->current.psz_rating )
1451 // TODO but VLC cannot yet handle rating per epg event
1452 vlc_epg_SetCurrent( p_epg, 0 );
1454 if( m->future.psz_name )
1457 if( p_epg->i_event > 0 )
1458 es_out_Control( p_demux->out, ES_OUT_SET_GROUP_EPG, TY_ES_GROUP, p_epg );
1459 vlc_epg_Delete( p_epg );
1461 p_demux->p_sys->xds.b_meta_changed = false;
1464 /* seek to an exact time position within the stream, if possible.
1465 * l_seek_time is in nanoseconds, the TIVO time standard.
1467 static int ty_stream_seek_time(demux_t *p_demux, uint64_t l_seek_time)
1469 demux_sys_t *p_sys = p_demux->p_sys;
1470 int i_seq_entry = 0;
1473 long l_cur_pos = stream_Tell(p_demux->s);
1474 unsigned i_cur_part = l_cur_pos / TIVO_PART_LENGTH;
1475 long l_seek_secs = l_seek_time / 1000000000;
1476 uint64_t l_fwd_stamp = 1;
1478 /* if we're not seekable, there's nothing to do */
1479 if (!p_sys->b_seekable || !p_sys->b_have_master)
1480 return VLC_EGENERIC;
1482 msg_Dbg(p_demux, "Skipping to time %02ld:%02ld:%02ld",
1483 l_seek_secs / 3600, (l_seek_secs / 60) % 60, l_seek_secs % 60);
1485 /* seek to the proper segment if necessary */
1486 /* first see if we need to go back */
1487 while (l_seek_time < p_sys->l_first_ty_pts) {
1488 msg_Dbg(p_demux, "skipping to prior segment.");
1489 /* load previous part */
1490 if (i_cur_part == 0) {
1491 stream_Seek(p_demux->s, l_cur_pos);
1492 msg_Err(p_demux, "Attempt to seek past BOF");
1493 return VLC_EGENERIC;
1495 stream_Seek(p_demux->s, (i_cur_part - 1) * TIVO_PART_LENGTH);
1497 parse_master(p_demux);
1499 /* maybe we need to go forward */
1500 while (l_seek_time > p_sys->l_final_ty_pts) {
1501 msg_Dbg(p_demux, "skipping to next segment.");
1502 /* load next part */
1503 if ((i_cur_part + 1) * TIVO_PART_LENGTH > p_sys->i_stream_size) {
1504 /* error; restore previous file position */
1505 stream_Seek(p_demux->s, l_cur_pos);
1506 msg_Err(p_demux, "seek error");
1507 return VLC_EGENERIC;
1509 stream_Seek(p_demux->s, (i_cur_part + 1) * TIVO_PART_LENGTH);
1511 parse_master(p_demux);
1514 /* our target is somewhere within this part;
1515 find the proper chunk using seq_table */
1516 for (i=1; i<p_sys->i_seq_table_size; i++) {
1517 if (p_sys->seq_table[i].l_timestamp > l_seek_time) {
1518 /* i-1 is the section we want; remember the next timestamp in case
1519 we have to use it (this section may not have a proper SEQ hdr
1520 for the time we're seeking) */
1521 msg_Dbg(p_demux, "stopping at seq entry %d.", i);
1522 l_fwd_stamp = p_sys->seq_table[i].l_timestamp;
1528 /* if we went through the entire last loop and didn't find our target,
1529 then we skip to the next part. What has happened is that the actual
1530 time we're seeking is within this part, but there isn't a SEQ hdr
1531 for it here. So we skip to the next part */
1532 if (i == p_sys->i_seq_table_size) {
1533 if ((i_cur_part + 1) * TIVO_PART_LENGTH > p_sys->i_stream_size) {
1534 /* error; restore previous file position */
1535 stream_Seek(p_demux->s, l_cur_pos);
1536 msg_Err(p_demux, "seek error");
1537 return VLC_EGENERIC;
1539 stream_Seek(p_demux->s, (i_cur_part + 1) * TIVO_PART_LENGTH);
1541 parse_master(p_demux);
1545 /* determine which chunk has our seek_time */
1546 for (i=0; i<p_sys->i_bits_per_seq_entry; i++) {
1547 long l_chunk_nr = i_seq_entry * p_sys->i_bits_per_seq_entry + i;
1548 long l_chunk_offset = (l_chunk_nr + 1) * CHUNK_SIZE;
1549 msg_Dbg(p_demux, "testing part %d chunk %ld mask 0x%02X bit %d",
1550 i_cur_part, l_chunk_nr,
1551 p_sys->seq_table[i_seq_entry].chunk_bitmask[i/8], i%8);
1552 if (p_sys->seq_table[i_seq_entry].chunk_bitmask[i/8] & (1 << (i%8))) {
1553 /* check this chunk's SEQ header timestamp */
1554 msg_Dbg(p_demux, "has SEQ. seeking to chunk at 0x%lX",
1555 (i_cur_part * TIVO_PART_LENGTH) + l_chunk_offset);
1556 stream_Seek(p_demux->s, (i_cur_part * TIVO_PART_LENGTH) +
1558 // TODO: we don't have to parse the full header set;
1559 // just test the seq_rec entry for its timestamp
1560 p_sys->i_stuff_cnt = 0;
1561 get_chunk_header(p_demux);
1562 // check ty PTS for the SEQ entry in this chunk
1563 if (p_sys->i_seq_rec < 0 || p_sys->i_seq_rec > p_sys->i_num_recs) {
1564 msg_Err(p_demux, "no SEQ hdr in chunk; table had one.");
1565 /* Seek to beginning of original chunk & reload it */
1566 stream_Seek(p_demux->s, (l_cur_pos / CHUNK_SIZE) * CHUNK_SIZE);
1567 p_sys->i_stuff_cnt = 0;
1568 get_chunk_header(p_demux);
1569 return VLC_EGENERIC;
1571 l_seek_secs = p_sys->rec_hdrs[p_sys->i_seq_rec].l_ty_pts /
1573 msg_Dbg(p_demux, "found SEQ hdr for timestamp %02ld:%02ld:%02ld",
1575 (l_seek_secs / 60) % 60, l_seek_secs % 60);
1576 if (p_sys->rec_hdrs[p_sys->i_seq_rec].l_ty_pts >= l_seek_time) {
1577 // keep this one? go back?
1578 /* for now, we take this one. it's the first SEQ hdr AFTER
1579 the time we were searching for. */
1580 msg_Dbg(p_demux, "seek target found.");
1583 msg_Dbg(p_demux, "timestamp too early. still scanning.");
1586 /* if we made it through this entire loop without finding our target,
1587 then we skip to the next section. What has happened is that the actual
1588 time we're seeking is within this section, but there isn't a SEQ hdr
1589 for it here. So we skip to the next closest one (l_fwd_stamp) */
1590 if (i == p_sys->i_bits_per_seq_entry)
1591 return ty_stream_seek_time(p_demux, l_fwd_stamp);
1593 /* current stream ptr is at beginning of data for this chunk,
1594 so we need to skip past any stream data prior to the seq_rec
1597 for (int j=0; j<p_sys->i_seq_rec; j++)
1598 i_skip_cnt += p_sys->rec_hdrs[j].l_rec_size;
1599 stream_Read(p_demux->s, NULL, i_skip_cnt);
1600 p_sys->i_cur_rec = p_sys->i_seq_rec;
1601 //p_sys->l_last_ty_pts = p_sys->rec_hdrs[p_sys->i_seq_rec].l_ty_pts;
1602 //p_sys->l_last_ty_pts_sync = p_sys->lastAudioPTS;
1608 /* parse a master chunk, filling the SEQ table and other variables.
1609 * We assume the stream is currently pointing to it.
1611 static void parse_master(demux_t *p_demux)
1613 demux_sys_t *p_sys = p_demux->p_sys;
1614 uint8_t mst_buf[32];
1615 uint32_t i, i_map_size;
1616 int64_t i_save_pos = stream_Tell(p_demux->s);
1619 /* Note that the entries in the SEQ table in the stream may have
1620 different sizes depending on the bits per entry. We store them
1621 all in the same size structure, so we have to parse them out one
1622 by one. If we had a dynamic structure, we could simply read the
1623 entire table directly from the stream into memory in place. */
1625 /* clear the SEQ table */
1626 free(p_sys->seq_table);
1628 /* parse header info */
1629 stream_Read(p_demux->s, mst_buf, 32);
1630 i_map_size = U32_AT(&mst_buf[20]); /* size of bitmask, in bytes */
1631 p_sys->i_bits_per_seq_entry = i_map_size * 8;
1632 i = U32_AT(&mst_buf[28]); /* size of SEQ table, in bytes */
1633 p_sys->i_seq_table_size = i / (8 + i_map_size);
1635 /* parse all the entries */
1636 p_sys->seq_table = calloc(p_sys->i_seq_table_size, sizeof(ty_seq_table_t));
1637 if (p_sys->seq_table == NULL)
1639 p_sys->i_seq_table_size = 0;
1642 for (unsigned i=0; i<p_sys->i_seq_table_size; i++) {
1643 stream_Read(p_demux->s, mst_buf, 8);
1644 p_sys->seq_table[i].l_timestamp = U64_AT(&mst_buf[0]);
1645 if (i_map_size > 8) {
1646 msg_Err(p_demux, "Unsupported SEQ bitmap size in master chunk");
1647 stream_Read(p_demux->s, NULL, i_map_size);
1649 stream_Read(p_demux->s, mst_buf + 8, i_map_size);
1650 memcpy(p_sys->seq_table[i].chunk_bitmask, &mst_buf[8], i_map_size);
1654 /* set up a few of our variables */
1655 p_sys->l_first_ty_pts = p_sys->seq_table[0].l_timestamp;
1656 p_sys->l_final_ty_pts =
1657 p_sys->seq_table[p_sys->i_seq_table_size - 1].l_timestamp;
1658 p_sys->b_have_master = true;
1660 i_pts_secs = p_sys->l_first_ty_pts / 1000000000;
1662 "first TY pts in master is %02"PRId64":%02"PRId64":%02"PRId64,
1663 i_pts_secs / 3600, (i_pts_secs / 60) % 60, i_pts_secs % 60 );
1664 i_pts_secs = p_sys->l_final_ty_pts / 1000000000;
1666 "final TY pts in master is %02"PRId64":%02"PRId64":%02"PRId64,
1667 i_pts_secs / 3600, (i_pts_secs / 60) % 60, i_pts_secs % 60 );
1669 /* seek past this chunk */
1670 stream_Seek(p_demux->s, i_save_pos + CHUNK_SIZE);
1674 /* ======================================================================== */
1675 /* "Peek" at some chunks. Skip over the Part header if we find it.
1676 * We parse the peeked data and determine audio type,
1677 * SA vs. DTivo, & Tivo Series.
1678 * Set global vars i_Pes_Length, i_Pts_Offset,
1679 * p_sys->tivo_series, p_sys->tivo_type, p_sys->audio_type */
1680 static int probe_stream(demux_t *p_demux)
1682 demux_sys_t *p_sys = p_demux->p_sys;
1683 const uint8_t *p_buf;
1685 bool b_probe_error = false;
1687 /* we need CHUNK_PEEK_COUNT chunks of data, first one might be a Part header, so ... */
1688 if (stream_Peek( p_demux->s, &p_buf, CHUNK_PEEK_COUNT * CHUNK_SIZE ) <
1689 CHUNK_PEEK_COUNT * CHUNK_SIZE) {
1690 msg_Err(p_demux, "Can't peek %d chunks", CHUNK_PEEK_COUNT);
1691 /* TODO: if seekable, then loop reading chunks into a temp buffer */
1692 return VLC_EGENERIC;
1695 /* the real work: analyze this chunk */
1696 for (i = 0; i < CHUNK_PEEK_COUNT; i++) {
1697 analyze_chunk(p_demux, p_buf);
1698 if (p_sys->tivo_series != TIVO_SERIES_UNKNOWN &&
1699 p_sys->audio_type != TIVO_AUDIO_UNKNOWN &&
1700 p_sys->tivo_type != TIVO_TYPE_UNKNOWN)
1702 p_buf += CHUNK_SIZE;
1705 /* the final tally */
1706 if (p_sys->tivo_series == TIVO_SERIES_UNKNOWN) {
1707 msg_Err(p_demux, "Can't determine Tivo Series.");
1708 b_probe_error = true;
1710 if (p_sys->audio_type == TIVO_AUDIO_UNKNOWN) {
1711 msg_Err(p_demux, "Can't determine Tivo Audio Type.");
1712 b_probe_error = true;
1714 if (p_sys->tivo_type == TIVO_TYPE_UNKNOWN) {
1715 msg_Err(p_demux, "Can't determine Tivo Type (SA/DTivo).");
1716 b_probe_error = true;
1718 return b_probe_error?VLC_EGENERIC:VLC_SUCCESS;
1722 /* ======================================================================== */
1723 /* gather statistics for this chunk & set our tivo-type vars accordingly */
1724 static void analyze_chunk(demux_t *p_demux, const uint8_t *p_chunk)
1726 demux_sys_t *p_sys = p_demux->p_sys;
1728 ty_rec_hdr_t *p_hdrs;
1729 int i_num_6e0, i_num_be0, i_num_9c0, i_num_3c0;
1732 /* skip if it's a Part header */
1733 if( U32_AT( &p_chunk[ 0 ] ) == TIVO_PES_FILEID )
1736 /* number of records in chunk (we ignore high order byte;
1737 * rarely are there > 256 chunks & we don't need that many anyway) */
1738 i_num_recs = p_chunk[0];
1739 if (i_num_recs < 5) {
1740 /* try again with the next chunk. Sometimes there are dead ones */
1744 p_chunk += 4; /* skip past rec count & SEQ bytes */
1745 //msg_Dbg(p_demux, "probe: chunk has %d recs", i_num_recs);
1746 p_hdrs = parse_chunk_headers(p_chunk, i_num_recs, &i_payload_size);
1748 * 1. check video packets. Presence of 0x6e0 means S1.
1749 * No 6e0 but have be0 means S2.
1750 * 2. probe for audio 0x9c0 vs 0x3c0 (AC3 vs Mpeg)
1751 * If AC-3, then we have DTivo.
1752 * If MPEG, search for PTS offset. This will determine SA vs. DTivo.
1754 i_num_6e0 = i_num_be0 = i_num_9c0 = i_num_3c0 = 0;
1755 for (i=0; i<i_num_recs; i++) {
1756 //msg_Dbg(p_demux, "probe: rec is %d/%d = 0x%04x", p_hdrs[i].subrec_type,
1757 //p_hdrs[i].rec_type,
1758 //p_hdrs[i].subrec_type << 8 | p_hdrs[i].rec_type);
1759 switch (p_hdrs[i].subrec_type << 8 | p_hdrs[i].rec_type) {
1774 msg_Dbg(p_demux, "probe: chunk has %d 0x6e0 recs, %d 0xbe0 recs.",
1775 i_num_6e0, i_num_be0);
1777 /* set up our variables */
1778 if (i_num_6e0 > 0) {
1779 msg_Dbg(p_demux, "detected Series 1 Tivo");
1780 p_sys->tivo_series = TIVO_SERIES1;
1781 p_sys->i_Pes_Length = SERIES1_PES_LENGTH;
1782 } else if (i_num_be0 > 0) {
1783 msg_Dbg(p_demux, "detected Series 2 Tivo");
1784 p_sys->tivo_series = TIVO_SERIES2;
1785 p_sys->i_Pes_Length = SERIES2_PES_LENGTH;
1787 if (i_num_9c0 > 0) {
1788 msg_Dbg(p_demux, "detected AC-3 Audio (DTivo)" );
1789 p_sys->audio_type = TIVO_AUDIO_AC3;
1790 p_sys->tivo_type = TIVO_TYPE_DTIVO;
1791 p_sys->i_Pts_Offset = AC3_PTS_OFFSET;
1792 p_sys->i_Pes_Length = AC3_PES_LENGTH;
1793 } else if (i_num_3c0 > 0) {
1794 p_sys->audio_type = TIVO_AUDIO_MPEG;
1795 msg_Dbg(p_demux, "detected MPEG Audio" );
1798 /* if tivo_type still unknown, we can check PTS location
1799 * in MPEG packets to determine tivo_type */
1800 if (p_sys->tivo_type == TIVO_TYPE_UNKNOWN) {
1801 uint32_t i_data_offset = (16 * i_num_recs);
1802 for (i=0; i<i_num_recs; i++) {
1803 if ((p_hdrs[i].subrec_type << 0x08 | p_hdrs[i].rec_type) == 0x3c0 &&
1804 p_hdrs[i].l_rec_size > 15) {
1805 /* first make sure we're aligned */
1806 int i_pes_offset = find_es_header(ty_MPEGAudioPacket,
1807 &p_chunk[i_data_offset], 5);
1808 if (i_pes_offset >= 0) {
1809 /* pes found. on SA, PES has hdr data at offset 6, not PTS. */
1810 //msg_Dbg(p_demux, "probe: mpeg es header found in rec %d at offset %d",
1812 if ((p_chunk[i_data_offset + 6 + i_pes_offset] & 0x80) == 0x80) {
1813 /* S1SA or S2(any) Mpeg Audio (PES hdr, not a PTS start) */
1814 if (p_sys->tivo_series == TIVO_SERIES1)
1815 msg_Dbg(p_demux, "detected Stand-Alone Tivo" );
1816 p_sys->tivo_type = TIVO_TYPE_SA;
1817 p_sys->i_Pts_Offset = SA_PTS_OFFSET;
1819 if (p_sys->tivo_series == TIVO_SERIES1)
1820 msg_Dbg(p_demux, "detected DirecTV Tivo" );
1821 p_sys->tivo_type = TIVO_TYPE_DTIVO;
1822 p_sys->i_Pts_Offset = DTIVO_PTS_OFFSET;
1827 i_data_offset += p_hdrs[i].l_rec_size;
1834 /* =========================================================================== */
1835 static int get_chunk_header(demux_t *p_demux)
1837 int i_readSize, i_num_recs;
1839 const uint8_t *p_peek;
1840 demux_sys_t *p_sys = p_demux->p_sys;
1841 int i_payload_size; /* sum of all records' sizes */
1843 msg_Dbg(p_demux, "parsing ty chunk #%d", p_sys->i_cur_chunk );
1845 /* if we have left-over filler space from the last chunk, get that */
1846 if (p_sys->i_stuff_cnt > 0) {
1847 stream_Read( p_demux->s, NULL, p_sys->i_stuff_cnt);
1848 p_sys->i_stuff_cnt = 0;
1851 /* read the TY packet header */
1852 i_readSize = stream_Peek( p_demux->s, &p_peek, 4 );
1853 p_sys->i_cur_chunk++;
1855 if ( (i_readSize < 4) || ( U32_AT(&p_peek[ 0 ] ) == 0 ))
1862 /* check if it's a PART Header */
1863 if( U32_AT( &p_peek[ 0 ] ) == TIVO_PES_FILEID )
1865 /* parse master chunk */
1866 parse_master(p_demux);
1867 return get_chunk_header(p_demux);
1870 /* number of records in chunk (8- or 16-bit number) */
1871 if (p_peek[3] & 0x80)
1873 /* 16 bit rec cnt */
1874 p_sys->i_num_recs = i_num_recs = (p_peek[1] << 8) + p_peek[0];
1875 p_sys->i_seq_rec = (p_peek[3] << 8) + p_peek[2];
1876 if (p_sys->i_seq_rec != 0xffff)
1878 p_sys->i_seq_rec &= ~0x8000;
1883 /* 8 bit reclen - tivo 1.3 format */
1884 p_sys->i_num_recs = i_num_recs = p_peek[0];
1885 p_sys->i_seq_rec = p_peek[1];
1887 p_sys->i_cur_rec = 0;
1888 p_sys->b_first_chunk = false;
1890 /*msg_Dbg( p_demux, "chunk has %d records", i_num_recs );*/
1892 free(p_sys->rec_hdrs);
1894 /* skip past the 4 bytes we "peeked" earlier */
1895 stream_Read( p_demux->s, NULL, 4 );
1897 /* read the record headers into a temp buffer */
1898 p_hdr_buf = malloc(i_num_recs * 16);
1899 if (stream_Read(p_demux->s, p_hdr_buf, i_num_recs * 16) < i_num_recs * 16) {
1905 p_sys->rec_hdrs = parse_chunk_headers(p_hdr_buf, i_num_recs,
1909 p_sys->i_stuff_cnt = CHUNK_SIZE - 4 -
1910 (p_sys->i_num_recs * 16) - i_payload_size;
1911 if (p_sys->i_stuff_cnt > 0)
1912 msg_Dbg( p_demux, "chunk has %d stuff bytes at end",
1913 p_sys->i_stuff_cnt );
1918 static ty_rec_hdr_t *parse_chunk_headers( const uint8_t *p_buf,
1919 int i_num_recs, int *pi_payload_size)
1922 ty_rec_hdr_t *p_hdrs, *p_rec_hdr;
1924 *pi_payload_size = 0;
1925 p_hdrs = malloc(i_num_recs * sizeof(ty_rec_hdr_t));
1927 for (i = 0; i < i_num_recs; i++)
1929 const uint8_t *record_header = p_buf + (i * 16);
1930 p_rec_hdr = &p_hdrs[i]; /* for brevity */
1931 p_rec_hdr->rec_type = record_header[3];
1932 p_rec_hdr->subrec_type = record_header[2] & 0x0f;
1933 if ((record_header[ 0 ] & 0x80) == 0x80)
1936 /* marker bit 2 set, so read extended data */
1937 b1 = ( ( ( record_header[ 0 ] & 0x0f ) << 4 ) |
1938 ( ( record_header[ 1 ] & 0xf0 ) >> 4 ) );
1939 b2 = ( ( ( record_header[ 1 ] & 0x0f ) << 4 ) |
1940 ( ( record_header[ 2 ] & 0xf0 ) >> 4 ) );
1942 p_rec_hdr->ex[0] = b1;
1943 p_rec_hdr->ex[1] = b2;
1944 p_rec_hdr->l_rec_size = 0;
1945 p_rec_hdr->l_ty_pts = 0;
1946 p_rec_hdr->b_ext = true;
1950 p_rec_hdr->l_rec_size = ( record_header[ 0 ] << 8 |
1951 record_header[ 1 ] ) << 4 | ( record_header[ 2 ] >> 4 );
1952 *pi_payload_size += p_rec_hdr->l_rec_size;
1953 p_rec_hdr->b_ext = false;
1954 p_rec_hdr->l_ty_pts = U64_AT( &record_header[ 8 ] );
1956 //fprintf( stderr, "parse_chunk_headers[%d] t=0x%x s=%d\n", i, p_rec_hdr->rec_type, p_rec_hdr->subrec_type );
1957 } /* end of record-header loop */