Compare commits

...

2 Commits

Author SHA1 Message Date
Niklas Haas
9e3030bc44 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

Backported-from: 38a5fcc02c

During backporting, I had to change the signature of `close_input()` to
`void close_input(FilterGraph *fg, int input_idx)` since this version of
FFmpeg does not contain a reference to the input index in the InputFilterPriv.
2025-11-27 13:27:54 +01:00
Niklas Haas
a665413248 avfilter/buffersrc: add av_buffersrc_get_status()
There is currently no way for API users to know that a buffersrc is no longer
accepting input, except by trying to feed it a frame and seeing what happens.

Of course, this is not possible if the user does not *have* a frame to feed,
but may still wish to know if the filter is still accepting input or not.

Since passing `frame == NULL` to `av_buffersrc_add_frame()` is already treated
as closing the input, we are left with no choice but to introduce a new
function for this.

We don't explicitly return the result of `ff_outlink_get_status()` to avoid
leaking internal status codes, and instead translate them all to AVERROR(EOF).

Backported-from: 623669a02c
2025-11-27 13:21:42 +01:00
5 changed files with 42 additions and 2 deletions

View File

@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2024-03-07
API changes, most recent first:
2025-09-xx - xxxxxxxxxx - lavfi 10.5.100 - buffersrc.h
Add av_buffersrc_get_status().
2024-09-23 - 6940a6de2f0 - lavu 59.38.100 - frame.h
Add AV_FRAME_DATA_VIEW_ID.

View File

@@ -2365,6 +2365,18 @@ finish:
fps->dropped_keyframe |= fps->last_dropped && (frame->flags & AV_FRAME_FLAG_KEY);
}
static void close_input(FilterGraph *fg, int input_idx)
{
InputFilterPriv *ifp = ifp_from_ifilter(fg->inputs[input_idx]);
FilterGraphPriv *fgp = fgp_from_fg(ifp->ifilter.graph);
if (!ifp->eof) {
sch_filter_receive_finish(fgp->sch, fgp->sch_idx, input_idx);
ifp->eof = 1;
}
}
static int close_output(OutputFilterPriv *ofp, FilterGraphThread *fgt)
{
FilterGraphPriv *fgp = fgp_from_fg(ofp->ofilter.graph);
@@ -3035,7 +3047,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(fg, input_idx);
continue;
}
if (ret < 0)
@@ -3052,6 +3064,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->filter))
close_input(fg, i);
}
}
for (unsigned i = 0; i < fg->nb_outputs; i++) {

View File

@@ -283,6 +283,16 @@ int av_buffersrc_close(AVFilterContext *ctx, int64_t pts, unsigned flags)
return (flags & AV_BUFFERSRC_FLAG_PUSH) ? push_frame(ctx->graph) : 0;
}
int av_buffersrc_get_status(AVFilterContext *ctx)
{
BufferSourceContext *s = ctx->priv;
if (!s->eof && ff_outlink_get_status(ctx->outputs[0]))
s->eof = 1;
return s->eof ? AVERROR(EOF) : 0;
}
static av_cold int init_video(AVFilterContext *ctx)
{
BufferSourceContext *c = ctx->priv;

View File

@@ -208,6 +208,14 @@ int av_buffersrc_add_frame_flags(AVFilterContext *buffer_src,
*/
int av_buffersrc_close(AVFilterContext *ctx, int64_t pts, unsigned flags);
/**
* Returns 0 or a negative AVERROR code. Currently, this will only ever
* return AVERROR(EOF), to indicate that the buffer source has been closed,
* either as a result of av_buffersrc_close(), or because the downstream
* filter is no longer accepting new data.
*/
int av_buffersrc_get_status(AVFilterContext *ctx);
/**
* @}
*/

View File

@@ -31,7 +31,7 @@
#include "version_major.h"
#define LIBAVFILTER_VERSION_MINOR 4
#define LIBAVFILTER_VERSION_MINOR 5
#define LIBAVFILTER_VERSION_MICRO 100