[FFmpeg-devel] [PATCH] avformat: add TiVo ty demuxer
Paul B Mahol
onemda at gmail.com
Wed Nov 1 15:40:27 EET 2017
Signed-off-by: Paul B Mahol <onemda at gmail.com>
---
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/ty.c | 775 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 777 insertions(+)
create mode 100644 libavformat/ty.c
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 591a14b978..790c165da3 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -490,6 +490,7 @@ OBJS-$(CONFIG_TRUEHD_MUXER) += rawenc.o
OBJS-$(CONFIG_TTA_DEMUXER) += tta.o apetag.o img2.o
OBJS-$(CONFIG_TTA_MUXER) += ttaenc.o apetag.o img2.o
OBJS-$(CONFIG_TTY_DEMUXER) += tty.o sauce.o
+OBJS-$(CONFIG_TY_DEMUXER) += ty.o
OBJS-$(CONFIG_TXD_DEMUXER) += txd.o
OBJS-$(CONFIG_UNCODEDFRAMECRC_MUXER) += uncodedframecrcenc.o framehash.o
OBJS-$(CONFIG_V210_DEMUXER) += v210.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 405ddb5ad9..3f72a566eb 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -320,6 +320,7 @@ static void register_all(void)
REGISTER_MUXDEMUX(TTA, tta);
REGISTER_DEMUXER (TXD, txd);
REGISTER_DEMUXER (TTY, tty);
+ REGISTER_DEMUXER (TY, ty);
REGISTER_MUXER (UNCODEDFRAMECRC, uncodedframecrc);
REGISTER_DEMUXER (V210, v210);
REGISTER_DEMUXER (V210X, v210x);
diff --git a/libavformat/ty.c b/libavformat/ty.c
new file mode 100644
index 0000000000..af1eae35a2
--- /dev/null
+++ b/libavformat/ty.c
@@ -0,0 +1,775 @@
+/*
+ * TiVo ty stream demuxer
+ * Copyright (C) 2005 VLC authors and VideoLAN
+ * Copyright (C) 2005 by Neal Symms (tivo at freakinzoo.com) - February 2005
+ * based on code by Christopher Wingert for tivo-mplayer
+ * tivo(at)wingert.org, February 2003
+ *
+ * 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 "libavutil/dict.h"
+#include "avformat.h"
+#include "internal.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;
+ int ext;
+ 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;
+ int stuff_cnt;
+ 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)
+{
+ if (AV_RB32(p->buf) == TIVO_PES_FILEID &&
+ AV_RB32(p->buf + 4) == 0x02 &&
+ AV_RB32(p->buf + 8) == CHUNK_SIZE) {
+ return AVPROBE_SCORE_MAX;
+ }
+ return 0;
+}
+
+static TyRecHdr *parse_chunk_headers(const uint8_t *buf,
+ int num_recs, int *payload_size)
+{
+ TyRecHdr *hdrs, *rec_hdr;
+ int i;
+
+ *payload_size = 0;
+ hdrs = av_calloc(num_recs, sizeof(TyRecHdr));
+
+ 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;
+ rec_hdr->ext = 1;
+ } else {
+ rec_hdr->rec_size = ( record_header[ 0 ] << 8 |
+ record_header[ 1 ] ) << 4 | ( record_header[ 2 ] >> 4 );
+ *payload_size += rec_hdr->rec_size;
+ rec_hdr->ext = 0;
+ 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 void 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;
+ int payload_size;
+
+ /* skip if it's a Part header */
+ if (AV_RB32(&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) */
+ num_recs = chunk[0];
+ if (num_recs < 5) {
+ /* try again with the next chunk. Sometimes there are dead ones */
+ return;
+ }
+
+ 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, &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.
+ */
+ 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 ((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);
+}
+
+static int ty_read_header(AVFormatContext *s)
+{
+ TYDemuxContext *ty = s->priv_data;
+ AVIOContext *pb = s->pb;
+ AVStream *st, *ast;
+ int i;
+
+ 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);
+
+ analyze_chunk(s, ty->chunk);
+ 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;
+ AVIOContext *pb = s->pb;
+ uint8_t mst_buf[32];
+ int64_t save_pos = avio_tell(pb);
+ uint32_t map_size; /* size of bitmask, in bytes */
+ uint32_t 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 */
+ avio_read(pb, mst_buf, 32);
+
+ map_size = AV_RB32(&mst_buf[20]); /* size of bitmask, in bytes */
+ i = AV_RB32(&mst_buf[28]); /* size of SEQ table, in bytes */
+
+ ty->seq_table_size = i / (8 + 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;
+ }
+
+ for (j = 0; j < ty->seq_table_size; j++) {
+ avio_read(pb, mst_buf, 8);
+ ty->seq_table[j].timestamp = AV_RB64(&mst_buf[0]);
+ if (map_size > 8) {
+ av_log(s, AV_LOG_ERROR, "Unsupported SEQ bitmap size in master chunk.\n");
+ avio_skip(pb, map_size);
+ } else {
+ avio_read(pb, mst_buf + 8, map_size);
+ memcpy(ty->seq_table[j].chunk_bitmask, &mst_buf[8], map_size);
+ }
+ }
+
+ /* seek past this chunk */
+ avio_seek(pb, save_pos + CHUNK_SIZE, SEEK_SET);
+}
+
+static int get_chunk_header(AVFormatContext *s)
+{
+ TYDemuxContext *ty = s->priv_data;
+ AVIOContext *pb = s->pb;
+ int readSize, num_recs;
+ uint8_t *hdr_buf;
+ uint8_t peek[4];
+ int payload_size;
+
+ 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 (ty->stuff_cnt > 0) {
+ avio_skip(pb, ty->stuff_cnt);
+ ty->stuff_cnt = 0;
+ }
+ if (avio_feof(pb))
+ return 0;
+
+ /* read the TY packet header */
+ readSize = avio_read( pb, &peek[0], 4 );
+ avio_seek(pb, -4, SEEK_CUR);
+ ty->cur_chunk++;
+
+ if ( (readSize < 4) || ( AV_RB32(&peek[ 0 ] ) == 0 )) {
+ /* EOF */
+ return 0;
+ }
+
+ /* check if it's a PART Header */
+ if (AV_RB32(&peek[0]) == TIVO_PES_FILEID) {
+ /* parse master chunk */
+ parse_master(s);
+ return get_chunk_header(s);
+ }
+
+ /* number of records in chunk (8- or 16-bit number) */
+ if (peek[3] & 0x80) {
+ /* 16 bit rec cnt */
+ ty->num_recs = num_recs = (peek[1] << 8) + peek[0];
+ ty->seq_rec = (peek[3] << 8) + peek[2];
+ if (ty->seq_rec != 0xffff) {
+ ty->seq_rec &= ~0x8000;
+ }
+ } else {
+ /* 8 bit reclen - TiVo 1.3 format */
+ ty->num_recs = num_recs = peek[0];
+ ty->seq_rec = peek[1];
+ }
+ ty->cur_rec = 0;
+ ty->first_chunk = 0;
+
+ ff_dlog( s, "chunk has %d records\n", num_recs );
+
+ av_freep(&ty->rec_hdrs);
+
+ /* skip past the 4 bytes we "peeked" earlier */
+ avio_skip(pb, 4);
+
+ /* read the record headers into a temp buffer */
+ hdr_buf = av_calloc(num_recs, 16);
+ if (avio_read(pb, hdr_buf, num_recs * 16) < num_recs * 16) {
+ av_free(hdr_buf);
+ return 0;
+ }
+ /* parse them */
+ ty->rec_hdrs = parse_chunk_headers(hdr_buf, num_recs, &payload_size);
+ av_free(hdr_buf);
+
+ ty->stuff_cnt = CHUNK_SIZE - 4 - (ty->num_recs * 16) - payload_size;
+ ff_dlog(s, "chunk has %d stuff bytes at end\n", ty->stuff_cnt);
+ return 1;
+}
+
+static int64_t get_pts(const uint8_t *buf)
+{
+ int a = buf[0] & 0xe >> 1;
+ int b = AV_RB16(buf + 1);
+ int c = AV_RB16(buf + 3);
+
+ if (!(1 & a & b & c))
+ return AV_NOPTS_VALUE;
+
+ a >>= 1;
+ b >>= 1;
+ c >>= 1;
+ return (((uint64_t)a) << 30) | (b << 15) | c;
+}
+
+static int DemuxRecVideo(AVFormatContext *s, TyRecHdr *rec_hdr, AVPacket *pkt)
+{
+ TYDemuxContext *ty = s->priv_data;
+ AVIOContext *pb = s->pb;
+ const int subrec_type = rec_hdr->subrec_type;
+ const int64_t rec_size = rec_hdr->rec_size;
+ int esOffset1;
+ int got_packet = 0;
+
+ avio_read(pb, ty->chunk, 32);
+ avio_seek(pb, -32, SEEK_CUR);
+
+ 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 */
+ esOffset1 = find_es_header(ty_VideoPacket, ty->chunk, 5);
+ if (esOffset1 != -1) {
+ ty->last_video_pts = get_pts(
+ &ty->chunk[ esOffset1 + 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 + esOffset1) {
+ int size = rec_hdr->rec_size - VIDEO_PES_LENGTH - esOffset1;
+ avio_skip(pb, VIDEO_PES_LENGTH + esOffset1);
+ av_get_packet(pb, pkt, 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 */
+ avio_skip(pb, rec_size);
+ return 0;
+ }
+ }
+ }
+ }
+
+ if (subrec_type == 0x06) {
+ /* type 6 (S1 DTivo) has no data, so we're done */
+ avio_skip(pb, rec_size);
+ return 0;
+ }
+ if (!got_packet) {
+ av_get_packet(pb, pkt, rec_hdr->rec_size);
+ pkt->stream_index = 0;
+ }
+
+ /* 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 0;
+}
+
+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 %d 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 %d 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 = get_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 DemuxRecAudio(AVFormatContext *s, TyRecHdr *rec_hdr, AVPacket *pkt)
+{
+ TYDemuxContext *ty = s->priv_data;
+ AVIOContext *pb = s->pb;
+ const int subrec_type = rec_hdr->subrec_type;
+ const int64_t rec_size = rec_hdr->rec_size;
+ int esOffset1;
+
+ 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 */
+ avio_read(pb, &ty->pes_buffer[ty->pes_buf_cnt], rec_size);
+ ty->pes_buf_cnt += rec_size;
+ return 0;
+ }
+
+ /* we have enough; reconstruct this frame with the new hdr */
+ avio_read(pb, &ty->pes_buffer[ty->pes_buf_cnt], need);
+ /* get the PTS out of this PES header (MPEG or AC3) */
+ if (ty->audio_type == TIVO_AUDIO_MPEG) {
+ esOffset1 = find_es_header(ty_MPEGAudioPacket,
+ ty->pes_buffer, 5);
+ } else {
+ esOffset1 = find_es_header(ty_AC3AudioPacket,
+ ty->pes_buffer, 5);
+ }
+ if (esOffset1 < 0) {
+ ff_dlog(s, "Can't find audio PES header in packet.\n");
+ } else {
+ ty->last_audio_pts = get_pts(
+ &ty->pes_buffer[ esOffset1 + ty->pts_Offset ] );
+ pkt->pts = ty->last_audio_pts;
+ }
+ ty->pes_buf_cnt = 0;
+
+ }
+ av_get_packet(pb, pkt, 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) {
+ av_get_packet(pb, pkt, rec_size);
+ pkt->stream_index = 1;
+ /* MPEG Audio with PES Header, either SA or DTiVo */
+ /* ================================================ */
+ esOffset1 = find_es_header(ty_MPEGAudioPacket, pkt->data, 5);
+
+ /* SA PES Header, No Audio Data */
+ /* ================================================ */
+ if ((esOffset1 == 0) && (rec_size == 16)) {
+ ty->last_audio_pts = get_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, esOffset1, 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 */
+ /* ================================================ */
+ av_get_packet(pb, pkt, rec_size);
+ pkt->stream_index = 1;
+ pkt->pts = ty->last_audio_pts;
+ } else if (subrec_type == 0x09) {
+ av_get_packet(pb, pkt, rec_size);
+ pkt->stream_index = 1;
+
+ /* DTiVo AC3 Audio Data with PES Header */
+ /* ================================================ */
+ esOffset1 = find_es_header(ty_AC3AudioPacket, pkt->data, 5);
+
+ /* Check for complete PES */
+ if (check_sync_pes(s, pkt, esOffset1, 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 */
+ avio_skip(pb, rec_size);
+ return 0;
+ }
+
+ return 0;
+}
+
+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;
+
+ if (avio_feof(pb))
+ return AVERROR_EOF;
+
+ if (ty->first_chunk || ty->cur_rec >= ty->num_recs ) {
+ if (get_chunk_header(s) == 0 || ty->num_recs == 0)
+ return AVERROR_EOF;
+ }
+
+ rec = &ty->rec_hdrs[ty->cur_rec];
+ rec_size = rec->rec_size;
+ if (rec->ext || rec_size <= 0) {
+ ty->cur_rec++;
+ return 0;
+ }
+
+ switch (rec->rec_type) {
+ case 0xe0:
+ DemuxRecVideo(s, rec, pkt);
+ break;
+ case 0xc0:
+ DemuxRecAudio(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 */
+ avio_skip(pb, rec->rec_size);
+ break;
+ }
+
+ ty->cur_rec++;
+
+ 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,
+ .extensions = "ty,ty+",
+};
--
2.11.0
More information about the ffmpeg-devel
mailing list