This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

commit 4444a755907e05dc4d4e4f9e2c91320b1b36a235
Author:     James Almer <[email protected]>
AuthorDate: Tue Mar 17 18:13:23 2026 -0300
Commit:     James Almer <[email protected]>
CommitDate: Sun May 17 11:16:56 2026 -0300

    avformat/movenc: support writing more than one entry per tref tag
    
    Signed-off-by: James Almer <[email protected]>
---
 libavformat/movenc.c        | 259 +++++++++++++++++++++++++++++++++++---------
 libavformat/movenc.h        |  13 ++-
 libavformat/movenchint.c    |   6 +-
 tests/ref/fate/copy-trac236 |   4 +-
 4 files changed, 223 insertions(+), 59 deletions(-)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 83f65f3417..d0346b6f4c 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -3839,15 +3839,15 @@ static int mov_write_minf_tag(AVFormatContext *s, 
AVIOContext *pb, MOVMuxContext
 static void get_pts_range(MOVMuxContext *mov, MOVTrack *track,
                           int64_t *start, int64_t *end, int elst)
 {
-    if (track->tag == MKTAG('t','m','c','d') && mov->nb_meta_tmcd) {
+    if (track->tag == MKTAG('t','m','c','d') && mov->nb_meta_tmcd && 
track->nb_src_track) {
         // tmcd tracks gets track_duration set in mov_write_moov_tag from
         // another track's duration, while the end_pts may be left at zero.
         // Calculate the pts duration for that track instead.
-        get_pts_range(mov, &mov->tracks[track->src_track], start, end, elst);
+        get_pts_range(mov, &mov->tracks[*track->src_track], start, end, elst);
         *start = av_rescale(*start, track->timescale,
-                            mov->tracks[track->src_track].timescale);
+                            mov->tracks[*track->src_track].timescale);
         *end   = av_rescale(*end, track->timescale,
-                            mov->tracks[track->src_track].timescale);
+                            mov->tracks[*track->src_track].timescale);
         return;
     }
     if (track->end_pts != AV_NOPTS_VALUE &&
@@ -4221,12 +4221,18 @@ static int mov_write_edts_tag(AVIOContext *pb, 
MOVMuxContext *mov,
 
 static int mov_write_tref_tag(AVIOContext *pb, MOVTrack *track)
 {
-    avio_wb32(pb, 20);   // size
+    int64_t pos = avio_tell(pb);
+    avio_wb32(pb, 0);   // size placeholder
     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 (int i = 0; i < track->nb_tref_tags; i++) {
+        MovTag *tag = &track->tref_tags[i];
+        avio_wb32(pb, 8 + tag->nb_id * sizeof(*tag->id)); // size (subatom)
+        avio_wl32(pb, tag->name);
+        for (int j = 0; j < tag->nb_id; j++)
+            avio_wb32(pb, tag->id[j]);
+    }
+    return update_size(pb, pos);
 }
 
 // goes at the end of each track!  ... Critical for PSP playback 
("Incompatible data" without it)
@@ -4254,7 +4260,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,
+    av_assert0(track->nb_src_track);
+    ff_sdp_write_media(buf, sizeof(buf), ctx->streams[0], *track->src_track,
                        NULL, NULL, 0, 0, ctx);
     av_strlcatf(buf, sizeof(buf), "a=control:streamid=%d\r\n", 
track->track_id);
     len = strlen(buf);
@@ -4392,7 +4399,7 @@ static int mov_write_trak_tag(AVFormatContext *s, 
AVIOContext *pb, MOVMuxContext
     if (mov->is_animated_avif)
         mov_write_edts_tag(pb, mov, track);
 
-    if (track->tref_tag)
+    if (track->nb_tref_tags)
         mov_write_tref_tag(pb, track);
 
     if ((ret = mov_write_mdia_tag(s, pb, mov, track)) < 0)
@@ -5218,6 +5225,58 @@ static int mov_setup_track_ids(MOVMuxContext *mov, 
AVFormatContext *s)
     return 0;
 }
 
+static int mov_find_tref_id(MOVMuxContext *mov, const MovTag *tag, uint32_t id)
+{
+    for (int i = 0; i < tag->nb_id; i++) {
+        if (tag->id[i] == id)
+            return 1;
+    }
+    return 0;
+}
+
+static int mov_add_tref_id(MOVMuxContext *mov, MovTag *tag, uint32_t id)
+{
+    int ret = mov_find_tref_id(mov, tag, id);
+
+    if (!ret) {
+        uint32_t *tmp = av_realloc_array(tag->id, tag->nb_id + 1, 
sizeof(*tag->id));
+        if (!tmp)
+            return AVERROR(ENOMEM);
+        tag->id = tmp;
+        tag->id[tag->nb_id++] = id;
+    }
+
+    return 0;
+}
+
+static MovTag *mov_find_tref_tag(MOVMuxContext *mov, const MOVTrack *trk, 
uint32_t name)
+{
+    for (int i = 0; i < trk->nb_tref_tags; i++) {
+        MovTag *entry = &trk->tref_tags[i];
+
+        if (entry->name == name)
+            return entry;
+    }
+    return NULL;
+}
+
+static MovTag *mov_add_tref_tag(MOVMuxContext *mov, MOVTrack *trk, uint32_t 
name)
+{
+    MovTag *tag = mov_find_tref_tag(mov, trk, name);
+
+    if (!tag) {
+        MovTag *tmp = av_realloc_array(trk->tref_tags, trk->nb_tref_tags + 1,
+                                       sizeof(*trk->tref_tags));
+        if (!tmp)
+            return NULL;
+        trk->tref_tags = tmp;
+        tag = &trk->tref_tags[trk->nb_tref_tags++];
+        *tag = (MovTag){ .name = name };
+    }
+
+    return tag;
+}
+
 static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,
                               AVFormatContext *s)
 {
@@ -5240,39 +5299,64 @@ static int mov_write_moov_tag(AVIOContext *pb, 
MOVMuxContext *mov,
 
     if (mov->chapter_track)
         for (i = 0; i < mov->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;
+            MovTag *tag = mov_add_tref_tag(mov, &mov->tracks[i], 
MKTAG('c','h','a','p'));
+            if (!tag)
+                return AVERROR(ENOMEM);
+
+            int ret = mov_add_tref_id(mov, tag, 
mov->tracks[mov->chapter_track].track_id);
+            if (ret < 0)
+                return ret;
         }
     for (i = 0; i < mov->nb_tracks; 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;
-        } else if (track->tag == MKTAG('l','v','c','1')) {
-            track->tref_tag = MKTAG('s','b','a','s');
-            track->tref_id = mov->tracks[track->src_track].track_id;
-        } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) {
-            const AVPacketSideData *sd = 
av_packet_side_data_get(track->st->codecpar->coded_side_data,
-                                                                 
track->st->codecpar->nb_coded_side_data,
-                                                                 
AV_PKT_DATA_FALLBACK_TRACK );
-            if (sd && sd->size == sizeof(int)) {
-                int *fallback = (int *)sd->data;
-                if (*fallback >= 0 && *fallback < mov->nb_tracks) {
-                    track->tref_tag = MKTAG('f','a','l','l');
-                    track->tref_id = mov->tracks[*fallback].track_id;
+            MovTag *tag = mov_add_tref_tag(mov, track, MKTAG('h','i','n','t'));
+            if (!tag)
+                return AVERROR(ENOMEM);
+
+            av_assert0(track->nb_src_track);
+            int ret = mov_add_tref_id(mov, tag, 
mov->tracks[*track->src_track].track_id);
+            if (ret < 0)
+                return ret;
+        } else if (track->tag == MKTAG('l','v','c','1') && 
track->nb_src_track) {
+            MovTag *tag = mov_add_tref_tag(mov, track, MKTAG('s','b','a','s'));
+            if (!tag)
+                return AVERROR(ENOMEM);
+
+            int ret = mov_add_tref_id(mov, tag, 
mov->tracks[*track->src_track].track_id);
+            if (ret < 0)
+                return ret;
+        } else {
+            if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) {
+                const AVPacketSideData *sd = 
av_packet_side_data_get(track->st->codecpar->coded_side_data,
+                                                                     
track->st->codecpar->nb_coded_side_data,
+                                                                     
AV_PKT_DATA_FALLBACK_TRACK );
+                if (sd && sd->size == sizeof(int)) {
+                    int *fallback = (int *)sd->data;
+                    if (*fallback >= 0 && *fallback < mov->nb_tracks) {
+                        MovTag *tag = mov_add_tref_tag(mov, track, 
MKTAG('f','a','l','l'));
+                        if (!tag)
+                            return AVERROR(ENOMEM);
+
+                        int ret = mov_add_tref_id(mov, tag, 
mov->tracks[*fallback].track_id);
+                        if (ret < 0)
+                            return ret;
+                    }
                 }
             }
-        }
-    }
-    for (i = 0; i < mov->nb_tracks; 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);
+            for (int j = 0; j < track->nb_src_track; j++) {
+                int src_trk = track->src_track[j];
+                MovTag *tag = mov_add_tref_tag(mov, &mov->tracks[src_trk], 
track->tag);
+                if (!tag)
+                    return AVERROR(ENOMEM);
+                int ret = mov_add_tref_id(mov, tag, track->track_id);
+                if (ret < 0)
+                    return ret;
+                //src_trk may have a different timescale than the tmcd track
+                track->track_duration = 
av_rescale(mov->tracks[src_trk].track_duration,
+                                                   track->timescale,
+                                                   
mov->tracks[src_trk].timescale);
+            }
         }
     }
 
@@ -7677,7 +7761,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;
+    track->src_track = av_malloc(sizeof(*track->src_track));
+    if (!track->src_track)
+        return AVERROR(ENOMEM);
+    *track->src_track = src_index;
+    track->nb_src_track = 1;
     track->timescale = mov->tracks[src_index].timescale;
     if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME)
         track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME;
