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]

Reply via email to