PR #25219 opened by Seungmin Kim (ehf) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25219 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25219.patch
After a frame_num gap that covers frame_num 0, the next picture's POC (types 1 and 2) comes out 2 * MaxFrameNum too small, and the pictures after the gap are dropped until it catches up. The commit message has the details. Tested: `make fate-h264` passes (232 tests, the new one included). Without the h264_slice.c change, the new test fails: pictures 15 onwards are missing from its output. The new test needs one sample in fate-suite: `h264/frame_num_gap_wrap.h264` (1920 bytes), attached here. >From d740a6e02a2f3d15fcec44f26416021cbd78bdeb Mon Sep 17 00:00:00 2001 From: Seungmin Kim <[email protected]> Date: Thu, 1 Oct 2026 00:30:23 +0900 Subject: [PATCH] avcodec/h264_slice: carry FrameNumOffset across a frame_num gap that wraps A frame_num gap is filled with inferred frames, and prev_frame_num steps through them, wrapping at MaxFrameNum, while prev_frame_num_offset keeps the offset of the last decoded picture. ff_h264_init_poc() then compares the next picture's frame_num with the inferred prev_frame_num, which lies below it, so its FrameNumOffset misses the wrap and its POC (types 1 and 2) comes out 2 * MaxFrameNum too small. The pictures after the gap are taken as older than the ones already output and are dropped until the POC catches up. An encoder that predicts past a lost frame (x264's x264_encoder_invalidate_reference(), as used for reference picture invalidation over RTP) produces such a gap whenever the loss covers the frame with frame_num 0. With x264's MaxFrameNum of 16, about a wrap's worth of pictures is dropped after every such loss; this decoder behind the browsers' WebCodecs and WebRTC on Linux drops the same ones. Advance prev_frame_num_offset where the inferred frames, or the frames the gap shortening skips, cross the wrap, as decoding the missing pictures would have. The new test predicts past the loss of frames 15 and 16 (frame_num 15 and 0) of a 176x144 x264 stream: 38 pictures out rather than 25. Sample: h264/frame_num_gap_wrap.h264 (1920 bytes). Signed-off-by: Seungmin Kim <[email protected]> --- libavcodec/h264_slice.c | 9 ++++++ tests/fate/h264.mak | 2 ++ tests/ref/fate/h264-frame-num-gap-wrap | 43 ++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 tests/ref/fate/h264-frame-num-gap-wrap diff --git a/libavcodec/h264_slice.c b/libavcodec/h264_slice.c index 2bcb64378d..78c42eced3 100644 --- a/libavcodec/h264_slice.c +++ b/libavcodec/h264_slice.c @@ -1457,6 +1457,11 @@ static int h264_field_start(H264Context *h, const H264SliceContext *sl, unwrap_prev_frame_num -= max_frame_num; if ((h->poc.frame_num - unwrap_prev_frame_num) > sps->ref_frame_count) { + /* The frames skipped here cross frame_num's wrap when the gap + * does and the kept part starts at or past 0. */ + if (unwrap_prev_frame_num < 0 && + h->poc.frame_num - sps->ref_frame_count - 1 >= 0) + h->poc.prev_frame_num_offset += max_frame_num; unwrap_prev_frame_num = (h->poc.frame_num - sps->ref_frame_count) - 1; if (unwrap_prev_frame_num < 0) unwrap_prev_frame_num += max_frame_num; @@ -1538,6 +1543,10 @@ static int h264_field_start(H264Context *h, const H264SliceContext *sl, h->poc.prev_frame_num++; h->poc.prev_frame_num %= 1 << sps->log2_max_frame_num; + /* A missing frame with frame_num 0 starts a new FrameNumOffset for the + * pictures after it, as a received one would (8.2.1). */ + if (!h->poc.prev_frame_num) + h->poc.prev_frame_num_offset += 1 << sps->log2_max_frame_num; h->cur_pic_ptr->frame_num = h->poc.prev_frame_num; h->cur_pic_ptr->invalid_gap = !sps->gaps_in_frame_num_allowed_flag; ff_thread_report_progress(&h->cur_pic_ptr->tf, INT_MAX, 0); diff --git a/tests/fate/h264.mak b/tests/fate/h264.mak index 1ee70f216d..22770e3c8a 100644 --- a/tests/fate/h264.mak +++ b/tests/fate/h264.mak @@ -197,6 +197,7 @@ FATE_H264 := $(FATE_H264:%=fate-h264-conformance-%) \ fate-h264-data-partitioning-ab \ fate-h264-data-partitioning-cip \ fate-h264-data-partitioning-cip-strict \ + fate-h264-frame-num-gap-wrap \ fate-h264-intra-refresh-recovery \ fate-h264-lossless \ fate-h264-3386 \ @@ -490,6 +491,7 @@ fate-h264-ref-pic-mod-overflow: CMD = framecrc -i $(TARGET_SAM fate-h264-twofields-packet: CMD = framecrc -i $(TARGET_SAMPLES)/h264/twofields_packet.mp4 -an -frames 30 fate-h264-unescaped-extradata: CMD = framecrc -i $(TARGET_SAMPLES)/h264/unescaped_extradata.mp4 -an -frames 10 fate-h264-3386: CMD = framecrc -i $(TARGET_SAMPLES)/h264/bbc2.sample.h264 +fate-h264-frame-num-gap-wrap: CMD = framecrc -i $(TARGET_SAMPLES)/h264/frame_num_gap_wrap.h264 fate-h264-missing-frame: CMD = framecrc -i $(TARGET_SAMPLES)/h264/nondeterministic_cut.h264 fate-h264-timecode: CMD = framecrc -i $(TARGET_SAMPLES)/h264/crew_cif_timecode-2.h264 diff --git a/tests/ref/fate/h264-frame-num-gap-wrap b/tests/ref/fate/h264-frame-num-gap-wrap new file mode 100644 index 0000000000..673b3c4915 --- /dev/null +++ b/tests/ref/fate/h264-frame-num-gap-wrap @@ -0,0 +1,43 @@ +#tb 0: 1/30 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 176x144 +#sar 0: 0/1 +0, 0, 0, 1, 38016, 0x07bdf766 +0, 1, 1, 1, 38016, 0x361bf636 +0, 2, 2, 1, 38016, 0xfe10f6c6 +0, 3, 3, 1, 38016, 0xc835f7f6 +0, 4, 4, 1, 38016, 0xae1ff866 +0, 5, 5, 1, 38016, 0x017ff836 +0, 6, 6, 1, 38016, 0xf06ff9c6 +0, 7, 7, 1, 38016, 0x85b1f956 +0, 8, 8, 1, 38016, 0x7030fae6 +0, 9, 9, 1, 38016, 0xaca9fb76 +0, 10, 10, 1, 38016, 0xe6e2fc06 +0, 11, 11, 1, 38016, 0x1eeafc96 +0, 12, 12, 1, 38016, 0xec2ffed6 +0, 13, 13, 1, 38016, 0x71ff0029 +0, 14, 14, 1, 38016, 0x78bbffde +0, 15, 15, 1, 38016, 0xd6e202c9 +0, 16, 16, 1, 38016, 0x93e0034d +0, 17, 17, 1, 38016, 0x4ece03d1 +0, 18, 18, 1, 38016, 0x527504b5 +0, 19, 19, 1, 38016, 0xdafa0551 +0, 20, 20, 1, 38016, 0x611e05ed +0, 21, 21, 1, 38016, 0xe4c30689 +0, 22, 22, 1, 38016, 0x66070725 +0, 23, 23, 1, 38016, 0xe4cc07c1 +0, 24, 24, 1, 38016, 0x6130085d +0, 25, 25, 1, 38016, 0xdb1508f9 +0, 26, 26, 1, 38016, 0x52990995 +0, 27, 27, 1, 38016, 0xc79e0a31 +0, 28, 28, 1, 38016, 0x3a420acd +0, 29, 29, 1, 38016, 0xaa670b69 +0, 30, 30, 1, 38016, 0x182b0c05 +0, 31, 31, 1, 38016, 0x83700ca1 +0, 32, 32, 1, 38016, 0x9a760d1d +0, 33, 33, 1, 38016, 0xaf8c0d99 +0, 34, 34, 1, 38016, 0xc2b20e15 +0, 35, 35, 1, 38016, 0xd3e80e91 +0, 36, 36, 1, 38016, 0xe32e0f0d +0, 37, 37, 1, 38016, 0xf0840f89 -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
