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++) {