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

Fixes #25320.

An interrupted CAVLC macroblock can leave residual coefficients in a slice 
context. Clearing `H264SliceContext.mb` for every slice context during 
`avcodec_flush_buffers()` prevents those values from affecting the next stream 
decoded by the same context.

Adds two public API FATE regression cases adapted from Pu Cheng's reproducer. 
On unmodified master they report 1 differing frame for `BA1_Sony_D.jsv` and 30 
for `BA_MW_D.264`; with this change both report zero.

Tested on AArch64 with the two new FATE tests and the existing H.264 
conformance tests for both samples.


>From c9b5510924d7db67a7a8350d0b1d2569ff003506 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Thu, 22 Oct 2026 17:35:52 +0000
Subject: [PATCH] avcodec/h264: clear pending coefficients on flush

An interrupted CAVLC macroblock can leave residual coefficients in a slice
context. The next stream decoded with the same context then incorporates them
after avcodec_flush_buffers(). Clear the coefficient buffer for every slice
context on flush and cover the reuse sequence with two conformance samples.

Fixes #25320.

Assisted-by: Fairy
---
 libavcodec/h264dec.c                      |   4 +
 tests/api/Makefile                        |   1 +
 tests/api/api-h264-flush-coeffs-test.c    | 208 ++++++++++++++++++++++
 tests/fate/api.mak                        |   5 +
 tests/ref/fate/api-h264-flush-coeffs-mw   |   2 +
 tests/ref/fate/api-h264-flush-coeffs-sony |   2 +
 6 files changed, 222 insertions(+)
 create mode 100644 tests/api/api-h264-flush-coeffs-test.c
 create mode 100644 tests/ref/fate/api-h264-flush-coeffs-mw
 create mode 100644 tests/ref/fate/api-h264-flush-coeffs-sony

diff --git a/libavcodec/h264dec.c b/libavcodec/h264dec.c
index 597bdae548a..9096e6ba7d2 100644
--- a/libavcodec/h264dec.c
+++ b/libavcodec/h264dec.c
@@ -484,6 +484,10 @@ static av_cold void h264_decode_flush(AVCodecContext 
*avctx)
     ff_h264_flush_change(h);
     ff_h264_sei_uninit(&h->sei);
 
+    /* A damaged slice may leave coefficients in an incomplete macroblock. */
+    for (i = 0; i < h->nb_slice_ctx; i++)
+        memset(h->slice_ctx[i].mb, 0, sizeof(h->slice_ctx[i].mb));
+
     for (i = 0; i < H264_MAX_PICTURE_COUNT; i++)
         ff_h264_unref_picture(&h->DPB[i]);
     h->cur_pic_ptr = NULL;
diff --git a/tests/api/Makefile b/tests/api/Makefile
index e51dc6c5837..2852aa668e1 100644
--- a/tests/api/Makefile
+++ b/tests/api/Makefile
@@ -1,6 +1,7 @@
 APITESTPROGS-$(call ALLYES, LIBASTCENC_ENCODER LIBASTCENC_DECODER) += 
api-astc-profile
 APITESTPROGS-$(call ENCDEC, FLAC, FLAC) += api-flac
 APITESTPROGS-$(call DEMDEC, H264, H264) += api-h264
+APITESTPROGS-$(call ALLYES, H264_DECODER H264_PARSER) += api-h264-flush-coeffs
 APITESTPROGS-$(call DEMDEC, H264, H264) += api-h264-slice
 APITESTPROGS-$(CONFIG_MOV_MUXER)        += api-movenc
 APITESTPROGS-$(CONFIG_MOVTEXT_ENCODER)  += api-movtext
