PR #25300 opened by Marti2203
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25300
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25300.patch

`block_contrast()` updates the running maximum only in the `else` branch of the 
minimum update, so a pixel that lowers the minimum is never compared against 
the maximum. The minimum starts at 255, so the first pixel of every block is 
always taken as a new minimum, and so is every pixel in the leading run of 
decreasing values. When the brightest pixels of a block are in that run, the 
block gets a lower contrast than it has, and blocks above the `contrast` 
threshold (default 125) are left out of the global motion search.

To check this against the real filter rather than a copy of the function, 
`repro.sh` runs deshake under gdb (on a `--disable-optimizations` build so 
`block_contrast()` isn't inlined). For each call it compares the return value 
with the actual max - min of the block's pixels:

```
./repro.sh /path/to/ffmpeg_g
# runs: ffmpeg_g -f lavfi -i mandelbrot=s=640x480:inner=convergence -vf 
"trim=start_frame=10:end_frame=12,deshake" -threads 1 -f null -
```

Before (c50d6fca):

```
block_contrast() calls: 4070, wrong result: 216, actual contrast > 125 but 
rated <= 125 (block skipped): 2
  block (240,240): block_contrast() returned 40, actual max-min 159
  block (416,376): block_contrast() returned 10, actual max-min 219
```

After:

```
block_contrast() calls: 4070, wrong result: 0, actual contrast > 125 but rated 
<= 125 (block skipped): 0
```

The fix compares every pixel against both the minimum and the maximum.

Testing: there is no FATE test for deshake. I didn't add a checksum test 
because deshake's output depends on floating point (a checksum test for #25179 
failed on x86-32 for that reason). The output of the `fate-filter-deshake-crop` 
test proposed in #25184 is the same with and without this patch. Output changes 
only when a misrated block changes the motion vote. For example, 
`mandelbrot=s=320x240` shaken with 
`crop=280:200:'20+10*sin(n)':'20+8*cos(n*1.3)'` gives different deshake output 
over 20 frames, while testsrc2, testsrc and smptebars give the same output. In 
practice the effect on the motion estimate is small, because the vote uses many 
blocks: with integer shifts of a static mandelbrot frame, the detected shift 
was the same before and after.

This issue was found by an LLM-Enhanced Static Analysis Engine and verified 
manually.

🤖 Generated with [Claude Code](https://claude.com/claude-code)



From 2192eb0eef93b9795ecde4c1d67a21e27da1cb2a Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 21 Oct 2026 18:18:14 +0800
Subject: [PATCH] avfilter/vf_deshake: fix block_contrast() missing the block
 maximum

block_contrast() updated the running maximum only in the else branch of
the minimum update, so a pixel that lowered the minimum was never
compared against the maximum. Since the minimum starts at 255, the first
pixel of every block is always taken as a new minimum, and any value in
the leading run of decreasing pixels is ignored for the maximum. A block
whose brightest pixels lie in that run is rated with a lower contrast
than it has, and blocks above the contrast threshold can be skipped by
the global motion search.

For example, with mandelbrot=s=640x480:inner=convergence, frames 10-11,
block_contrast() returns a wrong value for 216 of 4070 blocks, and the
blocks at (240,240) and (416,376), whose pixels span 159 and 219 levels,
are rated 40 and 10 and skipped with the default contrast=125.

Compare every pixel against both the minimum and the maximum.

Signed-off-by: Martin Mirchev <[email protected]>
---
 libavfilter/vf_deshake.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libavfilter/vf_deshake.c b/libavfilter/vf_deshake.c
index 8a2dbe2fa56..6cba4f82786 100644
--- a/libavfilter/vf_deshake.c
+++ b/libavfilter/vf_deshake.c
@@ -251,9 +251,8 @@ static int block_contrast(uint8_t *src, int x, int y, int 
stride, int blocksize)
             pos = (y + i) * stride + (x + j);
             if (src[pos] < lowest)
                 lowest = src[pos];
-            else if (src[pos] > highest) {
+            if (src[pos] > highest)
                 highest = src[pos];
-            }
         }
     }
 
-- 
2.54.0

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

Reply via email to