diff mbox

[FFmpeg-devel,09/21] libavformat/movenc: support for multiple and client-provided track references

Message ID 1471943019-14136-10-git-send-email-erkki.seppala.ext@nokia.com
State Superseded
Headers show

Commit Message

erkki.seppala.ext@nokia.com Aug. 23, 2016, 9:03 a.m. UTC
From: Erkki Seppälä <erkki.seppala.ext@nokia.com>

Instead of one track reference, allow multiple. In addition, allow
client to explicitly add track references with side packet
AV_PKG_DATA_TRACK_REFERENCES containing AVTrackReferences. MOVTrack's
track references can be manipulated with helper functions
ff_mov_*tref*.

Multiple track references can be useful in particular with timed meta
data tracks, indicating the track is related to multiple other tracks.

This information ends up in ISO media file box 'tref' as specified by
ISO/IEC 14496-12.

Signed-off-by: Erkki Seppälä <erkki.seppala.ext@nokia.com>
Signed-off-by: OZOPlayer <OZOPL@nokia.com>
---
 libavcodec/avcodec.h     |  17 +++-
 libavformat/movenc.c     | 245 ++++++++++++++++++++++++++++++++++++++++++-----
 libavformat/movenc.h     |  61 +++++++++++-
 libavformat/movenchint.c |  11 ++-
 4 files changed, 303 insertions(+), 31 deletions(-)

Comments

erkki.seppala.ext@nokia.com Aug. 25, 2016, 11:04 a.m. UTC | #1
Hello,

However long this patch is, it is also mostly useless: the standard does 
NOT support multiple distinct tref tags. Instead it supports multiple 
references for one or zero trefs, which is what FFmpeg already supports.

I will introduce a patch that instead just adds the ability to set the 
track reference type and add track references. The side packet API does 
not need to change as far as I can see.

On 08/23/2016 12:03 PM, erkki.seppala.ext@nokia.com wrote:
> From: Erkki Seppälä <erkki.seppala.ext@nokia.com>
>
> Instead of one track reference, allow multiple. In addition, allow
> client to explicitly add track references with side packet
> AV_PKG_DATA_TRACK_REFERENCES containing AVTrackReferences. MOVTrack's
> track references can be manipulated with helper functions
> ff_mov_*tref*.
>
> Multiple track references can be useful in particular with timed meta
> data tracks, indicating the track is related to multiple other tracks.
>
> This information ends up in ISO media file box 'tref' as specified by
> ISO/IEC 14496-12.
>
> Signed-off-by: Erkki Seppälä <erkki.seppala.ext@nokia.com>
> Signed-off-by: OZOPlayer <OZOPL@nokia.com>
erkki.seppala.ext@nokia.com Aug. 25, 2016, 11:30 a.m. UTC | #2
..though on more precise look it FFmpeg doesn't in fact implement 
multiple track references at all (MOVTrack has src_track while the patch 
introduces src_tracks). But the patch can be greatly simplified regardless.
diff mbox

Patch

diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index 756eda5..893b89b 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -1348,6 +1348,13 @@  typedef struct AVCPBProperties {
  * Types and functions for working with AVPacket.
  * @{
  */
+
+typedef struct AVTrackReferences {
+    char        tag[4];         /** 4cc used for describing this  */
+    int         nb_tracks;      /** number of tracks */
+    /** followed by: int tracks[nb_tracks]; -- tracks this track refers to */
+} AVTrackReferences;
+
 enum AVPacketSideDataType {
     AV_PKT_DATA_PALETTE,
 
@@ -1525,7 +1532,15 @@  enum AVPacketSideDataType {
      * should be associated with a video stream and containts data in the form
      * of the AVMasteringDisplayMetadata struct.
      */
-    AV_PKT_DATA_MASTERING_DISPLAY_METADATA
+    AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
+
+    /**
+     * Define track references (in particular applicaple for ISO MP4
+     * files). The data is a sequence of type AVTrackReferences
+     * (including the track list that follows it), for as long as
+     * indicated by the key's length.
+     */
+    AV_PKT_DATA_TRACK_REFERENCES
 };
 
 #define AV_PKT_DATA_QUALITY_FACTOR AV_PKT_DATA_QUALITY_STATS //DEPRECATED
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index c63fdc4..072e660 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -2631,14 +2631,30 @@  static int mov_write_edts_tag(AVIOContext *pb, MOVMuxContext *mov,
     return size;
 }
 
-static int mov_write_tref_tag(AVIOContext *pb, MOVTrack *track)
+static int mov_write_tref_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track)
 {
-    avio_wb32(pb, 20);   // size
+    int64_t pos = avio_tell(pb);
+    int64_t pos_sub;
+    int i;
+    int tref_idx;
+    avio_wb32(pb, 0);   // size
     ffio_wfourcc(pb, "tref");
-    avio_wb32(pb, 12);   // size (subatom)
-    avio_wl32(pb, track->tref_tag);
-    avio_wb32(pb, track->tref_id);
-    return 20;
+    for (tref_idx = 0; tref_idx < track->nb_trefs; tref_idx++) {
+        pos_sub = avio_tell(pb);
+        avio_wb32(pb, 0);   // size (subatom)
+        avio_wl32(pb, track->trefs[tref_idx].tag);
+        for (i = 0; i < track->trefs[tref_idx].nb_ids; i++) {
+            int stream_idx;
+            int tref_stream_id = track->trefs[tref_idx].ids[i];
+            for (stream_idx = 0; stream_idx < mov->nb_streams; ++stream_idx)
+                if (mov->tracks[stream_idx].st->id == tref_stream_id) {
+                    avio_wb32(pb, mov->tracks[stream_idx].track_id);
+                    break;
+                }
+        }
+        update_size(pb, pos_sub);
+    }
+    return update_size(pb, pos);
 }
 
 // goes at the end of each track!  ... Critical for PSP playback ("Incompatible data" without it)
@@ -2666,7 +2682,8 @@  static int mov_write_udta_sdp(AVIOContext *pb, MOVTrack *track)
     char buf[1000] = "";
     int len;
 
-    ff_sdp_write_media(buf, sizeof(buf), ctx->streams[0], track->src_track,
+    ff_sdp_write_media(buf, sizeof(buf), ctx->streams[0],
+                       track->nb_src_tracks ? track->src_tracks[0] : 0,
                        NULL, NULL, 0, 0, ctx);
     av_strlcatf(buf, sizeof(buf), "a=control:streamid=%d\r\n", track->track_id);
     len = strlen(buf);
@@ -2749,8 +2766,8 @@  static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext
                    "Not writing any edit list even though one would have been required\n");
     }
 
-    if (track->tref_tag)
-        mov_write_tref_tag(pb, track);
+    if (track->nb_trefs)
+        mov_write_tref_tag(pb, mov, track);
 
     if ((ret = mov_write_mdia_tag(s, pb, mov, track)) < 0)
         return ret;
@@ -3480,17 +3497,139 @@  static int mov_setup_track_ids(MOVMuxContext *mov, AVFormatContext *s)
     return 0;
 }
 
