PR #25562 opened by Jun Zhao (mypopydev) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25562 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25562.patch
When a filtergraph closes an input downstream (e.g. via trim on a looped input), ifp->eof is set while fgt->eof_in remains 0. Because choose_input() only checked fgt->eof_in and finished inputs were closed too late, the CLI could pick a closed input and choke all active ones, causing a 0% CPU deadlock. This patch skips ifp->eof inputs in choose_input(), closes finished inputs before selecting next_in, refreshes next_in when send_frame() hits EOF, and preserves ifp->eof across graph reconfiguration, along with a FATE regression test. >From 079f164a5776f16fa9dfdecf7a76998cc0c6e7cf Mon Sep 17 00:00:00 2001 From: Jun Zhao <[email protected]> Date: Wed, 28 Oct 2026 16:05:24 +0800 Subject: [PATCH 1/2] fftools/ffmpeg_filter: do not pick inputs closed by the filtergraph A filtergraph can close one of its inputs itself, e.g. when a filter such as trim returns EOF on a looped input whose upstream never sends EOF. Such inputs are marked with ifp->eof by close_input(), but choose_input() only skipped inputs that had received an EOF from upstream (fgt->eof_in). It could therefore pick a closed input, whose frames can never arrive, while choking every other input's decoder queue, deadlocking the CLI at 0% CPU. Skip graph-closed inputs in choose_input(); close inputs that no longer accept data before choosing the next input instead of only after read_frames() returns, so a just-closed input cannot be picked; restart input selection when sending a frame closes the selected input, so a stale fgt->next_in pointing at it is not reused; and re-send EOF for graph-closed inputs when reconfiguring the graph, so they are not resurrected. Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/25548 Signed-off-by: Jun Zhao <[email protected]> --- fftools/ffmpeg_filter.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c index 25c930f1380..c52371e16f2 100644 --- a/fftools/ffmpeg_filter.c +++ b/fftools/ffmpeg_filter.c @@ -2337,7 +2337,9 @@ static int configure_filtergraph(FilterGraph *fg, FilterGraphThread *fgt) /* send the EOFs for the finished inputs */ for (int i = 0; i < fg->nb_inputs; i++) { InputFilter *ifilter = fg->inputs[i]; - if (fgt->eof_in[i]) { + InputFilterPriv *ifp = ifp_from_ifilter(ifilter); + + if (fgt->eof_in[i] || ifp->eof) { ret = av_buffersrc_add_frame(ifilter->filter, NULL); if (ret < 0) goto fail; @@ -2497,8 +2499,9 @@ static int choose_input(const FilterGraph *fg, const FilterGraphThread *fgt) for (int i = 0; i < fg->nb_inputs; i++) { InputFilter *ifilter = fg->inputs[i]; + InputFilterPriv *ifp = ifp_from_ifilter(ifilter); - if (fgt->eof_in[i]) + if (fgt->eof_in[i] || ifp->eof) continue; nb_requests = av_buffersrc_get_nb_failed_requests(ifilter->filter); @@ -3057,6 +3060,14 @@ static int read_frames(FilterGraph *fg, FilterGraphThread *fgt, ret = avfilter_graph_request_oldest(fgt->graph); + + // ensure all inputs no longer accepting data are closed + for (int i = 0; i < fg->nb_inputs; i++) { + InputFilterPriv *ifp = ifp_from_ifilter(fg->inputs[i]); + if (av_buffersrc_get_status(ifp->ifilter.filter)) + close_input(ifp); + } + if (ret == AVERROR(EAGAIN)) { fgt->next_in = choose_input(fg, fgt); return 0; @@ -3568,7 +3579,7 @@ static int filter_thread(void *arg) av_log(fg, AV_LOG_VERBOSE, "Input %u no longer accepts new data\n", input_idx); close_input(ifp); - continue; + goto read_frames; } if (ret < 0) goto finish; @@ -3586,13 +3597,6 @@ 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++) { -- 2.54.0 >From e2e81a436a7867c52ca1e0b9ce0863c8957f32db Mon Sep 17 00:00:00 2001 From: Jun Zhao <[email protected]> Date: Wed, 28 Oct 2026 21:37:40 +0800 Subject: [PATCH 2/2] tests/fate: add a test for a filtergraph closing a looped input The graph closes its three looped inputs by letting trim return EOF while the main input is still active. On the unfixed code this deadlocks the CLI with a significant probability, so the test then only finishes by timeout; with the fix it passes deterministically. Signed-off-by: Jun Zhao <[email protected]> --- tests/fate/ffmpeg.mak | 16 +++++ tests/ref/fate/ffmpeg-filter-closed-input | 80 +++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 tests/ref/fate/ffmpeg-filter-closed-input diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak index ff5dfdd4c84..5c9218c7034 100644 --- a/tests/fate/ffmpeg.mak +++ b/tests/fate/ffmpeg.mak @@ -424,6 +424,22 @@ fate-ffmpeg-filter-in-eof: CMD = framecrc -filter_complex "[0][1]concat" -c:v rawvideo FATE_FFMPEG-$(call FRAMECRC, RAWVIDEO, RAWVIDEO, CONCAT_FILTER) += fate-ffmpeg-filter-in-eof +# Test that the CLI does not deadlock when a filtergraph closes one of its +# looped inputs (here via trim returning EOF) while other inputs are still +# active; the closed input must no longer be chosen for receiving frames. +# cf. #25548 +fate-ffmpeg-filter-closed-input: tests/data/vsynth1.yuv +fate-ffmpeg-filter-closed-input: CMD = framecrc -auto_conversion_filters -filter_complex_threads 1 \ + -stream_loop -1 -f rawvideo -s 352x288 -pix_fmt yuv420p -i $(TARGET_PATH)/tests/data/vsynth1.yuv \ + -stream_loop -1 -f rawvideo -s 352x288 -pix_fmt yuv420p -i $(TARGET_PATH)/tests/data/vsynth1.yuv \ + -stream_loop -1 -f rawvideo -s 352x288 -pix_fmt yuv420p -i $(TARGET_PATH)/tests/data/vsynth1.yuv \ + -stream_loop -1 -f rawvideo -s 352x288 -pix_fmt yuv420p -i $(TARGET_PATH)/tests/data/vsynth1.yuv \ + -filter_complex "[1:v]format=rgba[s0];[2:v]format=gray[m0];[m0][s0]scale2ref=w=iw:h=ih[m][s1];[3:v]format=rgba[f0];[f0][s1]scale2ref=w=iw:h=ih[f][s2];[f][m]alphamerge[k];[s2]gblur=sigma=6[bg];[bg][k]overlay,null,null,null,null,null,null,null,null,trim=duration=1.64,setpts=PTS-STARTPTS[l];[0:v][l]overlay=eof_action=pass,format=yuv420p[v]" \ + -map "[v]" -t 3 -c:v rawvideo +FATE_FFMPEG-$(call FRAMECRC, RAWVIDEO, RAWVIDEO, FORMAT_FILTER SCALE_FILTER \ + SCALE2REF_FILTER ALPHAMERGE_FILTER GBLUR_FILTER OVERLAY_FILTER \ + NULL_FILTER TRIM_FILTER SETPTS_FILTER) += fate-ffmpeg-filter-closed-input + # Test termination on streamcopy with -t as an output option. fate-ffmpeg-streamcopy-t: tests/data/vsynth1.yuv fate-ffmpeg-streamcopy-t: CMP = null diff --git a/tests/ref/fate/ffmpeg-filter-closed-input b/tests/ref/fate/ffmpeg-filter-closed-input new file mode 100644 index 00000000000..6d93fad06a0 --- /dev/null +++ b/tests/ref/fate/ffmpeg-filter-closed-input @@ -0,0 +1,80 @@ +#tb 0: 1/25 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 352x288 +#sar 0: 0/1 +0, 0, 0, 1, 152064, 0x70ddd346 +0, 1, 1, 1, 152064, 0x480bb52b +0, 2, 2, 1, 152064, 0x057c0c91 +0, 3, 3, 1, 152064, 0xd765b72d +0, 4, 4, 1, 152064, 0x7268e873 +0, 5, 5, 1, 152064, 0xc4b2d175 +0, 6, 6, 1, 152064, 0x0efc9e12 +0, 7, 7, 1, 152064, 0xaf8faa66 +0, 8, 8, 1, 152064, 0x598804ed +0, 9, 9, 1, 152064, 0x20b784aa +0, 10, 10, 1, 152064, 0xedeb5c93 +0, 11, 11, 1, 152064, 0x145c10f1 +0, 12, 12, 1, 152064, 0x60e3d0ea +0, 13, 13, 1, 152064, 0xf67a033b +0, 14, 14, 1, 152064, 0x4458c92f +0, 15, 15, 1, 152064, 0xc7bd4fce +0, 16, 16, 1, 152064, 0x05ea8e2b +0, 17, 17, 1, 152064, 0xb6c16f5b +0, 18, 18, 1, 152064, 0x3c10ad40 +0, 19, 19, 1, 152064, 0xa5792bae +0, 20, 20, 1, 152064, 0x8bed3d8c +0, 21, 21, 1, 152064, 0xb81d6e31 +0, 22, 22, 1, 152064, 0x8e8343d2 +0, 23, 23, 1, 152064, 0x91a5afbb +0, 24, 24, 1, 152064, 0x2604394d +0, 25, 25, 1, 152064, 0x9b45daec +0, 26, 26, 1, 152064, 0x5ce6d0bb +0, 27, 27, 1, 152064, 0x7f83274f +0, 28, 28, 1, 152064, 0x89741365 +0, 29, 29, 1, 152064, 0xd794ed13 +0, 30, 30, 1, 152064, 0x157dd959 +0, 31, 31, 1, 152064, 0x0b6332a4 +0, 32, 32, 1, 152064, 0xeb957ccc +0, 33, 33, 1, 152064, 0x06747080 +0, 34, 34, 1, 152064, 0xd10030fd +0, 35, 35, 1, 152064, 0x1692d6d4 +0, 36, 36, 1, 152064, 0x1363a845 +0, 37, 37, 1, 152064, 0x2fc18df0 +0, 38, 38, 1, 152064, 0x220fb900 +0, 39, 39, 1, 152064, 0x0d4f850c +0, 40, 40, 1, 152064, 0x92d0c7e2 +0, 41, 41, 1, 152064, 0xd4e49e08 +0, 42, 42, 1, 152064, 0x20cebfa9 +0, 43, 43, 1, 152064, 0x575c20ec +0, 44, 44, 1, 152064, 0xfd500471 +0, 45, 45, 1, 152064, 0x61b47e73 +0, 46, 46, 1, 152064, 0x09ef53ff +0, 47, 47, 1, 152064, 0x6e88c5c2 +0, 48, 48, 1, 152064, 0xbb87b483 +0, 49, 49, 1, 152064, 0x4bbad8ea +0, 50, 50, 1, 152064, 0x05b789ef +0, 51, 51, 1, 152064, 0x4bb46551 +0, 52, 52, 1, 152064, 0x9dddf64a +0, 53, 53, 1, 152064, 0x2a8380b0 +0, 54, 54, 1, 152064, 0x4de3b652 +0, 55, 55, 1, 152064, 0xedb5a8e6 +0, 56, 56, 1, 152064, 0xe20f7c23 +0, 57, 57, 1, 152064, 0x5ab58bac +0, 58, 58, 1, 152064, 0x1f1b8026 +0, 59, 59, 1, 152064, 0x91373915 +0, 60, 60, 1, 152064, 0x02344760 +0, 61, 61, 1, 152064, 0x30f5fcd5 +0, 62, 62, 1, 152064, 0xc711ad61 +0, 63, 63, 1, 152064, 0x24eca223 +0, 64, 64, 1, 152064, 0x52a48ddd +0, 65, 65, 1, 152064, 0xa91c0f05 +0, 66, 66, 1, 152064, 0x8e364e18 +0, 67, 67, 1, 152064, 0xb15d38c8 +0, 68, 68, 1, 152064, 0xf25f6acc +0, 69, 69, 1, 152064, 0xf34ddbff +0, 70, 70, 1, 152064, 0xfc7bf570 +0, 71, 71, 1, 152064, 0x9dc72412 +0, 72, 72, 1, 152064, 0x445d1d59 +0, 73, 73, 1, 152064, 0x2f2768ef +0, 74, 74, 1, 152064, 0xce09f9d6 -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
