PR #24893 opened by James Almer (jamrial) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24893 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24893.patch
This requires a bunch of additions and changes: #### 1 - Gain Map Parameters packet side data Given it's container level information in this case. It mirrors the existing frame side data type. #### 2 - Stream Groups referencing other Stream Groups ISOBMFF `tmap` item groups can reference either a single image item or a `grid` image item. This change is needed for the latter. #### 3 - Side data in Layered Video Stream Group Type To export the ISO 21496-1 payload that belongs to the `tmap` derived group #### 4 - Move ISO 21496-1 <-> `AVGainMapParams` conversion helpers from lavc to lavu Required by point 3. As usual, the commits are missing APIChanges entries and library version bumps until it's ready to merge, to prevent rebase conflicts. >From d8a080224dd40ab801dd270742923b261cd09294 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 00:11:37 -0300 Subject: [PATCH 01/21] avformat: allow Stream Group to reference other Stream Groups Some formats, like ISOBMFF, allow having references between items that we export as Stream Groups where the entries are other items that we too export as Stream Groups. This is implemented in a backwards compatible way, where if a group were to contain an entry that points to another group, the legacy nb_streams field will be set to 0 so callers not aware of the new feature don't attempt to parse the group at all. Signed-off-by: James Almer <[email protected]> --- libavformat/avformat.c | 19 +++++++-- libavformat/avformat.h | 78 ++++++++++++++++++++++++++++++++++--- libavformat/options.c | 76 ++++++++++++++++++++++++++++++++++-- libavformat/version_major.h | 2 + 4 files changed, 163 insertions(+), 12 deletions(-) diff --git a/libavformat/avformat.c b/libavformat/avformat.c index d0cda8f3b4..f0009eaae5 100644 --- a/libavformat/avformat.c +++ b/libavformat/avformat.c @@ -84,7 +84,14 @@ void ff_free_stream_group(AVStreamGroup **pstg) if (!stg) return; +#if FF_API_STREAM_GROUP_ENTRIES +FF_DISABLE_DEPRECATION_WARNINGS av_freep(&stg->streams); +FF_ENABLE_DEPRECATION_WARNINGS +#endif + for (int i = 0; i < stg->nb_entries; i++) + av_freep(&stg->entries[i]); + av_freep(&stg->entries); av_dict_free(&stg->metadata); av_freep(&stg->priv_data); switch (stg->type) { @@ -642,8 +649,10 @@ static int match_stream_specifier(const AVFormatContext *s, const AVStream *st, } if (group_idx < 0 || group_idx >= s->nb_stream_groups) return AVERROR(EINVAL); - for (unsigned j = 0; j < s->stream_groups[group_idx]->nb_streams; j++) { - if (st->index == s->stream_groups[group_idx]->streams[j]->index) { + for (unsigned j = 0; j < s->stream_groups[group_idx]->nb_entries; j++) { + if (s->stream_groups[group_idx]->entries[j]->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + if (st->index == s->stream_groups[group_idx]->entries[j]->stream->index) { found = 1; if (g) *g = s->stream_groups[group_idx]; @@ -772,9 +781,11 @@ int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st, return (index == st->index); /* If we requested a matching stream index, we have to ensure st is that. */ - nb_streams = g ? g->nb_streams : (p ? p->nb_stream_indexes : s->nb_streams); + nb_streams = g ? g->nb_entries : (p ? p->nb_stream_indexes : s->nb_streams); for (int i = 0; i < nb_streams && index >= 0; i++) { - unsigned idx = g ? g->streams[i]->index : (p ? p->stream_index[i] : i); + if (g && g->entries[i]->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + unsigned idx = g ? g->entries[i]->stream->index : (p ? p->stream_index[i] : i); const AVStream *candidate = s->streams[idx]; ret = match_stream_specifier(s, candidate, spec, NULL, NULL, NULL); if (ret < 0) diff --git a/libavformat/avformat.h b/libavformat/avformat.h index 07ed2764cd..93384b3e9d 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -1162,6 +1162,19 @@ enum AVStreamGroupParamsType { struct AVIAMFAudioElement; struct AVIAMFMixPresentation; +enum AVStreamGroupEntryType { + AV_STREAM_GROUP_ENTRY_STREAM, + AV_STREAM_GROUP_ENTRY_STREAM_GROUP, +}; + +typedef struct AVStreamGroupEntry { + enum AVStreamGroupEntryType type; + union { + AVStream *stream; + struct AVStreamGroup *stream_group; + }; +} AVStreamGroupEntry; + typedef struct AVStreamGroup { /** * A class for @ref avoptions. Set by avformat_stream_group_create(). @@ -1219,16 +1232,20 @@ typedef struct AVStreamGroup { */ AVDictionary *metadata; +#if FF_API_STREAM_GROUP_ENTRIES /** * Number of elements in AVStreamGroup.streams. * - * Set by avformat_stream_group_add_stream() must not be modified by any other code. + * Set by avformat_stream_group_add_stream(), must not be modified by any other code. + * @deprecated Use AVStreamGroup.nb_entries. */ + attribute_deprecated unsigned int nb_streams; +#endif /** - * A list of streams in the group. New entries are created with - * avformat_stream_group_add_stream(). + * A list of stream or groups in the group. New entries are created with + * or avformat_stream_group_add_stream(). * * - demuxing: entries are created by libavformat on group creation. * If AVFMTCTX_NOHEADER is set in ctx_flags, then new entries may also @@ -1236,7 +1253,10 @@ typedef struct AVStreamGroup { * - muxing: entries are created by the user before avformat_write_header(). * * Freed by libavformat in avformat_free_context(). + * + * @deprecated Use AVStreamGroup.entries. */ + attribute_deprecated AVStream **streams; /** @@ -1248,6 +1268,26 @@ typedef struct AVStreamGroup { * - muxing: may be set by the caller before avformat_write_header(). */ int disposition; + + /** + * Number of elements in AVStreamGroup.entries. + * + * Set by avformat_stream_group_add_entry(), must not be modified by any other code. + */ + unsigned int nb_entries; + + /** + * A list of streams or child groups in the group. New entries are created with + * or avformat_stream_group_add_stream() or avformat_stream_group_add_stream_group(). + * + * - demuxing: entries are created by libavformat on group creation. + * If AVFMTCTX_NOHEADER is set in ctx_flags, then new entries may also + * appear in av_read_frame(). + * - muxing: entries are created by the user before avformat_write_header(). + * + * Freed by libavformat in avformat_free_context(). + */ + AVStreamGroupEntry **entries; } AVStreamGroup; struct AVCodecParserContext *av_stream_get_parser(const AVStream *s); @@ -1414,7 +1454,8 @@ typedef struct AVFormatContext { unsigned int nb_stream_groups; /** * A list of all stream groups in the file. New groups are created with - * avformat_stream_group_create(), and filled with avformat_stream_group_add_stream(). + * avformat_stream_group_create(), and filled with avformat_stream_group_add_stream() + * and avformat_stream_group_add_stream_group. * * - demuxing: groups may be created by libavformat in avformat_open_input(). * If AVFMTCTX_NOHEADER is set in ctx_flags, then new groups may also @@ -2116,11 +2157,13 @@ const char *avformat_stream_group_name(enum AVStreamGroupParamsType type); * by avformat_stream_group_create(). * * New streams can be added to the group with avformat_stream_group_add_stream(). + * Child groups can be added to the group with avformat_stream_group_add_stream_group(). * * @param s media file handle * * @return newly created group or NULL on error. - * @see avformat_new_stream, avformat_stream_group_add_stream. + * @see avformat_new_stream. + * @see avformat_stream_group_add_stream, avformat_stream_group_add_stream_group. */ AVStreamGroup *avformat_stream_group_create(AVFormatContext *s, enum AVStreamGroupParamsType type, @@ -2170,6 +2213,31 @@ AVStream *avformat_new_stream(AVFormatContext *s, const struct AVCodec *c); */ int avformat_stream_group_add_stream(AVStreamGroup *stg, AVStream *st); +/** + * Add an already allocated stream group to another stream group. + * + * When demuxing, it may be called by the demuxer in read_header(). If the + * flag AVFMTCTX_NOHEADER is set in s.ctx_flags, then it may also + * be called in read_packet(). + * + * When muxing, may be called by the user before avformat_write_header() after + * having allocated a both the parent and child groups with + * avformat_stream_group_create(). + * + * User is required to call avformat_free_context() to clean up the allocation + * by avformat_stream_group_add_stream_group(). + * + * @param stg stream group belonging to a media file. + * @param group stream group in the media file to add to the group. + * + * @retval 0 success + * @retval AVERROR(EEXIST) the stream group was already in the group + * @retval "another negative error code" legitimate errors + * + * @see avformat_stream_group_create. + */ +int avformat_stream_group_add_stream_group(AVStreamGroup *stg, AVStreamGroup *group); + AVProgram *av_new_program(AVFormatContext *s, int id); diff --git a/libavformat/options.c b/libavformat/options.c index 042fe494cf..e1c094f538 100644 --- a/libavformat/options.c +++ b/libavformat/options.c @@ -547,13 +547,37 @@ fail: static int stream_group_add_stream(AVStreamGroup *stg, AVStream *st) { +#if FF_API_STREAM_GROUP_ENTRIES +FF_DISABLE_DEPRECATION_WARNINGS AVStream **streams = av_realloc_array(stg->streams, stg->nb_streams + 1, sizeof(*stg->streams)); if (!streams) return AVERROR(ENOMEM); +FF_ENABLE_DEPRECATION_WARNINGS +#endif + AVStreamGroupEntry **entries = av_realloc_array(stg->entries, stg->nb_entries + 1, + sizeof(*stg->entries)); + if (!entries) + return AVERROR(ENOMEM); + + stg->entries = entries; + stg->entries[stg->nb_entries] = av_mallocz(sizeof(*stg->entries[stg->nb_entries])); + if (!stg->entries[stg->nb_entries]) + return AVERROR(ENOMEM); + + stg->entries[stg->nb_entries]->type = AV_STREAM_GROUP_ENTRY_STREAM; + stg->entries[stg->nb_entries++]->stream = st; + +#if FF_API_STREAM_GROUP_ENTRIES +FF_DISABLE_DEPRECATION_WARNINGS stg->streams = streams; - stg->streams[stg->nb_streams++] = st; + if (stg->nb_streams == (stg->nb_entries - 1)) + stg->streams[stg->nb_streams++] = st; + else + stg->nb_streams = 0; +FF_ENABLE_DEPRECATION_WARNINGS +#endif return 0; } @@ -566,13 +590,59 @@ int avformat_stream_group_add_stream(AVStreamGroup *stg, AVStream *st) if (stgi->fmtctx != sti->fmtctx) return AVERROR(EINVAL); - for (int i = 0; i < stg->nb_streams; i++) - if (stg->streams[i]->index == st->index) + for (int i = 0; i < stg->nb_entries; i++) { + const AVStreamGroupEntry *entry = stg->entries[i]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + if (entry->stream->index == st->index) return AVERROR(EEXIST); + } return stream_group_add_stream(stg, st); } +static int stream_group_add_stream_group(AVStreamGroup *stg, AVStreamGroup *group) +{ + AVStreamGroupEntry **entries = av_realloc_array(stg->entries, stg->nb_entries + 1, + sizeof(*stg->entries)); + if (!entries) + return AVERROR(ENOMEM); + + stg->entries = entries; + stg->entries[stg->nb_entries] = av_mallocz(sizeof(*stg->entries[stg->nb_entries])); + if (!stg->entries[stg->nb_entries]) + return AVERROR(ENOMEM); + + stg->entries[stg->nb_entries]->type = AV_STREAM_GROUP_ENTRY_STREAM_GROUP; + stg->entries[stg->nb_entries++]->stream_group = group; +#if FF_API_STREAM_GROUP_ENTRIES +FF_DISABLE_DEPRECATION_WARNINGS + stg->nb_streams = 0; +FF_ENABLE_DEPRECATION_WARNINGS +#endif + + return 0; +} + +int avformat_stream_group_add_stream_group(AVStreamGroup *stg, AVStreamGroup *group) +{ + const FFStreamGroup *stgi = cffstreamgroup(stg); + const FFStreamGroup *groupi = cffstreamgroup(group); + + if (stgi->fmtctx != groupi->fmtctx) + return AVERROR(EINVAL); + + for (int i = 0; i < stg->nb_entries; i++) { + const AVStreamGroupEntry *entry = stg->entries[i]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM_GROUP) + continue; + if (entry->stream_group->index == group->index) + return AVERROR(EEXIST); + } + + return stream_group_add_stream_group(stg, group); +} + static int option_is_disposition(const AVOption *opt) { return opt->type == AV_OPT_TYPE_CONST && diff --git a/libavformat/version_major.h b/libavformat/version_major.h index f0eaa170b4..9a7955d9e4 100644 --- a/libavformat/version_major.h +++ b/libavformat/version_major.h @@ -49,6 +49,8 @@ #define FF_API_OLD_ID3V2_COMMENT (LIBAVFORMAT_VERSION_MAJOR < 64) +#define FF_API_STREAM_GROUP_ENTRIES (LIBAVFORMAT_VERSION_MAJOR < 64) + #define FF_API_R_FRAME_RATE 1 #endif /* AVFORMAT_VERSION_MAJOR_H */ -- 2.52.0 >From 8d9e643eabde174fbc3685f4d8d39b98d97e2a89 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:20:03 -0300 Subject: [PATCH 02/21] avformat/dashdec: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- libavformat/dashdec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libavformat/dashdec.c b/libavformat/dashdec.c index f3985788f4..a3fec0769d 100644 --- a/libavformat/dashdec.c +++ b/libavformat/dashdec.c @@ -2068,8 +2068,8 @@ static int open_demux_for_component(AVFormatContext *s, struct representation *p stg->id = s->nb_stream_groups; - for (int j = 0; j < istg->nb_streams; j++) { - AVStream *ist = istg->streams[j]; + for (int j = 0; j < istg->nb_entries; j++) { + AVStream *ist = istg->entries[j]->stream; AVStream *st = s->streams[ist->index + pls->stream_index]; ret = avformat_stream_group_add_stream(stg, st); if (ret < 0) @@ -2314,7 +2314,7 @@ static int dash_read_header(AVFormatContext *s) if (ret < 0) return ret; stg->id = stg->index; - stg->params.layered_video->el_index = stg->nb_streams - 1; + stg->params.layered_video->el_index = stg->nb_entries - 1; } return 0; -- 2.52.0 >From d9518361c4a258b0af22fd5047275ee6dfc738ef Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:20:10 -0300 Subject: [PATCH 03/21] avformat/demux: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- libavformat/demux.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libavformat/demux.c b/libavformat/demux.c index 97eaa5f9bc..b259c6d03d 100644 --- a/libavformat/demux.c +++ b/libavformat/demux.c @@ -3186,7 +3186,8 @@ int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options) lcevc = stg->params.layered_video; if (lcevc->width && lcevc->height) continue; - st = stg->streams[lcevc->el_index]; + av_assert0(stg->entries[lcevc->el_index]->type == AV_STREAM_GROUP_ENTRY_STREAM); + st = stg->entries[lcevc->el_index]->stream; lcevc->width = st->codecpar->width; lcevc->height = st->codecpar->height; } -- 2.52.0 >From 486abdeb3ff8f0ac46e6ad9c4d77d91c2768cb93 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 16:04:09 -0300 Subject: [PATCH 04/21] avformat/dump: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- libavformat/dump.c | 84 +++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/libavformat/dump.c b/libavformat/dump.c index d4c7cde2eb..3baa8d5ba3 100644 --- a/libavformat/dump.c +++ b/libavformat/dump.c @@ -711,8 +711,11 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, if (ret >= 0) av_log(NULL, AV_LOG_INFO, " %s", buf); av_log(NULL, AV_LOG_INFO, "\n"); - for (int k = 0; channel_count > 0 && k < stg->nb_streams; k++) { - AVStream *st = stg->streams[k]; + for (int k = 0; channel_count > 0 && k < stg->nb_entries; k++) { + const AVStreamGroupEntry *entry = stg->entries[k]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + const AVStream *st = entry->stream; int loglevel = printed[st->index] ? AV_LOG_VERBOSE : AV_LOG_INFO; av_log(NULL, loglevel, " "); dump_stream_format(ic, st->index, i, index, is_output, loglevel); @@ -769,16 +772,19 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, AVCodecContext *avctx = avcodec_alloc_context3(NULL); const char *ptr = NULL; av_log(NULL, AV_LOG_INFO, " Tile Grid:"); - if (avctx && stg->nb_streams && !avcodec_parameters_to_context(avctx, stg->streams[0]->codecpar)) { - avctx->width = tile_grid->width; - avctx->height = tile_grid->height; - avctx->coded_width = tile_grid->coded_width; - avctx->coded_height = tile_grid->coded_height; - if (ic->dump_separator) - av_opt_set(avctx, "dump_separator", ic->dump_separator, 0); - buf[0] = 0; - avcodec_string(buf, sizeof(buf), avctx, is_output); - ptr = av_stristr(buf, " "); + if (avctx && stg->nb_entries && stg->entries[0]->type == AV_STREAM_GROUP_ENTRY_STREAM) { + const AVStream *st = stg->entries[0]->stream; + if (!avcodec_parameters_to_context(avctx, st->codecpar)) { + avctx->width = tile_grid->width; + avctx->height = tile_grid->height; + avctx->coded_width = tile_grid->coded_width; + avctx->coded_height = tile_grid->coded_height; + if (ic->dump_separator) + av_opt_set(avctx, "dump_separator", ic->dump_separator, 0); + buf[0] = 0; + avcodec_string(buf, sizeof(buf), avctx, is_output); + ptr = av_stristr(buf, " "); + } } avcodec_free_context(&avctx); if (ptr) @@ -791,15 +797,22 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, " ", AV_LOG_INFO); for (int i = 0; i < tile_grid->nb_tiles; i++) { const AVStream *st = NULL; - if (tile_grid->offsets[i].idx < stg->nb_streams) - st = stg->streams[tile_grid->offsets[i].idx]; + if (tile_grid->offsets[i].idx < stg->nb_entries) { + const AVStreamGroupEntry *entry = stg->entries[tile_grid->offsets[i].idx]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + st = entry->stream; + } if (st && !printed[st->index]) { dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_VERBOSE); printed[st->index] = 1; } } - for (int i = 0; i < stg->nb_streams; i++) { - const AVStream *st = stg->streams[i]; + for (int i = 0; i < stg->nb_entries; i++) { + const AVStreamGroupEntry *entry = stg->entries[i]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + const AVStream *st = entry->stream; if (!printed[st->index]) { dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_INFO); printed[st->index] = 1; @@ -814,24 +827,30 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, const char *ptr = NULL; av_log(NULL, AV_LOG_INFO, " %s:", stg->type == AV_STREAM_GROUP_PARAMS_DOLBY_VISION ? "Dolby Vision" : "LCEVC"); - if (avctx && stg->nb_streams == 2 && - !avcodec_parameters_to_context(avctx, stg->streams[!layered_video->el_index]->codecpar)) { - avctx->width = layered_video->width; - avctx->height = layered_video->height; - avctx->coded_width = layered_video->width; - avctx->coded_height = layered_video->height; - if (ic->dump_separator) - av_opt_set(avctx, "dump_separator", ic->dump_separator, 0); - buf[0] = 0; - avcodec_string(buf, sizeof(buf), avctx, is_output); - ptr = av_stristr(buf, " "); + if (avctx && stg->nb_entries == 2 && + stg->entries[!layered_video->el_index]->type == AV_STREAM_GROUP_ENTRY_STREAM) { + const AVStream *st = stg->entries[!layered_video->el_index]->stream; + if (!avcodec_parameters_to_context(avctx, st->codecpar)) { + avctx->width = layered_video->width; + avctx->height = layered_video->height; + avctx->coded_width = layered_video->width; + avctx->coded_height = layered_video->height; + if (ic->dump_separator) + av_opt_set(avctx, "dump_separator", ic->dump_separator, 0); + buf[0] = 0; + avcodec_string(buf, sizeof(buf), avctx, is_output); + ptr = av_stristr(buf, " "); + } } avcodec_free_context(&avctx); if (ptr) av_log(NULL, AV_LOG_INFO, "%s", ptr); av_log(NULL, AV_LOG_INFO, "\n"); - for (int i = 0; i < stg->nb_streams; i++) { - const AVStream *st = stg->streams[i]; + for (int i = 0; i < stg->nb_entries; i++) { + const AVStreamGroupEntry *entry = stg->entries[i]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + const AVStream *st = entry->stream; dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_VERBOSE); printed[st->index] = 1; } @@ -839,8 +858,11 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, } case AV_STREAM_GROUP_PARAMS_TREF: { av_log(NULL, AV_LOG_INFO, " Track Reference:\n"); - for (int i = 0; i < stg->nb_streams; i++) { - const AVStream *st = stg->streams[i]; + for (int i = 0; i < stg->nb_entries; i++) { + const AVStreamGroupEntry *entry = stg->entries[i]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + const AVStream *st = entry->stream; dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_INFO); printed[st->index] = 1; } -- 2.52.0 >From a71c4c451951840786299b527c0a03f7d92f734c Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:20:23 -0300 Subject: [PATCH 05/21] avformat/iamf: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- libavformat/iamf_writer.c | 34 +++++++++++++++++++++++++--------- libavformat/iamfdec.c | 4 ++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/libavformat/iamf_writer.c b/libavformat/iamf_writer.c index 15f83b85b0..98d09bfe01 100644 --- a/libavformat/iamf_writer.c +++ b/libavformat/iamf_writer.c @@ -108,7 +108,7 @@ static int populate_audio_roll_distance(IAMFCodecConfig *codec_config) static int fill_codec_config(IAMFContext *iamf, const AVStreamGroup *stg, IAMFCodecConfig *codec_config, void *log_ctx) { - const AVStream *st = stg->streams[0]; + const AVStream *st = stg->entries[0]->stream; IAMFCodecConfig **tmp; int j, ret = 0; @@ -229,10 +229,18 @@ int ff_iamf_add_audio_element(IAMFContext *iamf, const AVStreamGroup *stg, void if (stg->type != AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT) return AVERROR(EINVAL); - if (!stg->nb_streams) { + if (!stg->nb_entries) { av_log(log_ctx, AV_LOG_ERROR, "Audio Element id %"PRId64" has no streams\n", stg->id); return AVERROR(EINVAL); } + for (int i = 0; i < stg->nb_entries; i++) { + if (stg->entries[i]->type != AV_STREAM_GROUP_ENTRY_STREAM) { + av_log(log_ctx, AV_LOG_ERROR, "Unsupported Stream Group entry #%d " + "in Audio Element id %"PRId64"\n", + i, stg->id); + return AVERROR(EINVAL); + } + } iamf_audio_element = stg->params.iamf_audio_element; if (iamf_audio_element->audio_element_type == AV_IAMF_AUDIO_ELEMENT_TYPE_SCENE) { @@ -250,8 +258,8 @@ int ff_iamf_add_audio_element(IAMFContext *iamf, const AVStreamGroup *stg, void av_log(log_ctx, AV_LOG_ERROR, "Unsupported ambisonics mode %d\n", layer->ambisonics_mode); return AVERROR_PATCHWELCOME; } - for (int i = 0; i < stg->nb_streams; i++) { - if (stg->streams[i]->codecpar->ch_layout.nb_channels > 1) { + for (int i = 0; i < stg->nb_entries; i++) { + if (stg->entries[i]->stream->codecpar->ch_layout.nb_channels > 1) { av_log(log_ctx, AV_LOG_ERROR, "Invalid amount of channels in a stream for MONO mode ambisonics\n"); return AVERROR(EINVAL); } @@ -334,12 +342,12 @@ int ff_iamf_add_audio_element(IAMFContext *iamf, const AVStreamGroup *stg, void audio_element->audio_element_id = stg->id; audio_element->codec_config_id = ret; - audio_element->substreams = av_calloc(stg->nb_streams, sizeof(*audio_element->substreams)); + audio_element->substreams = av_calloc(stg->nb_entries, sizeof(*audio_element->substreams)); if (!audio_element->substreams) { ret = AVERROR(ENOMEM); goto fail; } - audio_element->nb_substreams = stg->nb_streams; + audio_element->nb_substreams = stg->nb_entries; audio_element->layers = av_calloc(iamf_audio_element->nb_layers, sizeof(*audio_element->layers)); if (!audio_element->layers) { @@ -354,8 +362,8 @@ int ff_iamf_add_audio_element(IAMFContext *iamf, const AVStreamGroup *stg, void if (i) nb_channels -= iamf_audio_element->layers[i - 1]->ch_layout.nb_channels; - for (; nb_channels > 0 && j < stg->nb_streams; j++) { - const AVStream *st = stg->streams[j]; + for (; nb_channels > 0 && j < stg->nb_entries; j++) { + const AVStream *st = stg->entries[j]->stream; IAMFSubStream *substream = &audio_element->substreams[j]; substream->audio_substream_id = st->id; @@ -437,10 +445,18 @@ int ff_iamf_add_mix_presentation(IAMFContext *iamf, const AVStreamGroup *stg, vo if (stg->type != AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION) return AVERROR(EINVAL); - if (!stg->nb_streams) { + if (!stg->nb_entries) { av_log(log_ctx, AV_LOG_ERROR, "Mix Presentation id %"PRId64" has no streams\n", stg->id); return AVERROR(EINVAL); } + for (int i = 0; i < stg->nb_entries; i++) { + if (stg->entries[i]->type != AV_STREAM_GROUP_ENTRY_STREAM) { + av_log(log_ctx, AV_LOG_ERROR, "Unsupported Stream Group entry #%d " + "in Mix Presentation id %"PRId64"\n", + i, stg->id); + return AVERROR(EINVAL); + } + } for (int i = 0; i < iamf->nb_mix_presentations; i++) { if (stg->id == iamf->mix_presentations[i]->mix_presentation_id) { diff --git a/libavformat/iamfdec.c b/libavformat/iamfdec.c index 02f01abc6c..66870094f6 100644 --- a/libavformat/iamfdec.c +++ b/libavformat/iamfdec.c @@ -145,8 +145,8 @@ static int iamf_read_header(AVFormatContext *s) } av_assert0(audio_element); - for (int l = 0; l < audio_element->nb_streams; l++) { - ret = avformat_stream_group_add_stream(stg, audio_element->streams[l]); + for (int l = 0; l < audio_element->nb_entries; l++) { + ret = avformat_stream_group_add_stream(stg, audio_element->entries[l]->stream); if (ret < 0 && ret != AVERROR(EEXIST)) return ret; } -- 2.52.0 >From d026aed3296ebd12d7599035f070576e2c12de72 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:35:21 -0300 Subject: [PATCH 06/21] avformat/jpegmpfenc: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- libavformat/jpegmpfenc.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libavformat/jpegmpfenc.c b/libavformat/jpegmpfenc.c index 44d2a3b697..7cceb331a7 100644 --- a/libavformat/jpegmpfenc.c +++ b/libavformat/jpegmpfenc.c @@ -132,11 +132,15 @@ static int init_gain_map(AVFormatContext *ctx, const AVStreamGroup *sg) { MPFMuxContext *s = ctx->priv_data; const AVStreamGroupLayeredVideo *lv = sg->params.layered_video; - if (sg->nb_streams != 2 || lv->el_index >= sg->nb_streams) + if (sg->nb_entries != 2 || lv->el_index >= sg->nb_entries) return AVERROR(EINVAL); - const AVStream *bl = sg->streams[!lv->el_index]; - const AVStream *el = sg->streams[lv->el_index]; + if (sg->entries[!lv->el_index]->type != AV_STREAM_GROUP_ENTRY_STREAM || + sg->entries[ lv->el_index]->type != AV_STREAM_GROUP_ENTRY_STREAM) + return AVERROR(EINVAL); + + const AVStream *bl = sg->entries[!lv->el_index]->stream; + const AVStream *el = sg->entries[lv->el_index]->stream; MPFImage *base = &s->images[bl->index]; MPFImage *gain = &s->images[el->index]; -- 2.52.0 >From 697be9d6a1560ea900fc2e561af88d2179298d44 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:35:57 -0300 Subject: [PATCH 07/21] avformat/matroskadec: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- libavformat/matroskadec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/matroskadec.c b/libavformat/matroskadec.c index bceb4f7e17..b793f67fa0 100644 --- a/libavformat/matroskadec.c +++ b/libavformat/matroskadec.c @@ -3426,7 +3426,7 @@ static int matroska_parse_dovi_streams(AVFormatContext *s) if (err < 0) return err; - stg->params.layered_video->el_index = stg->nb_streams - 1; + stg->params.layered_video->el_index = stg->nb_entries - 1; stg->params.layered_video->width = bl_st->codecpar->width; stg->params.layered_video->height = bl_st->codecpar->height; -- 2.52.0 >From 2c140fbb51bf7794b89d694dcd9974ae41ccdc8b Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:04:42 -0300 Subject: [PATCH 08/21] avformat/mov: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- libavformat/mov.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/libavformat/mov.c b/libavformat/mov.c index 71c4e38c88..f350658d7a 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -1070,8 +1070,8 @@ static int mov_read_iacb(MOVContext *c, AVIOContext *pb, MOVAtom atom) } av_assert0(audio_element); - for (int l = 0; l < audio_element->nb_streams; l++) { - ret = avformat_stream_group_add_stream(stg, audio_element->streams[l]); + for (int l = 0; l < audio_element->nb_entries; l++) { + ret = avformat_stream_group_add_stream(stg, audio_element->entries[l]->stream); if (ret < 0 && ret != AVERROR(EEXIST)) goto fail; } @@ -5501,10 +5501,10 @@ static int mov_update_iamf_streams(MOVContext *c, const AVStream *st) stg = c->fc->stream_groups[j]; av_assert0(stg); - for (int j = 0; j < stg->nb_streams; j++) { + for (int j = 0; j < stg->nb_entries; j++) { const FFStream *sti = cffstream(st); - AVStream *out = stg->streams[j]; - FFStream *out_sti = ffstream(stg->streams[j]); + AVStream *out = stg->entries[j]->stream; + FFStream *out_sti = ffstream(stg->entries[j]->stream); out->codecpar->bit_rate = 0; @@ -11209,7 +11209,7 @@ static int mov_parse_tiles(AVFormatContext *s) } grid->tile_item_list[j] = item; - grid->tile_idx_list[j] = stg->nb_streams; + grid->tile_idx_list[j] = stg->nb_entries; err = avformat_stream_group_add_stream(stg, st); if (err < 0) { @@ -11217,10 +11217,10 @@ static int mov_parse_tiles(AVFormatContext *s) if (err != AVERROR(EEXIST)) return err; - for (l = 0; l < stg->nb_streams; l++) - if (stg->streams[l]->index == st->index) + for (l = 0; l < stg->nb_entries; l++) + if (stg->entries[l]->stream->index == st->index) break; - av_assert0(l < stg->nb_streams); + av_assert0(l < stg->nb_entries); grid->tile_idx_list[j] = l; } @@ -11417,7 +11417,7 @@ static int mov_parse_tmcd_streams(AVFormatContext *s) } } - if (!stg->nb_streams) { + if (!stg->nb_entries) { ff_remove_stream_group(s, stg); continue; } @@ -11426,7 +11426,7 @@ static int mov_parse_tmcd_streams(AVFormatContext *s) if (err < 0) return err; - stg->params.tref->metadata_index = stg->nb_streams - 1; + stg->params.tref->metadata_index = stg->nb_entries - 1; } export_orphan_timecode(s); @@ -11493,7 +11493,7 @@ static int mov_parse_cdsc_and_rndr_streams(AVFormatContext *s) if (err < 0) return err; - stg->params.tref->metadata_index = stg->nb_streams - 1; + stg->params.tref->metadata_index = stg->nb_entries - 1; } } @@ -11548,7 +11548,7 @@ static int mov_parse_lcevc_streams(AVFormatContext *s) if (err < 0) return err; - stg->params.layered_video->el_index = stg->nb_streams - 1; + stg->params.layered_video->el_index = stg->nb_entries - 1; } return 0; @@ -11619,7 +11619,7 @@ static int mov_parse_dovi_streams(AVFormatContext *s) if (err < 0) return err; - stg->params.layered_video->el_index = stg->nb_streams - 1; + stg->params.layered_video->el_index = stg->nb_entries - 1; stg->params.layered_video->width = st_base->codecpar->width; stg->params.layered_video->height = st_base->codecpar->height; } @@ -11642,8 +11642,8 @@ static void fix_stream_ids(AVFormatContext *s) AVStreamGroup *stg = s->stream_groups[i]; if (stg->type != AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT) continue; - for (int j = 0; j < stg->nb_streams; j++) { - AVStream *st = stg->streams[j]; + for (int j = 0; j < stg->nb_entries; j++) { + AVStream *st = stg->entries[j]->stream; lowest_iamf_id = FFMIN(lowest_iamf_id, st->id); } } @@ -11656,8 +11656,8 @@ static void fix_stream_ids(AVFormatContext *s) AVStreamGroup *stg = s->stream_groups[i]; if (stg->type != AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT) continue; - for (int j = 0; j < stg->nb_streams; j++) { - AVStream *st = stg->streams[j]; + for (int j = 0; j < stg->nb_entries; j++) { + AVStream *st = stg->entries[j]->stream; MOVStreamContext *sc = st->priv_data; st->id += highest_id; sc->iamf_stream_offset = highest_id; -- 2.52.0 >From f7a5befe91e77c5d20c9f3a5b42615b9f7a93430 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:33:14 -0300 Subject: [PATCH 09/21] avformat/movenc: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- libavformat/movenc.c | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 6492f4ffb3..7cdbf6bd59 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -8228,9 +8228,9 @@ static int mov_init_iamf_track(AVFormatContext *s) const AVStreamGroup *stg = s->stream_groups[i]; switch(stg->type) { case AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT: - for (int j = 0; j < stg->nb_streams; j++) { - first_iamf_idx = FFMIN(stg->streams[j]->index, first_iamf_idx); - last_iamf_idx = FFMAX(stg->streams[j]->index, last_iamf_idx); + for (int j = 0; j < stg->nb_entries; j++) { + first_iamf_idx = FFMIN(stg->entries[j]->stream->index, first_iamf_idx); + last_iamf_idx = FFMAX(stg->entries[j]->stream->index, last_iamf_idx); } ret = ff_iamf_add_audio_element(iamf, stg, s); @@ -8258,8 +8258,8 @@ static int mov_init_iamf_track(AVFormatContext *s) AVStreamGroup *stg = s->stream_groups[i]; if (stg->type != AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT) continue; - for (int j = 0; j < stg->nb_streams; j++) - stg->streams[j]->priv_data = track; + for (int j = 0; j < stg->nb_entries; j++) + stg->entries[j]->stream->priv_data = track; } ret = avio_open_dyn_buf(&track->iamf_buf); @@ -8415,15 +8415,24 @@ static int mov_init(AVFormatContext *s) for (i = 0; i < s->nb_stream_groups; i++) { AVStreamGroup *stg = s->stream_groups[i]; + for (int j = 0; j < stg->nb_entries; j++) { + if (stg->entries[j]->type != AV_STREAM_GROUP_ENTRY_STREAM) { + av_log(s, AV_LOG_ERROR, "Unsupported Stream Group entry #%d " + "in Stream Group id %"PRId64"\n", + i, stg->id); + return AVERROR(EINVAL); + } + } + if (stg->type == AV_STREAM_GROUP_PARAMS_LCEVC) { - if (stg->nb_streams != 2) { + if (stg->nb_entries != 2) { av_log(s, AV_LOG_ERROR, "Exactly two Streams are supported for Stream Groups of type LCEVC\n"); return AVERROR(EINVAL); } AVStreamGroupLayeredVideo *lcevc = stg->params.layered_video; if (lcevc->el_index > 1) return AVERROR(EINVAL); - AVStream *st = stg->streams[lcevc->el_index]; + AVStream *st = stg->entries[lcevc->el_index]->stream; if (st->codecpar->codec_id != AV_CODEC_ID_LCEVC) { av_log(s, AV_LOG_ERROR, "Stream #%u is not an LCEVC stream\n", lcevc->el_index); return AVERROR(EINVAL); @@ -8434,8 +8443,8 @@ static int mov_init(AVFormatContext *s) if (stg->type != AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT) continue; - for (int j = 0; j < stg->nb_streams; j++) { - AVStream *st = stg->streams[j]; + for (int j = 0; j < stg->nb_entries; j++) { + AVStream *st = stg->entries[j]->stream; if (st->priv_data) { av_log(s, AV_LOG_ERROR, "Stream %d is present in more than one Stream Group of type " @@ -8790,13 +8799,13 @@ static int mov_init(AVFormatContext *s) switch (stg->type) { case AV_STREAM_GROUP_PARAMS_LCEVC: { AVStreamGroupLayeredVideo *lcevc = stg->params.layered_video; - AVStream *st = stg->streams[lcevc->el_index]; + AVStream *st = stg->entries[lcevc->el_index]->stream; 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->el_index]) { + if (trk->st == stg->entries[!lcevc->el_index]->stream) { track->src_track = av_malloc(sizeof(*track->src_track)); if (!track->src_track) return AVERROR(ENOMEM); @@ -8814,12 +8823,12 @@ static int mov_init(AVFormatContext *s) const AVStreamGroupTREF *tref = stg->params.tref; MOVTrack *track; - if (tref->metadata_index >= stg->nb_streams) + if (tref->metadata_index >= stg->nb_entries) return AVERROR(EINVAL); - track = stg->streams[tref->metadata_index]->priv_data; - for (int j = 0; j < stg->nb_streams; j++) { - const AVStream *st2 = stg->streams[j]; + track = stg->entries[tref->metadata_index]->stream->priv_data; + for (int j = 0; j < stg->nb_entries; j++) { + const AVStream *st2 = stg->entries[j]->stream; int index = -1; if (j == tref->metadata_index) -- 2.52.0 >From bc51bbfb60af8934edb9d98360d34054e84d3923 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:06:23 -0300 Subject: [PATCH 10/21] avformat/mpegts: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- libavformat/mpegts.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c index a1b6065320..c013452e97 100644 --- a/libavformat/mpegts.c +++ b/libavformat/mpegts.c @@ -2720,7 +2720,7 @@ static void create_stream_groups(MpegTSContext *ts, struct Program *prg) switch (grp->type) { case AV_STREAM_GROUP_PARAMS_LCEVC: if (grp->streams[j]->codecpar->codec_id == AV_CODEC_ID_LCEVC) - stg->params.layered_video->el_index = stg->nb_streams - 1; + stg->params.layered_video->el_index = stg->nb_entries - 1; break; case AV_STREAM_GROUP_PARAMS_DOLBY_VISION: if (j == 0) { @@ -2728,7 +2728,7 @@ static void create_stream_groups(MpegTSContext *ts, struct Program *prg) stg->params.layered_video->height = grp->streams[j]->codecpar->height; } if (j == grp->nb_streams - 1) - stg->params.layered_video->el_index = stg->nb_streams - 1; + stg->params.layered_video->el_index = stg->nb_entries - 1; break; default: av_unreachable("Invalid group type!"); -- 2.52.0 >From 3ab47d1556b46bd60361146cb01c46aff3c8a67b Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:58:19 -0300 Subject: [PATCH 11/21] fftools/ffmpeg: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- fftools/cmdutils.c | 7 +++++-- fftools/ffmpeg_demux.c | 26 ++++++++++++++++++-------- fftools/ffmpeg_mux_init.c | 11 +++++++---- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/fftools/cmdutils.c b/fftools/cmdutils.c index c535f578a7..116988e5a9 100644 --- a/fftools/cmdutils.c +++ b/fftools/cmdutils.c @@ -1278,13 +1278,16 @@ unsigned stream_specifier_match(const StreamSpecifier *ss, ss->list_id); return 0; } - nb_streams = g->nb_streams; + nb_streams = g->nb_entries; break; default: av_assert0(0); } for (int i = start_stream; i < nb_streams; i++) { - const AVStream *candidate = s->streams[g ? g->streams[i]->index : + if (g && g->entries[i]->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + + const AVStream *candidate = s->streams[g ? g->entries[i]->stream->index : p ? p->stream_index[i] : i]; if (ss->media_type != AVMEDIA_TYPE_UNKNOWN && diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c index 8ae5486582..63c13e6918 100644 --- a/fftools/ffmpeg_demux.c +++ b/fftools/ffmpeg_demux.c @@ -617,7 +617,7 @@ static int do_bsf_graph(Demuxer *d, DemuxThreadContext *dt, DemuxStreamGroup *ds { const AVStreamGroup *stg = dsg->istg.stg; const AVStreamGroupLayeredVideo *lcevc = stg->params.layered_video; - const int enhancement = ds->ist.index == stg->streams[lcevc->el_index]->index; + const int enhancement = ds->ist.index == stg->entries[lcevc->el_index]->stream->index; AVBitStreamFilterContext *source = dsg->lcevc[enhancement]; int ret, flags = AV_BSF_SOURCE_FLAG_PUSH; @@ -1097,7 +1097,7 @@ int ist_use(InputStream *ist, int decoding_needed, if (!dsg->graph || istg->stg->type != AV_STREAM_GROUP_PARAMS_LCEVC) continue; const AVStreamGroupLayeredVideo *lcevc = istg->stg->params.layered_video; - if (ist->st->index == istg->stg->streams[lcevc->el_index]->index) + if (ist->st->index == istg->stg->entries[lcevc->el_index]->stream->index) break; AVBitStreamFilterContext *lcevc_merge = av_bsf_graph_get_filter(dsg->graph, "lcevc_merge"); @@ -1129,7 +1129,7 @@ int ist_use(InputStream *ist, int decoding_needed, if (ret < 0) return ret; - InputStream *lcevc_ist = d->f.streams[istg->stg->streams[lcevc->el_index]->index]; + InputStream *lcevc_ist = d->f.streams[istg->stg->entries[lcevc->el_index]->stream->index]; lcevc_ist->st->discard = 0; dsg->graph_enabled = 1; @@ -2032,11 +2032,11 @@ static int istg_parse_lcevc(const OptionsContext *o, Demuxer *d, DemuxStreamGrou const AVStreamGroup *stg = dsg->istg.stg; const AVStreamGroupLayeredVideo *lcevc = stg->params.layered_video; const AVBitStreamFilter *filter, *lcevc_filter = av_bsf_get_by_name("lcevc_merge"); - const InputStream *lcevc_ist = f->streams[stg->streams[lcevc->el_index]->index]; - const InputStream *base_ist = f->streams[stg->streams[!lcevc->el_index]->index]; + const InputStream *lcevc_ist = f->streams[stg->entries[lcevc->el_index]->stream->index]; + const InputStream *base_ist = f->streams[stg->entries[!lcevc->el_index]->stream->index]; int ret; - if (!lcevc_filter || stg->nb_streams != 2) + if (!lcevc_filter || stg->nb_entries != 2) return 0; dsg->graph = av_bsf_graph_alloc(); @@ -2092,13 +2092,23 @@ static int istg_add(const OptionsContext *o, Demuxer *d, AVStreamGroup *stg) switch (stg->type) { case AV_STREAM_GROUP_PARAMS_LCEVC: - for (int i = 0; i < istg->stg->nb_streams; i++) { - DemuxStream *ds = ds_from_ist(f->streams[istg->stg->streams[i]->index]); + for (int i = 0; i < istg->stg->nb_entries; i++) { + AVStreamGroupEntry *entry = istg->stg->entries[i]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) + return AVERROR_BUG; + DemuxStream *ds = ds_from_ist(f->streams[entry->stream->index]); ret = av_dynarray_add_nofree(&ds->dsg, &ds->nb_dsg, dsg); if (ret < 0) return ret; } break; + case AV_STREAM_GROUP_PARAMS_TILE_GRID: + for (int i = 0; i < istg->stg->nb_entries; i++) { + AVStreamGroupEntry *entry = istg->stg->entries[i]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) + return AVERROR_BUG; + } + break; default: break; } diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c index aef27e2a8f..34dad2d9e4 100644 --- a/fftools/ffmpeg_mux_init.c +++ b/fftools/ffmpeg_mux_init.c @@ -1685,8 +1685,11 @@ static int map_auto_video(Muxer *mux, const OptionsContext *o) if (!istg->fg) continue; - for (int j = 0; j < istg->stg->nb_streams; j++) { - AVStream *st = istg->stg->streams[j]; + for (int j = 0; j < istg->stg->nb_entries; j++) { + if (istg->stg->entries[j]->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + + AVStream *st = istg->stg->entries[j]->stream; if (st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) { score = 100000000; @@ -2733,8 +2736,8 @@ static int of_parse_group_token(Muxer *mux, const char *token, char *ptr) ret = AVERROR(EINVAL); goto end; } - for (unsigned i = 0; i < oc->stream_groups[idx]->nb_streams; i++) { - ret = avformat_stream_group_add_stream(stg, oc->stream_groups[idx]->streams[i]); + for (unsigned i = 0; i < oc->stream_groups[idx]->nb_entries; i++) { + ret = avformat_stream_group_add_stream(stg, oc->stream_groups[idx]->entries[i]->stream); if (ret < 0) goto end; } -- 2.52.0 >From 94148f110bd9fc553e584c65cf55d1dcdd67063e Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 18:58:15 -0300 Subject: [PATCH 12/21] fftools/ffplay: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- fftools/ffplay.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/fftools/ffplay.c b/fftools/ffplay.c index c9369d6a72..bd64794b65 100644 --- a/fftools/ffplay.c +++ b/fftools/ffplay.c @@ -1862,9 +1862,9 @@ static void uninit_bsf_graph(AVFormatContext *ic, int stream_index) av_bsf_graph_free(&stgi->graph); stgi->sink = NULL; - for (int i = 0; i < stgi->stg->nb_streams; i++) { - ici->streams[stgi->stg->streams[i]->index]->filter = NULL; - ici->streams[stgi->stg->streams[i]->index]->group = NULL; + for (int i = 0; i < stgi->stg->nb_entries; i++) { + ici->streams[stgi->stg->entries[i]->stream->index]->filter = NULL; + ici->streams[stgi->stg->entries[i]->stream->index]->group = NULL; } } sti->group = stgi; @@ -3341,7 +3341,7 @@ static int init_lcevc_graph(AVFormatContext *ic, int stream_index) const AVStreamGroupLayeredVideo *lcevc = stg->params.layered_video; AVStream *base_st = ic->streams[stream_index]; - AVStream *lcevc_st = stg->streams[lcevc->el_index]; + AVStream *lcevc_st = stg->entries[lcevc->el_index]->stream; Stream *lcevc_sti = ici->streams[lcevc_st->index]; Stream *base_sti = ici->streams[stream_index]; @@ -3831,11 +3831,18 @@ static int read_thread(void *arg) if (stg->type != AV_STREAM_GROUP_PARAMS_LCEVC) continue; - if (!av_bsf_get_by_name("lcevc_merge") || stg->nb_streams != 2) + if (!av_bsf_get_by_name("lcevc_merge") || stg->nb_entries != 2) continue; + for (int j = 0; j < stg->nb_entries; j++) { + AVStreamGroupEntry *entry = stg->entries[j]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) { + ret = AVERROR_BUG; + goto fail; + } + } const AVStreamGroupLayeredVideo *lcevc = stg->params.layered_video; - const AVStream *base_st = ic->streams[stg->streams[!lcevc->el_index]->index]; + const AVStream *base_st = ic->streams[stg->entries[!lcevc->el_index]->stream->index]; Stream *base_sti = ici->streams[base_st->index]; base_sti->group = ici->stream_groups[i]; -- 2.52.0 >From d05ab89354e0e00647f68f1d239ae155479f759e Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 19:02:57 -0300 Subject: [PATCH 13/21] fftools/ffprobe: stop using deprecated AVStreamGroup fields Signed-off-by: James Almer <[email protected]> --- fftools/ffprobe.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c index 3051fe43b5..f250629f68 100644 --- a/fftools/ffprobe.c +++ b/fftools/ffprobe.c @@ -2385,7 +2385,7 @@ static int show_stream_group(AVTextFormatContext *tfc, InputFile *ifile, AVStrea print_int("index", stg->index); if (fmt_ctx->iformat->flags & AVFMT_SHOW_IDS) print_fmt ("id", "0x%"PRIx64, stg->id); else print_str_opt("id", "N/A"); - print_int("nb_streams", stg->nb_streams); + print_int("nb_streams", stg->nb_entries); if (stg->type != AV_STREAM_GROUP_PARAMS_NONE) print_str("type", av_x_if_null(avformat_stream_group_name(stg->type), "unknown")); else @@ -2403,9 +2403,12 @@ static int show_stream_group(AVTextFormatContext *tfc, InputFile *ifile, AVStrea goto end; avtext_print_section_header(tfc, NULL, SECTION_ID_STREAM_GROUP_STREAMS); - for (unsigned i = 0; i < stg->nb_streams; i++) { - if (selected_streams[stg->streams[i]->index]) { - ret = show_stream(tfc, fmt_ctx, stg->streams[i]->index, &ifile->streams[stg->streams[i]->index], IN_STREAM_GROUP); + for (unsigned i = 0; i < stg->nb_entries; i++) { + AVStreamGroupEntry *entry = stg->entries[i]; + if (stg->entries[i]->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + if (selected_streams[stg->entries[i]->stream->index]) { + ret = show_stream(tfc, fmt_ctx, entry->stream->index, &ifile->streams[entry->stream->index], IN_STREAM_GROUP); if (ret < 0) break; } -- 2.52.0 >From 5c10ad089c6114e1026b03cba266616308a9178a Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 2 Oct 2026 16:31:57 +0200 Subject: [PATCH 14/21] avformat/dump: add AV_STREAM_GROUP_PARAMS_GAIN_MAP These were introduced by caa5824750, but the corresponding printout was never added; leading to them being printed with a bare label. Signed-off-by: Niklas Haas <[email protected]> --- libavformat/dump.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libavformat/dump.c b/libavformat/dump.c index 3baa8d5ba3..a95e49a9fa 100644 --- a/libavformat/dump.c +++ b/libavformat/dump.c @@ -868,6 +868,18 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, } break; } + case AV_STREAM_GROUP_PARAMS_GAIN_MAP: { + av_log(NULL, AV_LOG_INFO, " Gain Map:\n"); + for (int i = 0; i < stg->nb_entries; i++) { + const AVStreamGroupEntry *entry = stg->entries[i]; + if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) + continue; + const AVStream *st = entry->stream; + dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_INFO); + printed[st->index] = 1; + } + break; + } default: break; } -- 2.52.0 >From 545d094e07bccf9151a2403e42b164aab83e8699 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 16:08:06 -0300 Subject: [PATCH 15/21] avformat/dump: Support printing child groups within a group Only for Layered Video of Gain Map type for now. Signed-off-by: James Almer <[email protected]> --- libavformat/dump.c | 108 +++++++++++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 43 deletions(-) diff --git a/libavformat/dump.c b/libavformat/dump.c index a95e49a9fa..d56a4e22b4 100644 --- a/libavformat/dump.c +++ b/libavformat/dump.c @@ -684,34 +684,36 @@ static void dump_stream_format(const AVFormatContext *ic, int i, } static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, - int i, int index, int is_output) + int i, int index, int is_output, int log_level) { const AVStreamGroup *stg = ic->stream_groups[i]; + const char *group_indent = log_level != AV_LOG_INFO ? " " : ""; + const char *extra_indent = log_level != AV_LOG_INFO ? " " : " "; int flags = (is_output ? ic->oformat->flags : ic->iformat->flags); char buf[512]; int ret; - av_log(NULL, AV_LOG_INFO, " Stream group #%d:%d", index, i); + av_log(NULL, log_level, "%s Stream group #%d:%d", group_indent, index, i); if (flags & AVFMT_SHOW_IDS) - av_log(NULL, AV_LOG_INFO, "[0x%"PRIx64"]", stg->id); - av_log(NULL, AV_LOG_INFO, ":"); + av_log(NULL, log_level, "[0x%"PRIx64"]", stg->id); + av_log(NULL, log_level, ":"); switch (stg->type) { case AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT: { const AVIAMFAudioElement *audio_element = stg->params.iamf_audio_element; - av_log(NULL, AV_LOG_INFO, " IAMF Audio Element:"); - dump_disposition(stg->disposition, AV_LOG_INFO); - av_log(NULL, AV_LOG_INFO, "\n"); - dump_metadata(NULL, stg->metadata, " ", AV_LOG_INFO); + av_log(NULL, log_level, " IAMF Audio Element:"); + dump_disposition(stg->disposition, log_level); + av_log(NULL, log_level, "\n"); + dump_metadata(NULL, stg->metadata, " ", log_level); for (int j = 0; j < audio_element->nb_layers; j++) { const AVIAMFLayer *layer = audio_element->layers[j]; int channel_count = layer->ch_layout.nb_channels; - av_log(NULL, AV_LOG_INFO, " Layer %d:", j); + av_log(NULL, log_level, " Layer %d:", j); ret = av_channel_layout_describe(&layer->ch_layout, buf, sizeof(buf)); if (ret >= 0) - av_log(NULL, AV_LOG_INFO, " %s", buf); - av_log(NULL, AV_LOG_INFO, "\n"); - for (int k = 0; channel_count > 0 && k < stg->nb_entries; k++) { + av_log(NULL, log_level, " %s", buf); + av_log(NULL, log_level, "\n"); + for (int k = 0; log_level == AV_LOG_INFO && channel_count > 0 && k < stg->nb_entries; k++) { const AVStreamGroupEntry *entry = stg->entries[k]; if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) continue; @@ -727,14 +729,14 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, } case AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION: { const AVIAMFMixPresentation *mix_presentation = stg->params.iamf_mix_presentation; - av_log(NULL, AV_LOG_INFO, " IAMF Mix Presentation:"); - dump_disposition(stg->disposition, AV_LOG_INFO); - av_log(NULL, AV_LOG_INFO, "\n"); - dump_metadata(NULL, stg->metadata, " ", AV_LOG_INFO); - dump_dictionary(NULL, mix_presentation->annotations, "Annotations", " ", AV_LOG_INFO); + av_log(NULL, log_level, " IAMF Mix Presentation:"); + dump_disposition(stg->disposition, log_level); + av_log(NULL, log_level, "\n"); + dump_metadata(NULL, stg->metadata, " ", log_level); + dump_dictionary(NULL, mix_presentation->annotations, "Annotations", " ", log_level); for (int j = 0; j < mix_presentation->nb_submixes; j++) { AVIAMFSubmix *sub_mix = mix_presentation->submixes[j]; - av_log(NULL, AV_LOG_INFO, " Submix %d:\n", j); + av_log(NULL, log_level, " Submix %d:\n", j); for (int k = 0; k < sub_mix->nb_elements; k++) { const AVIAMFSubmixElement *submix_element = sub_mix->elements[k]; const AVStreamGroup *audio_element = NULL; @@ -745,24 +747,24 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, break; } if (audio_element) { - av_log(NULL, AV_LOG_INFO, " IAMF Audio Element #%d:%d", + av_log(NULL, log_level, " IAMF Audio Element #%d:%d", index, audio_element->index); if (flags & AVFMT_SHOW_IDS) - av_log(NULL, AV_LOG_INFO, "[0x%"PRIx64"]", audio_element->id); - av_log(NULL, AV_LOG_INFO, "\n"); - dump_dictionary(NULL, submix_element->annotations, "Annotations", " ", AV_LOG_INFO); + av_log(NULL, log_level, "[0x%"PRIx64"]", audio_element->id); + av_log(NULL, log_level, "\n"); + dump_dictionary(NULL, submix_element->annotations, "Annotations", " ", log_level); } } for (int k = 0; k < sub_mix->nb_layouts; k++) { const AVIAMFSubmixLayout *submix_layout = sub_mix->layouts[k]; - av_log(NULL, AV_LOG_INFO, " Layout #%d:", k); + av_log(NULL, log_level, " Layout #%d:", k); if (submix_layout->layout_type == 2 || submix_layout->layout_type == 3) { ret = av_channel_layout_describe(&submix_layout->sound_system, buf, sizeof(buf)); if (ret >= 0) - av_log(NULL, AV_LOG_INFO, " %s", buf); + av_log(NULL, log_level, " %s", buf); } - av_log(NULL, AV_LOG_INFO, "\n"); + av_log(NULL, log_level, "\n"); } } break; @@ -771,7 +773,7 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, const AVStreamGroupTileGrid *tile_grid = stg->params.tile_grid; AVCodecContext *avctx = avcodec_alloc_context3(NULL); const char *ptr = NULL; - av_log(NULL, AV_LOG_INFO, " Tile Grid:"); + av_log(NULL, log_level, " Tile Grid:"); if (avctx && stg->nb_entries && stg->entries[0]->type == AV_STREAM_GROUP_ENTRY_STREAM) { const AVStream *st = stg->entries[0]->stream; if (!avcodec_parameters_to_context(avctx, st->codecpar)) { @@ -788,13 +790,13 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, } avcodec_free_context(&avctx); if (ptr) - av_log(NULL, AV_LOG_INFO, "%s", ptr); - dump_disposition(stg->disposition, AV_LOG_INFO); - av_log(NULL, AV_LOG_INFO, "\n"); - dump_metadata(NULL, stg->metadata, " ", AV_LOG_INFO); + av_log(NULL, log_level, "%s", ptr); + dump_disposition(stg->disposition, log_level); + av_log(NULL, log_level, "\n"); + dump_metadata(NULL, stg->metadata, extra_indent, log_level); dump_sidedata(NULL, tile_grid->coded_side_data, tile_grid->nb_coded_side_data, tile_grid->width, tile_grid->height, (AVRational) {0,1}, - " ", AV_LOG_INFO); + extra_indent, log_level); for (int i = 0; i < tile_grid->nb_tiles; i++) { const AVStream *st = NULL; if (tile_grid->offsets[i].idx < stg->nb_entries) { @@ -814,7 +816,7 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, continue; const AVStream *st = entry->stream; if (!printed[st->index]) { - dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_INFO); + dump_stream_format(ic, st->index, i, index, is_output, log_level); printed[st->index] = 1; } } @@ -844,8 +846,13 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, } avcodec_free_context(&avctx); if (ptr) - av_log(NULL, AV_LOG_INFO, "%s", ptr); - av_log(NULL, AV_LOG_INFO, "\n"); + av_log(NULL, log_level, "%s", ptr); + dump_disposition(stg->disposition, log_level); + av_log(NULL, log_level, "\n"); + dump_metadata(NULL, stg->metadata, extra_indent, log_level); + dump_sidedata(NULL, layered_video->coded_side_data, layered_video->nb_coded_side_data, + layered_video->width, layered_video->height, (AVRational) {0,1}, + extra_indent, log_level); for (int i = 0; i < stg->nb_entries; i++) { const AVStreamGroupEntry *entry = stg->entries[i]; if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) @@ -857,26 +864,41 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed, break; } case AV_STREAM_GROUP_PARAMS_TREF: { - av_log(NULL, AV_LOG_INFO, " Track Reference:\n"); + av_log(NULL, log_level, " Track Reference:\n"); for (int i = 0; i < stg->nb_entries; i++) { const AVStreamGroupEntry *entry = stg->entries[i]; if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) continue; const AVStream *st = entry->stream; - dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_INFO); + dump_stream_format(ic, st->index, i, index, is_output, log_level); printed[st->index] = 1; } break; } case AV_STREAM_GROUP_PARAMS_GAIN_MAP: { - av_log(NULL, AV_LOG_INFO, " Gain Map:\n"); + const AVStreamGroupLayeredVideo *layered_video = stg->params.layered_video; + const char *ptr = NULL; + av_log(NULL, AV_LOG_INFO, " Gain Map:"); + buf[0] = 0; + av_strlcatf(buf, sizeof(buf), " %dx%d", layered_video->width, layered_video->height); + ptr = av_stristr(buf, " "); + if (ptr) + av_log(NULL, log_level, "%s", ptr); + dump_disposition(stg->disposition, log_level); + av_log(NULL, log_level, "\n"); + dump_metadata(NULL, stg->metadata, extra_indent, log_level); + dump_sidedata(NULL, layered_video->coded_side_data, layered_video->nb_coded_side_data, + layered_video->width, layered_video->height, (AVRational) {0,1}, + extra_indent, log_level); for (int i = 0; i < stg->nb_entries; i++) { const AVStreamGroupEntry *entry = stg->entries[i]; - if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) - continue; - const AVStream *st = entry->stream; - dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_INFO); - printed[st->index] = 1; + if (entry->type == AV_STREAM_GROUP_ENTRY_STREAM) { + const AVStream *st = entry->stream; + dump_stream_format(ic, st->index, i, index, is_output, log_level); + printed[st->index] = 1; + } else if (entry->type == AV_STREAM_GROUP_ENTRY_STREAM_GROUP) + dump_stream_group(ic, printed, entry->stream_group->index, index, + is_output, AV_LOG_VERBOSE); } break; } @@ -968,7 +990,7 @@ void av_dump_format(AVFormatContext *ic, int index, } for (i = 0; i < ic->nb_stream_groups; i++) - dump_stream_group(ic, printed, i, index, is_output); + dump_stream_group(ic, printed, i, index, is_output, AV_LOG_INFO); for (i = 0; i < ic->nb_streams; i++) if (!printed[i]) -- 2.52.0 >From 3c5d6fe7e21811f69be00ee561561f53beaa7283 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 17:08:04 -0300 Subject: [PATCH 16/21] avcodec/packet: add a Gain Map parameters side data type Mirroring the frame one. Will be used by demuxers as stream or stream group side data. Signed-off-by: James Almer <[email protected]> --- libavcodec/packet.c | 1 + libavcodec/packet.h | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/libavcodec/packet.c b/libavcodec/packet.c index 49b5a814e3..f681a6962a 100644 --- a/libavcodec/packet.c +++ b/libavcodec/packet.c @@ -312,6 +312,7 @@ const char *av_packet_side_data_name(enum AVPacketSideDataType type) case AV_PKT_DATA_RTCP_SR: return "RTCP Sender Report"; case AV_PKT_DATA_EXIF: return "EXIF metadata"; case AV_PKT_DATA_HEVC_CONF: return "HEVC enhancement-layer decoder configuration"; + case AV_PKT_DATA_GAIN_MAP_PARAMS: return "Gain map parameters"; } return NULL; } diff --git a/libavcodec/packet.h b/libavcodec/packet.h index 00774a7030..64f687a729 100644 --- a/libavcodec/packet.h +++ b/libavcodec/packet.h @@ -383,6 +383,13 @@ enum AVPacketSideDataType { */ AV_PKT_DATA_HEVC_CONF, + /** + * Parameters describing how to combine this gain map with its base image + * to form an alternate rendition. The payload is the AVGainMapParams + * struct defined in libavutil/gain_map.h. + */ + AV_PKT_DATA_GAIN_MAP_PARAMS, + /** * The number of side data types. * This is not part of the public API/ABI in the sense that it may -- 2.52.0 >From f604342c3e6c4f6175b593054add985a0218dc47 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 15:57:41 -0300 Subject: [PATCH 17/21] avformat: add side data fields to Layered Video Stream Group type Signed-off-by: James Almer <[email protected]> --- libavformat/avformat.c | 2 ++ libavformat/avformat.h | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/libavformat/avformat.c b/libavformat/avformat.c index f0009eaae5..27aad99388 100644 --- a/libavformat/avformat.c +++ b/libavformat/avformat.c @@ -118,6 +118,8 @@ FF_ENABLE_DEPRECATION_WARNINGS case AV_STREAM_GROUP_PARAMS_DOLBY_VISION: case AV_STREAM_GROUP_PARAMS_GAIN_MAP: av_opt_free(stg->params.layered_video); + av_packet_side_data_free(&stg->params.layered_video->coded_side_data, + &stg->params.layered_video->nb_coded_side_data); av_freep(&stg->params.layered_video); break; default: diff --git a/libavformat/avformat.h b/libavformat/avformat.h index 93384b3e9d..3296d42583 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -1121,6 +1121,19 @@ typedef struct AVStreamGroupLayeredVideo { * Height of the final image for presentation. */ int height; + + /** + * Additional data associated with the group. + * + * Should be allocated with av_packet_side_data_new() or + * av_packet_side_data_add(), and will be freed by avformat_free_context(). + */ + AVPacketSideData *coded_side_data; + + /** + * Amount of entries in @ref coded_side_data. + */ + int nb_coded_side_data; } AVStreamGroupLayeredVideo; #if FF_API_LCEVC_STRUCT -- 2.52.0 >From 1098cc600a028bd1a538ce63a9d70e0c6902029f Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 17:57:27 -0300 Subject: [PATCH 18/21] avformat/dump: print Gain Map Parameters side data Signed-off-by: James Almer <[email protected]> --- libavformat/dump.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/libavformat/dump.c b/libavformat/dump.c index d56a4e22b4..8d030d909a 100644 --- a/libavformat/dump.c +++ b/libavformat/dump.c @@ -25,6 +25,7 @@ #include "libavutil/avstring.h" #include "libavutil/channel_layout.h" #include "libavutil/display.h" +#include "libavutil/gain_map.h" #include "libavutil/iamf.h" #include "libavutil/intreadwrite.h" #include "libavutil/log.h" @@ -468,6 +469,33 @@ static void dump_tdrdi(void *ctx, const AVPacketSideData *sd, int log_level) av_log(ctx, log_level, "number of reference displays: %u", tdrdi->num_ref_displays); } +static void dump_gain_map_params(void *ctx, const AVPacketSideData *sd, int log_level) +{ + const AVGainMapParams *p = (const AVGainMapParams *) sd->data; + + av_log(ctx, log_level, "version=%d, nb_channels=%d, use_base_color_space=%d, " + "base_hdr_headroom=%f, alternate_hdr_headroom=%f, channels={ ", + p->version, p->nb_channels, p->use_base_color_space, + av_q2d(p->base_hdr_headroom), av_q2d(p->alternate_hdr_headroom)); + + if (!(p->nb_channels == 1 || p->nb_channels == 3)) { + av_log(ctx, AV_LOG_ERROR, "invalid data }\n"); + return; + } + + for (int c = 0; c < p->nb_channels; c++) { + const struct AVGainMapChannel *ch = &p->channels[c]; + av_log(ctx, log_level, + "{ gain_map_min=%f, gain_map_max=%f, gamma=%f, " + "base_offset=%f, alternate_offset=%f } ", + av_q2d(ch->gain_map_min), av_q2d(ch->gain_map_max), + av_q2d(ch->gamma), av_q2d(ch->base_offset), + av_q2d(ch->alternate_offset)); + } + + av_log(ctx, log_level, "}"); +} + static void dump_sidedata(void *ctx, const AVPacketSideData *side_data, int nb_side_data, int w, int h, AVRational avg_frame_rate, const char *indent, int log_level) @@ -532,6 +560,9 @@ static void dump_sidedata(void *ctx, const AVPacketSideData *side_data, int nb_s case AV_PKT_DATA_3D_REFERENCE_DISPLAYS: dump_tdrdi(ctx, sd, log_level); break; + case AV_PKT_DATA_GAIN_MAP_PARAMS: + dump_gain_map_params(ctx, sd, log_level); + break; default: if (name) av_log(ctx, log_level, "(%zu bytes)", sd->size); -- 2.52.0 >From 38cad793b4371051ef4f0d85222fdf92ba5f92de Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 17:05:57 -0300 Subject: [PATCH 19/21] avutil/gain_map: move ISO 21496-1 gain map payload parsing from libavcodec And export both functions. This is needed for a following change that will make use of these functions from lavf. Signed-off-by: James Almer <[email protected]> --- libavcodec/gain_map.c | 142 ----------------------------------- libavcodec/gain_map.h | 30 -------- libavcodec/mjpegenc_common.c | 4 +- libavutil/gain_map.c | 140 ++++++++++++++++++++++++++++++++++ libavutil/gain_map.h | 31 ++++++++ 5 files changed, 173 insertions(+), 174 deletions(-) diff --git a/libavcodec/gain_map.c b/libavcodec/gain_map.c index 172adc218b..e85e120634 100644 --- a/libavcodec/gain_map.c +++ b/libavcodec/gain_map.c @@ -28,148 +28,6 @@ #include "bytestream.h" #include "gain_map.h" -#define ISO21496_HEADER_SIZE 5 - -#define ISO21496_FLAG_MULTICHANNEL (1 << 7) -#define ISO21496_FLAG_USE_BASE_CG (1 << 6) -#define ISO21496_FLAG_RESERVED 0x3f - -static size_t iso21496_payload_size(int nb_channels) -{ - return 4 * 4 + (size_t) nb_channels * 10 * 4; -} - -static int make_q(int64_t num, uint32_t den, AVRational *out) -{ - if (!den) - return AVERROR_INVALIDDATA; - - /* Ensure the result fits in AVRational */ - av_reduce(&out->num, &out->den, num, den, INT32_MAX); - return 0; -} - -static int get_sq(GetByteContext *gb, AVRational *out) -{ - int32_t num = (int32_t) bytestream2_get_be32(gb); - uint32_t den = bytestream2_get_be32u(gb); - return make_q(num, den, out); -} - -static int get_uq(GetByteContext *gb, AVRational *out) -{ - uint32_t num = bytestream2_get_be32u(gb); - uint32_t den = bytestream2_get_be32u(gb); - return make_q(num, den, out); -} - -int ff_gain_map_params_from_iso21496(AVGainMapParams *restrict p, - const uint8_t *data, size_t size) -{ - int ret; - if (!p || !data) - return AVERROR(EINVAL); - - if (size < ISO21496_HEADER_SIZE || size > INT_MAX) - return AVERROR_INVALIDDATA; - - GetByteContext gb; - bytestream2_init(&gb, data, size); - - /* Parse header */ - const uint16_t minimum_version = bytestream2_get_be16(&gb); - const uint16_t writer_version = bytestream2_get_be16(&gb); - if (minimum_version > FF_GAIN_MAP_VERSION || writer_version < minimum_version) - return AVERROR_PATCHWELCOME; - const uint8_t flags = bytestream2_get_byte(&gb); - - /** - * While the spec does not require reserved bits to be 0, older versions - * of the ISO 21496-1 draft specification had extra flags that got removed - * from the final publication, so conservatively error out just in case; - * unless the writer version is >0 in which case these may be genuine - * additions to later revisions of the published spec, the validity of - * ignoring which is already guarded by the `minimum_version` check. - */ - if (writer_version == 0 && (flags & ISO21496_FLAG_RESERVED)) - return AVERROR_INVALIDDATA; - - p->version = FF_GAIN_MAP_VERSION; - p->nb_channels = (flags & ISO21496_FLAG_MULTICHANNEL) ? 3 : 1; - p->use_base_color_space = !!(flags & ISO21496_FLAG_USE_BASE_CG); - - /* Parse payload */ - if (bytestream2_get_bytes_left(&gb) < iso21496_payload_size(p->nb_channels)) - return AVERROR_INVALIDDATA; - - if ((ret = get_uq(&gb, &p->base_hdr_headroom)) < 0 || - (ret = get_uq(&gb, &p->alternate_hdr_headroom)) < 0) - return ret; - - for (int c = 0; c < p->nb_channels; c++) { - struct AVGainMapChannel *const ch = &p->channels[c]; - if ((ret = get_sq(&gb, &ch->gain_map_min)) < 0 || - (ret = get_sq(&gb, &ch->gain_map_max)) < 0 || - (ret = get_uq(&gb, &ch->gamma)) < 0 || - (ret = get_sq(&gb, &ch->base_offset)) < 0 || - (ret = get_sq(&gb, &ch->alternate_offset)) < 0) - return ret; - } - - /* nb_channels == 1 means every channel shares the same parameters */ - for (int c = p->nb_channels; c < 3; c++) - p->channels[c] = p->channels[0]; - - return av_gain_map_params_validate(p); -} - -static void put_q(PutByteContext *pb, AVRational q) -{ - bytestream2_put_be32(pb, (unsigned) q.num); - bytestream2_put_be32(pb, (unsigned) q.den); -} - -int ff_gain_map_params_to_iso21496(const AVGainMapParams *p, uint8_t *buf) -{ - if (!p || !buf) - return AVERROR(EINVAL); - - /* Ensures representability in the bitstream */ - int ret = av_gain_map_params_validate(p); - if (ret < 0) - return ret; - - if (p->version > FF_GAIN_MAP_VERSION) - return AVERROR_PATCHWELCOME; /* e.g. libraries out of sync */ - - PutByteContext pb; - bytestream2_init_writer(&pb, buf, FF_GAIN_MAP_MAX_PAYLOAD_SIZE); - - int channels = av_gain_map_channels_identical(p) ? 1 : p->nb_channels; - uint8_t flags = 0; - if (channels == 3) - flags |= ISO21496_FLAG_MULTICHANNEL; - if (p->use_base_color_space) - flags |= ISO21496_FLAG_USE_BASE_CG; - - bytestream2_put_be16(&pb, p->version); /* minimum_version */ - bytestream2_put_be16(&pb, p->version); /* writer_version */ - bytestream2_put_byte(&pb, flags); - put_q(&pb, p->base_hdr_headroom); - put_q(&pb, p->alternate_hdr_headroom); - - for (int c = 0; c < channels; c++) { - const struct AVGainMapChannel *const ch = &p->channels[c]; - put_q(&pb, ch->gain_map_min); - put_q(&pb, ch->gain_map_max); - put_q(&pb, ch->gamma); - put_q(&pb, ch->base_offset); - put_q(&pb, ch->alternate_offset); - } - - return bytestream2_tell_p(&pb); -} - int ff_gain_map_params_check_xmp(const AVGainMapParams *p) { if (av_gain_map_params_validate(p) < 0) diff --git a/libavcodec/gain_map.h b/libavcodec/gain_map.h index 53207e448b..7d98723ef5 100644 --- a/libavcodec/gain_map.h +++ b/libavcodec/gain_map.h @@ -31,42 +31,12 @@ /* May differ from AV_ISO21496_VERSION if libraries are not in sync */ #define FF_GAIN_MAP_VERSION 0 -#define FF_GAIN_MAP_MAX_PAYLOAD_SIZE 141 /* = 5 + 2*8 + 3*5*8 */ /* For XMP-based encoding */ #define FF_GAIN_MAP_XMP_IDENT "http://ns.adobe.com/xap/1.0/" #define FF_GAIN_MAP_XMP_NAMESPACE "http://ns.adobe.com/hdr-gain-map/1.0/" #define FF_GAIN_MAP_XMP_MAX_LEN 1024 -/** - * Parse the payload of an ISO 21496-1 gain map metadata blob. - * - * @param p struct to fill; overwritten on success, untouched on failure - * @param data payload, excluding container-specific identifiers - * @param size size of `data` in bytes - * - * @return >= 0 on success, or a negative AVERROR on failure - */ -int ff_gain_map_params_from_iso21496(AVGainMapParams *p, const uint8_t *data, - size_t size); - -/** - * Serialize an AVGainMapParams as an ISO 21496-1 gain map metadata blob. - * - * The output starts at the minimum_version field; the caller should prepend - * whatever identifier its container requires. - * - * @param p parameters to serialize - * @param buf Byte buffer to fill with the serialized data. Must contain at - * least FF_GAIN_MAP_MAX_PAYLOAD_SIZE bytes. - * - * @return Number of bytes written on success, or a negative AVERROR on failure - * - * @note Always succeeds if av_gain_map_params_validate() returns >= 0, and - * p->version <= AV_ISO21496_VERSION. - */ -int ff_gain_map_params_to_iso21496(const AVGainMapParams *p, uint8_t *buf); - /** * Returns 1 if the gain map parameters are compatible with the XMP-based * encoding, or 0 otherwise. diff --git a/libavcodec/mjpegenc_common.c b/libavcodec/mjpegenc_common.c index 235a46799a..bc58ca9903 100644 --- a/libavcodec/mjpegenc_common.c +++ b/libavcodec/mjpegenc_common.c @@ -182,7 +182,7 @@ int ff_mjpeg_add_gain_map_size(AVCodecContext *avctx, struct MJpegContext *m, if (ret < 0) return ret; - size_t extra_size = 4 + sizeof(AV_ISO21496_IDENTIFIER) + FF_GAIN_MAP_MAX_PAYLOAD_SIZE; + size_t extra_size = 4 + sizeof(AV_ISO21496_IDENTIFIER) + AV_GAIN_MAP_MAX_PAYLOAD_SIZE; if (m && m->xmp_gain_map) { if (!ff_gain_map_params_check_xmp(params)) { const int level = m->xmp_gain_map > 0 ? AV_LOG_ERROR : AV_LOG_WARNING; @@ -294,7 +294,7 @@ static void jpeg_put_comments(AVCodecContext *avctx, PutBitContext *p, flush_put_bits(p); /* pre-validated by ff_mjpeg_add_gain_map_size() */ - size = ff_gain_map_params_to_iso21496(gmp, put_bits_ptr(p)); + size = av_gain_map_params_to_iso21496(gmp, put_bits_ptr(p)); av_assert0(size >= 0); skip_put_bytes(p, size); AV_WB16(ptr, 2 + sizeof(AV_ISO21496_IDENTIFIER) + size); diff --git a/libavutil/gain_map.c b/libavutil/gain_map.c index 83c5bd31cf..03cb4ca578 100644 --- a/libavutil/gain_map.c +++ b/libavutil/gain_map.c @@ -18,6 +18,7 @@ #include "gain_map.h" #include "mem.h" +#include "libavcodec/bytestream.h" static void gain_map_params_default(AVGainMapParams *p) { @@ -72,6 +73,145 @@ int av_gain_map_params_validate(const AVGainMapParams *p) return 0; } +#define ISO21496_HEADER_SIZE 5 + +#define ISO21496_FLAG_MULTICHANNEL (1 << 7) +#define ISO21496_FLAG_USE_BASE_CG (1 << 6) +#define ISO21496_FLAG_RESERVED 0x3f + +static size_t iso21496_payload_size(int nb_channels) +{ + return 4 * 4 + (size_t) nb_channels * 10 * 4; +} + +static int make_q(int64_t num, uint32_t den, AVRational *out) +{ + if (!den) + return AVERROR_INVALIDDATA; + + /* Ensure the result fits in AVRational */ + av_reduce(&out->num, &out->den, num, den, INT32_MAX); + return 0; +} + +static int get_sq(GetByteContext *gb, AVRational *out) +{ + int32_t num = (int32_t) bytestream2_get_be32(gb); + uint32_t den = bytestream2_get_be32u(gb); + return make_q(num, den, out); +} + +static int get_uq(GetByteContext *gb, AVRational *out) +{ + uint32_t num = bytestream2_get_be32u(gb); + uint32_t den = bytestream2_get_be32u(gb); + return make_q(num, den, out); +} + +int av_gain_map_params_from_iso21496(AVGainMapParams *restrict p, + const uint8_t *data, size_t size) +{ + int ret; + if (!p || !data) + return AVERROR(EINVAL); + + if (size < ISO21496_HEADER_SIZE || size > INT_MAX) + return AVERROR_INVALIDDATA; + + GetByteContext gb; + bytestream2_init(&gb, data, size); + + /* Parse header */ + const uint16_t minimum_version = bytestream2_get_be16(&gb); + const uint16_t writer_version = bytestream2_get_be16(&gb); + if (minimum_version > AV_ISO21496_VERSION || writer_version < minimum_version) + return AVERROR_PATCHWELCOME; + const uint8_t flags = bytestream2_get_byte(&gb); + + /** + * While the spec does not require reserved bits to be 0, older versions + * of the ISO 21496-1 draft specification had extra flags that got removed + * from the final publication, so conservatively error out just in case; + * unless the writer version is >0 in which case these may be genuine + * additions to later revisions of the published spec, the validity of + * ignoring which is already guarded by the `minimum_version` check. + */ + if (writer_version == 0 && (flags & ISO21496_FLAG_RESERVED)) + return AVERROR_INVALIDDATA; + + p->version = AV_ISO21496_VERSION; + p->nb_channels = (flags & ISO21496_FLAG_MULTICHANNEL) ? 3 : 1; + p->use_base_color_space = !!(flags & ISO21496_FLAG_USE_BASE_CG); + + /* Parse payload */ + if (bytestream2_get_bytes_left(&gb) < iso21496_payload_size(p->nb_channels)) + return AVERROR_INVALIDDATA; + + if ((ret = get_uq(&gb, &p->base_hdr_headroom)) < 0 || + (ret = get_uq(&gb, &p->alternate_hdr_headroom)) < 0) + return ret; + + for (int c = 0; c < p->nb_channels; c++) { + struct AVGainMapChannel *const ch = &p->channels[c]; + if ((ret = get_sq(&gb, &ch->gain_map_min)) < 0 || + (ret = get_sq(&gb, &ch->gain_map_max)) < 0 || + (ret = get_uq(&gb, &ch->gamma)) < 0 || + (ret = get_sq(&gb, &ch->base_offset)) < 0 || + (ret = get_sq(&gb, &ch->alternate_offset)) < 0) + return ret; + } + + /* nb_channels == 1 means every channel shares the same parameters */ + for (int c = p->nb_channels; c < 3; c++) + p->channels[c] = p->channels[0]; + + return av_gain_map_params_validate(p); +} + +static void put_q(PutByteContext *pb, AVRational q) +{ + bytestream2_put_be32(pb, (unsigned) q.num); + bytestream2_put_be32(pb, (unsigned) q.den); +} + +int av_gain_map_params_to_iso21496(const AVGainMapParams *p, uint8_t *buf) +{ + if (!p || !buf) + return AVERROR(EINVAL); + + /* Ensures representability in the bitstream */ + int ret = av_gain_map_params_validate(p); + if (ret < 0) + return ret; + + PutByteContext pb; + bytestream2_init_writer(&pb, buf, AV_GAIN_MAP_MAX_PAYLOAD_SIZE); + + int channels = av_gain_map_channels_identical(p) ? 1 : p->nb_channels; + uint8_t flags = 0; + if (channels == 3) + flags |= ISO21496_FLAG_MULTICHANNEL; + if (p->use_base_color_space) + flags |= ISO21496_FLAG_USE_BASE_CG; + + bytestream2_put_be16(&pb, p->version); /* minimum_version */ + bytestream2_put_be16(&pb, p->version); /* writer_version */ + bytestream2_put_byte(&pb, flags); + put_q(&pb, p->base_hdr_headroom); + put_q(&pb, p->alternate_hdr_headroom); + + for (int c = 0; c < channels; c++) { + const struct AVGainMapChannel *const ch = &p->channels[c]; + put_q(&pb, ch->gain_map_min); + put_q(&pb, ch->gain_map_max); + put_q(&pb, ch->gamma); + put_q(&pb, ch->base_offset); + put_q(&pb, ch->alternate_offset); + } + + return bytestream2_tell_p(&pb); +} + int av_gain_map_channels_identical(const AVGainMapParams *p) { static_assert(sizeof(p->channels[0]) == sizeof(int[2]) * 5, diff --git a/libavutil/gain_map.h b/libavutil/gain_map.h index 7d922d7b40..0e24fa60e0 100644 --- a/libavutil/gain_map.h +++ b/libavutil/gain_map.h @@ -83,6 +83,37 @@ typedef struct AVGainMapParams { int nb_channels; } AVGainMapParams; +/** + * Parse the payload of an ISO 21496-1 gain map metadata blob. + * + * @param p struct to fill; overwritten on success, untouched on failure + * @param data payload, excluding container-specific identifiers + * @param size size of `data` in bytes + * + * @return >= 0 on success, or a negative AVERROR on failure + */ +int av_gain_map_params_from_iso21496(AVGainMapParams *p, const uint8_t *data, + size_t size); + +#define AV_GAIN_MAP_MAX_PAYLOAD_SIZE 141 /* = 5 + 2*8 + 3*5*8 */ + +/** + * Serialize an AVGainMapParams as an ISO 21496-1 gain map metadata blob. + * + * The output starts at the minimum_version field; the caller should prepend + * whatever identifier its container requires. + * + * @param p parameters to serialize + * @param buf Byte buffer to fill with the serialized data. Must contain at + * least AV_GAIN_MAP_MAX_PAYLOAD_SIZE bytes. + * + * @return Number of bytes written on success, or a negative AVERROR on failure + * + * @note Always succeeds if av_gain_map_params_validate() returns >= 0, and + * p->version <= AV_ISO21496_VERSION. + */ +int av_gain_map_params_to_iso21496(const AVGainMapParams *p, uint8_t *buf); + /** * Returns >= 0 if the given AVGainMapParams struct contains valid data; * or a negative AVERROR otherwise. -- 2.52.0 >From cfe03954e1b565f961f6a65f5541964a38a0ed67 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 12:23:44 -0300 Subject: [PATCH 20/21] avformat/mov: factorize setting item output metadata Signed-off-by: James Almer <[email protected]> --- libavformat/mov.c | 104 ++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 54 deletions(-) diff --git a/libavformat/mov.c b/libavformat/mov.c index f350658d7a..d12b17c084 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -11172,6 +11172,45 @@ fail: return err; } +static int mov_parse_heif_item_references(AVFormatContext *s, HEIFItem *item, + AVPacketSideData **coded_side_data, + int *nb_coded_side_data) +{ + MOVContext *mov = s->priv_data; + int err; + + for (int j = 0; j < item->nb_iref_list; j++) { + HEIFItem *ref = get_heif_item(mov, item->iref_list[j].item_id); + + av_assert0(ref); + switch(ref->type) { + case MKTAG('E','x','i','f'): + err = mov_parse_exif_item(s, coded_side_data, nb_coded_side_data, ref); + if (err < 0 && (s->error_recognition & AV_EF_EXPLODE)) + return err; + break; + default: + break; + } + } + + /* rotation */ + if (item->rotation || item->hflip || item->vflip) { + err = set_display_matrix_from_item(coded_side_data, nb_coded_side_data, item); + if (err < 0) + return err; + } + + /* ICC profile */ + if (item->icc_profile_size) { + err = set_icc_profile_from_item(coded_side_data, nb_coded_side_data, item); + if (err < 0) + return err; + } + + return 0; +} + static int mov_parse_tiles(AVFormatContext *s) { MOVContext *mov = s->priv_data; @@ -11257,37 +11296,10 @@ static int mov_parse_tiles(AVFormatContext *s) if (err < 0) return err; - for (int j = 0; j < grid->item->nb_iref_list; j++) { - HEIFItem *ref = get_heif_item(mov, grid->item->iref_list[j].item_id); - - av_assert0(ref); - switch(ref->type) { - case MKTAG('E','x','i','f'): - err = mov_parse_exif_item(s, &tile_grid->coded_side_data, - &tile_grid->nb_coded_side_data, ref); - if (err < 0 && (s->error_recognition & AV_EF_EXPLODE)) - return err; - break; - default: - break; - } - } - - /* rotation */ - if (grid->item->rotation || grid->item->hflip || grid->item->vflip) { - err = set_display_matrix_from_item(&tile_grid->coded_side_data, - &tile_grid->nb_coded_side_data, grid->item); - if (err < 0) - return err; - } - - /* ICC profile */ - if (grid->item->icc_profile_size) { - err = set_icc_profile_from_item(&tile_grid->coded_side_data, - &tile_grid->nb_coded_side_data, grid->item); - if (err < 0) - return err; - } + err = mov_parse_heif_item_references(s, grid->item, &tile_grid->coded_side_data, + &tile_grid->nb_coded_side_data); + if (err < 0) + return err; if (grid->item->name) av_dict_set(&stg->metadata, "title", grid->item->name, 0); @@ -11339,32 +11351,16 @@ static int mov_parse_heif_items(AVFormatContext *s) sc->chunk_offsets[0] = item->extent_offset + offset; + err = mov_parse_heif_item_references(s, item, &st->codecpar->coded_side_data, + &st->codecpar->nb_coded_side_data); + if (err < 0) + return err; + + if (item->name) + av_dict_set(&st->metadata, "title", item->name, 0); if (item->item_id == mov->primary_item_id) st->disposition |= AV_DISPOSITION_DEFAULT; - for (int j = 0; j < item->nb_iref_list; j++) { - HEIFItem *ref = get_heif_item(mov, item->iref_list[j].item_id); - - av_assert0(ref); - switch(ref->type) { - case MKTAG('E','x','i','f'): - err = mov_parse_exif_item(s, &st->codecpar->coded_side_data, - &st->codecpar->nb_coded_side_data, ref); - if (err < 0 && (s->error_recognition & AV_EF_EXPLODE)) - return err; - break; - default: - break; - } - } - - if (item->rotation || item->hflip || item->vflip) { - err = set_display_matrix_from_item(&st->codecpar->coded_side_data, - &st->codecpar->nb_coded_side_data, item); - if (err < 0) - return err; - } - mov_build_index(mov, st); } -- 2.52.0 >From 65124f72f389cd11609ac059f123d21911a5868e Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 5 Oct 2026 16:13:51 -0300 Subject: [PATCH 21/21] avformat/mov: support exporting derived images of type tmap as Gain Map Stream Groups Signed-off-by: James Almer <[email protected]> --- libavformat/isom.h | 10 ++ libavformat/mov.c | 317 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 290 insertions(+), 37 deletions(-) diff --git a/libavformat/isom.h b/libavformat/isom.h index 5474d700d6..92ca7515bd 100644 --- a/libavformat/isom.h +++ b/libavformat/isom.h @@ -297,6 +297,7 @@ typedef struct HEIFItemRef { typedef struct HEIFItem { AVStream *st; + AVStreamGroup *stg; HEIFItemRef *iref_list; int nb_iref_list; char *name; @@ -322,6 +323,13 @@ typedef struct HEIFGrid { int nb_tiles; } HEIFGrid; +typedef struct HEIFLayeredVideo { + HEIFItem *item; + HEIFItem **item_list; + int16_t *item_id_list; + int nb_items; +} HEIFLayeredVideo; + typedef struct MOVContext { const AVClass *class; ///< class for private options AVFormatContext *fc; @@ -387,6 +395,8 @@ typedef struct MOVContext { int nb_heif_item; HEIFGrid *heif_grid; int nb_heif_grid; + HEIFLayeredVideo *heif_tmap; + int nb_heif_tmap; int64_t idat_offset; int interleaved_read; AVDictionary* decryption_keys; diff --git a/libavformat/mov.c b/libavformat/mov.c index d12b17c084..04ef47b9ef 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -46,6 +46,7 @@ #include "libavutil/opt.h" #include "libavutil/aes.h" #include "libavutil/aes_ctr.h" +#include "libavutil/gain_map.h" #include "libavutil/pixdesc.h" #include "libavutil/sha.h" #include "libavutil/spherical.h" @@ -9660,13 +9661,103 @@ fail: return ret; } +static int mov_read_iref_dimg_tmap(MOVContext *c, AVIOContext *pb, + HEIFItem *item, int version, int entries) +{ + HEIFLayeredVideo *tmap = av_realloc_array(c->heif_tmap, c->nb_heif_tmap + 1U, + sizeof(*c->heif_tmap)); + int ret; + + if (!tmap) + return AVERROR(ENOMEM); + c->heif_tmap = tmap; + + if (entries != 2) { + avpriv_report_missing_feature(c->fc, "Derived image item of type %s with an " + "item references count other than 2", + av_fourcc2str(item->type)); + return 0; + } + + tmap = &tmap[c->nb_heif_tmap]; + + tmap->item_id_list = av_malloc_array(entries, sizeof(*tmap->item_id_list)); + tmap->item_list = av_calloc(entries, sizeof(*tmap->item_list)); + if (!tmap->item_id_list || !tmap->item_list) { + ret = AVERROR(ENOMEM); + goto fail; + } + + /* 'to' item ids */ + for (int i = 0; i < entries; i++) { + tmap->item_id_list[i] = version ? avio_rb32(pb) : avio_rb16(pb); + + if (avio_feof(pb)) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + } + + tmap->nb_items = entries; + tmap->item = item; + ++c->nb_heif_tmap; + + return 0; +fail: + av_freep(&tmap->item_list); + av_freep(&tmap->item_id_list); + + return ret; +} + +static int mov_read_iref_dimg_grid(MOVContext *c, AVIOContext *pb, + HEIFItem *item, int version, int entries) +{ + HEIFGrid *grid = av_realloc_array(c->heif_grid, c->nb_heif_grid + 1U, + sizeof(*c->heif_grid)); + int ret; + + if (!grid) + return AVERROR(ENOMEM); + c->heif_grid = grid; + grid = &grid[c->nb_heif_grid]; + + grid->tile_id_list = av_malloc_array(entries, sizeof(*grid->tile_id_list)); + grid->tile_idx_list = av_calloc(entries, sizeof(*grid->tile_idx_list)); + grid->tile_item_list = av_calloc(entries, sizeof(*grid->tile_item_list)); + if (!grid->tile_id_list || !grid->tile_item_list || !grid->tile_idx_list) { + ret = AVERROR(ENOMEM); + goto fail; + } + /* 'to' item ids */ + for (int i = 0; i < entries; i++) { + grid->tile_id_list[i] = version ? avio_rb32(pb) : avio_rb16(pb); + + if (avio_feof(pb)) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + } + + grid->nb_tiles = entries; + grid->item = item; + ++c->nb_heif_grid; + + return 0; +fail: + av_freep(&grid->tile_id_list); + av_freep(&grid->tile_idx_list); + av_freep(&grid->tile_item_list); + + return ret; +} + static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version) { HEIFItem *item = NULL; - HEIFGrid *grid; - int entries, i; + int entries; int from_item_id = version ? avio_rb32(pb) : avio_rb16(pb); - int ret = 0; + int ret; for (int i = 0; i < c->nb_heif_grid; i++) { if (c->heif_grid[i].item->item_id == from_item_id) { @@ -9683,6 +9774,7 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version) switch (item->type) { case MKTAG('g','r','i','d'): case MKTAG('i','o','v','l'): + case MKTAG('t','m','a','p'): break; default: avpriv_report_missing_feature(c->fc, "Derived Image item of type %s", @@ -9692,7 +9784,7 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version) break; } if (!item) { - av_log(c->fc, AV_LOG_ERROR, "Missing grid information\n"); + av_log(c->fc, AV_LOG_ERROR, "Missing derived image information\n"); return AVERROR_INVALIDDATA; } @@ -9703,42 +9795,20 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version) return AVERROR_INVALIDDATA; } - grid = av_realloc_array(c->heif_grid, c->nb_heif_grid + 1U, - sizeof(*c->heif_grid)); - if (!grid) - return AVERROR(ENOMEM); - c->heif_grid = grid; - grid = &grid[c->nb_heif_grid]; - - grid->tile_id_list = av_malloc_array(entries, sizeof(*grid->tile_id_list)); - grid->tile_idx_list = av_calloc(entries, sizeof(*grid->tile_idx_list)); - grid->tile_item_list = av_calloc(entries, sizeof(*grid->tile_item_list)); - if (!grid->tile_id_list || !grid->tile_item_list || !grid->tile_idx_list) { - ret = AVERROR(ENOMEM); - goto fail; - } - /* 'to' item ids */ - for (i = 0; i < entries; i++) { - grid->tile_id_list[i] = version ? avio_rb32(pb) : avio_rb16(pb); - - if (avio_feof(pb)) { - ret = AVERROR_INVALIDDATA; - goto fail; - } - } - - grid->nb_tiles = entries; - grid->item = item; - ++c->nb_heif_grid; - av_log(c->fc, AV_LOG_TRACE, "dimg: from_item_id %d, entries %d\n", from_item_id, entries); - return 0; -fail: - av_freep(&grid->tile_id_list); - av_freep(&grid->tile_idx_list); - av_freep(&grid->tile_item_list); + switch (item->type) { + case MKTAG('g','r','i','d'): + case MKTAG('i','o','v','l'): + ret = mov_read_iref_dimg_grid(c, pb, item, version, entries); + break; + case MKTAG('t','m','a','p'): + ret = mov_read_iref_dimg_tmap(c, pb, item, version, entries); + break; + default: + av_unreachable("item->type should have been checked above"); + } return ret; } @@ -10748,6 +10818,11 @@ static int mov_read_close(AVFormatContext *s) av_freep(&mov->heif_grid[i].tile_item_list); } av_freep(&mov->heif_grid); + for (i = 0; i < mov->nb_heif_tmap; i++) { + av_freep(&mov->heif_tmap[i].item_id_list); + av_freep(&mov->heif_tmap[i].item_list); + } + av_freep(&mov->heif_tmap); return 0; } @@ -11305,6 +11380,168 @@ static int mov_parse_tiles(AVFormatContext *s) av_dict_set(&stg->metadata, "title", grid->item->name, 0); if (grid->item->item_id == mov->primary_item_id) stg->disposition |= AV_DISPOSITION_DEFAULT; + + grid->item->stg = stg; + } + + return 0; +} + +static int mov_parse_heif_layered_video(AVFormatContext *s) +{ + MOVContext *mov = s->priv_data; + + for (int i = 0; i < mov->nb_heif_tmap; i++) { + AVStreamGroup *stg = avformat_stream_group_create(s, AV_STREAM_GROUP_PARAMS_GAIN_MAP, NULL); + AVStreamGroupLayeredVideo *layered_video; + const HEIFLayeredVideo *tmap = &mov->heif_tmap[i]; + int64_t offset = 0; + int err, loop = 1; + + if (!stg) + return AVERROR(ENOMEM); + + switch (tmap->item->type) { + case MKTAG('t','m','a','p'): + break; + default: + av_unreachable("Only tmap is supported"); + } + + if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) { + av_log(s, AV_LOG_WARNING, "tmap box with non seekable input\n"); + ff_remove_stream_group(s, stg); + continue; + } + + stg->id = tmap->item->item_id; + layered_video = stg->params.layered_video; + + for (int j = 0; j < tmap->nb_items; j++) { + int item_id = tmap->item_id_list[j]; + int k; + + for (k = 0; k < mov->nb_heif_item; k++) { + HEIFItem *item = mov->heif_item[k]; + AVStreamGroup *child_stg = NULL; + AVStream *st; + + if (!item || item->item_id != item_id) + continue; + st = item->st; + child_stg = item->stg; + if (!st && !child_stg) { + av_log(s, AV_LOG_WARNING, "HEIF item id %d from tmap id %d doesn't " + "reference a stream or grid\n", + item_id, tmap->item->item_id); + ff_remove_stream_group(s, stg); + loop = 0; + break; + } + av_assert0(!st != !child_stg); + + tmap->item_list[j] = item; + + if (st) + err = avformat_stream_group_add_stream(stg, st); + else + err = avformat_stream_group_add_stream_group(stg, child_stg); + if (err < 0) { + if (err == AVERROR(EEXIST)) + err = AVERROR_INVALIDDATA; + + return err; + } + + if (item->item_id != mov->primary_item_id) { + if (st) + st->disposition |= AV_DISPOSITION_DEPENDENT; + if (child_stg) + child_stg->disposition |= AV_DISPOSITION_DEPENDENT; + } + + break; + } + + if (k == mov->nb_heif_item) { + av_assert0(loop); + av_log(s, AV_LOG_WARNING, "HEIF item id %d referenced by grid id %d doesn't " + "exist\n", + item_id, tmap->item->item_id); + ff_remove_stream_group(s, stg); + loop = 0; + } + if (!loop) + break; + } + + if (!loop) + continue; + + layered_video->width = tmap->item->width; + layered_video->height = tmap->item->height; + layered_video->el_index = stg->nb_entries - 1; + + if (tmap->item->is_idat_relative) { + if (!mov->idat_offset) { + av_log(s, AV_LOG_ERROR, "missing idat box required by the image grid\n"); + return AVERROR_INVALIDDATA; + } + offset = mov->idat_offset; + } + + if (offset > INT64_MAX - tmap->item->extent_offset) + return AVERROR_INVALIDDATA; + + int64_t pos = avio_tell(s->pb); + avio_seek(s->pb, tmap->item->extent_offset + offset, SEEK_SET); + avio_r8(s->pb); /* version */ + + size_t sd_size; + AVGainMapParams *gain = av_gain_map_params_alloc(&sd_size); + if (!gain) + return AVERROR(ENOMEM); + + uint8_t *payload = av_malloc(tmap->item->extent_length); + if (!payload) { + av_free(gain); + return AVERROR(ENOMEM); + } + + err = avio_read(s->pb, payload, tmap->item->extent_length); + if (err != tmap->item->extent_length) { + av_free(gain); + av_free(payload); + return err < 0 ? err : AVERROR_INVALIDDATA; + } + + avio_seek(s->pb, pos, SEEK_SET); + + err = av_gain_map_params_from_iso21496(gain, payload, tmap->item->extent_length); + av_free(payload); + if (err < 0) { + av_free(gain); + return err; + } + + AVPacketSideData *sd = av_packet_side_data_add(&layered_video->coded_side_data, + &layered_video->nb_coded_side_data, + AV_PKT_DATA_GAIN_MAP_PARAMS, + (uint8_t *)gain, sd_size, 0); + if (!sd) { + av_free(gain); + return AVERROR(ENOMEM); + } + + err = mov_parse_heif_item_references(s, tmap->item, &layered_video->coded_side_data, + &layered_video->nb_coded_side_data); + if (err < 0) + return err; + + if (tmap->item->name) + av_dict_set(&stg->metadata, "title", tmap->item->name, 0); + if (tmap->item->item_id == mov->primary_item_id) + stg->disposition |= AV_DISPOSITION_DEFAULT; } return 0; @@ -11370,6 +11607,12 @@ static int mov_parse_heif_items(AVFormatContext *s) return err; } + if (mov->nb_heif_tmap) { + err = mov_parse_heif_layered_video(s); + if (err < 0) + return err; + } + return 0; } -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
