| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * TiVo ty stream demuxer | ||
| 3 | * Copyright (c) 2005 VLC authors and VideoLAN | ||
| 4 | * Copyright (c) 2005 by Neal Symms (tivo@freakinzoo.com) - February 2005 | ||
| 5 | * based on code by Christopher Wingert for tivo-mplayer | ||
| 6 | * tivo(at)wingert.org, February 2003 | ||
| 7 | * Copyright (c) 2017 Paul B Mahol | ||
| 8 | * | ||
| 9 | * This file is part of FFmpeg. | ||
| 10 | * | ||
| 11 | * FFmpeg is free software; you can redistribute it and/or | ||
| 12 | * modify it under the terms of the GNU Lesser General Public | ||
| 13 | * License as published by the Free Software Foundation; either | ||
| 14 | * version 2.1 of the License, or (at your option) any later version. | ||
| 15 | * | ||
| 16 | * FFmpeg 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 GNU | ||
| 19 | * Lesser General Public License for more details. | ||
| 20 | * | ||
| 21 | * You should have received a copy of the GNU Lesser General Public | ||
| 22 | * License along with FFmpeg; if not, write to the Free Software | ||
| 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 24 | */ | ||
| 25 | |||
| 26 | #include "libavutil/intreadwrite.h" | ||
| 27 | #include "libavutil/mem.h" | ||
| 28 | #include "avformat.h" | ||
| 29 | #include "demux.h" | ||
| 30 | #include "internal.h" | ||
| 31 | #include "mpeg.h" | ||
| 32 | |||
| 33 | #define SERIES1_PES_LENGTH 11 /* length of audio PES hdr on S1 */ | ||
| 34 | #define SERIES2_PES_LENGTH 16 /* length of audio PES hdr on S2 */ | ||
| 35 | #define AC3_PES_LENGTH 14 /* length of audio PES hdr for AC3 */ | ||
| 36 | #define VIDEO_PES_LENGTH 16 /* length of video PES header */ | ||
| 37 | #define DTIVO_PTS_OFFSET 6 /* offs into PES for MPEG PTS on DTivo */ | ||
| 38 | #define SA_PTS_OFFSET 9 /* offset into PES for MPEG PTS on SA */ | ||
| 39 | #define AC3_PTS_OFFSET 9 /* offset into PES for AC3 PTS on DTivo */ | ||
| 40 | #define VIDEO_PTS_OFFSET 9 /* offset into PES for video PTS on all */ | ||
| 41 | #define AC3_PKT_LENGTH 1536 /* size of TiVo AC3 pkts (w/o PES hdr) */ | ||
| 42 | |||
| 43 | static const uint8_t ty_VideoPacket[] = { 0x00, 0x00, 0x01, 0xe0 }; | ||
| 44 | static const uint8_t ty_MPEGAudioPacket[] = { 0x00, 0x00, 0x01, 0xc0 }; | ||
| 45 | static const uint8_t ty_AC3AudioPacket[] = { 0x00, 0x00, 0x01, 0xbd }; | ||
| 46 | |||
| 47 | #define TIVO_PES_FILEID 0xf5467abd | ||
| 48 | #define CHUNK_SIZE (128 * 1024) | ||
| 49 | #define CHUNK_PEEK_COUNT 3 /* number of chunks to probe */ | ||
| 50 | |||
| 51 | typedef struct TyRecHdr { | ||
| 52 | int32_t rec_size; | ||
| 53 | uint8_t ex[2]; | ||
| 54 | uint8_t rec_type; | ||
| 55 | uint8_t subrec_type; | ||
| 56 | uint64_t ty_pts; /* TY PTS in the record header */ | ||
| 57 | } TyRecHdr; | ||
| 58 | |||
| 59 | typedef enum { | ||
| 60 | TIVO_TYPE_UNKNOWN, | ||
| 61 | TIVO_TYPE_SA, | ||
| 62 | TIVO_TYPE_DTIVO | ||
| 63 | } TiVo_type; | ||
| 64 | |||
| 65 | typedef enum { | ||
| 66 | TIVO_SERIES_UNKNOWN, | ||
| 67 | TIVO_SERIES1, | ||
| 68 | TIVO_SERIES2 | ||
| 69 | } TiVo_series; | ||
| 70 | |||
| 71 | typedef enum { | ||
| 72 | TIVO_AUDIO_UNKNOWN, | ||
| 73 | TIVO_AUDIO_AC3, | ||
| 74 | TIVO_AUDIO_MPEG | ||
| 75 | } TiVo_audio; | ||
| 76 | |||
| 77 | typedef struct TYDemuxContext { | ||
| 78 | unsigned cur_chunk; | ||
| 79 | unsigned cur_chunk_pos; | ||
| 80 | int64_t cur_pos; | ||
| 81 | TiVo_type tivo_type; /* TiVo type (SA / DTiVo) */ | ||
| 82 | TiVo_series tivo_series; /* Series1 or Series2 */ | ||
| 83 | TiVo_audio audio_type; /* AC3 or MPEG */ | ||
| 84 | int pes_length; /* Length of Audio PES header */ | ||
| 85 | int pts_offset; /* offset into audio PES of PTS */ | ||
| 86 | uint8_t pes_buffer[20]; /* holds incomplete pes headers */ | ||
| 87 | int pes_buf_cnt; /* how many bytes in our buffer */ | ||
| 88 | size_t ac3_pkt_size; /* length of ac3 pkt we've seen so far */ | ||
| 89 | uint64_t last_ty_pts; /* last TY timestamp we've seen */ | ||
| 90 | |||
| 91 | int64_t first_audio_pts; | ||
| 92 | int64_t last_audio_pts; | ||
| 93 | int64_t last_video_pts; | ||
| 94 | |||
| 95 | TyRecHdr *rec_hdrs; /* record headers array */ | ||
| 96 | int cur_rec; /* current record in this chunk */ | ||
| 97 | int num_recs; /* number of recs in this chunk */ | ||
| 98 | int first_chunk; | ||
| 99 | |||
| 100 | uint8_t chunk[CHUNK_SIZE]; | ||
| 101 | } TYDemuxContext; | ||
| 102 | |||
| 103 | 7480 | static int ty_probe(const AVProbeData *p) | |
| 104 | { | ||
| 105 | int i; | ||
| 106 | |||
| 107 |
2/2✓ Branch 0 taken 9147 times.
✓ Branch 1 taken 7480 times.
|
16627 | for (i = 0; i + 12 < p->buf_size; i += CHUNK_SIZE) { |
| 108 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9147 times.
|
9147 | if (AV_RB32(p->buf + i) == TIVO_PES_FILEID && |
| 109 | ✗ | AV_RB32(p->buf + i + 4) == 0x02 && | |
| 110 | ✗ | AV_RB32(p->buf + i + 8) == CHUNK_SIZE) { | |
| 111 | ✗ | return AVPROBE_SCORE_MAX; | |
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | 7480 | return 0; | |
| 116 | } | ||
| 117 | |||
| 118 | ✗ | static TyRecHdr *parse_chunk_headers(const uint8_t *buf, | |
| 119 | int num_recs) | ||
| 120 | { | ||
| 121 | TyRecHdr *hdrs, *rec_hdr; | ||
| 122 | int i; | ||
| 123 | |||
| 124 | ✗ | hdrs = av_calloc(num_recs, sizeof(TyRecHdr)); | |
| 125 | ✗ | if (!hdrs) | |
| 126 | ✗ | return NULL; | |
| 127 | |||
| 128 | ✗ | for (i = 0; i < num_recs; i++) { | |
| 129 | ✗ | const uint8_t *record_header = buf + (i * 16); | |
| 130 | |||
| 131 | ✗ | rec_hdr = &hdrs[i]; /* for brevity */ | |
| 132 | ✗ | rec_hdr->rec_type = record_header[3]; | |
| 133 | ✗ | rec_hdr->subrec_type = record_header[2] & 0x0f; | |
| 134 | ✗ | if ((record_header[0] & 0x80) == 0x80) { | |
| 135 | uint8_t b1, b2; | ||
| 136 | |||
| 137 | /* marker bit 2 set, so read extended data */ | ||
| 138 | ✗ | b1 = (((record_header[0] & 0x0f) << 4) | | |
| 139 | ✗ | ((record_header[1] & 0xf0) >> 4)); | |
| 140 | ✗ | b2 = (((record_header[1] & 0x0f) << 4) | | |
| 141 | ✗ | ((record_header[2] & 0xf0) >> 4)); | |
| 142 | |||
| 143 | ✗ | rec_hdr->ex[0] = b1; | |
| 144 | ✗ | rec_hdr->ex[1] = b2; | |
| 145 | ✗ | rec_hdr->rec_size = 0; | |
| 146 | ✗ | rec_hdr->ty_pts = 0; | |
| 147 | } else { | ||
| 148 | ✗ | rec_hdr->rec_size = (record_header[0] << 8 | | |
| 149 | ✗ | record_header[1]) << 4 | | |
| 150 | ✗ | (record_header[2] >> 4); | |
| 151 | ✗ | rec_hdr->ty_pts = AV_RB64(&record_header[8]); | |
| 152 | } | ||
| 153 | } | ||
| 154 | ✗ | return hdrs; | |
| 155 | } | ||
| 156 | |||
| 157 | ✗ | static int find_es_header(const uint8_t *header, | |
| 158 | const uint8_t *buffer, int search_len) | ||
| 159 | { | ||
| 160 | int count; | ||
| 161 | |||
| 162 | ✗ | for (count = 0; count < search_len; count++) { | |
| 163 | ✗ | if (!memcmp(&buffer[count], header, 4)) | |
| 164 | ✗ | return count; | |
| 165 | } | ||
| 166 | ✗ | return -1; | |
| 167 | } | ||
| 168 | |||
| 169 | ✗ | static int analyze_chunk(AVFormatContext *s, const uint8_t *chunk) | |
| 170 | { | ||
| 171 | ✗ | TYDemuxContext *ty = s->priv_data; | |
| 172 | int num_recs, i; | ||
| 173 | TyRecHdr *hdrs; | ||
| 174 | int num_6e0, num_be0, num_9c0, num_3c0; | ||
| 175 | |||
| 176 | /* skip if it's a Part header */ | ||
| 177 | ✗ | if (AV_RB32(&chunk[0]) == TIVO_PES_FILEID) | |
| 178 | ✗ | return 0; | |
| 179 | |||
| 180 | /* number of records in chunk (we ignore high order byte; | ||
| 181 | * rarely are there > 256 chunks & we don't need that many anyway) */ | ||
| 182 | ✗ | num_recs = chunk[0]; | |
| 183 | ✗ | if (num_recs < 5) { | |
| 184 | /* try again with the next chunk. Sometimes there are dead ones */ | ||
| 185 | ✗ | return 0; | |
| 186 | } | ||
| 187 | |||
| 188 | ✗ | chunk += 4; /* skip past rec count & SEQ bytes */ | |
| 189 | ff_dlog(s, "probe: chunk has %d recs\n", num_recs); | ||
| 190 | ✗ | hdrs = parse_chunk_headers(chunk, num_recs); | |
| 191 | ✗ | if (!hdrs) | |
| 192 | ✗ | return AVERROR(ENOMEM); | |
| 193 | |||
| 194 | /* scan headers. | ||
| 195 | * 1. check video packets. Presence of 0x6e0 means S1. | ||
| 196 | * No 6e0 but have be0 means S2. | ||
| 197 | * 2. probe for audio 0x9c0 vs 0x3c0 (AC3 vs Mpeg) | ||
| 198 | * If AC-3, then we have DTivo. | ||
| 199 | * If MPEG, search for PTS offset. This will determine SA vs. DTivo. | ||
| 200 | */ | ||
| 201 | ✗ | num_6e0 = num_be0 = num_9c0 = num_3c0 = 0; | |
| 202 | ✗ | for (i = 0; i < num_recs; i++) { | |
| 203 | ✗ | switch (hdrs[i].subrec_type << 8 | hdrs[i].rec_type) { | |
| 204 | ✗ | case 0x6e0: | |
| 205 | ✗ | num_6e0++; | |
| 206 | ✗ | break; | |
| 207 | ✗ | case 0xbe0: | |
| 208 | ✗ | num_be0++; | |
| 209 | ✗ | break; | |
| 210 | ✗ | case 0x3c0: | |
| 211 | ✗ | num_3c0++; | |
| 212 | ✗ | break; | |
| 213 | ✗ | case 0x9c0: | |
| 214 | ✗ | num_9c0++; | |
| 215 | ✗ | break; | |
| 216 | } | ||
| 217 | } | ||
| 218 | ff_dlog(s, "probe: chunk has %d 0x6e0 recs, %d 0xbe0 recs.\n", | ||
| 219 | num_6e0, num_be0); | ||
| 220 | |||
| 221 | /* set up our variables */ | ||
| 222 | ✗ | if (num_6e0 > 0) { | |
| 223 | ff_dlog(s, "detected Series 1 Tivo\n"); | ||
| 224 | ✗ | ty->tivo_series = TIVO_SERIES1; | |
| 225 | ✗ | ty->pes_length = SERIES1_PES_LENGTH; | |
| 226 | ✗ | } else if (num_be0 > 0) { | |
| 227 | ff_dlog(s, "detected Series 2 Tivo\n"); | ||
| 228 | ✗ | ty->tivo_series = TIVO_SERIES2; | |
| 229 | ✗ | ty->pes_length = SERIES2_PES_LENGTH; | |
| 230 | } | ||
| 231 | ✗ | if (num_9c0 > 0) { | |
| 232 | ff_dlog(s, "detected AC-3 Audio (DTivo)\n"); | ||
| 233 | ✗ | ty->audio_type = TIVO_AUDIO_AC3; | |
| 234 | ✗ | ty->tivo_type = TIVO_TYPE_DTIVO; | |
| 235 | ✗ | ty->pts_offset = AC3_PTS_OFFSET; | |
| 236 | ✗ | ty->pes_length = AC3_PES_LENGTH; | |
| 237 | ✗ | } else if (num_3c0 > 0) { | |
| 238 | ✗ | ty->audio_type = TIVO_AUDIO_MPEG; | |
| 239 | ff_dlog(s, "detected MPEG Audio\n"); | ||
| 240 | } | ||
| 241 | |||
| 242 | /* if tivo_type still unknown, we can check PTS location | ||
| 243 | * in MPEG packets to determine tivo_type */ | ||
| 244 | ✗ | if (ty->tivo_type == TIVO_TYPE_UNKNOWN) { | |
| 245 | ✗ | uint32_t data_offset = 16 * num_recs; | |
| 246 | |||
| 247 | ✗ | for (i = 0; i < num_recs; i++) { | |
| 248 | ✗ | if (data_offset + hdrs[i].rec_size > CHUNK_SIZE) | |
| 249 | ✗ | break; | |
| 250 | |||
| 251 | ✗ | if ((hdrs[i].subrec_type << 8 | hdrs[i].rec_type) == 0x3c0 && hdrs[i].rec_size > 15) { | |
| 252 | /* first make sure we're aligned */ | ||
| 253 | ✗ | int pes_offset = find_es_header(ty_MPEGAudioPacket, | |
| 254 | &chunk[data_offset], 5); | ||
| 255 | ✗ | if (pes_offset >= 0) { | |
| 256 | /* pes found. on SA, PES has hdr data at offset 6, not PTS. */ | ||
| 257 | ✗ | if ((chunk[data_offset + 6 + pes_offset] & 0x80) == 0x80) { | |
| 258 | /* S1SA or S2(any) Mpeg Audio (PES hdr, not a PTS start) */ | ||
| 259 | ✗ | if (ty->tivo_series == TIVO_SERIES1) | |
| 260 | ff_dlog(s, "detected Stand-Alone Tivo\n"); | ||
| 261 | ✗ | ty->tivo_type = TIVO_TYPE_SA; | |
| 262 | ✗ | ty->pts_offset = SA_PTS_OFFSET; | |
| 263 | } else { | ||
| 264 | ✗ | if (ty->tivo_series == TIVO_SERIES1) | |
| 265 | ff_dlog(s, "detected DirecTV Tivo\n"); | ||
| 266 | ✗ | ty->tivo_type = TIVO_TYPE_DTIVO; | |
| 267 | ✗ | ty->pts_offset = DTIVO_PTS_OFFSET; | |
| 268 | } | ||
| 269 | ✗ | break; | |
| 270 | } | ||
| 271 | } | ||
| 272 | ✗ | data_offset += hdrs[i].rec_size; | |
| 273 | } | ||
| 274 | } | ||
| 275 | ✗ | av_free(hdrs); | |
| 276 | |||
| 277 | ✗ | return 0; | |
| 278 | } | ||
| 279 | |||
| 280 | ✗ | static int ty_read_header(AVFormatContext *s) | |
| 281 | { | ||
| 282 | ✗ | TYDemuxContext *ty = s->priv_data; | |
| 283 | ✗ | AVIOContext *pb = s->pb; | |
| 284 | AVStream *st, *ast; | ||
| 285 | ✗ | int i, ret = 0; | |
| 286 | |||
| 287 | ✗ | ty->first_audio_pts = AV_NOPTS_VALUE; | |
| 288 | ✗ | ty->last_audio_pts = AV_NOPTS_VALUE; | |
| 289 | ✗ | ty->last_video_pts = AV_NOPTS_VALUE; | |
| 290 | |||
| 291 | ✗ | for (i = 0; i < CHUNK_PEEK_COUNT; i++) { | |
| 292 | ✗ | avio_read(pb, ty->chunk, CHUNK_SIZE); | |
| 293 | |||
| 294 | ✗ | ret = analyze_chunk(s, ty->chunk); | |
| 295 | ✗ | if (ret < 0) | |
| 296 | ✗ | return ret; | |
| 297 | ✗ | if (ty->tivo_series != TIVO_SERIES_UNKNOWN && | |
| 298 | ✗ | ty->audio_type != TIVO_AUDIO_UNKNOWN && | |
| 299 | ✗ | ty->tivo_type != TIVO_TYPE_UNKNOWN) | |
| 300 | ✗ | break; | |
| 301 | } | ||
| 302 | |||
| 303 | ✗ | if (ty->tivo_series == TIVO_SERIES_UNKNOWN || | |
| 304 | ✗ | ty->audio_type == TIVO_AUDIO_UNKNOWN || | |
| 305 | ✗ | ty->tivo_type == TIVO_TYPE_UNKNOWN) | |
| 306 | ✗ | return AVERROR_INVALIDDATA; | |
| 307 | |||
| 308 | ✗ | st = avformat_new_stream(s, NULL); | |
| 309 | ✗ | if (!st) | |
| 310 | ✗ | return AVERROR(ENOMEM); | |
| 311 | ✗ | st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; | |
| 312 | ✗ | st->codecpar->codec_id = AV_CODEC_ID_MPEG2VIDEO; | |
| 313 | ✗ | ffstream(st)->need_parsing = AVSTREAM_PARSE_FULL_RAW; | |
| 314 | ✗ | avpriv_set_pts_info(st, 64, 1, 90000); | |
| 315 | |||
| 316 | ✗ | ast = avformat_new_stream(s, NULL); | |
| 317 | ✗ | if (!ast) | |
| 318 | ✗ | return AVERROR(ENOMEM); | |
| 319 | ✗ | ast->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; | |
| 320 | |||
| 321 | ✗ | if (ty->audio_type == TIVO_AUDIO_MPEG) { | |
| 322 | ✗ | ast->codecpar->codec_id = AV_CODEC_ID_MP2; | |
| 323 | ✗ | ffstream(ast)->need_parsing = AVSTREAM_PARSE_FULL_RAW; | |
| 324 | } else { | ||
| 325 | ✗ | ast->codecpar->codec_id = AV_CODEC_ID_AC3; | |
| 326 | } | ||
| 327 | ✗ | avpriv_set_pts_info(ast, 64, 1, 90000); | |
| 328 | |||
| 329 | ✗ | ty->first_chunk = 1; | |
| 330 | |||
| 331 | ✗ | avio_seek(pb, 0, SEEK_SET); | |
| 332 | |||
| 333 | ✗ | return 0; | |
| 334 | } | ||
| 335 | |||
| 336 | ✗ | static int get_chunk(AVFormatContext *s) | |
| 337 | { | ||
| 338 | ✗ | TYDemuxContext *ty = s->priv_data; | |
| 339 | ✗ | AVIOContext *pb = s->pb; | |
| 340 | int read_size, num_recs; | ||
| 341 | |||
| 342 | ff_dlog(s, "parsing ty chunk #%d\n", ty->cur_chunk); | ||
| 343 | |||
| 344 | /* if we have left-over filler space from the last chunk, get that */ | ||
| 345 | ✗ | if (avio_feof(pb)) | |
| 346 | ✗ | return AVERROR_EOF; | |
| 347 | |||
| 348 | /* read the TY packet header */ | ||
| 349 | ✗ | read_size = avio_read(pb, ty->chunk, CHUNK_SIZE); | |
| 350 | ✗ | ty->cur_chunk++; | |
| 351 | |||
| 352 | ✗ | if ((read_size < 4) || (AV_RB32(ty->chunk) == 0)) { | |
| 353 | ✗ | return AVERROR_EOF; | |
| 354 | } | ||
| 355 | |||
| 356 | /* check if it's a PART Header */ | ||
| 357 | ✗ | if (AV_RB32(ty->chunk) == TIVO_PES_FILEID) { | |
| 358 | /* skip master chunk and read new chunk */ | ||
| 359 | ✗ | return get_chunk(s); | |
| 360 | } | ||
| 361 | |||
| 362 | /* number of records in chunk (8- or 16-bit number) */ | ||
| 363 | ✗ | if (ty->chunk[3] & 0x80) { | |
| 364 | /* 16 bit rec cnt */ | ||
| 365 | ✗ | ty->num_recs = num_recs = (ty->chunk[1] << 8) + ty->chunk[0]; | |
| 366 | } else { | ||
| 367 | /* 8 bit reclen - TiVo 1.3 format */ | ||
| 368 | ✗ | ty->num_recs = num_recs = ty->chunk[0]; | |
| 369 | } | ||
| 370 | ✗ | ty->cur_rec = 0; | |
| 371 | ✗ | ty->first_chunk = 0; | |
| 372 | |||
| 373 | ff_dlog(s, "chunk has %d records\n", num_recs); | ||
| 374 | ✗ | ty->cur_chunk_pos = 4; | |
| 375 | |||
| 376 | ✗ | av_freep(&ty->rec_hdrs); | |
| 377 | |||
| 378 | ✗ | if (num_recs * 16 >= CHUNK_SIZE - 4) | |
| 379 | ✗ | return AVERROR_INVALIDDATA; | |
| 380 | |||
| 381 | ✗ | ty->rec_hdrs = parse_chunk_headers(ty->chunk + 4, num_recs); | |
| 382 | ✗ | if (!ty->rec_hdrs) | |
| 383 | ✗ | return AVERROR(ENOMEM); | |
| 384 | ✗ | ty->cur_chunk_pos += 16 * num_recs; | |
| 385 | |||
| 386 | ✗ | return 0; | |
| 387 | } | ||
| 388 | |||
| 389 | ✗ | static int demux_video(AVFormatContext *s, TyRecHdr *rec_hdr, AVPacket *pkt) | |
| 390 | { | ||
| 391 | ✗ | TYDemuxContext *ty = s->priv_data; | |
| 392 | ✗ | const int subrec_type = rec_hdr->subrec_type; | |
| 393 | ✗ | const int64_t rec_size = rec_hdr->rec_size; | |
| 394 | int es_offset1, ret; | ||
| 395 | ✗ | int got_packet = 0; | |
| 396 | |||
| 397 | ✗ | if (subrec_type != 0x02 && subrec_type != 0x0c && | |
| 398 | ✗ | subrec_type != 0x08 && rec_size > 4) { | |
| 399 | /* get the PTS from this packet if it has one. | ||
| 400 | * on S1, only 0x06 has PES. On S2, however, most all do. | ||
| 401 | * Do NOT Pass the PES Header to the MPEG2 codec */ | ||
| 402 | ✗ | es_offset1 = find_es_header(ty_VideoPacket, ty->chunk + ty->cur_chunk_pos, 5); | |
| 403 | ✗ | if (es_offset1 != -1) { | |
| 404 | ✗ | ty->last_video_pts = ff_parse_pes_pts( | |
| 405 | ✗ | ty->chunk + ty->cur_chunk_pos + es_offset1 + VIDEO_PTS_OFFSET); | |
| 406 | ✗ | if (subrec_type != 0x06) { | |
| 407 | /* if we found a PES, and it's not type 6, then we're S2 */ | ||
| 408 | /* The packet will have video data (& other headers) so we | ||
| 409 | * chop out the PES header and send the rest */ | ||
| 410 | ✗ | if (rec_size >= VIDEO_PES_LENGTH + es_offset1) { | |
| 411 | ✗ | int size = rec_hdr->rec_size - VIDEO_PES_LENGTH - es_offset1; | |
| 412 | |||
| 413 | ✗ | ty->cur_chunk_pos += VIDEO_PES_LENGTH + es_offset1; | |
| 414 | ✗ | if ((ret = av_new_packet(pkt, size)) < 0) | |
| 415 | ✗ | return ret; | |
| 416 | ✗ | memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, size); | |
| 417 | ✗ | ty->cur_chunk_pos += size; | |
| 418 | ✗ | pkt->stream_index = 0; | |
| 419 | ✗ | got_packet = 1; | |
| 420 | } else { | ||
| 421 | ff_dlog(s, "video rec type 0x%02x has short PES" | ||
| 422 | " (%"PRId64" bytes)\n", subrec_type, rec_size); | ||
| 423 | /* nuke this block; it's too short, but has PES marker */ | ||
| 424 | ✗ | ty->cur_chunk_pos += rec_size; | |
| 425 | ✗ | return 0; | |
| 426 | } | ||
| 427 | } | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | ✗ | if (subrec_type == 0x06) { | |
| 432 | /* type 6 (S1 DTivo) has no data, so we're done */ | ||
| 433 | ✗ | ty->cur_chunk_pos += rec_size; | |
| 434 | ✗ | return 0; | |
| 435 | } | ||
| 436 | |||
| 437 | ✗ | if (!got_packet) { | |
| 438 | ✗ | if ((ret = av_new_packet(pkt, rec_size)) < 0) | |
| 439 | ✗ | return ret; | |
| 440 | ✗ | memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size); | |
| 441 | ✗ | ty->cur_chunk_pos += rec_size; | |
| 442 | ✗ | pkt->stream_index = 0; | |
| 443 | ✗ | got_packet = 1; | |
| 444 | } | ||
| 445 | |||
| 446 | /* if it's not a continue blk, then set PTS */ | ||
| 447 | ✗ | if (subrec_type != 0x02) { | |
| 448 | ✗ | if (subrec_type == 0x0c && pkt->size >= 6) | |
| 449 | ✗ | pkt->data[5] |= 0x08; | |
| 450 | ✗ | if (subrec_type == 0x07) { | |
| 451 | ✗ | ty->last_ty_pts = rec_hdr->ty_pts; | |
| 452 | } else { | ||
| 453 | /* yes I know this is a cheap hack. It's the timestamp | ||
| 454 | used for display and skipping fwd/back, so it | ||
| 455 | doesn't have to be accurate to the millisecond. | ||
| 456 | I adjust it here by roughly one 1/30 sec. Yes it | ||
| 457 | will be slightly off for UK streams, but it's OK. | ||
| 458 | */ | ||
| 459 | ✗ | ty->last_ty_pts += 35000000; | |
| 460 | //ty->last_ty_pts += 33366667; | ||
| 461 | } | ||
| 462 | /* set PTS for this block before we send */ | ||
| 463 | ✗ | if (ty->last_video_pts > AV_NOPTS_VALUE) { | |
| 464 | ✗ | pkt->pts = ty->last_video_pts; | |
| 465 | /* PTS gets used ONCE. | ||
| 466 | * Any subsequent frames we get BEFORE next PES | ||
| 467 | * header will have their PTS computed in the codec */ | ||
| 468 | ✗ | ty->last_video_pts = AV_NOPTS_VALUE; | |
| 469 | } | ||
| 470 | } | ||
| 471 | |||
| 472 | ✗ | return got_packet; | |
| 473 | } | ||
| 474 | |||
| 475 | ✗ | static int check_sync_pes(AVFormatContext *s, AVPacket *pkt, | |
| 476 | int32_t offset, int32_t rec_len) | ||
| 477 | { | ||
| 478 | ✗ | TYDemuxContext *ty = s->priv_data; | |
| 479 | |||
| 480 | ✗ | if (offset < 0 || offset + ty->pes_length > rec_len) { | |
| 481 | /* entire PES header not present */ | ||
| 482 | ff_dlog(s, "PES header at %"PRId32" not complete in record. storing.\n", offset); | ||
| 483 | /* save the partial pes header */ | ||
| 484 | ✗ | if (offset < 0) { | |
| 485 | /* no header found, fake some 00's (this works, believe me) */ | ||
| 486 | ✗ | memset(ty->pes_buffer, 0, 4); | |
| 487 | ✗ | ty->pes_buf_cnt = 4; | |
| 488 | if (rec_len > 4) | ||
| 489 | ff_dlog(s, "PES header not found in record of %"PRId32" bytes!\n", rec_len); | ||
| 490 | ✗ | return -1; | |
| 491 | } | ||
| 492 | /* copy the partial pes header we found */ | ||
| 493 | ✗ | memcpy(ty->pes_buffer, pkt->data + offset, rec_len - offset); | |
| 494 | ✗ | ty->pes_buf_cnt = rec_len - offset; | |
| 495 | |||
| 496 | ✗ | if (offset > 0) { | |
| 497 | /* PES Header was found, but not complete, so trim the end of this record */ | ||
| 498 | ✗ | pkt->size -= rec_len - offset; | |
| 499 | ✗ | return 1; | |
| 500 | } | ||
| 501 | ✗ | return -1; /* partial PES, no audio data */ | |
| 502 | } | ||
| 503 | /* full PES header present, extract PTS */ | ||
| 504 | ✗ | ty->last_audio_pts = ff_parse_pes_pts(&pkt->data[ offset + ty->pts_offset]); | |
| 505 | ✗ | if (ty->first_audio_pts == AV_NOPTS_VALUE) | |
| 506 | ✗ | ty->first_audio_pts = ty->last_audio_pts; | |
| 507 | ✗ | pkt->pts = ty->last_audio_pts; | |
| 508 | ✗ | memmove(pkt->data + offset, pkt->data + offset + ty->pes_length, rec_len - ty->pes_length); | |
| 509 | ✗ | pkt->size -= ty->pes_length; | |
| 510 | ✗ | return 0; | |
| 511 | } | ||
| 512 | |||
| 513 | ✗ | static int demux_audio(AVFormatContext *s, TyRecHdr *rec_hdr, AVPacket *pkt) | |
| 514 | { | ||
| 515 | ✗ | TYDemuxContext *ty = s->priv_data; | |
| 516 | ✗ | const int subrec_type = rec_hdr->subrec_type; | |
| 517 | ✗ | const int64_t rec_size = rec_hdr->rec_size; | |
| 518 | int es_offset1, ret; | ||
| 519 | |||
| 520 | ✗ | if (subrec_type == 2) { | |
| 521 | ✗ | int need = 0; | |
| 522 | /* SA or DTiVo Audio Data, no PES (continued block) | ||
| 523 | * ================================================ | ||
| 524 | */ | ||
| 525 | |||
| 526 | /* continue PES if previous was incomplete */ | ||
| 527 | ✗ | if (ty->pes_buf_cnt > 0) { | |
| 528 | ✗ | need = ty->pes_length - ty->pes_buf_cnt; | |
| 529 | |||
| 530 | ff_dlog(s, "continuing PES header\n"); | ||
| 531 | /* do we have enough data to complete? */ | ||
| 532 | ✗ | if (need >= rec_size) { | |
| 533 | /* don't have complete PES hdr; save what we have and return */ | ||
| 534 | ✗ | memcpy(ty->pes_buffer + ty->pes_buf_cnt, ty->chunk + ty->cur_chunk_pos, rec_size); | |
| 535 | ✗ | ty->cur_chunk_pos += rec_size; | |
| 536 | ✗ | ty->pes_buf_cnt += rec_size; | |
| 537 | ✗ | return 0; | |
| 538 | } | ||
| 539 | |||
| 540 | /* we have enough; reconstruct this frame with the new hdr */ | ||
| 541 | ✗ | memcpy(ty->pes_buffer + ty->pes_buf_cnt, ty->chunk + ty->cur_chunk_pos, need); | |
| 542 | ✗ | ty->cur_chunk_pos += need; | |
| 543 | /* get the PTS out of this PES header (MPEG or AC3) */ | ||
| 544 | ✗ | if (ty->audio_type == TIVO_AUDIO_MPEG) { | |
| 545 | ✗ | es_offset1 = find_es_header(ty_MPEGAudioPacket, | |
| 546 | ✗ | ty->pes_buffer, 5); | |
| 547 | } else { | ||
| 548 | ✗ | es_offset1 = find_es_header(ty_AC3AudioPacket, | |
| 549 | ✗ | ty->pes_buffer, 5); | |
| 550 | } | ||
| 551 | ✗ | if (es_offset1 < 0) { | |
| 552 | ff_dlog(s, "Can't find audio PES header in packet.\n"); | ||
| 553 | } else { | ||
| 554 | ✗ | ty->last_audio_pts = ff_parse_pes_pts( | |
| 555 | ✗ | &ty->pes_buffer[es_offset1 + ty->pts_offset]); | |
| 556 | ✗ | pkt->pts = ty->last_audio_pts; | |
| 557 | } | ||
| 558 | ✗ | ty->pes_buf_cnt = 0; | |
| 559 | |||
| 560 | } | ||
| 561 | ✗ | if ((ret = av_new_packet(pkt, rec_size - need)) < 0) | |
| 562 | ✗ | return ret; | |
| 563 | ✗ | memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size - need); | |
| 564 | ✗ | ty->cur_chunk_pos += rec_size - need; | |
| 565 | ✗ | pkt->stream_index = 1; | |
| 566 | |||
| 567 | /* S2 DTivo has AC3 packets with 2 padding bytes at end. This is | ||
| 568 | * not allowed in the AC3 spec and will cause problems. So here | ||
| 569 | * we try to trim things. */ | ||
| 570 | /* Also, S1 DTivo has alternating short / long AC3 packets. That | ||
| 571 | * is, one packet is short (incomplete) and the next packet has | ||
| 572 | * the first one's missing data, plus all of its own. Strange. */ | ||
| 573 | ✗ | if (ty->audio_type == TIVO_AUDIO_AC3 && | |
| 574 | ✗ | ty->tivo_series == TIVO_SERIES2) { | |
| 575 | ✗ | if (ty->ac3_pkt_size + pkt->size > AC3_PKT_LENGTH) { | |
| 576 | ✗ | pkt->size -= 2; | |
| 577 | ✗ | ty->ac3_pkt_size = 0; | |
| 578 | } else { | ||
| 579 | ✗ | ty->ac3_pkt_size += pkt->size; | |
| 580 | } | ||
| 581 | } | ||
| 582 | ✗ | } else if (subrec_type == 0x03) { | |
| 583 | ✗ | if ((ret = av_new_packet(pkt, rec_size)) < 0) | |
| 584 | ✗ | return ret; | |
| 585 | ✗ | memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size); | |
| 586 | ✗ | ty->cur_chunk_pos += rec_size; | |
| 587 | ✗ | pkt->stream_index = 1; | |
| 588 | /* MPEG Audio with PES Header, either SA or DTiVo */ | ||
| 589 | /* ================================================ */ | ||
| 590 | ✗ | es_offset1 = find_es_header(ty_MPEGAudioPacket, pkt->data, 5); | |
| 591 | |||
| 592 | /* SA PES Header, No Audio Data */ | ||
| 593 | /* ================================================ */ | ||
| 594 | ✗ | if ((es_offset1 == 0) && (rec_size == 16)) { | |
| 595 | ✗ | ty->last_audio_pts = ff_parse_pes_pts(&pkt->data[SA_PTS_OFFSET]); | |
| 596 | ✗ | if (ty->first_audio_pts == AV_NOPTS_VALUE) | |
| 597 | ✗ | ty->first_audio_pts = ty->last_audio_pts; | |
| 598 | ✗ | av_packet_unref(pkt); | |
| 599 | ✗ | return 0; | |
| 600 | } | ||
| 601 | /* DTiVo Audio with PES Header */ | ||
| 602 | /* ================================================ */ | ||
| 603 | |||
| 604 | /* Check for complete PES */ | ||
| 605 | ✗ | if (check_sync_pes(s, pkt, es_offset1, rec_size) == -1) { | |
| 606 | /* partial PES header found, nothing else. | ||
| 607 | * we're done. */ | ||
| 608 | ✗ | av_packet_unref(pkt); | |
| 609 | ✗ | return 0; | |
| 610 | } | ||
| 611 | ✗ | } else if (subrec_type == 0x04) { | |
| 612 | /* SA Audio with no PES Header */ | ||
| 613 | /* ================================================ */ | ||
| 614 | ✗ | if ((ret = av_new_packet(pkt, rec_size)) < 0) | |
| 615 | ✗ | return ret; | |
| 616 | ✗ | memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size); | |
| 617 | ✗ | ty->cur_chunk_pos += rec_size; | |
| 618 | ✗ | pkt->stream_index = 1; | |
| 619 | ✗ | pkt->pts = ty->last_audio_pts; | |
| 620 | ✗ | } else if (subrec_type == 0x09) { | |
| 621 | ✗ | if ((ret = av_new_packet(pkt, rec_size)) < 0) | |
| 622 | ✗ | return ret; | |
| 623 | ✗ | memcpy(pkt->data, ty->chunk + ty->cur_chunk_pos, rec_size); | |
| 624 | ✗ | ty->cur_chunk_pos += rec_size ; | |
| 625 | ✗ | pkt->stream_index = 1; | |
| 626 | |||
| 627 | /* DTiVo AC3 Audio Data with PES Header */ | ||
| 628 | /* ================================================ */ | ||
| 629 | ✗ | es_offset1 = find_es_header(ty_AC3AudioPacket, pkt->data, 5); | |
| 630 | |||
| 631 | /* Check for complete PES */ | ||
| 632 | ✗ | if (check_sync_pes(s, pkt, es_offset1, rec_size) == -1) { | |
| 633 | /* partial PES header found, nothing else. we're done. */ | ||
| 634 | ✗ | av_packet_unref(pkt); | |
| 635 | ✗ | return 0; | |
| 636 | } | ||
| 637 | /* S2 DTivo has invalid long AC3 packets */ | ||
| 638 | ✗ | if (ty->tivo_series == TIVO_SERIES2) { | |
| 639 | ✗ | if (pkt->size > AC3_PKT_LENGTH) { | |
| 640 | ✗ | pkt->size -= 2; | |
| 641 | ✗ | ty->ac3_pkt_size = 0; | |
| 642 | } else { | ||
| 643 | ✗ | ty->ac3_pkt_size = pkt->size; | |
| 644 | } | ||
| 645 | } | ||
| 646 | } else { | ||
| 647 | /* Unsupported/Unknown */ | ||
| 648 | ✗ | ty->cur_chunk_pos += rec_size; | |
| 649 | ✗ | return 0; | |
| 650 | } | ||
| 651 | |||
| 652 | ✗ | return 1; | |
| 653 | } | ||
| 654 | |||
| 655 | ✗ | static int ty_read_packet(AVFormatContext *s, AVPacket *pkt) | |
| 656 | { | ||
| 657 | ✗ | TYDemuxContext *ty = s->priv_data; | |
| 658 | ✗ | AVIOContext *pb = s->pb; | |
| 659 | TyRecHdr *rec; | ||
| 660 | ✗ | int64_t rec_size = 0; | |
| 661 | ✗ | int ret = 0; | |
| 662 | |||
| 663 | ✗ | if (avio_feof(pb)) | |
| 664 | ✗ | return AVERROR_EOF; | |
| 665 | |||
| 666 | ✗ | while (ret <= 0) { | |
| 667 | ✗ | if (!ty->rec_hdrs || ty->first_chunk || ty->cur_rec >= ty->num_recs) { | |
| 668 | ✗ | if (get_chunk(s) < 0 || ty->num_recs <= 0) | |
| 669 | ✗ | return AVERROR_EOF; | |
| 670 | } | ||
| 671 | |||
| 672 | ✗ | rec = &ty->rec_hdrs[ty->cur_rec]; | |
| 673 | ✗ | rec_size = rec->rec_size; | |
| 674 | ✗ | ty->cur_rec++; | |
| 675 | |||
| 676 | ✗ | if (rec_size <= 0) | |
| 677 | ✗ | continue; | |
| 678 | |||
| 679 | ✗ | if (ty->cur_chunk_pos + rec->rec_size > CHUNK_SIZE) | |
| 680 | ✗ | return AVERROR_INVALIDDATA; | |
| 681 | |||
| 682 | ✗ | if (avio_feof(pb)) | |
| 683 | ✗ | return AVERROR_EOF; | |
| 684 | |||
| 685 | ✗ | switch (rec->rec_type) { | |
| 686 | ✗ | case VIDEO_ID: | |
| 687 | ✗ | ret = demux_video(s, rec, pkt); | |
| 688 | ✗ | break; | |
| 689 | ✗ | case AUDIO_ID: | |
| 690 | ✗ | ret = demux_audio(s, rec, pkt); | |
| 691 | ✗ | break; | |
| 692 | ✗ | default: | |
| 693 | ff_dlog(s, "Invalid record type 0x%02x\n", rec->rec_type); | ||
| 694 | case 0x01: | ||
| 695 | case 0x02: | ||
| 696 | case 0x03: /* TiVo data services */ | ||
| 697 | case 0x05: /* unknown, but seen regularly */ | ||
| 698 | ✗ | ty->cur_chunk_pos += rec->rec_size; | |
| 699 | ✗ | break; | |
| 700 | } | ||
| 701 | } | ||
| 702 | |||
| 703 | ✗ | return 0; | |
| 704 | } | ||
| 705 | |||
| 706 | ✗ | static int ty_read_close(AVFormatContext *s) | |
| 707 | { | ||
| 708 | ✗ | TYDemuxContext *ty = s->priv_data; | |
| 709 | |||
| 710 | ✗ | av_freep(&ty->rec_hdrs); | |
| 711 | |||
| 712 | ✗ | return 0; | |
| 713 | } | ||
| 714 | |||
| 715 | const FFInputFormat ff_ty_demuxer = { | ||
| 716 | .p.name = "ty", | ||
| 717 | .p.long_name = NULL_IF_CONFIG_SMALL("TiVo TY Stream"), | ||
| 718 | .p.extensions = "ty,ty+", | ||
| 719 | .p.flags = AVFMT_TS_DISCONT, | ||
| 720 | .priv_data_size = sizeof(TYDemuxContext), | ||
| 721 | .read_probe = ty_probe, | ||
| 722 | .read_header = ty_read_header, | ||
| 723 | .read_packet = ty_read_packet, | ||
| 724 | .read_close = ty_read_close, | ||
| 725 | }; | ||
| 726 |