[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