PR #24969 opened by Niklas Haas (haasn) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24969 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24969.patch
Not sure if this new API is overkill or if it would be better to just add two new `unsigned` in the public struct, but I figured this way buys us future flexibility for free. We can also drop the last commit; but I'm not sure if anybody actually uses this field besides fftools. >From 65907ef330064666531d85482e6cd6fd8fc054aa Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 9 Oct 2026 16:20:40 +0200 Subject: [PATCH 1/4] avfilter: apply max_buffered_frames per media type Minor semantic change to prepare for the upcoming commits. This shouldn't regress anything because the new behavior is strictly more permissive than the old behavior, and the distinction is only relevant for the uncommon case of combined audio-video filters. Signed-off-by: Niklas Haas <[email protected]> --- libavfilter/avfilter.c | 5 ++++- libavfilter/avfilter.h | 3 ++- libavfilter/avfilter_internal.h | 2 +- libavfilter/avfiltergraph.c | 11 ++++++++--- libavfilter/version.h | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c index 46068719b1..5081ef81e8 100644 --- a/libavfilter/avfilter.c +++ b/libavfilter/avfilter.c @@ -149,12 +149,14 @@ int ff_append_outpad_free_name(AVFilterContext *f, AVFilterPad *p) int avfilter_link(AVFilterContext *src, unsigned srcpad, AVFilterContext *dst, unsigned dstpad) { + FFFilterGraph *graphi; FilterLinkInternal *li; AVFilterLink *link; av_assert0(src->graph); av_assert0(dst->graph); av_assert0(src->graph == dst->graph); + graphi = fffiltergraph(src->graph); if (src->nb_outputs <= srcpad || dst->nb_inputs <= dstpad || src->outputs[srcpad] || dst->inputs[dstpad]) @@ -190,7 +192,8 @@ int avfilter_link(AVFilterContext *src, unsigned srcpad, av_assert0(AV_PIX_FMT_NONE == -1 && AV_SAMPLE_FMT_NONE == -1); link->format = -1; link->colorspace = AVCOL_SPC_UNSPECIFIED; - ff_framequeue_init(&li->fifo, &fffiltergraph(src->graph)->frame_queues); + av_assert1(link->type >= 0 && link->type < FF_ARRAY_ELEMS(graphi->frame_queues)); + ff_framequeue_init(&li->fifo, &graphi->frame_queues[link->type]); return 0; } diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index a6b875c37a..e9a6ea8775 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -621,7 +621,8 @@ typedef struct AVFilterGraph { char *aresample_swr_opts; ///< swr options to use for the auto-inserted aresample filters, Access ONLY through AVOptions /** - * Sets the maximum number of buffered frames in the filtergraph combined. + * Sets the maximum number of combined buffered frames of each media type + * in the filtergraph. * * Zero means no limit. This field must be set before calling * avfilter_graph_config(). diff --git a/libavfilter/avfilter_internal.h b/libavfilter/avfilter_internal.h index 808dd6af38..8b0d2d89f1 100644 --- a/libavfilter/avfilter_internal.h +++ b/libavfilter/avfilter_internal.h @@ -148,7 +148,7 @@ typedef struct FFFilterGraph { void *thread; avfilter_execute_func *thread_execute; - FFFrameQueueGlobal frame_queues; + FFFrameQueueGlobal frame_queues[AVMEDIA_TYPE_NB]; } FFFilterGraph; static inline FFFilterGraph *fffiltergraph(AVFilterGraph *graph) diff --git a/libavfilter/avfiltergraph.c b/libavfilter/avfiltergraph.c index 7b58883c65..54ac87c35f 100644 --- a/libavfilter/avfiltergraph.c +++ b/libavfilter/avfiltergraph.c @@ -93,7 +93,8 @@ AVFilterGraph *avfilter_graph_alloc(void) ret = &graph->p; ret->av_class = &filtergraph_class; av_opt_set_defaults(ret); - ff_framequeue_global_init(&graph->frame_queues); + for (int i = 0; i < FF_ARRAY_ELEMS(graph->frame_queues); i++) + ff_framequeue_global_init(&graph->frame_queues[i]); return ret; } @@ -1458,10 +1459,14 @@ static int graph_config_pointers(AVFilterGraph *graph, void *log_ctx) int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx) { + FFFilterGraph *graphi = fffiltergraph(graphctx); int ret; - if (graphctx->max_buffered_frames) - fffiltergraph(graphctx)->frame_queues.max_queued = graphctx->max_buffered_frames; + for (int i = 0; i < FF_ARRAY_ELEMS(graphi->frame_queues); i++) { + const size_t max_queued = graphctx->max_buffered_frames; + if (max_queued) + graphi->frame_queues[i].max_queued = max_queued; + } if ((ret = graph_check_validity(graphctx, log_ctx))) return ret; if ((ret = graph_config_formats(graphctx, log_ctx))) diff --git a/libavfilter/version.h b/libavfilter/version.h index 523a7fe0a6..7c653f48fa 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -32,7 +32,7 @@ #include "version_major.h" #define LIBAVFILTER_VERSION_MINOR 5 -#define LIBAVFILTER_VERSION_MICRO 100 +#define LIBAVFILTER_VERSION_MICRO 101 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ -- 2.52.0 >From e3f3d9de4ba1ecb2b470a10b0cc95a2453b495c5 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 9 Oct 2026 16:49:15 +0200 Subject: [PATCH 2/4] avfilter: add avfilter_graph_set_max_buffered_frames() This is a new API that offers two improvements over the existing field: 1. It allows setting a different limit per media type 2. It allows updating the limit after the graph has been configured, as long as this would not cause already existing frames to exceed the limit I'm not sure if the second point is ever useful, but it's easy to implement regardless. Because the old field will be deprecated, the new API just overrides the old value wholesale. The alternative was to respect both (i.e. use the smaller of the two limits); but this way, callers can use the new API to e.g. selectively uncap the limit on audio frames while remaining backwards compatible with older libavfilter. This does require parking the value as a sentinel (0) until avfilter_graph_config(), so we can detect whether or not the value was ever set using the new API in between alloc() and config(). This is safe since 0 is not a legal value of either option (due to the fact that it's treated as SIZE_MAX explicitly). Signed-off-by: Niklas Haas <[email protected]> --- doc/APIchanges | 3 +++ libavfilter/avfilter.h | 17 +++++++++++++++++ libavfilter/avfiltergraph.c | 35 ++++++++++++++++++++++++++++++----- libavfilter/version.h | 4 ++-- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/doc/APIchanges b/doc/APIchanges index d8ad804560..ab78fd55c7 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,9 @@ The last version increases of all libraries were on 2026-06-23. API changes, most recent first: +2026-10-xx - xxxxxxxxxx - lavfi 12.6.100 - avfilter.h + Add avfilter_graph_set_max_buffered_frames(). + 2026-10-xx - xxxxxxxxxx - lavu 61.13.100 - raw_color_params.h Add AV_RAW_COLOR_PARAMS_RW2 and AVRW2RawColorParams. diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index e9a6ea8775..23977679fe 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -703,6 +703,23 @@ enum { AVFILTER_AUTO_CONVERT_NONE = -1, /**< all automatic conversions disabled */ }; +/** + * Set the maximum number of combined buffered frames of a given media type in + * the filtergraph. When set, this overrides the value inherited from + * AVFilterGraph.max_buffered_frames for that media type. + * + * @param graph the filter graph + * @param type the media type to set the limit for + * @param frames the maximum number of buffered frames, or zero to disable + * @return >= 0 in case of success, or a negative error + * + * @note This will return AVERROR(EINVAL) if the requested limit is lower + * than the number of frames already buffered. + */ +int avfilter_graph_set_max_buffered_frames(AVFilterGraph *graph, + enum AVMediaType type, + unsigned frames); + /** * Check validity and configure all the links and formats in the graph. * diff --git a/libavfilter/avfiltergraph.c b/libavfilter/avfiltergraph.c index 54ac87c35f..9b512e4e17 100644 --- a/libavfilter/avfiltergraph.c +++ b/libavfilter/avfiltergraph.c @@ -93,8 +93,11 @@ AVFilterGraph *avfilter_graph_alloc(void) ret = &graph->p; ret->av_class = &filtergraph_class; av_opt_set_defaults(ret); - for (int i = 0; i < FF_ARRAY_ELEMS(graph->frame_queues); i++) - ff_framequeue_global_init(&graph->frame_queues[i]); + for (int i = 0; i < FF_ARRAY_ELEMS(graph->frame_queues); i++) { + FFFrameQueueGlobal *fqg = &graph->frame_queues[i]; + ff_framequeue_global_init(fqg); + fqg->max_queued = 0; /* initialized by avfilter_graph_config() */ + } return ret; } @@ -165,6 +168,22 @@ void avfilter_graph_set_auto_convert(AVFilterGraph *graph, unsigned flags) fffiltergraph(graph)->disable_auto_convert = flags; } +int avfilter_graph_set_max_buffered_frames(AVFilterGraph *graph, + enum AVMediaType type, + unsigned frames) +{ + FFFilterGraph *graphi = fffiltergraph(graph); + if (type < 0 || type >= FF_ARRAY_ELEMS(graphi->frame_queues)) + return AVERROR(EINVAL); + + FFFrameQueueGlobal *fqg = &graphi->frame_queues[type]; + size_t max_queued = frames ? frames : SIZE_MAX; + if (fqg->queued > max_queued) + return AVERROR(EINVAL); + fqg->max_queued = max_queued; + return 0; +} + AVFilterContext *avfilter_graph_alloc_filter(AVFilterGraph *graph, const AVFilter *filter, const char *name) @@ -1462,10 +1481,16 @@ int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx) FFFilterGraph *graphi = fffiltergraph(graphctx); int ret; + size_t max_queued = graphctx->max_buffered_frames; + if (!max_queued) + max_queued = SIZE_MAX; + for (int i = 0; i < FF_ARRAY_ELEMS(graphi->frame_queues); i++) { - const size_t max_queued = graphctx->max_buffered_frames; - if (max_queued) - graphi->frame_queues[i].max_queued = max_queued; + /* only infer the value from graphctx->max_buffered_frames if it was + * not already set by avfilter_graph_set_max_buffer_size() */ + FFFrameQueueGlobal *fqg = &graphi->frame_queues[i]; + if (!fqg->max_queued) + fqg->max_queued = max_queued; } if ((ret = graph_check_validity(graphctx, log_ctx))) return ret; diff --git a/libavfilter/version.h b/libavfilter/version.h index 7c653f48fa..f191d98883 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -31,8 +31,8 @@ #include "version_major.h" -#define LIBAVFILTER_VERSION_MINOR 5 -#define LIBAVFILTER_VERSION_MICRO 101 +#define LIBAVFILTER_VERSION_MINOR 6 +#define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ -- 2.52.0 >From d153c8f39c1ac73e45419965b3e000df84dd42ae Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 9 Oct 2026 17:04:30 +0200 Subject: [PATCH 3/4] fftools/ffmpeg_filter: split -max_buffered_frames per media type This allows callers to (optionally) set a specific limit for audio and video frames, separately. Since these are not really stream options, I had to add a new parsing logic to handle the :a and :v suffixes, but I don't think it's too ugly. As an aside, also fixes a memleak if setting the option somehow fails. Signed-off-by: Niklas Haas <[email protected]> --- doc/ffmpeg.texi | 6 +++++- fftools/ffmpeg.h | 2 +- fftools/ffmpeg_filter.c | 9 ++++++--- fftools/ffmpeg_opt.c | 39 +++++++++++++++++++++++++++++++++++---- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi index 5d2eb4d1e3..29f00f3ba1 100644 --- a/doc/ffmpeg.texi +++ b/doc/ffmpeg.texi @@ -1444,7 +1444,7 @@ Defines how many threads are used to process a filter pipeline. Each pipeline will produce a thread pool with this many threads available for parallel processing. The default is the number of available CPUs. -@item -filter_buffered_frames @var{nb_frames} (@emph{global}) +@item -filter_buffered_frames[:@var{media_type}] @var{nb_frames} (@emph{global}) Defines the maximum number of buffered frames allowed in a filtergraph. Under normal circumstances, a filtergraph should not buffer more than a few frames, especially if frames are being fed to it and read from it in a balanced way @@ -1453,6 +1453,10 @@ to limit the total number of frames buffered across all links in a filtergraph. If more frames are generated, filtering is aborted and an error is returned. The default value is 0, which means no limit. +The optional suffix @var{media_type} may be used to set a different limit +for audio and video frames, using the suffixes @code{a} and @code{v}, +respectively. If the suffix is omitted, the limit applies to both audio and +video frames, separately. @item -pre[:@var{stream_specifier}] @var{preset_name} (@emph{output,per-stream}) Specify the preset for matching stream(s). diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h index f51867435d..fbf44bf65a 100644 --- a/fftools/ffmpeg.h +++ b/fftools/ffmpeg.h @@ -770,7 +770,7 @@ extern float max_error_rate; extern char *filter_nbthreads; extern int filter_complex_nbthreads; -extern int filter_buffered_frames; +extern int filter_buffered_frames[AVMEDIA_TYPE_NB]; extern int vstats_version; extern int print_graphs; extern char *print_graphs_file; diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c index a0a6e082b3..fc17e4f83e 100644 --- a/fftools/ffmpeg_filter.c +++ b/fftools/ffmpeg_filter.c @@ -2220,10 +2220,13 @@ static int configure_filtergraph(FilterGraph *fg, FilterGraphThread *fgt) fgt->graph->nb_threads = filter_complex_nbthreads; } - if (filter_buffered_frames) { - ret = av_opt_set_int(fgt->graph, "max_buffered_frames", filter_buffered_frames, 0); + for (int type = 0; type < AVMEDIA_TYPE_NB; type++) { + const int limit = filter_buffered_frames[type]; + if (limit <= 0) + continue; + ret = avfilter_graph_set_max_buffered_frames(fgt->graph, type, limit); if (ret < 0) - return ret; + goto fail; } hw_device = hw_device_for_filter(); diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c index 265bfba35f..d94a24c532 100644 --- a/fftools/ffmpeg_opt.c +++ b/fftools/ffmpeg_opt.c @@ -72,7 +72,7 @@ int stdin_interaction = 1; float max_error_rate = 2.0/3; char *filter_nbthreads; int filter_complex_nbthreads = 0; -int filter_buffered_frames = 0; +int filter_buffered_frames[AVMEDIA_TYPE_NB] = {0}; int vstats_version = 2; int print_graphs = 0; char *print_graphs_file = NULL; @@ -467,6 +467,37 @@ static int opt_filter_threads(void *optctx, const char *opt, const char *arg) return 0; } +static int opt_filter_buffered_frames(void *optctx, const char *opt, const char *arg) +{ + enum AVMediaType type; + const char *suffix = strchr(opt, ':'); + suffix = suffix ? suffix + 1 : NULL; + if (!suffix) + type = AVMEDIA_TYPE_UNKNOWN; + else if (!strcmp(suffix, "a")) + type = AVMEDIA_TYPE_AUDIO; + else if (!strcmp(suffix, "v")) + type = AVMEDIA_TYPE_VIDEO; + else{ + av_log(NULL, AV_LOG_ERROR, "Invalid media type '%s' for option '%s', " + "expected 'a' or 'v'.\n", suffix, opt); + return AVERROR(EINVAL); + } + + double num; + int ret = parse_number(opt, arg, OPT_TYPE_INT, 0, INT_MAX, &num); + if (ret < 0) + return ret; + + if (type != AVMEDIA_TYPE_UNKNOWN) { + filter_buffered_frames[type] = num; + } else { + for (int i = 0; i < AVMEDIA_TYPE_NB; i++) + filter_buffered_frames[i] = num; + } + return 0; +} + static int opt_abort_on(void *optctx, const char *opt, const char *arg) { static const AVOption opts[] = { @@ -1746,9 +1777,9 @@ const OptionDef options[] = { { "filter_threads", OPT_TYPE_FUNC, OPT_FUNC_ARG | OPT_EXPERT, { .func_arg = opt_filter_threads }, "number of non-complex filter threads" }, - { "filter_buffered_frames", OPT_TYPE_INT, OPT_EXPERT, - { &filter_buffered_frames }, - "maximum number of buffered frames in a filter graph" }, + { "filter_buffered_frames", OPT_TYPE_FUNC, OPT_FUNC_ARG | OPT_EXPERT, + { .func_arg = opt_filter_buffered_frames }, + "maximum number of buffered audio/video frames in a filter graph", "nb_frames" }, { "reinit_filter", OPT_TYPE_INT, OPT_PERSTREAM | OPT_INPUT | OPT_EXPERT, { .off = OFFSET(reinit_filters) }, "reinit filtergraph on input parameter changes", "" }, -- 2.52.0 >From 94ee437e64e76fa874d78b10d2eeede77cac0688 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 9 Oct 2026 17:17:45 +0200 Subject: [PATCH 4/4] avfilter: deprecate AVFilterGraph.max_buffered_frames In favor of the new API. Signed-off-by: Niklas Haas <[email protected]> --- doc/APIchanges | 4 ++++ libavfilter/avfilter.h | 5 +++++ libavfilter/avfiltergraph.c | 16 ++++++++++++---- libavfilter/version.h | 2 +- libavfilter/version_major.h | 2 ++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/doc/APIchanges b/doc/APIchanges index ab78fd55c7..a9594b1c4e 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,10 @@ The last version increases of all libraries were on 2026-06-23. API changes, most recent first: +2026-10-xx - xxxxxxxxxx - lavfi 12.7.100 - avfilter.h + Deprecate AVFilterGraph.max_buffered_frames in favor of + avfilter_graph_set_max_buffered_frames(). + 2026-10-xx - xxxxxxxxxx - lavfi 12.6.100 - avfilter.h Add avfilter_graph_set_max_buffered_frames(). diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index 23977679fe..36857d6cce 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -620,14 +620,19 @@ typedef struct AVFilterGraph { char *aresample_swr_opts; ///< swr options to use for the auto-inserted aresample filters, Access ONLY through AVOptions +#if FF_API_MAX_BUFFERED_FRAMES /** * Sets the maximum number of combined buffered frames of each media type * in the filtergraph. * * Zero means no limit. This field must be set before calling * avfilter_graph_config(). + * + * @deprecated use avfilter_graph_set_max_buffered_frames() instead */ + attribute_deprecated unsigned max_buffered_frames; +#endif } AVFilterGraph; /** diff --git a/libavfilter/avfiltergraph.c b/libavfilter/avfiltergraph.c index 9b512e4e17..f3e6376aa7 100644 --- a/libavfilter/avfiltergraph.c +++ b/libavfilter/avfiltergraph.c @@ -45,6 +45,7 @@ #define F AV_OPT_FLAG_FILTERING_PARAM #define V AV_OPT_FLAG_VIDEO_PARAM #define A AV_OPT_FLAG_AUDIO_PARAM +FF_DISABLE_DEPRECATION_WARNINGS static const AVOption filtergraph_options[] = { { "thread_type", "Allowed thread types", OFFSET(thread_type), AV_OPT_TYPE_FLAGS, { .i64 = AVFILTER_THREAD_SLICE }, 0, INT_MAX, F|V|A, .unit = "thread_type" }, @@ -56,10 +57,13 @@ static const AVOption filtergraph_options[] = { AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, F|V }, {"aresample_swr_opts" , "default aresample filter options" , OFFSET(aresample_swr_opts) , AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, F|A }, +#if FF_API_MAX_BUFFERED_FRAMES {"max_buffered_frames" , "maximum number of buffered frames allowed", OFFSET(max_buffered_frames), - AV_OPT_TYPE_UINT, {.i64 = 0}, 0, UINT_MAX, F|V|A }, + AV_OPT_TYPE_UINT, {.i64 = 0}, 0, UINT_MAX, F|V|A|AV_OPT_FLAG_DEPRECATED }, +#endif { NULL }, }; +FF_ENABLE_DEPRECATION_WARNINGS static const AVClass filtergraph_class = { .class_name = "AVFilterGraph", @@ -1481,9 +1485,13 @@ int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx) FFFilterGraph *graphi = fffiltergraph(graphctx); int ret; - size_t max_queued = graphctx->max_buffered_frames; - if (!max_queued) - max_queued = SIZE_MAX; + size_t max_queued = SIZE_MAX; +#if FF_API_MAX_BUFFERED_FRAMES +FF_DISABLE_DEPRECATION_WARNINGS + if (graphctx->max_buffered_frames) + max_queued = graphctx->max_buffered_frames; +FF_ENABLE_DEPRECATION_WARNINGS +#endif for (int i = 0; i < FF_ARRAY_ELEMS(graphi->frame_queues); i++) { /* only infer the value from graphctx->max_buffered_frames if it was diff --git a/libavfilter/version.h b/libavfilter/version.h index f191d98883..0050874108 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -31,7 +31,7 @@ #include "version_major.h" -#define LIBAVFILTER_VERSION_MINOR 6 +#define LIBAVFILTER_VERSION_MINOR 7 #define LIBAVFILTER_VERSION_MICRO 100 diff --git a/libavfilter/version_major.h b/libavfilter/version_major.h index a5d93410f3..45f63a8936 100644 --- a/libavfilter/version_major.h +++ b/libavfilter/version_major.h @@ -35,4 +35,6 @@ * the public API and may change, break or disappear at any time. */ +#define FF_API_MAX_BUFFERED_FRAMES (LIBAVFILTER_VERSION_MAJOR < 13) + #endif /* AVFILTER_VERSION_MAJOR_H */ -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
