PR #25543 opened by Jake (jakefineman)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25543
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25543.patch
When the model output has the same size as its input (SRCNN-style models),
vf_sr pre-scales the input into the output frame and runs the model in place on
the output frame. The queued task then records the output frame as its input,
ff_dnn_get_result() overwrites `in` with it, and the original input frame is
never freed: one frame leaks for every filtered frame.
The fix frees the input frame right after it has been scaled into the output
frame.
**Testing**
TensorFlow backend, macOS arm64, with a [1,-1,-1,1] Identity model, so the
output size equals the input size and the pre-scale path is taken:
testsrc2=s=320x240:r=25,format=gray8,sr=dnn_backend=tensorflow:model=ident.pb:input=x:output=y:scale_factor=2
| | master | this PR |
|---|---|---|
| `leaks --atExit`, 50 frames: leaked AVFrame roots (frame + 81920 B buffer) |
51 | 0 |
| max RSS, 100 to 1000 frames | +74.4 MB (~82.7 KB per frame) | +0.6 MB |
| framemd5, 30 frames, gray8 and yuv420p | | identical to master |
With async=0 the frame leak is the same on master and gone with the patch. Both
builds also show 41 small 32-byte leaks at 50 frames, which are unrelated to
this change.
Not tested: the OpenVINO and Torch backends (the change is in the
backend-independent filter code), and the ESPCN path, whose code is unchanged.
vf_sr has no FATE test.
Separately, the `async_state != DAST_SUCCESS` return in filter_frame() still
leaks `out` (and `in` on the ESPCN path); that is left for a separate patch.
>From a6a3a1d78e2b7a2a3a96803f94d978dfa00fe187 Mon Sep 17 00:00:00 2001
From: Jake Fineman <[email protected]>
Date: Tue, 27 Oct 2026 15:15:28 -0400
Subject: [PATCH] avfilter/vf_sr: free the input frame after pre-scaling it
When the model output has the same size as its input (SRCNN-style
models), config_output() sets up sws_pre_scale and filter_frame()
upscales the input frame into the output frame, then runs the model in
place with ff_dnn_execute_model(&ctx->dnnctx, out, out). The queued task
therefore records out as its input frame, and ff_dnn_get_result()
overwrites the caller's in pointer with it. The original input frame is
never freed: the later "in != out" check compares out with itself, so
one input frame is leaked for every frame filtered.
Free the input frame as soon as it has been scaled into out. Its
properties have already been copied and nothing reads it afterwards,
since sws_uv_scale is only set up on the other path. On the pre-scale
path the av_frame_free(&in) on the execute failure path becomes a
no-op; on the ESPCN path it still frees the input frame.
Signed-off-by: Jake Fineman <[email protected]>
---
libavfilter/vf_sr.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/libavfilter/vf_sr.c b/libavfilter/vf_sr.c
index 8f1ccee56aa..6fe5193e97f 100644
--- a/libavfilter/vf_sr.c
+++ b/libavfilter/vf_sr.c
@@ -129,6 +129,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
sws_scale(ctx->sws_pre_scale,
(const uint8_t **)in->data, in->linesize, 0, in->height,
out->data, out->linesize);
+ av_frame_free(&in);
dnn_result = ff_dnn_execute_model(&ctx->dnnctx, out, out);
} else {
dnn_result = ff_dnn_execute_model(&ctx->dnnctx, in, out);
--
2.54.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]