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]
