PR #22605 opened by Zhao Zhili (quink)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22605
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22605.patch

libvidstab's vsTransformPrepare() takes different internal code paths
for in-place (src == dest) vs. separate-buffer operation. Its persistent
state (srcMalloced, destbuf) becomes inconsistent when the two modes are
mixed across frames, producing incorrect stabilization output.

Whether a given frame is writable depends on pipeline scheduling and
frame reference management, which can change between FFmpeg versions.
Since FFmpeg 8.1, changes in the scheduler caused some frames to arrive
as non-writable, leading to alternation between in-place and
separate-buffer paths that triggered the inconsistency.

Fix this by always calling av_frame_make_writable() to ensure frames
are writable, so we consistently take the in-place path.

Fix #22595

Signed-off-by: Zhao Zhili <[email protected]>


>From 6b63a5daa0deae99b419df1f7bdb870d3273b1eb Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Wed, 25 Mar 2026 00:35:19 +0800
Subject: [PATCH] avfilter/vidstabtransform: always use in-place transform path

libvidstab's vsTransformPrepare() takes different internal code paths
for in-place (src == dest) vs. separate-buffer operation. Its persistent
state (srcMalloced, destbuf) becomes inconsistent when the two modes are
mixed across frames, producing incorrect stabilization output.

Whether a given frame is writable depends on pipeline scheduling and
frame reference management, which can change between FFmpeg versions.
Since FFmpeg 8.1, changes in the scheduler caused some frames to arrive
as non-writable, leading to alternation between in-place and
separate-buffer paths that triggered the inconsistency.

Fix this by always calling av_frame_make_writable() to ensure frames
are writable, so we consistently take the in-place path.

Fix #22595

Signed-off-by: Zhao Zhili <[email protected]>
---
 libavfilter/vf_vidstabtransform.c | 39 ++++++++++++-------------------
 1 file changed, 15 insertions(+), 24 deletions(-)

diff --git a/libavfilter/vf_vidstabtransform.c 
b/libavfilter/vf_vidstabtransform.c
index 55a5c9960d..abf652aa05 100644
--- a/libavfilter/vf_vidstabtransform.c
+++ b/libavfilter/vf_vidstabtransform.c
@@ -232,45 +232,36 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     VSTransformData* td = &(tc->td);
 
     AVFilterLink *outlink = inlink->dst->outputs[0];
-    int direct = 0;
     AVFrame *out;
     VSFrame inframe;
     int plane;
+    int ret;
 
-    if (av_frame_is_writable(in)) {
-        direct = 1;
-        out = in;
-    } else {
-        out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
-        if (!out) {
-            av_frame_free(&in);
-            return AVERROR(ENOMEM);
-        }
-        av_frame_copy_props(out, in);
+    /* libvidstab's vsTransformPrepare() takes different internal code paths
+     * for in-place (src == dest) vs. separate-buffer operation, and its
+     * persistent state (srcMalloced, destbuf) becomes inconsistent when the
+     * two modes are mixed across frames. Whether a frame is writable depends
+     * on pipeline scheduling, so alternation between the paths is not
+     * deterministic. Always ensure the frame is writable so we consistently
+     * take the in-place path, avoiding the inconsistency.
+     */
+    ret = av_frame_make_writable(in);
+    if (ret < 0) {
+        av_frame_free(&in);
+        return ret;
     }
+    out = in;
 
     for (plane = 0; plane < vsTransformGetSrcFrameInfo(td)->planes; plane++) {
         inframe.data[plane] = in->data[plane];
         inframe.linesize[plane] = in->linesize[plane];
     }
-    if (direct) {
-        vsTransformPrepare(td, &inframe, &inframe);
-    } else { // separate frames
-        VSFrame outframe;
-        for (plane = 0; plane < vsTransformGetDestFrameInfo(td)->planes; 
plane++) {
-            outframe.data[plane] = out->data[plane];
-            outframe.linesize[plane] = out->linesize[plane];
-        }
-        vsTransformPrepare(td, &inframe, &outframe);
-    }
+    vsTransformPrepare(td, &inframe, &inframe);
 
     vsDoTransform(td, vsGetNextTransform(td, &tc->trans));
 
     vsTransformFinish(td);
 
-    if (!direct)
-        av_frame_free(&in);
-
     return ff_filter_frame(outlink, out);
 }
 
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to