diff --git a/tests/api/api-h264-flush-coeffs-test.c 
b/tests/api/api-h264-flush-coeffs-test.c
new file mode 100644
index 00000000000..f1615a99d2d
--- /dev/null
+++ b/tests/api/api-h264-flush-coeffs-test.c
@@ -0,0 +1,208 @@
+/*
+ * H.264 decoder flush regression test, based on the reproducer by Pu Cheng
+ * in FFmpeg issue #25320.
+ *
+ * Without the flush fix, residual coefficients of an incomplete macroblock
+ * survive avcodec_flush_buffers() and are added to the next stream.
+ *
+ * Usage: h264_partial_flush <stream> <bytes>
+ * e.g. FATE h264-conformance/BA1_Sony_D.jsv
+ *
+ * Decoder R receives the first access unit of <stream> cut to <bytes> bytes
+ * (as after an interrupted transfer), is flushed, then decodes the whole
+ * <stream>. Decoder F is newly opened and decodes only <stream>.
+ */
+#include <libavcodec/avcodec.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* ---- helpers: split an Annex B file into access units, hash pictures ---- */
+
+#define MAX_FRAMES 4096
+
+typedef struct AUList { AVPacket **au; int n; } AUList;
+
+static int load_aus(const char *path, AUList *l)
+{
+    FILE *fp = fopen(path, "rb");
+    const AVCodec *c = avcodec_find_decoder(AV_CODEC_ID_H264);
+    AVCodecParserContext *pc = av_parser_init(AV_CODEC_ID_H264);
+    AVCodecContext *ctx = avcodec_alloc_context3(c);
+    uint8_t *buf;
+    long size;
+    int off = 0;
+
+    if (!fp || !pc || !ctx)
+        return -1;
+    fseek(fp, 0, SEEK_END);
+    size = ftell(fp);
+    fseek(fp, 0, SEEK_SET);
+    buf = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE);
+    if (fread(buf, 1, size, fp) != (size_t)size)
+        return -1;
+    fclose(fp);
+    l->au = NULL;
+    l->n = 0;
+    for (;;) {
+        uint8_t *data;
+        int data_size, eof = off >= size;
+        int used = av_parser_parse2(pc, ctx, &data, &data_size,
+                                    eof ? NULL : buf + off, eof ? 0 : size - 
off,
+                                    AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
+        off += used;
+        if (data_size) {
+            AVPacket *p = av_packet_alloc();
+            av_new_packet(p, data_size);
+            memcpy(p->data, data, data_size);
+            l->au = realloc(l->au, (l->n + 1) * sizeof(*l->au));
+            l->au[l->n++] = p;
+        } else if (eof)
+            break;
+    }
+    av_parser_close(pc);
+    avcodec_free_context(&ctx);
+    av_free(buf);
+    return l->n;
+}
+
+static void free_aus(AUList *l)
+{
+    for (int i = 0; i < l->n; i++)
+        av_packet_free(&l->au[i]);
+    free(l->au);
+    l->au = NULL;
+    l->n = 0;
+}
+
+static AVCodecContext *open_h264(void)
+{
+    const AVCodec *c = avcodec_find_decoder(AV_CODEC_ID_H264);
+    AVCodecContext *ctx = avcodec_alloc_context3(c);
+    if (!ctx)
+        return NULL;
+    ctx->thread_count = 1;
+    if (avcodec_open2(ctx, c, NULL) < 0)
+        return NULL;
+    return ctx;
+}
+
+static uint64_t hash_frame(const AVFrame *f)
+{
+    uint64_t h = 1469598103934665603ULL;
+    for (int p = 0; p < 3 && f->data[p]; p++) {
+        int w = p ? AV_CEIL_RSHIFT(f->width, 1) : f->width;
+        int hh = p ? AV_CEIL_RSHIFT(f->height, 1) : f->height;
+        for (int y = 0; y < hh; y++)
+            for (int x = 0; x < w; x++)
+                h = (h ^ f->data[p][y * f->linesize[p] + x]) * 
1099511628211ULL;
+    }
+    return h;
+}
+
+/* send every AU, then drain to EOF; returns the number of frames.
+ * If keep is set, a reference to frame number keep_idx is stored in it. */
+static int decode_keep(AVCodecContext *ctx, const AUList *l, uint64_t *hashes, 
int max,
+                       int keep_idx, AVFrame *keep)
+{
+    AVFrame *f = av_frame_alloc();
+    int n = 0;
+    for (int i = 0; i <= l->n; i++) {
+        avcodec_send_packet(ctx, i < l->n ? l->au[i] : NULL);
+        while (avcodec_receive_frame(ctx, f) >= 0) {
+            if (hashes && n < max)
+                hashes[n] = hash_frame(f);
+            if (keep && n == keep_idx)
+                av_frame_ref(keep, f);
+            n++;
+            av_frame_unref(f);
+        }
+    }
+    av_frame_free(&f);
+    return n;
+}
+
+static int decode_all(AVCodecContext *ctx, const AUList *l, uint64_t *hashes, 
int max)
+{
+    return decode_keep(ctx, l, hashes, max, -1, NULL);
+}
+
+/* ---- end of helpers ---- */
+
+int main(int argc, char **argv)
+{
+    AUList s;
+    AVCodecContext *r, *f;
+    AVPacket *cut = av_packet_alloc();
+    AVFrame *fr = av_frame_alloc(), *ff = av_frame_alloc();
+    uint64_t hr[MAX_FRAMES], hf[MAX_FRAMES];
+    int nr, nf, first = -1, last = -1, ndiff = 0, ret;
+
+    setvbuf(stdout, NULL, _IONBF, 0);
+    if (argc < 3 || load_aus(argv[1], &s) <= 0)
+        return 2;
+    av_new_packet(cut, FFMIN(atoi(argv[2]), s.au[0]->size));
+    memcpy(cut->data, s.au[0]->data, cut->size);
+
+    r = open_h264();
+    f = open_h264();
+    ret = avcodec_send_packet(r, cut);
+    printf("R sent the first access unit cut to %d of %d bytes: send %d",
+           cut->size, s.au[0]->size, ret);
+    ret = avcodec_receive_frame(r, fr);
+    printf(", receive %s\n", ret == AVERROR(EAGAIN) ? "EAGAIN" : ret < 0 ? 
"error" : "frame");
+    av_frame_unref(fr);
+    avcodec_flush_buffers(r);
+
+    nr = decode_all(r, &s, hr, MAX_FRAMES);
+    nf = decode_all(f, &s, hf, MAX_FRAMES);
+    for (int i = 0; i < FFMIN(FFMIN(nr, nf), MAX_FRAMES); i++)
+        if (hr[i] != hf[i]) {
+            ndiff++;
+            if (first < 0)
+                first = i;
+            last = i;
+        }
+    printf("R %d frames, F %d frames; frames that differ: %d", nr, nf, ndiff);
+    if (ndiff == 1)
+        printf(" (frame %d)", first);
+    else if (ndiff)
+        printf(" (frames %d to %d)", first, last);
+    printf("\n");
+
+    if (first >= 0) {
+        /* decode again, keep the first differing frame and locate the damage 
*/
+        AVCodecContext *r2 = open_h264(), *f2 = open_h264();
+        int mbw, minmb = -1, nmb = 0;
+        avcodec_send_packet(r2, cut);
+        avcodec_receive_frame(r2, fr);
+        av_frame_unref(fr);
+        avcodec_flush_buffers(r2);
+        decode_keep(r2, &s, NULL, 0, first, fr);
+        decode_keep(f2, &s, NULL, 0, first, ff);
+        mbw = (fr->width + 15) / 16;
+        for (int my = 0; my < (fr->height + 15) / 16; my++)
+            for (int mx = 0; mx < mbw; mx++) {
+                int d = 0;
+                for (int y = my * 16; y < FFMIN(my * 16 + 16, fr->height); y++)
+                    for (int x = mx * 16; x < FFMIN(mx * 16 + 16, fr->width); 
x++)
+                        d |= fr->data[0][y * fr->linesize[0] + x] != 
ff->data[0][y * ff->linesize[0] + x];
+                if (d) {
+                    nmb++;
+                    if (minmb < 0)
+                        minmb = my * mbw + mx;
+                }
+            }
+        printf("frame %d: luma differs in %d of %d macroblocks, the first is 
macroblock %d\n",
+               first, nmb, mbw * ((fr->height + 15) / 16), minmb);
+        avcodec_free_context(&r2);
+        avcodec_free_context(&f2);
+    }
+    avcodec_free_context(&r);
+    avcodec_free_context(&f);
+    av_packet_free(&cut);
+    av_frame_free(&fr);
+    av_frame_free(&ff);
+    free_aus(&s);
+    return 0;
+}
diff --git a/tests/fate/api.mak b/tests/fate/api.mak
index ca836a40e3d..6a02c2c5405 100644
--- a/tests/fate/api.mak
+++ b/tests/fate/api.mak
@@ -30,6 +30,11 @@ FATE_API_SAMPLES_LIBAVFORMAT-$(call DEMDEC, H264, H264, 
H264_PARSER) += fate-api
 fate-api-h264: $(APITESTSDIR)/api-h264-test$(EXESUF)
 fate-api-h264: CMD = run $(APITESTSDIR)/api-h264-test$(EXESUF) 
