mirror of https://github.com/FFmpeg/FFmpeg.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
789 lines
27 KiB
789 lines
27 KiB
/* |
|
* TiVo ty stream demuxer |
|
* Copyright (c) 2005 VLC authors and VideoLAN |
|
* Copyright (c) 2005 by Neal Symms (tivo@freakinzoo.com) - February 2005 |
|
* based on code by Christopher Wingert for tivo-mplayer |
|
* tivo(at)wingert.org, February 2003 |
|
* Copyright (c) 2017 Paul B Mahol |
|
* |
|
* This file is part of FFmpeg. |
|
* |
|
* FFmpeg is free software; you can redistribute it and/or |
|
* modify it under the terms of the GNU Lesser General Public |
|
* License as published by the Free Software Foundation; either |
|
* version 2.1 of the License, or (at your option) any later version. |
|
* |
|
* FFmpeg is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
* Lesser General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Lesser General Public |
|
* License along with FFmpeg; if not, write to the Free Software |
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
*/ |
|
|
|
#include "libavutil/intreadwrite.h" |
|
#include "avformat.h" |
|
#include "internal.h" |
|
#include "mpeg.h" |
|
|
|
#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 TIVO_PES_FILEID 0xf5467abd |
|
#define CHUNK_SIZE (128 * 1024) |
|
#define CHUNK_PEEK_COUNT 3 /* number of chunks to probe */ |
|
|
|
typedef struct TyRecHdr { |
|
int64_t rec_size; |
|
uint8_t ex[2]; |
|
uint8_t rec_type; |
|
uint8_t subrec_type; |
|
uint64_t ty_pts; /* TY PTS in the record header */ |
|
} TyRecHdr; |
|
|
|
typedef enum { |
|
TIVO_TYPE_UNKNOWN, |
|
TIVO_TYPE_SA, |
|
TIVO_TYPE_DTIVO |
|
} TiVo_type; |
|
|
|
typedef enum { |
|
TIVO_SERIES_UNKNOWN, |
|
TIVO_SERIES1, |
|
TIVO_SERIES2 |
|
} TiVo_series; |
|
|
|
typedef enum { |
|
TIVO_AUDIO_UNKNOWN, |
|
TIVO_AUDIO_AC3, |
|
TIVO_AUDIO_MPEG |
|
} TiVo_audio; |
|
|
|
typedef struct TySeqTable { |
|
uint64_t timestamp; |
|
uint8_t chunk_bitmask[8]; |
|
} TySeqTable; |
|
|
|
typedef struct TYDemuxContext { |
|
unsigned cur_chunk; |
|
unsigned cur_chunk_pos; |
|
int64_t cur_pos; |
|
TiVo_type tivo_type; /* TiVo type (SA / DTiVo) */ |
|
TiVo_series tivo_series; /* Series1 or Series2 */ |
|
TiVo_audio audio_type; /* AC3 or MPEG */ |
|
int pes_length; /* Length of Audio PES header */ |
|
int pts_offset; /* offset into audio PES of PTS */ |
|
uint8_t pes_buffer[20]; /* holds incomplete pes headers */ |
|
int pes_buf_cnt; /* how many bytes in our buffer */ |
|
size_t ac3_pkt_size; /* length of ac3 pkt we've seen so far */ |
|
uint64_t last_ty_pts; /* last TY timestamp we've seen */ |
|
unsigned seq_table_size; /* number of entries in SEQ table */ |
|
|
|
int64_t first_audio_pts; |
|
int64_t last_audio_pts; |
|
int64_t last_video_pts; |
|
|
|
TyRecHdr *rec_hdrs; /* record headers array */ |
|
int cur_rec; /* current record in this chunk */ |
|
int num_recs; /* number of recs in this chunk */ |
|
int seq_rec; /* record number where seq start is */ |
|
TySeqTable *seq_table; /* table of SEQ entries from mstr chk */ |
|
int first_chunk; |
|
|
|
uint8_t chunk[CHUNK_SIZE]; |
|
} TYDemuxContext; |
|
|
|
static int ty_probe(AVProbeData *p) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i + 12 < p->buf_size; i += CHUNK_SIZE) { |
|
if (AV_RB32(p->buf + i) == TIVO_PES_FILEID && |
|
AV_RB32(p->buf + i + 4) == 0x02 && |
|
AV_RB32(p->buf + i + 8) == CHUNK_SIZE) { |
|
return AVPROBE_SCORE_MAX; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static TyRecHdr *parse_chunk_headers(const uint8_t *buf, |
|
int num_recs) |
|
{ |
|
TyRecHdr *hdrs, *rec_hdr; |
|
int i; |
|
|
|
hdrs = av_calloc(num_recs, sizeof(TyRecHdr)); |
|
if (!hdrs) |
|
return NULL; |
|
|
|
for (i = 0; i < num_recs; i++) { |
|
const uint8_t *record_header = buf + (i * 16); |
|
|
|
rec_hdr = &hdrs[i]; /* for brevity */ |
|
rec_hdr->rec_type = record_header[3]; |
|
rec_hdr->subrec_type = record_header[2] & 0x0f; |
|
if ((record_header[0] & 0x80) == 0x80) { |
|
uint8_t b1, b2; |
|
|
|
/* marker bit 2 set, so read extended data */ |
|
b1 = (((record_header[0] & 0x0f) << 4) | |
|
((record_header[1] & 0xf0) >> 4)); |
|
b2 = (((record_header[1] & 0x0f) << 4) | |
|
((record_header[2] & 0xf0) >> 4)); |
|
|
|
rec_hdr->ex[0] = b1; |
|
rec_hdr->ex[1] = b2; |
|
rec_hdr->rec_size = 0; |
|
rec_hdr->ty_pts = 0; |
|
} else { |
|
rec_hdr->rec_size = (record_header[0] << 8 | |
|
record_header[1]) << 4 | |
|
(record_header[2] >> 4); |
|
rec_hdr->ty_pts = AV_RB64(&record_header[8]); |
|
} |
|
} |
|
return hdrs; |
|
} |
|
|
|
static int find_es_header(const uint8_t *header, |
|
const uint8_t *buffer, int search_len) |
|
{ |
|
int count; |
|
|
|
for (count = 0; count < search_len; count++) { |
|
if (!memcmp(&buffer[count], header, 4)) |
|
return count; |
|
} |
|
return -1; |
|
} |
|
|
|
static int analyze_chunk(AVFormatContext *s, const uint8_t *chunk) |
|
{ |
|
TYDemuxContext *ty = s->priv_data; |
|
int num_recs, i; |
|
TyRecHdr *hdrs; |
|
int num_6e0, num_be0, num_9c0, num_3c0; |
|
|
|
/* skip if it's a Part header */ |
|
if (AV_RB32(&chunk[0]) == TIVO_PES_FILEID) |
|
return 0; |
|
|
|
/* number of records in chunk (we ignore high order byte; |
|
* rarely are there > 256 chunks & we don't need that many anyway) */ |
|
num_recs = chunk[0]; |
|
if (num_recs < 5) { |
|
/* try again with the next chunk. Sometimes there are dead ones */ |
|
return 0; |
|
} |
|
|
|
chunk += 4; /* skip past rec count & SEQ bytes */ |
|
ff_dlog(s, "probe: chunk has %d recs\n", num_recs); |
|
hdrs = parse_chunk_headers(chunk, num_recs); |
|
if (!hdrs) |
|
return AVERROR(ENOMEM); |
|
|
|
/* 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. |
|
*/ |
|
num_6e0 = num_be0 = num_9c0 = num_3c0 = 0; |
|
for (i = 0; i < num_recs; i++) { |
|
switch (hdrs[i].subrec_type << 8 | hdrs[i].rec_type) { |
|
case 0x6e0: |
|
num_6e0++; |
|
break; |
|
case 0xbe0: |
|
num_be0++; |
|
break; |
|
case 0x3c0: |
|
num_3c0++; |
|
break; |
|
case 0x9c0: |
|
num_9c0++; |
|
break; |
|
} |
|
} |
|
ff_dlog(s, "probe: chunk has %d 0x6e0 recs, %d 0xbe0 recs.\n", |
|
num_6e0, num_be0); |
|
|
|
/* set up our variables */ |
|
if (num_6e0 > 0) { |
|
ff_dlog(s, "detected Series 1 Tivo\n"); |
|
ty->tivo_series = TIVO_SERIES1; |
|
ty->pes_length = SERIES1_PES_LENGTH; |
|
} else if (num_be0 > 0) { |
|
ff_dlog(s, "detected Series 2 Tivo\n"); |
|
ty->tivo_series = TIVO_SERIES2; |
|
ty->pes_length = SERIES2_PES_LENGTH; |
|
} |
|
if (num_9c0 > 0) { |
|
ff_dlog(s, "detected AC-3 Audio (DTivo)\n"); |
|
ty->audio_type = TIVO_AUDIO_AC3; |
|
ty->tivo_type = TIVO_TYPE_DTIVO; |
|
ty->pts_offset = AC3_PTS_OFFSET; |
|
ty->pes_length = AC3_PES_LENGTH; |
|
} else if (num_3c0 > 0) { |
|
ty->audio_type = TIVO_AUDIO_MPEG; |
|
ff_dlog(s, "detected MPEG Audio\n"); |
|
} |
|
|
|
/* if tivo_type still unknown, we can check PTS location |
|
* in MPEG packets to determine tivo_type */ |
|
if (ty->tivo_type == TIVO_TYPE_UNKNOWN) { |
|
uint32_t data_offset = 16 * num_recs; |
|
|
|
for (i = 0; i < num_recs; i++) { |
|
if (data_offset + hdrs[i].rec_size > CHUNK_SIZE) |
|
break; |
|
|
|
if ((hdrs[i].subrec_type << 0x08 | hdrs[i].rec_type) == 0x3c0 && hdrs[i].rec_size > 15) { |
|
/* first make sure we're aligned */ |
|
int pes_offset = find_es_header(ty_MPEGAudioPacket, |
|
&chunk[data_offset], 5); |
|
if (pes_offset >= 0) { |
|
/* pes found. on SA, PES has hdr data at offset 6, not PTS. */ |
|
if ((chunk[data_offset + 6 + pes_offset] & 0x80) == 0x80) { |
|
/* S1SA or S2(any) Mpeg Audio (PES hdr, not a PTS start) */ |
|
if (ty->tivo_series == TIVO_SERIES1) |
|
ff_dlog(s, "detected Stand-Alone Tivo\n"); |
|
ty->tivo_type = TIVO_TYPE_SA; |
|
ty->pts_offset = SA_PTS_OFFSET; |
|
} else { |
|
if (ty->tivo_series == TIVO_SERIES1) |
|
ff_dlog(s, "detected DirecTV Tivo\n"); |
|
ty->tivo_type = TIVO_TYPE_DTIVO; |
|
ty->pts_offset = DTIVO_PTS_OFFSET; |
|
} |
|
break; |
|
} |
|
} |
|
data_offset += hdrs[i].rec_size; |
|
} |
|
} |
|
av_free(hdrs); |
|
|
|
return 0; |
|
} |
|
|
|
static int ty_read_header(AVFormatContext *s) |
|
{ |
|
TYDemuxContext *ty = s->priv_data; |
|
AVIOContext *pb = s->pb; |
|
AVStream *st, *ast; |
|
int i, ret = 0; |
|
|
|
ty->first_audio_pts = AV_NOPTS_VALUE; |
|
ty->last_audio_pts = AV_NOPTS_VALUE; |
|
ty->last_video_pts = AV_NOPTS_VALUE; |
|
|
|
for (i = 0; i < CHUNK_PEEK_COUNT; i++) { |
|
avio_read(pb, ty->chunk, CHUNK_SIZE); |
|
|
|
ret = analyze_chunk(s, ty->chunk); |
|
if (ret < 0) |
|
return ret; |
|
if (ty->tivo_series != TIVO_SERIES_UNKNOWN && |
|
ty->audio_type != TIVO_AUDIO_UNKNOWN && |
|
ty->tivo_type != TIVO_TYPE_UNKNOWN) |
|
break; |
|
} |
|
|
|
if (ty->tivo_series == TIVO_SERIES_UNKNOWN || |
|
ty->audio_type == TIVO_AUDIO_UNKNOWN || |
|
ty->tivo_type == TIVO_TYPE_UNKNOWN) |
|
return AVERROR(EIO); |
|
|
|
st = avformat_new_stream(s, NULL); |
|
if (!st) |
|
return AVERROR(ENOMEM); |
|
st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; |
|
st->codecpar->codec_id = AV_CODEC_ID_MPEG2VIDEO; |
|
st->need_parsing = AVSTREAM_PARSE_FULL_RAW; |
|
avpriv_set_pts_info(st, 64, 1, 90000); |
|
|
|
ast = avformat_new_stream(s, NULL); |
|
if (!ast) |
|
return AVERROR(ENOMEM); |
|
ast->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; |
|
|
|
if (ty->audio_type == TIVO_AUDIO_MPEG) { |
|
ast->codecpar->codec_id = AV_CODEC_ID_MP2; |
|
ast->need_parsing = AVSTREAM_PARSE_FULL_RAW; |
|
} else { |
|
ast->codecpar->codec_id = AV_CODEC_ID_AC3; |
|
} |
|
avpriv_set_pts_info(ast, 64, 1, 90000); |
|
|
|
ty->first_chunk = 1; |
|
|
|
avio_seek(pb, 0, SEEK_SET); |
|
|
|
return 0; |
|
} |
|
|
|
/* parse a master chunk, filling the SEQ table and other variables. |
|
* We assume the stream is currently pointing to it. |
|
*/ |
|
static void parse_master(AVFormatContext *s) |
|
{ |
|
TYDemuxContext *ty = s->priv_data; |
|
unsigned map_size; /* size of bitmask, in bytes */ |
|
unsigned i, j; |
|
|
|
/* 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 */ |
|
av_freep(&ty->seq_table); |
|
|
|
/* parse header info */ |
|
|
|
map_size = AV_RB32(ty->chunk + 20); /* size of bitmask, in bytes */ |
|
i = AV_RB32(ty->chunk + 28); /* size of SEQ table, in bytes */ |
|
|
|
ty->seq_table_size = i / (8LL + map_size); |
|
|
|
if (ty->seq_table_size == 0) { |
|
ty->seq_table = NULL; |
|
return; |
|
} |
|
|
|
/* parse all the entries */ |
|
ty->seq_table = av_calloc(ty->seq_table_size, sizeof(TySeqTable)); |
|
if (ty->seq_table == NULL) { |
|
ty->seq_table_size = 0; |
|
return; |
|
} |
|
|
|
ty->cur_chunk_pos = 32; |
|
for (j = 0; j < ty->seq_table_size; j++) { |
|
if (ty->cur_chunk_pos >= CHUNK_SIZE - 8) |
|
return; |
|
ty->seq_table[j].timestamp = AV_RB64(ty->chunk + ty->cur_chunk_pos); |
|
ty->cur_chunk_pos += 8; |
|
if (map_size > 8) { |
|
av_log(s, AV_LOG_ERROR, "Unsupported SEQ bitmap size in master chunk.\n"); |
|
ty->cur_chunk_pos += map_size; |
|
} else { |
|
memcpy(ty->seq_table[j].chunk_bitmask, ty->chunk + ty->cur_chunk_pos, map_size); |
|
} |
|
} |
|
} |
|
|
|
static int get_chunk(AVFormatContext *s) |
|
{ |
|
TYDemuxContext *ty = s->priv_data; |
|
AVIOContext *pb = s->pb; |
|
int read_size, num_recs; |
|
|
|
ff_dlog(s, "parsing ty chunk #%d\n", ty->cur_chunk); |
|
|
|
/* if we have left-over filler space from the last chunk, get that */ |
|
if (avio_feof(pb)) |
|
return AVERROR_EOF; |
|
|
|
/* read the TY packet header */ |
|
read_size = avio_read(pb, ty->chunk, CHUNK_SIZE); |
|
ty->cur_chunk++; |
|
|
|
if ((read_size < 4) || (AV_RB32(ty->chunk) == 0)) { |
|
return AVERROR_EOF; |
|
} |
|
|
|
/* check if it's a PART Header */ |
|
if (AV_RB32(ty->chunk) == TIVO_PES_FILEID) { |
|
parse_master(s); /* parse master chunk */ |
|
return get_chunk(s); |
|
} |
|
|
|
/* number of records in chunk (8- or 16-bit number) */ |
|
if (ty->chunk[3] & 0x80) { |
|
/* 16 bit rec cnt */ |
|
ty->num_recs = num_recs = (ty->chunk[1] << 8) + ty->chunk[0]; |
|
ty->seq_rec = (ty->chunk[3] << 8) + ty->chunk[2]; |
|
if (ty->seq_rec != 0xffff) { |
|
ty->seq_rec &= ~0x8000; |
|
} |
|
} else { |
|
/* 8 bit reclen - TiVo 1.3 format */ |
|
ty->num_recs = num_recs = ty->chunk[0]; |
|
ty->seq_rec = ty->chunk[1]; |
|
} |
|
ty->cur_rec = 0; |
|
ty->first_chunk = 0; |
|
|
|
ff_dlog(s, "chunk has %d records\n", num_recs); |
|
ty->cur_chunk_pos = 4; |
|
|
|
av_freep(&ty->rec_hdrs); |
|
|
|
if (num_recs * 16 >= CHUNK_SIZE - 4) |
|
return AVERROR_INVALIDDATA; |
|
|
|
ty->rec_hdrs = parse_chunk_headers(ty->chunk + 4, num_recs); |
|
if (!ty->rec_hdrs) |
|
return AVERROR(ENOMEM); |
|
ty->cur_chunk_pos += 16 * num_recs; |
|
|
|
return 0; |
|
} |
|
|
|
static int demux_video(AVFormatContext *s, TyRecHdr *rec_hdr, AVPacket *pkt) |
|
{ |
|
TYDemuxContext *ty = s->priv_data; |
|
const int subrec_type = rec_hdr->subrec_type; |
|
const int64_t rec_size = rec_hdr->rec_size; |
|
int es_offset1; |
|
int got_packet = 0; |
|
|
|
if (subrec_type != 0x02 && subrec_type != 0x0c && |
|
subrec_type != 0x08 && rec_size > 4) { |
|
/* 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 */ |
|
es_offset1 = find_es_header(ty_VideoPacket, ty->chunk + ty->cur_chunk_pos, 5); |
|
if (es_offset1 != -1) { |
|
ty->last_video_pts = ff_parse_pes_pts( |
|
ty->chunk + ty->cur_chunk_pos + es_offset1 + VIDEO_PTS_OFFSET); |
|
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 (rec_size >= VIDEO_PES_LENGTH + es_offset1) { |
|
int size = rec_hdr->rec_size - VIDEO_PES_LENGTH - es_offset1; |
|
|
|
ty->cur_chunk_pos += VIDEO_PES_LENGTH + es_offset1; |
|
if (av_new_packet(pkt, size) < 0) |
|
return AVERROR(ENOMEM); |
|
memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, size); |
|
ty->cur_chunk_pos += size; |
|
pkt->stream_index = 0; |
|
got_packet = 1; |
|
} else { |
|
ff_dlog(s, "video rec type 0x%02x has short PES" |
|
" (%"PRId64" bytes)\n", subrec_type, rec_size); |
|
/* nuke this block; it's too short, but has PES marker */ |
|
ty->cur_chunk_pos += rec_size; |
|
return 0; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (subrec_type == 0x06) { |
|
/* type 6 (S1 DTivo) has no data, so we're done */ |
|
ty->cur_chunk_pos += rec_size; |
|
return 0; |
|
} |
|
|
|
if (!got_packet) { |
|
if (av_new_packet(pkt, rec_size) < 0) |
|
return AVERROR(ENOMEM); |
|
memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size); |
|
ty->cur_chunk_pos += rec_size; |
|
pkt->stream_index = 0; |
|
got_packet = 1; |
|
} |
|
|
|
/* if it's not a continue blk, then set PTS */ |
|
if (subrec_type != 0x02) { |
|
if (subrec_type == 0x0c && pkt->size >= 6) |
|
pkt->data[5] |= 0x08; |
|
if (subrec_type == 0x07) { |
|
ty->last_ty_pts = rec_hdr->ty_pts; |
|
} 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. |
|
*/ |
|
ty->last_ty_pts += 35000000; |
|
//ty->last_ty_pts += 33366667; |
|
} |
|
/* set PTS for this block before we send */ |
|
if (ty->last_video_pts > AV_NOPTS_VALUE) { |
|
pkt->pts = ty->last_video_pts; |
|
/* PTS gets used ONCE. |
|
* Any subsequent frames we get BEFORE next PES |
|
* header will have their PTS computed in the codec */ |
|
ty->last_video_pts = AV_NOPTS_VALUE; |
|
} |
|
} |
|
|
|
return got_packet; |
|
} |
|
|
|
static int check_sync_pes(AVFormatContext *s, AVPacket *pkt, |
|
int32_t offset, int32_t rec_len) |
|
{ |
|
TYDemuxContext *ty = s->priv_data; |
|
|
|
if (offset < 0 || offset + ty->pes_length > rec_len) { |
|
/* entire PES header not present */ |
|
ff_dlog(s, "PES header at %"PRId32" not complete in record. storing.\n", offset); |
|
/* save the partial pes header */ |
|
if (offset < 0) { |
|
/* no header found, fake some 00's (this works, believe me) */ |
|
memset(ty->pes_buffer, 0, 4); |
|
ty->pes_buf_cnt = 4; |
|
if (rec_len > 4) |
|
ff_dlog(s, "PES header not found in record of %"PRId32" bytes!\n", rec_len); |
|
return -1; |
|
} |
|
/* copy the partial pes header we found */ |
|
memcpy(ty->pes_buffer, pkt->data + offset, rec_len - offset); |
|
ty->pes_buf_cnt = rec_len - offset; |
|
|
|
if (offset > 0) { |
|
/* PES Header was found, but not complete, so trim the end of this record */ |
|
pkt->size -= rec_len - offset; |
|
return 1; |
|
} |
|
return -1; /* partial PES, no audio data */ |
|
} |
|
/* full PES header present, extract PTS */ |
|
ty->last_audio_pts = ff_parse_pes_pts(&pkt->data[ offset + ty->pts_offset]); |
|
if (ty->first_audio_pts == AV_NOPTS_VALUE) |
|
ty->first_audio_pts = ty->last_audio_pts; |
|
pkt->pts = ty->last_audio_pts; |
|
memmove(pkt->data + offset, pkt->data + offset + ty->pes_length, rec_len - ty->pes_length); |
|
pkt->size -= ty->pes_length; |
|
return 0; |
|
} |
|
|
|
static int demux_audio(AVFormatContext *s, TyRecHdr *rec_hdr, AVPacket *pkt) |
|
{ |
|
TYDemuxContext *ty = s->priv_data; |
|
const int subrec_type = rec_hdr->subrec_type; |
|
const int64_t rec_size = rec_hdr->rec_size; |
|
int es_offset1; |
|
|
|
if (subrec_type == 2) { |
|
int need = 0; |
|
/* SA or DTiVo Audio Data, no PES (continued block) |
|
* ================================================ |
|
*/ |
|
|
|
/* continue PES if previous was incomplete */ |
|
if (ty->pes_buf_cnt > 0) { |
|
need = ty->pes_length - ty->pes_buf_cnt; |
|
|
|
ff_dlog(s, "continuing PES header\n"); |
|
/* do we have enough data to complete? */ |
|
if (need >= rec_size) { |
|
/* don't have complete PES hdr; save what we have and return */ |
|
memcpy(ty->pes_buffer + ty->pes_buf_cnt, ty->chunk + ty->cur_chunk_pos, rec_size); |
|
ty->cur_chunk_pos += rec_size; |
|
ty->pes_buf_cnt += rec_size; |
|
return 0; |
|
} |
|
|
|
/* we have enough; reconstruct this frame with the new hdr */ |
|
memcpy(ty->pes_buffer + ty->pes_buf_cnt, ty->chunk + ty->cur_chunk_pos, need); |
|
ty->cur_chunk_pos += need; |
|
/* get the PTS out of this PES header (MPEG or AC3) */ |
|
if (ty->audio_type == TIVO_AUDIO_MPEG) { |
|
es_offset1 = find_es_header(ty_MPEGAudioPacket, |
|
ty->pes_buffer, 5); |
|
} else { |
|
es_offset1 = find_es_header(ty_AC3AudioPacket, |
|
ty->pes_buffer, 5); |
|
} |
|
if (es_offset1 < 0) { |
|
ff_dlog(s, "Can't find audio PES header in packet.\n"); |
|
} else { |
|
ty->last_audio_pts = ff_parse_pes_pts( |
|
&ty->pes_buffer[es_offset1 + ty->pts_offset]); |
|
pkt->pts = ty->last_audio_pts; |
|
} |
|
ty->pes_buf_cnt = 0; |
|
|
|
} |
|
if (av_new_packet(pkt, rec_size - need) < 0) |
|
return AVERROR(ENOMEM); |
|
memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size - need); |
|
ty->cur_chunk_pos += rec_size - need; |
|
pkt->stream_index = 1; |
|
|
|
/* 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 (ty->audio_type == TIVO_AUDIO_AC3 && |
|
ty->tivo_series == TIVO_SERIES2) { |
|
if (ty->ac3_pkt_size + pkt->size > AC3_PKT_LENGTH) { |
|
pkt->size -= 2; |
|
ty->ac3_pkt_size = 0; |
|
} else { |
|
ty->ac3_pkt_size += pkt->size; |
|
} |
|
} |
|
} else if (subrec_type == 0x03) { |
|
if (av_new_packet(pkt, rec_size) < 0) |
|
return AVERROR(ENOMEM); |
|
memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size); |
|
ty->cur_chunk_pos += rec_size; |
|
pkt->stream_index = 1; |
|
/* MPEG Audio with PES Header, either SA or DTiVo */ |
|
/* ================================================ */ |
|
es_offset1 = find_es_header(ty_MPEGAudioPacket, pkt->data, 5); |
|
|
|
/* SA PES Header, No Audio Data */ |
|
/* ================================================ */ |
|
if ((es_offset1 == 0) && (rec_size == 16)) { |
|
ty->last_audio_pts = ff_parse_pes_pts(&pkt->data[SA_PTS_OFFSET]); |
|
if (ty->first_audio_pts == AV_NOPTS_VALUE) |
|
ty->first_audio_pts = ty->last_audio_pts; |
|
av_packet_unref(pkt); |
|
return 0; |
|
} |
|
/* DTiVo Audio with PES Header */ |
|
/* ================================================ */ |
|
|
|
/* Check for complete PES */ |
|
if (check_sync_pes(s, pkt, es_offset1, rec_size) == -1) { |
|
/* partial PES header found, nothing else. |
|
* we're done. */ |
|
av_packet_unref(pkt); |
|
return 0; |
|
} |
|
} else if (subrec_type == 0x04) { |
|
/* SA Audio with no PES Header */ |
|
/* ================================================ */ |
|
if (av_new_packet(pkt, rec_size) < 0) |
|
return AVERROR(ENOMEM); |
|
memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size); |
|
ty->cur_chunk_pos += rec_size; |
|
pkt->stream_index = 1; |
|
pkt->pts = ty->last_audio_pts; |
|
} else if (subrec_type == 0x09) { |
|
if (av_new_packet(pkt, rec_size) < 0) |
|
return AVERROR(ENOMEM); |
|
memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size); |
|
ty->cur_chunk_pos += rec_size ; |
|
pkt->stream_index = 1; |
|
|
|
/* DTiVo AC3 Audio Data with PES Header */ |
|
/* ================================================ */ |
|
es_offset1 = find_es_header(ty_AC3AudioPacket, pkt->data, 5); |
|
|
|
/* Check for complete PES */ |
|
if (check_sync_pes(s, pkt, es_offset1, rec_size) == -1) { |
|
/* partial PES header found, nothing else. we're done. */ |
|
av_packet_unref(pkt); |
|
return 0; |
|
} |
|
/* S2 DTivo has invalid long AC3 packets */ |
|
if (ty->tivo_series == TIVO_SERIES2) { |
|
if (pkt->size > AC3_PKT_LENGTH) { |
|
pkt->size -= 2; |
|
ty->ac3_pkt_size = 0; |
|
} else { |
|
ty->ac3_pkt_size = pkt->size; |
|
} |
|
} |
|
} else { |
|
/* Unsupported/Unknown */ |
|
ty->cur_chunk_pos += rec_size; |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
static int ty_read_packet(AVFormatContext *s, AVPacket *pkt) |
|
{ |
|
TYDemuxContext *ty = s->priv_data; |
|
AVIOContext *pb = s->pb; |
|
TyRecHdr *rec; |
|
int64_t rec_size = 0; |
|
int ret = 0; |
|
|
|
if (avio_feof(pb)) |
|
return AVERROR_EOF; |
|
|
|
while (ret <= 0) { |
|
if (!ty->rec_hdrs || ty->first_chunk || ty->cur_rec >= ty->num_recs) { |
|
if (get_chunk(s) < 0 || ty->num_recs <= 0) |
|
return AVERROR_EOF; |
|
} |
|
|
|
rec = &ty->rec_hdrs[ty->cur_rec]; |
|
rec_size = rec->rec_size; |
|
ty->cur_rec++; |
|
|
|
if (rec_size <= 0) |
|
continue; |
|
|
|
if (ty->cur_chunk_pos + rec->rec_size > CHUNK_SIZE) |
|
return AVERROR_INVALIDDATA; |
|
|
|
if (avio_feof(pb)) |
|
return AVERROR_EOF; |
|
|
|
switch (rec->rec_type) { |
|
case VIDEO_ID: |
|
ret = demux_video(s, rec, pkt); |
|
break; |
|
case AUDIO_ID: |
|
ret = demux_audio(s, rec, pkt); |
|
break; |
|
default: |
|
ff_dlog(s, "Invalid record type 0x%02x\n", rec->rec_type); |
|
case 0x01: |
|
case 0x02: |
|
case 0x03: /* TiVo data services */ |
|
case 0x05: /* unknown, but seen regularly */ |
|
ty->cur_chunk_pos += rec->rec_size; |
|
break; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int ty_read_close(AVFormatContext *s) |
|
{ |
|
TYDemuxContext *ty = s->priv_data; |
|
|
|
av_freep(&ty->seq_table); |
|
av_freep(&ty->rec_hdrs); |
|
|
|
return 0; |
|
} |
|
|
|
AVInputFormat ff_ty_demuxer = { |
|
.name = "ty", |
|
.long_name = NULL_IF_CONFIG_SMALL("TiVo TY Stream"), |
|
.priv_data_size = sizeof(TYDemuxContext), |
|
.read_probe = ty_probe, |
|
.read_header = ty_read_header, |
|
.read_packet = ty_read_packet, |
|
.read_close = ty_read_close, |
|
.extensions = "ty,ty+", |
|
.flags = AVFMT_TS_DISCONT, |
|
};
|
|
|