From patchwork Sat Nov 4 10:51:09 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 5856 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.2.161.90 with SMTP id m26csp861336jah; Sat, 4 Nov 2017 03:57:52 -0700 (PDT) X-Google-Smtp-Source: ABhQp+QhJlqV0q4PkdypW3HZueSFEE/vmTuKo6Rn6kNAJRCyZWpAJPhNPRokILMLqIXWr9EhBGYy X-Received: by 10.223.139.221 with SMTP id w29mr7761664wra.243.1509793072201; Sat, 04 Nov 2017 03:57:52 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1509793072; cv=none; d=google.com; s=arc-20160816; b=cS154Evl21ConEORKgACSL3/ZxMg8W0SiclE3LOEaH+/TdC3QYdnEVpQP+OyRf2tH3 eXOpBcz+kQgtCsHgcgngBigc9b97bp2Ifb7PnEWtqpTtMNsQguf3GgigSN42WlKzQodw Q+n2iSStmoIZvJ7xfcBkSiQ/KYJA9HWNfxo4gXUJ2FlaEVv7cMv80PzjSLbHGIYL0n9c wH97tVOcHF4wTwqv2IHId6ycNh5PxgPQPtGuPagtVHedwq/i0WaIF9846gxlSFcjGgXW wcMasJ0C8kLaOci7+pw9dTg4clvBOEjBEQZFP5/pImrzGOtkPoGBY1bRJrpjYTQ+S1H7 TM3w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:message-id:date:to:from:dkim-signature :delivered-to:arc-authentication-results; bh=aO0IMixivfaaLwKAY3tTEXzhtD3kquFO7fAuLJYRSiM=; b=yQ0d1Cv4VKxJpsRqgz8dpDUbeEU2blPLyRo42fNsVvBZgjBkQtoROWLC4aP8Qfm+Tc Au1F/7H1wHYIpzPdPkOPJYzj34dH08ChAY1QSpai7Hko2Q9YNc5b+Sbxn86P0VZTjgHD 5NJIQQLQZhnBWzFpIHg6cBYeDoyq/Ux6kSzBHmlhmlLDIFKd0UmmfzU/KsRiomYgx5+G 0kf3qQ7rQXUjddTdjzCqPbAtTL/Y0fwqAdgl6bmdzIMELJS5yjYyLUSwziFiaDbUsJWr UgVt60HLrO+YCL5Fup9FHcDd0xO9XtfJSiVppVjjBAwpGp6cEeVDEpWeFLPr+kF8W9Ob S+Sg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=szdppkAF; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id 21si7790899wrx.324.2017.11.04.03.57.50; Sat, 04 Nov 2017 03:57:52 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=szdppkAF; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 351BD68A06A; Sat, 4 Nov 2017 12:57:38 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wm0-f43.google.com (mail-wm0-f43.google.com [74.125.82.43]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 6F6DE689ED5 for ; Sat, 4 Nov 2017 12:57:32 +0200 (EET) Received: by mail-wm0-f43.google.com with SMTP id r68so5900999wmr.3 for ; Sat, 04 Nov 2017 03:57:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=HQ2yinnkvh0uiry+/IbycjJRRCVmb+YoK25lXUHccf8=; b=szdppkAFCr4DYP0Qgc0QiW/E56NbXT8deFPODVmN28w1RCojZRtajuWVhLVSMMAE6a W0ce6/MJl3Gg5olAIrVaplR1I+4JCLxsrYIXeVJlid5RcvoqhhFgfATahoGEDv+OZ1H8 jSeCMvK7oSC8BuDj9GP0cbD7huaUIb44aci2m86Y1hOXwQQgNpvxjng3QEdQfYY6G7jg K1d38Zx8z5QKWvdW+8FCOQF5gW+TlMEtmWG0Y6ggliujRexLBpKdCQIecSwnoDvCqwHx +IUzRGVf9bxYtKSUzq1nLMJoqz0i+NL8CNLsuB6LlAo4QEcJn+zxxGWM6jlBFxEndtQU cqMg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=HQ2yinnkvh0uiry+/IbycjJRRCVmb+YoK25lXUHccf8=; b=Ij2asI0YDRp5x05vecAKbPwVuijkyKLs1HFBBwBhpeTJI9mu15jN6mAz3Y1Qp2wAra VDtnSMetYXuA9gGlE8qPxbXF/3vAx7jSPPX+WPo9cW3unc/31EkkAbD+l9tNwLeXDaW8 iqLulR5kMorqMzEZEzlcpfKcMizGYQlzpRWynQ1mkFHSCUnTE2z2i/WLh0lR0Wk7Tzt/ sIoGDtgEMNfalkHTOKr7ByLCyYtLihwI0vU9X4ZU5YOCK0x6broBRJGsbmdbzcGZObpU J9PyFz8HGhDQUveYhBdPdsV2zto+BfMPkmvF1lnOB7tigrF0PrYH/A7i5Zs7Zqrs7lhr FoTg== X-Gm-Message-State: AMCzsaU2MINGyqiWhOOm6FJFOcalFXMTdpMt5VDWYW2hDje0liq6VDEP ESdziPudOmNF++0R/xOPp2eyng== X-Received: by 10.80.194.65 with SMTP id t1mr8715930edf.304.1509792714164; Sat, 04 Nov 2017 03:51:54 -0700 (PDT) Received: from localhost.localdomain ([94.250.174.60]) by smtp.gmail.com with ESMTPSA id c34sm8637610ede.84.2017.11.04.03.51.51 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 04 Nov 2017 03:51:52 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Sat, 4 Nov 2017 11:51:09 +0100 Message-Id: <20171104105109.29160-1-onemda@gmail.com> X-Mailer: git-send-email 2.11.0 Subject: [FFmpeg-devel] [PATCH] avformat: add TiVo ty demuxer X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Paul B Mahol --- libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/ty.c | 765 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 767 insertions(+) create mode 100644 libavformat/ty.c diff --git a/libavformat/Makefile b/libavformat/Makefile index caebe5b146..045200e882 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..bd871cba2e --- /dev/null +++ b/libavformat/ty.c @@ -0,0 +1,765 @@ +/* + * 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) +{ + 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) +{ + 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 ((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++) { + 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); + 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 %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 = 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->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; +} + +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+", + .flags = AVFMT_TS_DISCONT, +};