From 38a5fcc02c2ef1bcb37d2e53eddde8eccc0c75ed Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Wed, 24 Sep 2025 15:23:04 +0200 Subject: [PATCH] fftools/ffmpeg_filter: close all no-longer needed inputs Currently, the thread loop of ffmpeg_filter essentially works like this: while (1) { frame, idx = get_from_decoder(); err = send_to_filter_graph(frame); if (err) { // i.e. EOF close_input(idx); continue; } while (filtered_frame = get_filtered_frame()) send_to_encoder(filtered_frame); } The exact details are not 100% correct since the actual control flow is a bit more complicated as a result of the scheduler, but this is the general flow. Notably, this leaves the possibility of leaving a no-longer-needed input permanently open if the filter graph starts producing infinite frames (during the second loop) *after* it finishes reading from an input, e.g. in a filter graph like -af atrim,apad. This patch avoids this issue by always querying the status of all filter graph inputs and explicitly closing any that were closed downstream; after each round of reading output frames. As a result, information about the filtergraph being closed can now propagate back upstream, even if the filter is no longer requesting any input frames (i.e. input_idx == fg->nb_inputs). Fixes: https://trac.ffmpeg.org/ticket/11061 See-Also: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20457#issuecomment-6208 --- fftools/ffmpeg_filter.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c index 9f962e6b8c..d5ae59ec03 100644 --- a/fftools/ffmpeg_filter.c +++ b/fftools/ffmpeg_filter.c @@ -2616,6 +2616,16 @@ finish: fps->dropped_keyframe |= fps->last_dropped && (frame->flags & AV_FRAME_FLAG_KEY); } +static void close_input(InputFilterPriv *ifp) +{ + FilterGraphPriv *fgp = fgp_from_fg(ifp->ifilter.graph); + + if (!ifp->eof) { + sch_filter_receive_finish(fgp->sch, fgp->sch_idx, ifp->ifilter.index); + ifp->eof = 1; + } +} + static int close_output(OutputFilterPriv *ofp, FilterGraphThread *fgt) { FilterGraphPriv *fgp = fgp_from_fg(ofp->ofilter.graph); @@ -3343,7 +3353,7 @@ static int filter_thread(void *arg) if (ret == AVERROR_EOF) { av_log(fg, AV_LOG_VERBOSE, "Input %u no longer accepts new data\n", input_idx); - sch_filter_receive_finish(fgp->sch, fgp->sch_idx, input_idx); + close_input(ifp); continue; } if (ret < 0) @@ -3362,6 +3372,13 @@ read_frames: av_err2str(ret)); goto finish; } + + // ensure all inputs no longer accepting data are closed + for (int i = 0; fgt.graph && i < fg->nb_inputs; i++) { + InputFilterPriv *ifp = ifp_from_ifilter(fg->inputs[i]); + if (av_buffersrc_get_status(ifp->ifilter.filter)) + close_input(ifp); + } } for (unsigned i = 0; i < fg->nb_outputs; i++) {