From c751ad2c3690e2dd16de7dfefa651779e6a9498b Mon Sep 17 00:00:00 2001 From: James Almer Date: Sat, 18 Oct 2025 21:02:21 -0300 Subject: [PATCH] fftools/ffmpeg_mux_init: allow creating streams from filtergraphs created out of stream groups Several formats are being designed where more than one independent video or audio stream within a container are part of what should be a single combined output. This is the case for HEIF (Defining a grid where the decoded output of several separate video streams are to be placed to generate a single output image) and IAMF (Defining audio streams where channels are present in separate coded stream). AVStreamGroup was designed and implemented in libavformat to convey this information, but the actual merging is left to the caller. This change allows the FFmpeg CLI to take said information, parse it, and create filtergraphs to merge the streams, making the combined output be usable automatically further in the process. Signed-off-by: James Almer --- fftools/ffmpeg.h | 1 + fftools/ffmpeg_mux_init.c | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h index 0b9f8a20c6..bf2c6c8c71 100644 --- a/fftools/ffmpeg.h +++ b/fftools/ffmpeg.h @@ -500,6 +500,7 @@ typedef struct InputStreamGroup { int index; + FilterGraph *fg; AVStreamGroup *stg; } InputStreamGroup; diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c index c24b69f2d1..7c7302bac1 100644 --- a/fftools/ffmpeg_mux_init.c +++ b/fftools/ffmpeg_mux_init.c @@ -1598,6 +1598,7 @@ fail: static int map_auto_video(Muxer *mux, const OptionsContext *o) { AVFormatContext *oc = mux->fc; + InputStreamGroup *best_istg = NULL; InputStream *best_ist = NULL; int64_t best_score = 0; int qcr; @@ -1609,8 +1610,35 @@ static int map_auto_video(Muxer *mux, const OptionsContext *o) qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0); for (int j = 0; j < nb_input_files; j++) { InputFile *ifile = input_files[j]; + InputStreamGroup *file_best_istg = NULL; InputStream *file_best_ist = NULL; int64_t file_best_score = 0; + for (int i = 0; i < ifile->nb_stream_groups; i++) { + InputStreamGroup *istg = ifile->stream_groups[i]; + int64_t score = 0; + + if (!istg->fg) + continue; + + for (int j = 0; j < istg->stg->nb_streams; j++) { + AVStream *st = istg->stg->streams[j]; + + if (st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) { + score = 100000000; + break; + } + } + + switch (istg->stg->type) { + default: + continue; + } + + if (score > file_best_score) { + file_best_score = score; + file_best_istg = istg; + } + } for (int i = 0; i < ifile->nb_streams; i++) { InputStream *ist = ifile->streams[i]; int64_t score; @@ -1630,6 +1658,15 @@ static int map_auto_video(Muxer *mux, const OptionsContext *o) continue; file_best_score = score; file_best_ist = ist; + file_best_istg = NULL; + } + } + if (file_best_istg) { + file_best_score -= 5000000*!!(file_best_istg->stg->disposition & AV_DISPOSITION_DEFAULT); + if (file_best_score > best_score) { + best_score = file_best_score; + best_istg = file_best_istg; + best_ist = NULL; } } if (file_best_ist) { @@ -1639,9 +1676,19 @@ static int map_auto_video(Muxer *mux, const OptionsContext *o) if (file_best_score > best_score) { best_score = file_best_score; best_ist = file_best_ist; + best_istg = NULL; } } } + if (best_istg) { + FilterGraph *fg = best_istg->fg; + OutputFilter *ofilter = fg->outputs[0]; + + av_assert0(fg->nb_outputs == 1); + av_log(mux, AV_LOG_VERBOSE, "Creating output stream from stream group derived complex filtergraph %d.\n", fg->index); + + return ost_add(mux, o, AVMEDIA_TYPE_VIDEO, NULL, ofilter, NULL, NULL); + } if (best_ist) return ost_add(mux, o, AVMEDIA_TYPE_VIDEO, best_ist, NULL, NULL, NULL);