Commit 195954b861 for ffmpeg

commit 195954b8610b1892abbe2960929ea8eb12ce4c67
Author: Jun Zhao <barryjzhao@tencent.com>
Date:   Wed Oct 28 16:05:24 2026 +0800

    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 <barryjzhao@tencent.com>

diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 25c930f138..c52371e16f 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++) {