From patchwork Wed Oct 11 01:11:08 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Stebbins X-Patchwork-Id: 5522 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.2.161.90 with SMTP id m26csp166849jah; Tue, 10 Oct 2017 18:11:15 -0700 (PDT) X-Google-Smtp-Source: AOwi7QDLAMh1Gs1lOCSjANXdwQhRVUyUAieivdCCc+tl9324/CD00FFbAekgYyruhdndBPHEZ0R4 X-Received: by 10.28.74.80 with SMTP id x77mr11240893wma.79.1507684275592; Tue, 10 Oct 2017 18:11:15 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1507684275; cv=none; d=google.com; s=arc-20160816; b=OtRglIrRim1Ec2pQ8Pk9G+jkWXnpoC5971eCP94VBnfVmPhdvU/C+ANdWOE9NtYXQH vz1VSfMHtelTu58RC53c1uCqFj6aVO7C1pxXEtb5kmO/UbouBcfH2yT3CLLWoQLetI+l v59CpQTxLgjVXVM4fZEET/T3mcStJFxa7ta0RvGl26Fh/lM77H7XUJhY89V4HvohB8jw wsWiTTIVLXRFDz2hLDHZ6ZABQ+HkD8fXEJHUbuItfHGDVktgPUYr0QvIwDL/o0Nm+vmw OcnI/uF6emoeRy1i0ybgxEbfDnnHnXUFiTFd2cj+HTW0+3JePxLdB5pcUCHMEnN6QIWJ VbaQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:message-id:date:to:from :delivered-to:arc-authentication-results; bh=oq2ONVw8fiEgEGMreEn9+UVYtj9DmA3CAcECHiq/Iko=; b=WjaUtbJSrynrfZ9TzATlegyTNbLRbWgMvLZrMQyngFvhk2Alk4I4U5Zk77W/QDscNH Gyy9NlVgTnGo+x84sr3pIfnkQUg3+ZgsLhYxadeZ4CG+jGjgemCVJDw5NleYoACqnJM8 ZKJ5Xmnvj3ovgbJhJe+QoioN3Hsjp27VryKv14s5gKoShODVNr9M6lmhg/CgOuoVBVGA dBfO5j8wOptQ1dKWzxp+48hSm3sZqtGKbaHDzWi9lNB/LMFTdLJfqhqAGJ5VS8f/Q+Rw Pu2bPt3FL0hyUJ9+Uw47x01KUl9J716t7BP5h0zOKnBU1K3x07ZaRhh5OlWpF+6wH+Ed vL/A== ARC-Authentication-Results: i=1; mx.google.com; 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 Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id u2si10293818wra.454.2017.10.10.18.11.15; Tue, 10 Oct 2017 18:11:15 -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; 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 Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id B3C4E689BCA; Wed, 11 Oct 2017 04:11:09 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail.jetheaddev.com (mail.jetheaddev.com [70.164.99.34]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id ABE31689BB7 for ; Wed, 11 Oct 2017 04:11:07 +0300 (EEST) Received: from dionysus.jetheaddev.com (10.13.12.63) by cas.jetheaddev.com (192.168.13.27) with Microsoft SMTP Server (TLS) id 14.3.351.0; Tue, 10 Oct 2017 18:11:08 -0700 Received: from dionysus.jetheaddev.com (localhost [127.0.0.1]) by dionysus.jetheaddev.com (8.15.2/8.14.7) with ESMTP id v9B1B8PA032070 for ; Tue, 10 Oct 2017 18:11:08 -0700 Received: (from jstebbins@localhost) by dionysus.jetheaddev.com (8.15.2/8.15.2/Submit) id v9B1B8nh032069 for ffmpeg-devel@ffmpeg.org; Tue, 10 Oct 2017 18:11:08 -0700 From: John Stebbins To: Date: Tue, 10 Oct 2017 18:11:08 -0700 Message-ID: <20171011011108.32025-1-jstebbins@jetheaddev.com> X-Mailer: git-send-email 2.13.6 MIME-Version: 1.0 X-Originating-IP: [10.13.12.63] Subject: [FFmpeg-devel] [PATCH] mov: fix decode of fragments that overlap in time 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 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" When keyframe intervals of dash segments are not perfectly aligned, fragments in the stream can overlap in time. The previous sorting by timestamp causes packets to be read out of decode order and results in decode errors. Insert new "trun" index entries into index_entries in the order that the trun are referenced by the sidx. --- libavformat/isom.h | 26 +- libavformat/mov.c | 684 ++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 485 insertions(+), 225 deletions(-) diff --git a/libavformat/isom.h b/libavformat/isom.h index b9380e9dcc..395d20d25c 100644 --- a/libavformat/isom.h +++ b/libavformat/isom.h @@ -93,7 +93,6 @@ typedef struct MOVFragment { unsigned duration; unsigned size; unsigned flags; - int64_t time; } MOVFragment; typedef struct MOVTrackExt { @@ -109,17 +108,28 @@ typedef struct MOVSbgp { unsigned int index; } MOVSbgp; +typedef struct MOVFragmentStreamInfo { + int id; + int64_t sidx_pts; + int64_t first_tfra_pts; + int64_t tfdt_dts; + int index_entry; +} MOVFragmentStreamInfo; + typedef struct MOVFragmentIndexItem { int64_t moof_offset; - int64_t time; int headers_read; + int current; + int nb_stream_info; + MOVFragmentStreamInfo * stream_info; } MOVFragmentIndexItem; typedef struct MOVFragmentIndex { - unsigned track_id; - unsigned item_count; - unsigned current_item; - MOVFragmentIndexItem *items; + int allocated_size; + int complete; + int current; + int nb_items; + MOVFragmentIndexItem * item; } MOVFragmentIndex; typedef struct MOVIndexRange { @@ -250,9 +260,7 @@ typedef struct MOVContext { int moov_retry; int use_mfra_for; int has_looked_for_mfra; - MOVFragmentIndex** fragment_index_data; - unsigned fragment_index_count; - int fragment_index_complete; + MOVFragmentIndex frag_index; 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 899690d920..4fe091f290 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -1168,6 +1168,211 @@ static int mov_read_moov(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; /* now go for mdat */ } +static MOVFragmentStreamInfo * get_frag_stream_info( + MOVFragmentIndex *frag_index, + int index, + int id) +{ + int i; + MOVFragmentIndexItem * item; + + if (index < 0 || index >= frag_index->nb_items) + return NULL; + item = &frag_index->item[index]; + for (i = 0; i < item->nb_stream_info; i++) + if (item->stream_info[i].id == id) + return &item->stream_info[i]; + + // This shouldn't happen + return NULL; +} + +static void set_frag_stream(MOVFragmentIndex *frag_index, int id) +{ + int i; + MOVFragmentIndexItem * item; + + if (frag_index->current < 0 || + frag_index->current >= frag_index->nb_items) + return; + + item = &frag_index->item[frag_index->current]; + for (i = 0; i < item->nb_stream_info; i++) + if (item->stream_info[i].id == id) { + item->current = i; + return; + } + + // id not found. This shouldn't happen. + item->current = -1; +} + +static MOVFragmentStreamInfo * get_current_frag_stream_info( + MOVFragmentIndex *frag_index) +{ + MOVFragmentIndexItem * item = &frag_index->item[frag_index->current]; + if (item->current >= 0 && item->current < item->nb_stream_info) + return &item->stream_info[item->current]; + + // This shouldn't happen + return NULL; +} + +static int search_frag_moof_offset(MOVFragmentIndex *frag_index, int64_t offset) +{ + int a, b, m; + int64_t moof_offset; + + // Optimize for appending new entries + if (!frag_index->nb_items || + frag_index->item[frag_index->nb_items - 1].moof_offset < offset) + return frag_index->nb_items; + + a = -1; + b = frag_index->nb_items; + + while (b - a > 1) { + m = (a + b) >> 1; + moof_offset = frag_index->item[m].moof_offset; + if (moof_offset >= offset) + b = m; + if (moof_offset <= offset) + a = m; + } + return b; +} + +static int64_t get_stream_info_time(MOVFragmentStreamInfo * frag_stream_info) +{ + + if (frag_stream_info) { + if (frag_stream_info->sidx_pts != AV_NOPTS_VALUE) + return frag_stream_info->sidx_pts; + if (frag_stream_info->first_tfra_pts != AV_NOPTS_VALUE) + return frag_stream_info->first_tfra_pts; + if (frag_stream_info->tfdt_dts != AV_NOPTS_VALUE) + return frag_stream_info->tfdt_dts; + } + return AV_NOPTS_VALUE; +} + +static int64_t get_frag_time(MOVFragmentIndex *frag_index, + int index, int track_id) +{ + MOVFragmentStreamInfo * frag_stream_info; + int64_t timestamp; + int i; + + if (track_id >= 0) { + frag_stream_info = get_frag_stream_info(frag_index, index, track_id); + return frag_stream_info->sidx_pts; + } + + for (i = 0; i < frag_index->item[index].nb_stream_info; i++) { + frag_stream_info = &frag_index->item[index].stream_info[i]; + timestamp = get_stream_info_time(frag_stream_info); + if (timestamp != AV_NOPTS_VALUE) + return timestamp; + } + return AV_NOPTS_VALUE; +} + +static int search_frag_timestamp(MOVFragmentIndex *frag_index, + AVStream *st, int64_t timestamp) +{ + int a, b, m; + int64_t frag_time; + int id = -1; + + if (st) { + // If the stream is referenced by any sidx, limit the search + // to fragments that referenced this stream in the sidx + MOVStreamContext *sc = st->priv_data; + if (sc->has_sidx) + id = st->id; + } + + a = -1; + b = frag_index->nb_items; + + while (b - a > 1) { + m = (a + b) >> 1; + frag_time = get_frag_time(frag_index, m, id); + if (frag_time != AV_NOPTS_VALUE) { + if (frag_time >= timestamp) + b = m; + if (frag_time <= timestamp) + a = m; + } + } + return a; +} + +static int update_frag_index(MOVContext *c, int64_t offset) +{ + int index, i; + MOVFragmentIndexItem * item; + MOVFragmentStreamInfo * frag_stream_info; + + // If moof_offset already exists in frag_index, return index to it + index = search_frag_moof_offset(&c->frag_index, offset); + if (index < c->frag_index.nb_items && + c->frag_index.item[index].moof_offset == offset) + return index; + + // offset is not yet in frag index. + // Insert new item at index (sorted by moof offset) + item = av_fast_realloc(c->frag_index.item, + &c->frag_index.allocated_size, + (c->frag_index.nb_items + 1) * + sizeof(*c->frag_index.item)); + if(!item) + return -1; + c->frag_index.item = item; + + frag_stream_info = av_realloc_array(NULL, c->fc->nb_streams, + sizeof(*item->stream_info)); + if (!frag_stream_info) + return -1; + + for (i = 0; i < c->fc->nb_streams; i++) { + frag_stream_info[i].id = c->fc->streams[i]->id; + frag_stream_info[i].sidx_pts = AV_NOPTS_VALUE; + frag_stream_info[i].tfdt_dts = AV_NOPTS_VALUE; + frag_stream_info[i].first_tfra_pts = AV_NOPTS_VALUE; + frag_stream_info[i].index_entry = -1; + } + + if (index < c->frag_index.nb_items) + memmove(c->frag_index.item + index + 1, c->frag_index.item + index, + c->frag_index.nb_items - index); + + item = &c->frag_index.item[index]; + item->headers_read = 0; + item->current = 0; + item->nb_stream_info = c->fc->nb_streams; + item->moof_offset = offset; + item->stream_info = frag_stream_info; + c->frag_index.nb_items++; + + return index; +} + +static void fix_frag_index_entries(MOVFragmentIndex *frag_index, int index, + int id, int entries) +{ + int i; + MOVFragmentStreamInfo * frag_stream_info; + + if (index < 0) + return; + for (i = index; i < frag_index->nb_items; i++) { + frag_stream_info = get_frag_stream_info(frag_index, i, id); + if (frag_stream_info && frag_stream_info->index_entry >= 0) + frag_stream_info->index_entry += entries; + } +} + static int mov_read_moof(MOVContext *c, AVIOContext *pb, MOVAtom atom) { if (!c->has_looked_for_mfra && c->use_mfra_for > 0) { @@ -1187,6 +1392,7 @@ static int mov_read_moof(MOVContext *c, AVIOContext *pb, MOVAtom atom) } c->fragment.moof_offset = c->fragment.implicit_offset = avio_tell(pb) - 8; av_log(c->fc, AV_LOG_TRACE, "moof offset %"PRIx64"\n", c->fragment.moof_offset); + c->frag_index.current = update_frag_index(c, c->fragment.moof_offset); return mov_read_default(c, pb, atom); } @@ -4118,8 +4324,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, found = 0; + int flags, track_id, i; avio_r8(pb); /* version */ flags = avio_rb24(pb); @@ -4128,6 +4333,7 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (!track_id) return AVERROR_INVALIDDATA; frag->track_id = track_id; + set_frag_stream(&c->frag_index, track_id); for (i = 0; i < c->trex_count; i++) if (c->trex_data[i].track_id == frag->track_id) { trex = &c->trex_data[i]; @@ -4149,35 +4355,8 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom) avio_rb32(pb) : trex->size; frag->flags = flags & MOV_TFHD_DEFAULT_FLAGS ? avio_rb32(pb) : trex->flags; - frag->time = AV_NOPTS_VALUE; - 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 (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; } @@ -4232,6 +4411,8 @@ static int mov_read_tfdt(MOVContext *c, AVIOContext *pb, MOVAtom atom) AVStream *st = NULL; MOVStreamContext *sc; int version, i; + MOVFragmentStreamInfo * frag_stream_info; + int64_t base_media_decode_time; for (i = 0; i < c->fc->nb_streams; i++) { if (c->fc->streams[i]->id == frag->track_id) { @@ -4249,10 +4430,16 @@ static int mov_read_tfdt(MOVContext *c, AVIOContext *pb, MOVAtom atom) version = avio_r8(pb); avio_rb24(pb); /* flags */ if (version) { - sc->track_end = avio_rb64(pb); + base_media_decode_time = avio_rb64(pb); } else { - sc->track_end = avio_rb32(pb); + base_media_decode_time = avio_rb32(pb); } + + frag_stream_info = get_current_frag_stream_info(&c->frag_index); + if (frag_stream_info) + frag_stream_info->tfdt_dts = base_media_decode_time; + sc->track_end = base_media_decode_time; + return 0; } @@ -4263,10 +4450,15 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) MOVStreamContext *sc; MOVStts *ctts_data; uint64_t offset; - int64_t dts; + int64_t dts, pts = AV_NOPTS_VALUE; int data_offset = 0; unsigned entries, first_sample_flags = frag->flags; int flags, distance, i; + int64_t prev_dts = AV_NOPTS_VALUE; + int next_frag_index = -1, index_entry_pos; + size_t requested_size; + AVIndexEntry *new_entries; + MOVFragmentStreamInfo * frag_stream_info; for (i = 0; i < c->fc->nb_streams; i++) { if (c->fc->streams[i]->id == frag->track_id) { @@ -4281,6 +4473,23 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) sc = st->priv_data; if (sc->pseudo_stream_id+1 != frag->stsd_id && sc->pseudo_stream_id != -1) return 0; + + // Find the next frag_index index that has a valid index_entry for + // the current track_id. + // + // A valid index_entry means the trun for the fragment was read + // and it's samples are in index_entries at the given position. + // New index entries will be inserted before the index_entry found. + index_entry_pos = st->nb_index_entries; + for (i = c->frag_index.current + 1; i < c->frag_index.nb_items; i++) { + frag_stream_info = get_frag_stream_info(&c->frag_index, i, frag->track_id); + if (frag_stream_info && frag_stream_info->index_entry >= 0) { + next_frag_index = i; + index_entry_pos = frag_stream_info->index_entry; + break; + } + } + avio_r8(pb); /* version */ flags = avio_rb24(pb); entries = avio_rb32(pb); @@ -4290,18 +4499,99 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) return AVERROR_INVALIDDATA; if (flags & MOV_TRUN_DATA_OFFSET) data_offset = avio_rb32(pb); if (flags & MOV_TRUN_FIRST_SAMPLE_FLAGS) first_sample_flags = avio_rb32(pb); - dts = sc->track_end - sc->time_offset; - offset = frag->base_data_offset + data_offset; + + frag_stream_info = get_current_frag_stream_info(&c->frag_index); + if (frag_stream_info) + { + if (frag_stream_info->first_tfra_pts != AV_NOPTS_VALUE && + c->use_mfra_for == FF_MOV_FLAG_MFRA_PTS) { + pts = frag_stream_info->first_tfra_pts; + av_log(c->fc, AV_LOG_DEBUG, "found mfra time %"PRId64 + ", using it for pts\n", pts); + } else if (frag_stream_info->sidx_pts != AV_NOPTS_VALUE) { + // FIXME: sidx earliest_presentation_time is *PTS*, s.b. + // pts = frag_stream_info->sidx_pts; + dts = frag_stream_info->sidx_pts - sc->time_offset; + av_log(c->fc, AV_LOG_DEBUG, "found sidx time %"PRId64 + ", using it for pts\n", pts); + } else if (frag_stream_info->tfdt_dts != AV_NOPTS_VALUE) { + dts = frag_stream_info->tfdt_dts - sc->time_offset; + av_log(c->fc, AV_LOG_DEBUG, "found tfdt time %"PRId64 + ", using it for dts\n", dts); + } else { + dts = sc->track_end - sc->time_offset; + av_log(c->fc, AV_LOG_DEBUG, "found track end time %"PRId64 + ", using it for dts\n", dts); + } + } else { + dts = sc->track_end - sc->time_offset; + av_log(c->fc, AV_LOG_DEBUG, "found track end time %"PRId64 + ", using it for dts\n", dts); + } + offset = frag->base_data_offset + data_offset; distance = 0; av_log(c->fc, AV_LOG_TRACE, "first sample flags 0x%x\n", first_sample_flags); + + // realloc space for new index entries + if((unsigned)st->nb_index_entries + entries >= UINT_MAX / sizeof(AVIndexEntry)) { + entries = UINT_MAX / sizeof(AVIndexEntry) - st->nb_index_entries; + av_log(c->fc, AV_LOG_ERROR, "Failed to add index entry\n"); + } + if (entries <= 0) + return -1; + + requested_size = (st->nb_index_entries + entries) * sizeof(AVIndexEntry); + new_entries = av_fast_realloc(st->index_entries, + &st->index_entries_allocated_size, + requested_size); + if(!new_entries) + return AVERROR(ENOMEM); + st->index_entries= new_entries; + + requested_size = (st->nb_index_entries + entries) * sizeof(*sc->ctts_data); + ctts_data = av_fast_realloc(sc->ctts_data, &sc->ctts_allocated_size, + requested_size); + if (!ctts_data) + return AVERROR(ENOMEM); + sc->ctts_data = ctts_data; + + // In case there were samples without ctts entries, ensure they get + // zero valued entries. This ensures clips which mix boxes with and + // without ctts entries don't pickup uninitialized data. + memset(sc->ctts_data + sc->ctts_count, 0, + (st->nb_index_entries - sc->ctts_count) * sizeof(*sc->ctts_data)); + + if (index_entry_pos < st->nb_index_entries) { + // Make hole in index_entries and ctts_data for new samples + memmove(st->index_entries + index_entry_pos + entries, + st->index_entries + index_entry_pos, + sizeof(*st->index_entries) * + (st->nb_index_entries - index_entry_pos)); + memmove(sc->ctts_data + index_entry_pos + entries, + sc->ctts_data + index_entry_pos, + sizeof(*sc->ctts_data) * (sc->ctts_count - index_entry_pos)); + if (index_entry_pos < sc->current_sample) { + sc->current_sample += entries; + } + } + + st->nb_index_entries += entries; + sc->ctts_count = st->nb_index_entries; + + // Record the index_entry position in frag_index of this fragment + if (frag_stream_info) + frag_stream_info->index_entry = index_entry_pos; + + if (index_entry_pos > 0) + prev_dts = st->index_entries[index_entry_pos-1].timestamp; + for (i = 0; i < entries && !pb->eof_reached; i++) { unsigned sample_size = frag->size; int sample_flags = i ? frag->flags : first_sample_flags; unsigned sample_duration = frag->duration; unsigned ctts_duration = 0; int keyframe = 0; - int ctts_index = 0; - int old_nb_index_entries = st->nb_index_entries; + int index_entry_flags = 0; if (flags & MOV_TRUN_SAMPLE_DURATION) sample_duration = avio_rb32(pb); if (flags & MOV_TRUN_SAMPLE_SIZE) sample_size = avio_rb32(pb); @@ -4309,27 +4599,22 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) if (flags & MOV_TRUN_SAMPLE_CTS) ctts_duration = avio_rb32(pb); mov_update_dts_shift(sc, ctts_duration); - if (frag->time != AV_NOPTS_VALUE) { - if (c->use_mfra_for == FF_MOV_FLAG_MFRA_PTS) { - int64_t pts = frag->time; - av_log(c->fc, AV_LOG_DEBUG, "found frag time %"PRId64 - " sc->dts_shift %d ctts.duration %d" - " sc->time_offset %"PRId64" flags & MOV_TRUN_SAMPLE_CTS %d\n", pts, - sc->dts_shift, ctts_duration, - sc->time_offset, flags & MOV_TRUN_SAMPLE_CTS); - dts = pts - sc->dts_shift; - if (flags & MOV_TRUN_SAMPLE_CTS) { - dts -= ctts_duration; - } else { - dts -= sc->time_offset; - } - av_log(c->fc, AV_LOG_DEBUG, "calculated into dts %"PRId64"\n", dts); + if (pts != AV_NOPTS_VALUE) { + dts = pts - sc->dts_shift; + if (flags & MOV_TRUN_SAMPLE_CTS) { + dts -= ctts_duration; } else { - dts = frag->time - sc->time_offset; - av_log(c->fc, AV_LOG_DEBUG, "found frag time %"PRId64 - ", using it for dts\n", dts); + dts -= sc->time_offset; } - frag->time = AV_NOPTS_VALUE; + av_log(c->fc, AV_LOG_DEBUG, + "pts %"PRId64" calculated dts %"PRId64 + " sc->dts_shift %d ctts.duration %d" + " sc->time_offset %"PRId64 + " flags & MOV_TRUN_SAMPLE_CTS %d\n", + pts, dts, + sc->dts_shift, ctts_duration, + sc->time_offset, flags & MOV_TRUN_SAMPLE_CTS); + pts = AV_NOPTS_VALUE; } if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) @@ -4338,47 +4623,28 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) keyframe = !(sample_flags & (MOV_FRAG_SAMPLE_FLAG_IS_NON_SYNC | MOV_FRAG_SAMPLE_FLAG_DEPENDS_YES)); - if (keyframe) + if (keyframe) { distance = 0; - ctts_index = av_add_index_entry(st, offset, dts, sample_size, distance, - keyframe ? AVINDEX_KEYFRAME : 0); - if (ctts_index >= 0 && old_nb_index_entries < st->nb_index_entries) { - unsigned int size_needed = st->nb_index_entries * sizeof(*sc->ctts_data); - unsigned int request_size = size_needed > sc->ctts_allocated_size ? - FFMAX(size_needed, 2 * sc->ctts_allocated_size) : size_needed; - unsigned int old_ctts_size = sc->ctts_allocated_size; - ctts_data = av_fast_realloc(sc->ctts_data, &sc->ctts_allocated_size, request_size); - if (!ctts_data) { - av_freep(&sc->ctts_data); - return AVERROR(ENOMEM); - } - sc->ctts_data = ctts_data; - - // In case there were samples without ctts entries, ensure they get - // zero valued entries. This ensures clips which mix boxes with and - // without ctts entries don't pickup uninitialized data. - memset((uint8_t*)(sc->ctts_data) + old_ctts_size, 0, sc->ctts_allocated_size - old_ctts_size); - - if (ctts_index != old_nb_index_entries) { - memmove(sc->ctts_data + ctts_index + 1, sc->ctts_data + ctts_index, - sizeof(*sc->ctts_data) * (sc->ctts_count - ctts_index)); - if (ctts_index <= sc->current_sample) { - // if we inserted a new item before the current sample, move the - // counter ahead so it is still pointing to the same sample. - sc->current_sample++; - } - } - - sc->ctts_data[ctts_index].count = 1; - sc->ctts_data[ctts_index].duration = ctts_duration; - sc->ctts_count++; - } else { - av_log(c->fc, AV_LOG_ERROR, "Failed to add index entry\n"); + index_entry_flags |= AVINDEX_KEYFRAME; } + // Fragments can overlap in time. Discard overlapping frames after + // decoding. + if (prev_dts >= dts) + index_entry_flags |= AVINDEX_DISCARD_FRAME; + + st->index_entries[index_entry_pos].pos = offset; + st->index_entries[index_entry_pos].timestamp = dts; + st->index_entries[index_entry_pos].size= sample_size; + st->index_entries[index_entry_pos].min_distance= distance; + st->index_entries[index_entry_pos].flags = index_entry_flags; + + sc->ctts_data[index_entry_pos].count = 1; + sc->ctts_data[index_entry_pos].duration = ctts_duration; + index_entry_pos++; av_log(c->fc, AV_LOG_TRACE, "AVIndex stream %d, sample %d, offset %"PRIx64", dts %"PRId64", " - "size %u, distance %d, keyframe %d\n", st->index, ctts_index, - offset, dts, sample_size, distance, keyframe); + "size %u, distance %d, keyframe %d\n", st->index, + index_entry_pos, offset, dts, sample_size, distance, keyframe); distance++; dts += sample_duration; offset += sample_size; @@ -4386,6 +4652,44 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) sc->duration_for_fps += sample_duration; sc->nb_frames_for_fps ++; } + if (i < entries) { + // EOF found before reading all entries. Fix the hole this would + // leave in index_entries and ctts_data + int gap = entries - i; + memmove(st->index_entries + index_entry_pos, + st->index_entries + index_entry_pos + gap, + sizeof(*st->index_entries) * + (st->nb_index_entries - (index_entry_pos + gap))); + memmove(sc->ctts_data + index_entry_pos, + sc->ctts_data + index_entry_pos + gap, + sizeof(*sc->ctts_data) * + (sc->ctts_count - (index_entry_pos + gap))); + + st->nb_index_entries -= gap; + sc->ctts_count -= gap; + if (index_entry_pos < sc->current_sample) { + sc->current_sample -= gap; + } + entries = i; + } + + // The end of this new fragment may overlap in time with the start + // of the next fragment in index_entries. Mark the samples in the next + // fragment that overlap with AVINDEX_DISCARD_FRAME + prev_dts = AV_NOPTS_VALUE; + if (index_entry_pos > 0) + prev_dts = st->index_entries[index_entry_pos-1].timestamp; + for (i = index_entry_pos; i < st->nb_index_entries; i++) { + if (prev_dts < st->index_entries[i].timestamp) + break; + st->index_entries[i].flags |= AVINDEX_DISCARD_FRAME; + } + + // If a hole was created to insert the new index_entries into, + // the index_entry recorded for all subsequent moof must + // be incremented by the number of entries inserted. + fix_frag_index_entries(&c->frag_index, next_frag_index, + frag->track_id, entries); if (pb->eof_reached) return AVERROR_EOF; @@ -4401,14 +4705,12 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom) static int mov_read_sidx(MOVContext *c, AVIOContext *pb, MOVAtom atom) { - int64_t offset = avio_tell(pb) + atom.size, pts; + int64_t offset = avio_tell(pb) + atom.size, pts, timestamp; uint8_t version; - unsigned i, track_id; + unsigned i, j, track_id, item_count; AVStream *st = NULL; AVStream *ref_st = NULL; MOVStreamContext *sc, *ref_sc = NULL; - MOVFragmentIndex *index = NULL; - MOVFragmentIndex **tmp; AVRational timescale; version = avio_r8(pb); @@ -4450,57 +4752,46 @@ static int mov_read_sidx(MOVContext *c, AVIOContext *pb, MOVAtom atom) 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); - } + item_count = avio_rb16(pb); - for (i = 0; i < index->item_count; i++) { + for (i = 0; i < item_count; i++) { + int index; + MOVFragmentStreamInfo * frag_stream_info; 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); + timestamp = av_rescale_q(pts, st->time_base, timescale); + + index = update_frag_index(c, offset); + frag_stream_info = get_frag_stream_info(&c->frag_index, index, track_id); + if (frag_stream_info) + frag_stream_info->sidx_pts = timestamp; + 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; sc->has_sidx = 1; if (offset == avio_size(pb)) { - for (i = 0; i < c->fc->nb_streams; i++) { - if (c->fc->streams[i]->id == c->fragment_index_data[0]->track_id) { - ref_st = c->fc->streams[i]; - ref_sc = ref_st->priv_data; - break; + // Find first entry in fragment index that came from an sidx. + // This will pretty much always be the first entry. + for (i = 0; i < c->frag_index.nb_items; i++) { + MOVFragmentIndexItem * item = &c->frag_index.item[i]; + for (j = 0; ref_st == NULL && j < item->nb_stream_info; j++) { + MOVFragmentStreamInfo * si; + si = &item->stream_info[j]; + if (si->sidx_pts != AV_NOPTS_VALUE) { + ref_st = c->fc->streams[i]; + ref_sc = ref_st->priv_data; + break; + } } } for (i = 0; i < c->fc->nb_streams; i++) { @@ -4511,7 +4802,7 @@ static int mov_read_sidx(MOVContext *c, AVIOContext *pb, MOVAtom atom) } } - c->fragment_index_complete = 1; + c->frag_index.complete = 1; } return 0; @@ -5661,9 +5952,9 @@ static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom) return err; } if (c->found_moov && c->found_mdat && - ((!(pb->seekable & AVIO_SEEKABLE_NORMAL) || c->fc->flags & AVFMT_FLAG_IGNIDX || c->fragment_index_complete) || + ((!(pb->seekable & AVIO_SEEKABLE_NORMAL) || c->fc->flags & AVFMT_FLAG_IGNIDX || c->frag_index.complete) || start_pos + a.size == avio_size(pb))) { - if (!(pb->seekable & AVIO_SEEKABLE_NORMAL) || c->fc->flags & AVFMT_FLAG_IGNIDX || c->fragment_index_complete) + if (!(pb->seekable & AVIO_SEEKABLE_NORMAL) || c->fc->flags & AVFMT_FLAG_IGNIDX || c->frag_index.complete) c->next_root_atom = start_pos + a.size; c->atom_depth --; return 0; @@ -6008,12 +6299,10 @@ static int mov_read_close(AVFormatContext *s) av_freep(&mov->trex_data); av_freep(&mov->bitrates); - for (i = 0; i < mov->fragment_index_count; i++) { - MOVFragmentIndex* index = mov->fragment_index_data[i]; - av_freep(&index->items); - av_freep(&mov->fragment_index_data[i]); + for (i = 0; i < mov->frag_index.nb_items; i++) { + av_freep(&mov->frag_index.item[i].stream_info); } - av_freep(&mov->fragment_index_data); + av_freep(&mov->frag_index.item); av_freep(&mov->aes_decrypt); av_freep(&mov->chapter_tracks); @@ -6057,48 +6346,27 @@ static void export_orphan_timecode(AVFormatContext *s) static int read_tfra(MOVContext *mov, AVIOContext *f) { - MOVFragmentIndex* index = NULL; int version, fieldlength, i, j; int64_t pos = avio_tell(f); uint32_t size = avio_rb32(f); - void *tmp; + unsigned track_id, item_count; if (avio_rb32(f) != MKBETAG('t', 'f', 'r', 'a')) { return 1; } av_log(mov->fc, AV_LOG_VERBOSE, "found tfra\n"); - index = av_mallocz(sizeof(MOVFragmentIndex)); - if (!index) { - return AVERROR(ENOMEM); - } - - tmp = av_realloc_array(mov->fragment_index_data, - mov->fragment_index_count + 1, - sizeof(MOVFragmentIndex*)); - if (!tmp) { - av_freep(&index); - return AVERROR(ENOMEM); - } - mov->fragment_index_data = tmp; - mov->fragment_index_data[mov->fragment_index_count++] = index; version = avio_r8(f); avio_rb24(f); - index->track_id = avio_rb32(f); + track_id = avio_rb32(f); fieldlength = avio_rb32(f); - index->item_count = avio_rb32(f); - index->items = av_mallocz_array( - index->item_count, sizeof(MOVFragmentIndexItem)); - if (!index->items) { - index->item_count = 0; - return AVERROR(ENOMEM); - } - for (i = 0; i < index->item_count; i++) { + item_count = avio_rb32(f); + for (i = 0; i < item_count; i++) { int64_t time, offset; + int index; + MOVFragmentStreamInfo * frag_stream_info; if (avio_feof(f)) { - index->item_count = 0; - av_freep(&index->items); return AVERROR_INVALIDDATA; } @@ -6109,8 +6377,16 @@ static int read_tfra(MOVContext *mov, AVIOContext *f) time = avio_rb32(f); offset = avio_rb32(f); } - index->items[i].time = time; - index->items[i].moof_offset = offset; + + // The first sample of each stream in a fragment is always a random + // access sample. So it's entry in the tfra can be used as the + // initial PTS of the fragment. + index = update_frag_index(mov, offset); + frag_stream_info = get_frag_stream_info(&mov->frag_index, index, track_id); + if (frag_stream_info && + frag_stream_info->first_tfra_pts == AV_NOPTS_VALUE) + frag_stream_info->first_tfra_pts = time; + for (j = 0; j < ((fieldlength >> 4) & 3) + 1; j++) avio_r8(f); for (j = 0; j < ((fieldlength >> 2) & 3) + 1; j++) @@ -6371,12 +6647,9 @@ static int mov_read_header(AVFormatContext *s) } ff_configure_buffers_for_index(s, AV_TIME_BASE); - for (i = 0; i < mov->fragment_index_count; i++) { - MOVFragmentIndex *idx = mov->fragment_index_data[i]; - for (j = 0; j < idx->item_count; j++) - if (idx->items[j].moof_offset <= mov->fragment.moof_offset) - idx->items[j].headers_read = 1; - } + for (i = 0; i < mov->frag_index.nb_items; i++) + if (mov->frag_index.item[i].moof_offset <= mov->fragment.moof_offset) + mov->frag_index.item[i].headers_read = 1; return 0; } @@ -6414,42 +6687,28 @@ static int should_retry(AVIOContext *pb, int error_code) { return 1; } -static int mov_switch_root(AVFormatContext *s, int64_t target) +static int mov_switch_root(AVFormatContext *s, int64_t target, int index) { MOVContext *mov = s->priv_data; - int i, j; - int already_read = 0; + if (index >= 0 && index < mov->frag_index.nb_items) + target = mov->frag_index.item[index].moof_offset; 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 (index < 0 || index >= mov->frag_index.nb_items) + index = search_frag_moof_offset(&mov->frag_index, target); + if (index < mov->frag_index.nb_items) { + if (index + 1 < mov->frag_index.nb_items) + mov->next_root_atom = mov->frag_index.item[index + 1].moof_offset; + if (mov->frag_index.item[index].headers_read) + return 0; + mov->frag_index.item[index].headers_read = 1; } - if (already_read) - return 0; - mov->found_mdat = 0; if (mov_read_default(mov, s->pb, (MOVAtom){ AV_RL32("root"), INT64_MAX }) < 0 || @@ -6497,7 +6756,7 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) if (!sample || (mov->next_root_atom && sample->pos > mov->next_root_atom)) { if (!mov->next_root_atom) return AVERROR_EOF; - if ((ret = mov_switch_root(s, mov->next_root_atom)) < 0) + if ((ret = mov_switch_root(s, mov->next_root_atom, -1)) < 0) return ret; goto retry; } @@ -6621,25 +6880,18 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) static int mov_seek_fragment(AVFormatContext *s, AVStream *st, int64_t timestamp) { MOVContext *mov = s->priv_data; - MOVStreamContext *sc = st->priv_data; - int i, j; + int index; - if (!mov->fragment_index_complete) + if (!mov->frag_index.complete) return 0; - for (i = 0; i < mov->fragment_index_count; i++) { - if (mov->fragment_index_data[i]->track_id == st->id || !sc->has_sidx) { - MOVFragmentIndex *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); - } - } - } - } + index = search_frag_timestamp(&mov->frag_index, st, timestamp); + if (index < 0) + index = 0; + if (!mov->frag_index.item[index].headers_read) + return mov_switch_root(s, -1, index); + if (index + 1 < mov->frag_index.nb_items) + mov->next_root_atom = mov->frag_index.item[index + 1].moof_offset; return 0; }