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

## What this fixes

Chromium calls `avcodec_flush_buffers()` on every `<video>` seek, and on the
implicit seek-to-zero that the `<video loop>` attribute performs at EOF.
Without a `.flush` callback registered, the libopenh264 wrapper passes the
flush through to nothing — Cisco's `ISVCDecoder` keeps its DPB and reference
frames from the prior GOP. The next IDR is accepted but subsequent P-frames
reference the now-stale state and fail. In Chromium's media pipeline this
surfaces as `Failed to send video packet for decoding` from
`media/filters/ffmpeg_video_decoder.cc` and a frozen `<video>` element on
the second loop iteration. Linux desktop builds where libopenh264 is the
only H.264 decode path are affected; macOS/Windows take other paths.

## Approaches tried

1. `FlushFrame()` alone — drains output buffers but leaves reference state
intact. Bug persists.
2. `FlushFrame()` + clearing `DECODER_OPTION_END_OF_STREAM` — same. The EOS
flag isn't the corrupt state.
3. Destroy + recreate via `svc_decode_close` + `svc_decode_init` — reliable.
Cisco's public API exposes no non-destructive reset, so this is the only
correct option. Flush is a rare event (per seek / per loop boundary), so
the overhead is negligible.

The `s->decoder = NULL` line added to `svc_decode_close` is defensive
hardening so any future caller running close → init twice (which the new
flush does) can't double-free.

## Testing

- `make` builds clean on macOS arm64 and Linux x86_64.
- `make fate SAMPLES=~/fate-suite` passes (no regressions vs. master).
- Manual repro: standalone

## Reproducer

Reproducer artifacts for reviewers:

- `repro_flush.c` — standalone C program. Models Chromium's `<video loop>`
pipeline: decode all packets, `avcodec_flush_buffers()`, decode the same
packets again, compare frame counts.
- `input_bf.h264` — 90-frame raw Annex-B stream with B-frames
(`-bf 3 -profile:v high`). B-frames are required to trigger the bug;
reordering is what causes Cisco's DPB to retain stale references across
the flush.

Build and run:
```
clang -O2 -o repro_flush repro_flush.c \
$(pkg-config --cflags --libs libavformat libavcodec libavutil)
./repro_flush input_bf.h264
```

Expected on master (unpatched):
```
iter1: 90 frames decoded
[libopenh264] PrefetchPic ERROR, pSps->iNumRefFrames:4
[libopenh264] DecodeFrame failed
iter2: 4 frames decoded
RESULT: BUG
```

Expected with this patch:
```
iter1: 90 frames decoded
iter2: 90 frames decoded
RESULT: OK
```


From 066e391ae34bb59b524a40c2ef420b8148618235 Mon Sep 17 00:00:00 2001
From: Scott Kidder <[email protected]>
Date: Wed, 20 May 2026 17:17:24 -0700
Subject: [PATCH] avcodec/libopenh264dec: implement flush callback

Cisco's ISVCDecoder retains DPB and reference state across
avcodec_flush_buffers(), causing the next IDR to be accepted but
subsequent P-frames to fail decode after a seek or loop. Cisco's
API does not expose a non-destructive reset; tear down and rebuild
the decoder via the existing close/init callbacks.

Signed-off-by: Scott Kidder <[email protected]>
---
 libavcodec/libopenh264dec.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/libavcodec/libopenh264dec.c b/libavcodec/libopenh264dec.c
index b6a9bba2dc..e26b554ece 100644
--- a/libavcodec/libopenh264dec.c
+++ b/libavcodec/libopenh264dec.c
@@ -42,12 +42,22 @@ static av_cold int svc_decode_close(AVCodecContext *avctx)
 {
     SVCContext *s = avctx->priv_data;
 
-    if (s->decoder)
+    if (s->decoder) {
         WelsDestroyDecoder(s->decoder);
+        s->decoder = NULL;
+    }
 
     return 0;
 }
 
+static av_cold int svc_decode_init(AVCodecContext *avctx);
+
+static void svc_decode_flush(AVCodecContext *avctx)
+{
+    svc_decode_close(avctx);
+    svc_decode_init(avctx);
+}
+
 static av_cold int svc_decode_init(AVCodecContext *avctx)
 {
     SVCContext *s = avctx->priv_data;
@@ -162,6 +172,7 @@ const FFCodec ff_libopenh264_decoder = {
     .init           = svc_decode_init,
     FF_CODEC_DECODE_CB(svc_decode_frame),
     .close          = svc_decode_close,
+    .flush          = svc_decode_flush,
     .p.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_DR1,
     .caps_internal  = FF_CODEC_CAP_SETS_PKT_DTS |
                       FF_CODEC_CAP_INIT_CLEANUP,
-- 
2.52.0

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

Reply via email to