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

`vp3_update_thread_context()` compares each `s->qps[i]` with `s1->qps[1]` 
instead of `s1->qps[i]`. If a thread's old `qps[i]` happens to equal the new 
`qps[1]`, `qmat[i]` is not copied. `qps[]` is still copied when another index 
differs, so the next frame with the same quantizers skips `init_dequantizer()` 
and is dequantized with a stale matrix. Theora output then depends on the 
thread count, and nothing is logged.

Only streams that use more than one quantizer per frame and change them between 
frames are affected. Single-quantizer streams always take the copy path for 
index 0 and never use indices 1 and 2. The fix is the one-character index 
change.

Verification (master a497ab4d07, release and ASan builds):
- None of the Theora/VP3 samples in fate-suite (`vp3/*`, `ogg/*.ogv`) uses 
changing multi-quantizer frames. They decode identically with `-threads 1/4/8`, 
before and after the fix.
- `ogg/empty_theora_packets.ogv` uses three quantizers (31, 20, 41) in every 
frame. After remuxing it to Matroska, I set the first quantizer of the first 
frame to 20 (one byte in the frame header). Before the fix, the framemd5 
differs between `-threads 1`, `2`, `3`, `4` and `8` (5 of 9 frames are wrong 
with 4 threads). After the fix, all thread counts match `-threads 1`.
- `fate-vp3` and `fate-oggtheora-demux` pass unchanged.
- The new `fate-theora-frame-threads-qps` (second patch) needs no new sample. 
It does the same remux and one-byte patch with `run_with_patched_temp` and 
decodes with 4 frame threads. Its reference equals the single-threaded output 
of unpatched master. It fails without the fix and passes with it.

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

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



From 8c6e1079723cfc8c79549f1d89c572321e187bd5 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Tue, 27 Oct 2026 11:45:20 +0800
Subject: [PATCH 1/2] avcodec/vp3: fix quantizer matrix update with frame
 threading

vp3_update_thread_context() compared every s->qps[i] with s1->qps[1]
instead of s1->qps[i]. When a thread's previous qps[i] happened to
equal the new qps[1], qmat[i] was not copied while qps[] still was, so
the next frame with the same quantizers was dequantized with a stale
matrix and the output depended on the number of threads.

This only affects Theora streams that use more than one quantizer per
frame.

Signed-off-by: Martin Mirchev <[email protected]>
---
 libavcodec/vp3.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavcodec/vp3.c b/libavcodec/vp3.c
index ca468b047c1..9b6b9901d89 100644
--- a/libavcodec/vp3.c
+++ b/libavcodec/vp3.c
@@ -2517,7 +2517,7 @@ static int vp3_update_thread_context(AVCodecContext *dst, 
const AVCodecContext *
     if (s != s1) {
         // copy qscale data if necessary
         for (int i = 0; i < 3; i++) {
-            if (s->qps[i] != s1->qps[1]) {
+            if (s->qps[i] != s1->qps[i]) {
                 qps_changed = 1;
                 memcpy(&s->qmat[i], &s1->qmat[i], sizeof(s->qmat[i]));
             }
-- 
2.54.0


From 550045cd97d56f467bfa4c0b53a2e12072bf7a66 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Tue, 27 Oct 2026 11:45:32 +0800
Subject: [PATCH 2/2] fate/vpx: add a test for Theora quantizer changes with
 frame threading

Patch the first quantizer of the first frame of a remuxed existing
sample that uses three quantizers per frame, and decode it with four
frame threads.

Signed-off-by: Martin Mirchev <[email protected]>
---
 tests/fate/vpx.mak                      | 11 +++++++++++
 tests/ref/fate/theora-frame-threads-qps | 14 ++++++++++++++
 2 files changed, 25 insertions(+)
 create mode 100644 tests/ref/fate/theora-frame-threads-qps

diff --git a/tests/fate/vpx.mak b/tests/fate/vpx.mak
index 764db4ce8bb..57d137d4104 100644
--- a/tests/fate/vpx.mak
+++ b/tests/fate/vpx.mak
@@ -28,6 +28,17 @@ fate-theora-coeff-level64: CMD = framecrc -flags +bitexact 
-i $(TARGET_SAMPLES)/
 FATE_VP3-$(call FRAMECRC, OGG, THEORA) += fate-theora-offset
 fate-theora-offset: CMD = framecrc -flags +bitexact -i 
$(TARGET_SAMPLES)/vp3/offset_test.ogv
 
+# Remux a sample that uses three quantizers per frame (31, 20, 41) and set the
+# first quantizer of the first frame to 20. With frame threading, the matrix
+# for the first quantizer must then be updated for the following frames.
+FATE_VP3-$(call FRAMECRC, OGG MATROSKA, THEORA, MATROSKA_MUXER) += 
fate-theora-frame-threads-qps
+fate-theora-frame-threads-qps: CMD = run_with_patched_temp \
+    "$(FFMPEG) -nostdin -v error -i 
$(TARGET_SAMPLES)/ogg/empty_theora_packets.ogv \
+    -map 0:v -c copy -fflags +bitexact -write_crc32 0 -y" \
+    "$(FFMPEG) -nostdin -v error -bitexact -f framecrc - \
+    -flags +bitexact -threads 4 -thread_type frame -i" \
+    mkv 3751 "\0024"
+
 FATE_VP3-$(call FRAMECRC, AVI, VP3) += fate-vp31
 fate-vp31: CMD = framecrc -flags +bitexact -i $(TARGET_SAMPLES)/vp3/vp31.avi
 
diff --git a/tests/ref/fate/theora-frame-threads-qps 
b/tests/ref/fate/theora-frame-threads-qps
new file mode 100644
index 00000000000..042961253e5
--- /dev/null
+++ b/tests/ref/fate/theora-frame-threads-qps
@@ -0,0 +1,14 @@
+#tb 0: 1/25
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 320x240
+#sar 0: 1/1
+0,          0,          0,        1,   115200, 0xa9a6e9db
+0,         30,         30,        1,   115200, 0xdc0e7882
+0,         38,         38,        1,   115200, 0x217ca531
+0,         41,         41,        1,   115200, 0xf1f098c4
+0,         71,         71,        1,   115200, 0xf1f098c4
+0,         79,         79,        1,   115200, 0x6791ee7f
+0,         82,         82,        1,   115200, 0x63d6731c
+0,        112,        112,        1,   115200, 0x63d6731c
+0,        142,        142,        1,   115200, 0x63d6731c
-- 
2.54.0

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

Reply via email to