[FFmpeg-devel] [PATCH] mov.c: read fragment start dts from fragmented mp4
Mika Raento
mikie at iki.fi
Thu Oct 9 16:43:08 CEST 2014
fate passes with these changes, no new tests added. - mika
On 9 October 2014 08:53, Mika Raento <mikie at iki.fi> wrote:
> If present, an MFRA box and its TFRAs are read for fragment start times.
>
> Without this change, timestamps for discontinuous fragmented mp4 are
> wrong, and cause audio/video desync and are not usable for generating
> HLS.
> ---
> libavformat/isom.h | 14 ++++++
> libavformat/mov.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 145 insertions(+)
>
> diff --git a/libavformat/isom.h b/libavformat/isom.h
> index 979e967..2b49b55 100644
> --- a/libavformat/isom.h
> +++ b/libavformat/isom.h
> @@ -78,6 +78,7 @@ typedef struct MOVFragment {
> unsigned duration;
> unsigned size;
> unsigned flags;
> + int64_t time;
> } MOVFragment;
>
> typedef struct MOVTrackExt {
> @@ -93,6 +94,17 @@ typedef struct MOVSbgp {
> unsigned int index;
> } MOVSbgp;
>
> +typedef struct MOVFragmentIndexItem {
> + int64_t time;
> +} MOVFragmentIndexItem;
> +
> +typedef struct MOVFragmentIndex {
> + unsigned track_id;
> + unsigned current_item_index;
> + unsigned item_count;
> + MOVFragmentIndexItem *items;
> +} MOVFragmentIndex;
> +
> typedef struct MOVStreamContext {
> AVIOContext *pb;
> int pb_is_copied;
> @@ -171,6 +183,8 @@ typedef struct MOVContext {
> int *bitrates; ///< bitrates read before streams creation
> int bitrates_count;
> int moov_retry;
> + MOVFragmentIndex** fragment_index_data;
> + unsigned fragment_index_count;
> } MOVContext;
>
> int ff_mp4_read_descr_len(AVIOContext *pb);
> diff --git a/libavformat/mov.c b/libavformat/mov.c
> index fdd0671..2b14c48 100644
> --- a/libavformat/mov.c
> +++ b/libavformat/mov.c
> @@ -2738,6 +2738,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;
>
> avio_r8(pb); /* version */
> @@ -2756,6 +2757,15 @@ 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 ?
> @@ -2768,6 +2778,20 @@ 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;
> + if (index) {
> + // TODO: should check moof index from mfhd, rather than just
> + // relying on this code seeing the moofs in the same order as they
> + // are in the mfra, and only once each.
> + if (index->current_item_index == index->item_count) {
> + av_log(c->fc, AV_LOG_WARNING, "track %u has a fragment index "
> + "but it doesn't have entries for all moofs, at moof "
> + "%u\n", frag->track_id, index->current_item_index);
> + } else if (index->current_item_index < index->item_count) {
> + frag->time = index->items[index->current_item_index].time;
> + }
> + index->current_item_index++;
> + }
> av_dlog(c->fc, "frag flags 0x%x\n", frag->flags);
> return 0;
> }
> @@ -2860,6 +2884,10 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
> 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;
> + if (frag->time != AV_NOPTS_VALUE) {
> + av_log(c->fc, AV_LOG_DEBUG, "found frag time %"PRId64"\n", frag->time);
> + dts = frag->time;
> + }
> offset = frag->base_data_offset + data_offset;
> distance = 0;
> av_dlog(c->fc, "first sample flags 0x%x\n", first_sample_flags);
> @@ -3513,6 +3541,13 @@ 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]);
> + }
> + av_freep(&mov->fragment_index_data);
> +
> return 0;
> }
>
> @@ -3550,6 +3585,99 @@ static void export_orphan_timecode(AVFormatContext *s)
> }
> }
>
> +static int read_tfra(AVFormatContext *s)
> +{
> + MOVContext *mov = s->priv_data;
> + AVIOContext *f = s->pb;
> + MOVFragmentIndex* index = NULL;
> + int version, fieldlength, i, j, err;
> + int64_t pos = avio_tell(f);
> + uint32_t size = avio_rb32(f);
> + if (avio_rb32(f) != MKBETAG('t', 'f', 'r', 'a')) {
> + return -1;
> + }
> + av_log(s, AV_LOG_VERBOSE, "found tfra\n");
> + index = av_mallocz(sizeof(MOVFragmentIndex));
> + if (!index)
> + return AVERROR(ENOMEM);
> + mov->fragment_index_count++;
> + if ((err = av_reallocp(&mov->fragment_index_data,
> + mov->fragment_index_count *
> + sizeof(MOVFragmentIndex*))) < 0) {
> + av_freep(&index);
> + return err;
> + }
> + mov->fragment_index_data[mov->fragment_index_count - 1] =
> + index;
> +
> + version = avio_r8(f);
> + avio_rb24(f);
> + index->track_id = avio_rb32(f);
> + fieldlength = avio_rb32(f);
> + index->item_count = avio_rb32(f);
> + index->items = av_mallocz(
> + index->item_count * sizeof(MOVFragmentIndexItem));
> + if (!index->items)
> + return AVERROR(ENOMEM);
> + for (i = 0; i < index->item_count; i++) {
> + int64_t time;
> + if (version == 1) {
> + time = avio_rb64(f);
> + avio_rb64(f); /* offset */
> + } else {
> + time = avio_rb32(f);
> + avio_rb32(f); /* offset */
> + }
> + index->items[i].time = time;
> + for (j = 0; j < ((fieldlength >> 4) & 3) + 1; j++)
> + avio_r8(f);
> + for (j = 0; j < ((fieldlength >> 2) & 3) + 1; j++)
> + avio_r8(f);
> + for (j = 0; j < ((fieldlength >> 0) & 3) + 1; j++)
> + avio_r8(f);
> + }
> +
> + avio_seek(f, pos + size, SEEK_SET);
> + return 0;
> +}
> +
> +static int mov_read_mfra(AVFormatContext *s)
> +{
> + AVIOContext *f = s->pb;
> + int64_t stream_size = avio_size(f);
> + int64_t original_pos = avio_tell(f);
> + int32_t mfra_size;
> + int ret = -1;
> + if ((ret = avio_seek(f, stream_size - 4, SEEK_SET)) < 0) goto fail;
> + mfra_size = avio_rb32(f);
> + if (mfra_size < 0 || mfra_size > stream_size) {
> + av_log(s, AV_LOG_DEBUG, "doesn't look like mfra (unreasonable size)\n");
> + ret = -1;
> + goto fail;
> + }
> + if ((ret = avio_seek(f, -mfra_size, SEEK_CUR)) < 0) goto fail;
> + if (avio_rb32(f) != mfra_size) {
> + av_log(s, AV_LOG_DEBUG, "doesn't look like mfra (size mismatch)\n");
> + ret = -1;
> + goto fail;
> + }
> + if (avio_rb32(f) != MKBETAG('m', 'f', 'r', 'a')) {
> + av_log(s, AV_LOG_DEBUG, "doesn't look like mfra (tag mismatch)\n");
> + goto fail;
> + }
> + av_log(s, AV_LOG_VERBOSE, "stream has mfra\n");
> + while (!read_tfra(s)) {
> + /* Empty */
> + }
> +fail:
> + ret = avio_seek(f, original_pos, SEEK_SET);
> + if (ret < 0)
> + av_log(s, AV_LOG_ERROR, "failed to seek back after looking for mfra");
> + else
> + ret = 0;
> + return ret;
> +}
> +
> static int mov_read_header(AVFormatContext *s)
> {
> MOVContext *mov = s->priv_data;
> @@ -3565,6 +3693,9 @@ static int mov_read_header(AVFormatContext *s)
> else
> atom.size = INT64_MAX;
>
> + if (pb->seekable) {
> + mov_read_mfra(s);
> + }
> /* check MOV header */
> do {
> if (mov->moov_retry)
> --
> 1.9.3 (Apple Git-50)
>
More information about the ffmpeg-devel
mailing list