$(TARGET_SAMPLES)/h264-conformance/SVA_NL2_E.264
 
+FATE_API_SAMPLES_LIBAVFORMAT-$(call ALLYES, H264_DECODER H264_PARSER) += 
fate-api-h264-flush-coeffs-sony fate-api-h264-flush-coeffs-mw
+fate-api-h264-flush-coeffs-sony fate-api-h264-flush-coeffs-mw: 
$(APITESTSDIR)/api-h264-flush-coeffs-test$(EXESUF)
+fate-api-h264-flush-coeffs-sony: CMD = run 
$(APITESTSDIR)/api-h264-flush-coeffs-test$(EXESUF) 
$(TARGET_SAMPLES)/h264-conformance/BA1_Sony_D.jsv 1000
+fate-api-h264-flush-coeffs-mw:   CMD = run 
$(APITESTSDIR)/api-h264-flush-coeffs-test$(EXESUF) 
$(TARGET_SAMPLES)/h264-conformance/BA_MW_D.264 1000
+
 FATE_API_SAMPLES_LIBAVFORMAT-$(call DEMDEC, H264, H264) += fate-api-h264-slice
 fate-api-h264-slice: $(APITESTSDIR)/api-h264-slice-test$(EXESUF)
 fate-api-h264-slice: CMD = run $(APITESTSDIR)/api-h264-slice-test$(EXESUF) 2 
$(TARGET_SAMPLES)/h264/crew_cif.nal
diff --git a/tests/ref/fate/api-h264-flush-coeffs-mw 
b/tests/ref/fate/api-h264-flush-coeffs-mw
new file mode 100644
index 00000000000..5b671863123
--- /dev/null
+++ b/tests/ref/fate/api-h264-flush-coeffs-mw
@@ -0,0 +1,2 @@
+R sent the first access unit cut to 1000 of 2384 bytes: send 0, receive frame
+R 100 frames, F 100 frames; frames that differ: 0
diff --git a/tests/ref/fate/api-h264-flush-coeffs-sony 
b/tests/ref/fate/api-h264-flush-coeffs-sony
new file mode 100644
index 00000000000..27b3ae97947
--- /dev/null
+++ b/tests/ref/fate/api-h264-flush-coeffs-sony
@@ -0,0 +1,2 @@
+R sent the first access unit cut to 1000 of 3184 bytes: send 0, receive frame
+R 17 frames, F 17 frames; frames that differ: 0
-- 
2.54.0

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

Reply via email to