@@ -7794,6 +7882,11 @@ static void mov_free(AVFormatContext *s)
         av_freep(&track->extradata);
         av_freep(&track->extradata_size);
 
+        for (int i = 0; i < track->nb_tref_tags; i++)
+            av_freep(&track->tref_tags[i].id);
+        av_freep(&track->tref_tags);
+        av_freep(&track->src_track);
+
         ff_mov_cenc_free(&track->cenc);
         ffio_free_dyn_buf(&track->mdat_buf);
 
@@ -8451,24 +8544,79 @@ static int mov_init(AVFormatContext *s)
     for (i = 0; i < s->nb_stream_groups; i++) {
         AVStreamGroup *stg = s->stream_groups[i];
 
-        if (stg->type != AV_STREAM_GROUP_PARAMS_LCEVC)
-            continue;
+        switch (stg->type) {
+        case AV_STREAM_GROUP_PARAMS_LCEVC: {
+            AVStreamGroupLCEVC *lcevc = stg->params.lcevc;
+            AVStream *st    = stg->streams[lcevc->lcevc_index];
+            MOVTrack *track = st->priv_data;
+
+            for (int j = 0; j < mov->nb_tracks; j++) {
+                MOVTrack *trk = &mov->tracks[j];
+
+                if (trk->st == stg->streams[!lcevc->lcevc_index]) {
+                    track->src_track = av_malloc(sizeof(*track->src_track));
+                    if (!track->src_track)
+                        return AVERROR(ENOMEM);
+                    *track->src_track = j;
+                    track->nb_src_track = 1;
+                    break;
+                }
+            }
 
-        AVStreamGroupLCEVC *lcevc = stg->params.lcevc;
-        AVStream *st    = stg->streams[lcevc->lcevc_index];
-        MOVTrack *track = st->priv_data;
+            track->par->width = lcevc->width;
+            track->par->height = track->height = lcevc->height;
+            break;
+        }
+        case AV_STREAM_GROUP_PARAMS_TREF: {
+            const AVStreamGroupTREF *tref = stg->params.tref;
+            MOVTrack *track;
 
-        for (int j = 0; j < mov->nb_tracks; j++) {
-            MOVTrack *trk = &mov->tracks[j];
+            if (tref->metadata_index >= stg->nb_streams)
+                return AVERROR(EINVAL);
 
-            if (trk->st == stg->streams[!lcevc->lcevc_index]) {
-                track->src_track = j;
-                break;
+            track = stg->streams[tref->metadata_index]->priv_data;
+            for (int j = 0; j < stg->nb_streams; j++) {
+                const AVStream *st2 = stg->streams[j];
+                int index = -1;
+
+                for (int k = 0; k < mov->nb_tracks; k++) {
+                    if (mov->tracks[k].st != st2)
+                        continue;
+                    index = k;
+                    break;
+                }
+                if (index < 0)
+                    return AVERROR(EINVAL);
+
+                int *tmp = av_realloc(track->src_track,
+                                      (track->nb_src_track + 1) * 
sizeof(*track->src_track));
+                if (!tmp)
+                    return AVERROR(ENOMEM);
+                track->src_track = tmp;
+                track->src_track[track->nb_src_track++] = index;
             }
+            break;
         }
+        default:
+            break;
+        }
+    }
 
-        track->par->width = lcevc->width;
-        track->par->height = track->height = lcevc->height;
+    /* fill src_track for tmcd tracks not covered by stream groups or
+     * created by this muxer, retaining the old behavior of src_track
+     * being arbitrarily 0. */
+    for (i = 0; i < s->nb_streams; i++) {
+        AVStream *st = s->streams[i];
+        MOVTrack *track = st->priv_data;
+        if (st->codecpar->codec_tag != MKTAG('t','m','c','d') ||
+            track->nb_src_track)
+            continue;
+
+        track->src_track = av_malloc(sizeof(*track->src_track));
+        if (!track->src_track)
+            return AVERROR(ENOMEM);
+        *track->src_track = 0;
+        track->nb_src_track = 1;
     }
 
     enable_tracks(s);
@@ -8918,8 +9066,13 @@ static int avif_write_trailer(AVFormatContext *s)
     if (mov->is_animated_avif && mov->nb_streams > 1) {
         // For animated avif with alpha channel, we need to write a tref tag
         // with type "auxl".
-        mov->tracks[1].tref_tag = MKTAG('a', 'u', 'x', 'l');
-        mov->tracks[1].tref_id = 1;
+        MovTag *tag = mov_add_tref_tag(mov, &mov->tracks[1], MKTAG('a', 'u', 
'x', 'l'));
+        if (!tag)
+            return AVERROR(ENOMEM);
+
+        int ret = mov_add_tref_id(mov, tag, 1);
+        if (ret < 0)
+            return ret;
     }
     mov_write_identification(pb, s);
     mov_write_meta_tag(pb, mov, s);
diff --git a/libavformat/movenc.h b/libavformat/movenc.h
index 34e6d445cf..f47d1381a9 100644
--- a/libavformat/movenc.h
+++ b/libavformat/movenc.h
@@ -84,6 +84,12 @@ typedef struct MOVFragmentInfo {
     int size;
 } MOVFragmentInfo;
 
+typedef struct MovTag {
+    uint32_t    name;
+    int         nb_id;
+    uint32_t   *id; ///< trackID of the referenced track
+} MovTag;
+
 typedef struct MOVTrack {
     int         mode;
     int         entry_version;
@@ -125,8 +131,8 @@ 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
+    int         nb_tref_tags;
+    MovTag      *tref_tags;
     int64_t     start_dts;
     int64_t     start_cts;
     int64_t     end_pts;
@@ -135,7 +141,8 @@ typedef struct MOVTrack {
     int64_t     dts_shift;
 
     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_track;
+    int         *src_track;   ///< the tracks that this hint (or tmcd) track 
describes
     AVFormatContext *rtp_ctx; ///< the format context for the hinting rtp muxer
     uint32_t    prev_rtp_ts;
     int64_t     cur_rtp_ts_unwrapped;
diff --git a/libavformat/movenchint.c b/libavformat/movenchint.c
index 1fa22fbffb..65b3908938 100644
--- a/libavformat/movenchint.c
+++ b/libavformat/movenchint.c
@@ -36,7 +36,11 @@ int ff_mov_init_hinting(AVFormatContext *s, int index, int 
src_index)
     int ret = AVERROR(ENOMEM);
 
     track->tag = MKTAG('r','t','p',' ');
-    track->src_track = src_index;
+    track->src_track = av_malloc(sizeof(*track->src_track));
+    if (!track->src_track)
+        return AVERROR(ENOMEM);
+    *track->src_track = src_index;
+    track->nb_src_track = 1;
 
     track->par = avcodec_parameters_alloc();
     if (!track->par)
diff --git a/tests/ref/fate/copy-trac236 b/tests/ref/fate/copy-trac236
index 1f20e9ea7e..a4920c9913 100644
--- a/tests/ref/fate/copy-trac236
+++ b/tests/ref/fate/copy-trac236
@@ -1,5 +1,5 @@
-3e3497985d54991e09f82ca3dd7eda79 *tests/data/fate/copy-trac236.mov
-630910 tests/data/fate/copy-trac236.mov
+cd72fa882e38a18d48cf139b66097b9c *tests/data/fate/copy-trac236.mov
+630918 tests/data/fate/copy-trac236.mov
 #tb 0: 100/2997
 #media_type 0: video
 #codec_id 0: rawvideo

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to