+MOVTRef *ff_mov_find_tref(MOVTrack *track, uint32_t tag)
+{
+    int i;
+    MOVTRef *tref = NULL;
+
+    for (i = 0; i < track->nb_trefs && !tref; ++i) {
+        if (track->trefs[i].tag == tag) {
+            tref = track->trefs + i;
+        }
+    }
+
+    return tref;
+}
+
+int ff_mov_find_or_add_tref(MOVTrack *track, uint32_t tag, MOVTRef **tref_ret)
+{
+    int ret;
+    int i;
+    MOVTRef *tref = ff_mov_find_tref(track, tag);
+    *tref_ret = NULL;
+
+    for (i = 0; i < track->nb_trefs && !tref; ++i) {
+        if (track->trefs[i].tag == tag) {
+            tref = track->trefs + i;
+        }
+    }
+
+    if (!tref) {
+        ret = av_reallocp_array(&track->trefs, track->nb_trefs + 1, sizeof(*track->trefs));
+        if (ret < 0)
+            return ret;
+        tref = track->trefs + track->nb_trefs;
+        track->nb_trefs++;
+        tref->tag = tag;
+        tref->ids = NULL;
+        tref->nb_ids = 0;
+    }
+
+    *tref_ret = tref;
+    return 0;
+}
+
+int ff_mov_add_tref_track(MOVTRef *tref, int id)
+{
+    int ret = av_reallocp_array(&tref->ids, tref->nb_ids + 1, sizeof(tref->ids));
+    if (ret >= 0) {
+        tref->ids[tref->nb_ids] = id;
+        tref->nb_ids++;
+    }
+    return ret;
+}
+
+// replaces all tref ids with ones that refer to the track's src_tracks
+int ff_mov_map_trefs_from_src_tracks(MOVMuxContext *mov, MOVTrack *track, MOVTRef *tref)
+{
+    int ret;
+    int i;
+
+    ret = av_reallocp_array(&tref->ids, track->nb_src_tracks, sizeof(tref->ids));
+    if (ret < 0) {
+        // so we may have added in the tag, but it has no ids.. but we
+        // probably cannot undo it either, because we couldn't
+        // allocate memory.
+        return ret;
+    }
+    tref->nb_ids = track->nb_src_tracks;
+
+    for (i = 0; i < track->nb_src_tracks; ++i) {
+        tref->ids[i] = mov->tracks[track->src_tracks[i]].st->id;
+    }
+    return ret;
+}
+
+/** sets a single tref id */
+static int mov_set_one_tref_track(MOVTRef *tref, int id)
+{
+    int ret;
+    ret = av_reallocp_array(&tref->ids, 1, sizeof(*tref->ids));
+    if (ret < 0)
+        return ret;
+    tref->nb_ids = 1;
+
+    tref->ids[0] = id;
+    return ret;
+}
+
+static int mov_copy_tref_side_data(MOVMuxContext *mov, MOVTrack *track, AVFormatContext *s)
+{
+    int size;
+    int ret = 0;
+    int i;
+    char *ptr = (void*) av_stream_get_side_data(track->st,
+                                                AV_PKT_DATA_TRACK_REFERENCES,
+                                                &size);
+
+    if (!ptr)
+        return 0;
+
+    while (ret == 0 && size > 0) {
+        AVTrackReferences *refs = (void*) ptr;
+        MOVTRef *tref;
+        int cur_size;
+        int* stream_ids = (void*) (refs + 1);
+        if (ff_mov_find_or_add_tref(track, MKTAG(refs->tag[0], refs->tag[1], refs->tag[2], refs->tag[3]),
+                                    &tref) < 0)
+            ret = -1;
+
+        for (i = 0; i < refs->nb_tracks; i++)
+            ff_mov_add_tref_track(tref, stream_ids[i]);
+
+        cur_size = sizeof(*refs) + refs->nb_tracks * sizeof(*stream_ids);
+        ptr += cur_size;
+        size -= cur_size;
+    }
+    return ret;
+}
+
 static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,
                               AVFormatContext *s)
 {
     int i;
     int64_t pos = avio_tell(pb);
+    int ret;
     avio_wb32(pb, 0); /* size placeholder*/
     ffio_wfourcc(pb, "moov");
 
     mov_setup_track_ids(mov, s);
 
     for (i = 0; i < mov->nb_streams; i++) {
+        mov_copy_tref_side_data(mov, &mov->tracks[i], s);
+    }
+
+    for (i = 0; i < mov->nb_streams; i++) {
         if (mov->tracks[i].entry <= 0 && !(mov->flags & FF_MOV_FLAG_FRAGMENT))
             continue;
 
@@ -3502,14 +3641,27 @@  static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,
 
     if (mov->chapter_track)
         for (i = 0; i < s->nb_streams; i++) {
-            mov->tracks[i].tref_tag = MKTAG('c','h','a','p');
-            mov->tracks[i].tref_id  = mov->tracks[mov->chapter_track].track_id;
+            if (!ff_codec_get_id(ff_codec_metadata_tags, mov->tracks[i].tag)) {
+                MOVTrack *track = &mov->tracks[i];
+                MOVTRef *tref = NULL;
+                ret = ff_mov_find_or_add_tref(track, MKTAG('c','h','a','p'), &tref);
+                if (ret < 0)
+                    return ret;
+                mov_set_one_tref_track(tref, mov->chapter_track);
+                track->nb_src_tracks = 1; /* this seems an odd hardcoded number.. */
+            }
         }
     for (i = 0; i < mov->nb_streams; i++) {
         MOVTrack *track = &mov->tracks[i];
         if (track->tag == MKTAG('r','t','p',' ')) {
-            track->tref_tag = MKTAG('h','i','n','t');
-            track->tref_id = mov->tracks[track->src_track].track_id;
+            MOVTrack *track = &mov->tracks[i];
+            MOVTRef *tref;
+            ret = ff_mov_find_or_add_tref(track, MKTAG('h','i','n','t'), &tref);
+            if (ret < 0)
+                return ret;
+            ret = ff_mov_map_trefs_from_src_tracks(mov, track, tref);
+            if (ret < 0)
+                return ret;
         } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) {
             int * fallback, size;
             fallback = (int*)av_stream_get_side_data(track->st,
@@ -3517,21 +3669,55 @@  static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,
                                                      &size);
             if (fallback != NULL && size == sizeof(int)) {
                 if (*fallback >= 0 && *fallback < mov->nb_streams) {
-                    track->tref_tag = MKTAG('f','a','l','l');
-                    track->tref_id = mov->tracks[*fallback].track_id;
+                    MOVTRef *tref;
+                    ret = ff_mov_find_or_add_tref(track, MKTAG('f','a','l','l'), &tref);
+                    if (ret < 0)
+                        return ret;
+                    ret = mov_set_one_tref_track(tref, *fallback);
+                    if (ret < 0)
+                        return ret;
                 }
             }
         }
     }
     for (i = 0; i < mov->nb_streams; i++) {
-        if (mov->tracks[i].tag == MKTAG('t','m','c','d')) {
-            int src_trk = mov->tracks[i].src_track;
-            mov->tracks[src_trk].tref_tag = mov->tracks[i].tag;
-            mov->tracks[src_trk].tref_id  = mov->tracks[i].track_id;
-            //src_trk may have a different timescale than the tmcd track
-            mov->tracks[i].track_duration = av_rescale(mov->tracks[src_trk].track_duration,
-                                                       mov->tracks[i].timescale,
-                                                       mov->tracks[src_trk].timescale);
+        MOVTrack *track = &mov->tracks[i];
+        if (ff_codec_get_id(ff_codec_metadata_tags, track->tag) &&
+            track->nb_src_tracks) {
+            MOVTRef *tref;
+            ret = ff_mov_find_or_add_tref(track, MKTAG('c','d','s','c'), &tref);
+            if (ret < 0)
+                return ret;
+            ret = ff_mov_map_trefs_from_src_tracks(mov, track, tref);
+            if (ret < 0)
+                return ret;
+        }
+    }
+
+    for (i = 0; i < mov->nb_streams; i++) {
+        MOVTRef *tref = ff_mov_find_tref(&mov->tracks[i], MKTAG('t','m','c','d'));
+        if (tref) {
+            /* This fragment only works if there is one source track */
+            if (mov->tracks[i].nb_src_tracks > 1) {
+                return -1;
+            } else {
+                int src_trk_idx = mov->tracks[i].src_tracks[0];
+                MOVTrack *src_trk = &mov->tracks[src_trk_idx];
+                MOVTRef *src_tref = NULL;
+
+                ret = ff_mov_find_or_add_tref(src_trk, MKTAG('t','m','c','d'), &src_tref);
+                if (ret < 0)
+                    return ret;
+
+                ret = mov_set_one_tref_track(src_tref, mov->tracks[i].st->id);
+                if (ret < 0)
+                    return ret;
+
+                //src_trk may have a different timescale than the tmcd track
+                mov->tracks[i].track_duration = av_rescale(src_trk->track_duration,
+                                                           mov->tracks[i].timescale,
+                                                           src_trk->timescale);
+            }
         }
     }
 
@@ -5239,7 +5425,11 @@  static int mov_create_timecode_track(AVFormatContext *s, int index, int src_inde
     /* tmcd track based on video stream */
     track->mode      = mov->mode;
     track->tag       = MKTAG('t','m','c','d');
-    track->src_track = src_index;
+    ret = av_reallocp_array(&track->src_tracks, 1, sizeof(*track->src_tracks));
+    if (ret < 0)
+        return ret;
+    track->nb_src_tracks = 1;
+    track->src_tracks[0] = src_index;
     track->timescale = mov->tracks[src_index].timescale;
     if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME)
         track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME;
@@ -5321,7 +5511,7 @@  static void enable_tracks(AVFormatContext *s)
 static void mov_free(AVFormatContext *s)
 {
     MOVMuxContext *mov = s->priv_data;
-    int i;
+    int i, j;
 
     if (mov->chapter_track) {
         if (mov->tracks[mov->chapter_track].par)
@@ -5341,6 +5531,11 @@  static void mov_free(AVFormatContext *s)
             av_freep(&mov->tracks[i].vos_data);
 
         ff_mov_cenc_free(&mov->tracks[i].cenc);
+        av_freep(&mov->tracks[i].src_tracks);
+        for (j = 0; j < mov->tracks[i].nb_trefs; j++) {
+            av_freep(&mov->tracks[i].trefs[j].ids);
+        }
+        av_freep(&mov->tracks[i].trefs);
     }
 
     av_freep(&mov->tracks);
diff --git a/libavformat/movenc.h b/libavformat/movenc.h
index ea76e39..5bf9469 100644
--- a/libavformat/movenc.h
+++ b/libavformat/movenc.h
@@ -78,6 +78,12 @@  typedef struct MOVFragmentInfo {
     int size;
 } MOVFragmentInfo;
 
+typedef struct MOVTRef {
+    uint32_t tag;
+    int      nb_ids;
+    int*     ids;
+} MOVTRef;
+
 typedef struct MOVTrack {
     int         mode;
     int         entry;
@@ -110,15 +116,16 @@  typedef struct MOVTrack {
     unsigned    cluster_capacity;
     int         audio_vbr;
     int         height; ///< active picture (w/o VBI) height for D-10/IMX
-    uint32_t    tref_tag;
-    int         tref_id; ///< trackID of the referenced track
+    MOVTRef*    trefs;
+    int         nb_trefs;
     int64_t     start_dts;
     int64_t     start_cts;
     int64_t     end_pts;
     int         end_reliable;
 
     int         hint_track;   ///< the track that hints this track, -1 if no hint track is set
-    int         src_track;    ///< the track that this hint (or tmcd) track describes
+    int         nb_src_tracks;  ///< number of src tracks
+    int         *src_tracks;    ///< the tracks that this hint (or tmcd or cdsc) track describes
     AVFormatContext *rtp_ctx; ///< the format context for the hinting rtp muxer
     uint32_t    prev_rtp_ts;
     int64_t     cur_rtp_ts_unwrapped;
@@ -248,4 +255,52 @@  int ff_mov_add_hinted_packet(AVFormatContext *s, AVPacket *pkt,
                              uint8_t *sample_data, int sample_size);
 void ff_mov_close_hinting(MOVTrack *track);
 
+/**
+ * @brief Finds a track reference of certain tag from a track; if not
+ * found, return NULL
+ *
+ * @param track The track to search from
+ * @param tag The tag (4cc) to search for
+ * @return a MOVTRef describing the track reference or NULL if not found.
+ */
+MOVTRef *ff_mov_find_tref(MOVTrack *track, uint32_t tag);
+
+/**
+ * @brief Finds a track reference of certain tag from a track; if not
+ * found, create an empty track reference object for the track put it
+ * into the track.
+ *
+ * @param track The track to search from
+ * @param tag The tag (4cc) to search for
+ * @param tref_ret A pointer to the the found or created tref is
+ *                 returned here. Must not be NULL.
+ * @return Zero on success, an AVERROR error code on failure. On error
+ *         no modifications have been performed.
+ */
+int ff_mov_find_or_add_tref(MOVTrack *track, uint32_t tag, MOVTRef **tref_ret);
+
+/**
+ * @brief Adds a a track to a track reference that may or may not be
+ * put into a track
+ *
+ * @param tref The track reference to add the track for
+ * @param tag The tag (4cc) of the track reference
+ * @return Zero on success, an AVERROR error code on failure. On error
+ *         no modifications have been performed.
+ */
+int ff_mov_add_tref_track(MOVTRef *tref, int id);
+
+/**
+ * @brief Copies track->src_tracks from a track to the give tref
+ *
+ * @param mov The MOV muxer context
+ * @param track The track to take the src_tracks from
+ * @param tref The tref to translate the src_tracks into
+ * @return Zero on success, an AVERROR error code on failure. On error
+ *         no modifications have been performed. However, probably
+ *         prior to this call src_tracks has been manipulated and now
+ *         it is out-of-sync, so this is pretty fatal.
+ */
+int ff_mov_map_trefs_from_src_tracks(MOVMuxContext *mov, MOVTrack *track, MOVTRef *tref);
+
 #endif /* AVFORMAT_MOVENC_H */
diff --git a/libavformat/movenchint.c b/libavformat/movenchint.c
index 964026e..61ad09b 100644
--- a/libavformat/movenchint.c
+++ b/libavformat/movenchint.c
@@ -32,10 +32,17 @@  int ff_mov_init_hinting(AVFormatContext *s, int index, int src_index)
     MOVTrack *track     = &mov->tracks[index];
     MOVTrack *src_track = &mov->tracks[src_index];
     AVStream *src_st    = s->streams[src_index];
-    int ret = AVERROR(ENOMEM);
+    int ret;
 
     track->tag = MKTAG('r','t','p',' ');
-    track->src_track = src_index;
+    ret = av_reallocp_array(&track->src_tracks,
+                            track->nb_src_tracks + 1,
+                            sizeof(*track->src_tracks));
+    if (ret != 0)
+        goto fail;
+    ret = AVERROR(ENOMEM); /* default error for the rest of the errors */
+    track->src_tracks[track->nb_src_tracks] = src_index;
+    track->nb_src_tracks++;
 
     track->par = avcodec_parameters_alloc();
     if (!track->par)