From 4ab56667594842283dc5ae07f0daba2a2cb4d3af Mon Sep 17 00:00:00 2001 From: Rodger Combs Date: Mon, 20 Jul 2015 15:00:35 -0500 Subject: [PATCH] lavf/mov: add support for sidx fragment indexes Fixes trac #3842 --- libavformat/isom.h | 2 + libavformat/mov.c | 245 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 208 insertions(+), 39 deletions(-) diff --git a/libavformat/isom.h b/libavformat/isom.h index aee9d6e8c7..6e921c04a0 100644 --- a/libavformat/isom.h +++ b/libavformat/isom.h @@ -103,6 +103,7 @@ typedef struct MOVSbgp { typedef struct MOVFragmentIndexItem { int64_t moof_offset; int64_t time; + int headers_read; } MOVFragmentIndexItem; typedef struct MOVFragmentIndex { @@ -197,6 +198,7 @@ typedef struct MOVContext { int has_looked_for_mfra; MOVFragmentIndex** fragment_index_data; unsigned fragment_index_count; + int fragment_index_complete; int atom_depth; unsigned int aax_mode; ///< 'aax' file has been detected uint8_t file_key[20]; diff --git a/libavformat/mov.c b/libavformat/mov.c index 78596f564f..4c073a3cda 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -3349,7 +3349,7 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) MOVFragment *frag = &c->fragment; MOVTrackExt *trex = NULL; MOVFragmentIndex* index = NULL; - int flags, track_id, i; + int flags, track_id, i, found = 0; avio_r8(pb); /* version */ flags = avio_rb24(pb); @@ -3367,15 +3367,6 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_log(c->fc, AV_LOG_ERROR, "could not find corresponding trex\n"); return AVERROR_INVALIDDATA; } - for (i = 0; i < c->fragment_index_count; i++) { - MOVFragmentIndex* candidate = c->fragment_index_data[i]; - if (candidate->track_id == frag->track_id) { - av_log(c->fc, AV_LOG_DEBUG, - "found fragment index for track %u\n", frag->track_id); - index = candidate; - break; - } - } frag->base_data_offset = flags & MOV_TFHD_BASE_DATA_OFFSET ? avio_rb64(pb) : flags & MOV_TFHD_DEFAULT_BASE_IS_MOOF ? @@ -3389,23 +3380,32 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) frag->flags = flags & MOV_TFHD_DEFAULT_FLAGS ? avio_rb32(pb) : trex->flags; frag->time = AV_NOPTS_VALUE; - if (index) { - int i, found = 0; - for (i = index->current_item; i < index->item_count; i++) { - if (frag->implicit_offset == index->items[i].moof_offset) { - av_log(c->fc, AV_LOG_DEBUG, "found fragment index entry " - "for track %u and moof_offset %"PRId64"\n", - frag->track_id, index->items[i].moof_offset); - frag->time = index->items[i].time; - index->current_item = i + 1; - found = 1; + for (i = 0; i < c->fragment_index_count; i++) { + int j; + MOVFragmentIndex* candidate = c->fragment_index_data[i]; + if (candidate->track_id == frag->track_id) { + av_log(c->fc, AV_LOG_DEBUG, + "found fragment index for track %u\n", frag->track_id); + index = candidate; + for (j = index->current_item; j < index->item_count; j++) { + if (frag->implicit_offset == index->items[j].moof_offset) { + av_log(c->fc, AV_LOG_DEBUG, "found fragment index entry " + "for track %u and moof_offset %"PRId64"\n", + frag->track_id, index->items[j].moof_offset); + frag->time = index->items[j].time; + index->current_item = j + 1; + found = 1; + break; + } } + if (found) + break; } - if (!found) { - av_log(c->fc, AV_LOG_WARNING, "track %u has a fragment index " - "but it doesn't have an (in-order) entry for moof_offset " - "%"PRId64"\n", frag->track_id, frag->implicit_offset); - } + } + if (index && !found) { + av_log(c->fc, AV_LOG_DEBUG, "track %u has a fragment index but " + "it doesn't have an (in-order) entry for moof_offset " + "%"PRId64"\n", frag->track_id, frag->implicit_offset); } av_log(c->fc, AV_LOG_TRACE, "frag flags 0x%x\n", frag->flags); return 0; @@ -3596,7 +3596,106 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) return AVERROR_EOF; frag->implicit_offset = offset; - st->duration = sc->track_end = dts + sc->time_offset; + + sc->track_end = dts + sc->time_offset; + if (st->duration < sc->track_end) + st->duration = sc->track_end; + + return 0; +} + +static int mov_read_sidx(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + int64_t offset = avio_tell(pb) + atom.size, pts; + uint8_t version; + unsigned i, track_id; + AVStream *st = NULL; + MOVStreamContext *sc; + MOVFragmentIndex *index = NULL; + MOVFragmentIndex **tmp; + AVRational timescale; + + version = avio_r8(pb); + if (version > 1) { + avpriv_request_sample(c->fc, "sidx version %u", version); + return AVERROR_PATCHWELCOME; + } + + avio_rb24(pb); // flags + + track_id = avio_rb32(pb); // Reference ID + for (i = 0; i < c->fc->nb_streams; i++) { + if (c->fc->streams[i]->id == track_id) { + st = c->fc->streams[i]; + break; + } + } + if (!st) { + av_log(c->fc, AV_LOG_ERROR, "could not find corresponding track id %d\n", track_id); + return AVERROR_INVALIDDATA; + } + + sc = st->priv_data; + + timescale = av_make_q(1, avio_rb32(pb)); + + if (version == 0) { + pts = avio_rb32(pb); + offset += avio_rb32(pb); + } else { + pts = avio_rb64(pb); + offset += avio_rb64(pb); + } + + avio_rb16(pb); // reserved + + index = av_mallocz(sizeof(MOVFragmentIndex)); + if (!index) + return AVERROR(ENOMEM); + + index->track_id = track_id; + + index->item_count = avio_rb16(pb); + index->items = av_mallocz_array(index->item_count, sizeof(MOVFragmentIndexItem)); + + if (!index->items) { + av_freep(&index); + return AVERROR(ENOMEM); + } + + for (i = 0; i < index->item_count; i++) { + uint32_t size = avio_rb32(pb); + uint32_t duration = avio_rb32(pb); + if (size & 0x80000000) { + avpriv_request_sample(c->fc, "sidx reference_type 1"); + av_freep(&index->items); + av_freep(&index); + return AVERROR_PATCHWELCOME; + } + avio_rb32(pb); // sap_flags + index->items[i].moof_offset = offset; + index->items[i].time = av_rescale_q(pts, st->time_base, timescale); + offset += size; + pts += duration; + } + + st->duration = sc->track_end = pts; + + tmp = av_realloc_array(c->fragment_index_data, + c->fragment_index_count + 1, + sizeof(MOVFragmentIndex*)); + if (!tmp) { + av_freep(&index->items); + av_freep(&index); + return AVERROR(ENOMEM); + } + + c->fragment_index_data = tmp; + c->fragment_index_data[c->fragment_index_count++] = index; + + if (offset == avio_size(pb)) + c->fragment_index_complete = 1; + return 0; } @@ -3854,6 +3953,7 @@ static const MOVParseTableEntry mov_default_parse_table[] = { { MKTAG('a','l','a','c'), mov_read_alac }, /* alac specific atom */ { MKTAG('a','v','c','C'), mov_read_glbl }, { MKTAG('p','a','s','p'), mov_read_pasp }, +{ MKTAG('s','i','d','x'), mov_read_sidx }, { MKTAG('s','t','b','l'), mov_read_default }, { MKTAG('s','t','c','o'), mov_read_stco }, { MKTAG('s','t','p','s'), mov_read_stps }, @@ -3978,9 +4078,9 @@ static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom) return err; } if (c->found_moov && c->found_mdat && - ((!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX) || + ((!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX || c->fragment_index_complete) || start_pos + a.size == avio_size(pb))) { - if (!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX) + if (!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX || c->fragment_index_complete) c->next_root_atom = start_pos + a.size; c->atom_depth --; return 0; @@ -4585,6 +4685,52 @@ static int should_retry(AVIOContext *pb, int error_code) { return 1; } +static int mov_switch_root(AVFormatContext *s, int64_t target) +{ + MOVContext *mov = s->priv_data; + int i, j; + int already_read = 0; + + if (avio_seek(s->pb, target, SEEK_SET) != target) { + av_log(mov->fc, AV_LOG_ERROR, "root atom offset 0x%"PRIx64": partial file\n", target); + return AVERROR_INVALIDDATA; + } + + mov->next_root_atom = 0; + + for (i = 0; i < mov->fragment_index_count; i++) { + MOVFragmentIndex *index = mov->fragment_index_data[i]; + int found = 0; + for (j = 0; j < index->item_count; j++) { + MOVFragmentIndexItem *item = &index->items[j]; + if (found) { + mov->next_root_atom = item->moof_offset; + break; // Advance to next index in outer loop + } else if (item->moof_offset == target) { + index->current_item = FFMIN(j, index->current_item); + if (item->headers_read) + already_read = 1; + item->headers_read = 1; + found = 1; + } + } + if (!found) + index->current_item = 0; + } + + if (already_read) + return 0; + + mov->found_mdat = 0; + + if (mov_read_default(mov, s->pb, (MOVAtom){ AV_RL32("root"), INT64_MAX }) < 0 || + avio_feof(s->pb)) + return AVERROR_EOF; + av_log(s, AV_LOG_TRACE, "read fragments, offset 0x%"PRIx64"\n", avio_tell(s->pb)); + + return 1; +} + static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) { MOVContext *mov = s->priv_data; @@ -4595,19 +4741,11 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) mov->fc = s; retry: sample = mov_find_next_sample(s, &st); - if (!sample) { - mov->found_mdat = 0; + if (!sample || (mov->next_root_atom && sample->pos > mov->next_root_atom)) { if (!mov->next_root_atom) return AVERROR_EOF; - if (avio_seek(s->pb, mov->next_root_atom, SEEK_SET) != mov->next_root_atom) { - av_log(mov->fc, AV_LOG_ERROR, "next root atom offset 0x%"PRIx64": partial file\n", mov->next_root_atom); - return AVERROR_INVALIDDATA; - } - mov->next_root_atom = 0; - if (mov_read_default(mov, s->pb, (MOVAtom){ AV_RL32("root"), INT64_MAX }) < 0 || - avio_feof(s->pb)) - return AVERROR_EOF; - av_log(s, AV_LOG_TRACE, "read fragments, offset 0x%"PRIx64"\n", avio_tell(s->pb)); + if ((ret = mov_switch_root(s, mov->next_root_atom)) < 0) + return ret; goto retry; } sc = st->priv_data; @@ -4685,12 +4823,41 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) return 0; } +static int mov_seek_fragment(AVFormatContext *s, AVStream *st, int64_t timestamp) +{ + MOVContext *mov = s->priv_data; + int i, j; + + if (!mov->fragment_index_complete) + return 0; + + for (i = 0; i < mov->fragment_index_count; i++) { + if (mov->fragment_index_data[i]->track_id == st->id) { + MOVFragmentIndex *index = index = mov->fragment_index_data[i]; + for (j = index->item_count - 1; j >= 0; j--) { + if (index->items[j].time <= timestamp) { + if (index->items[j].headers_read) + return 0; + + return mov_switch_root(s, index->items[j].moof_offset); + } + } + } + } + + return 0; +} + static int mov_seek_stream(AVFormatContext *s, AVStream *st, int64_t timestamp, int flags) { MOVStreamContext *sc = st->priv_data; int sample, time_sample; int i; + int ret = mov_seek_fragment(s, st, timestamp); + if (ret < 0) + return ret; + sample = av_index_search_timestamp(st, timestamp, flags); av_log(s, AV_LOG_TRACE, "stream %d, timestamp %"PRId64", sample %d\n", st->index, timestamp, sample); if (sample < 0 && st->nb_index_entries && timestamp < st->index_entries[0].timestamp)