PR #24325 opened by frankplow
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24325
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24325.patch
In VVC, there are two schemes for addressing reference pictures in
the DPB:
1. Normally, the POC of the reference picture is signaled using a delta
from the current picture POC.
2. In the case of long-term reference pictures (LTRPs), which may be far
away from the current POC, the absolute value of the LTRP POC is
signalled. This scheme has two sub-schemes:
a. Signalling the entirety of the absolute value of the LTRP
POC would be expensive, so in most cases we only signal the
sps_log2_max_pic_order_cnt_lsb_minus4 least-significant bits of the
LTRP POC.
b. In the case of a collision in the least-significant bits,
the full POC is signalled.
Both schemes are implemented in find_ref_idx. The use_msb parameter
of find_ref_idx is nonzero in the case of schemes 1 and 2b, and zero in
the case of scheme 2a.
This commit fixes a bug in scheme 2a. The issue arises from the fact
that the current picture is stored in the DPB at the time find_ref_idx
is invoked in FFmpeg's implementation, whereas in the specification the
current picture is not yet considered part of the DPB. The current
picture may have a POC which shares least-significant bits with an LTRP.
The specification does not identify this as a collision however (see the
condition in delta_poc_msb_cycle_present_flag), and so scheme 2a is used
rather than scheme 2b. If the current frame precedes the correct LTRP
in the DPB, FFmpeg's implementation then interprets the signalling to
mean a frame which references itself. The problematic callsite
identifies this as an issue and returns an AVERROR_INVALIDDATA, but this
scenario can occur in valid bitstreams. For example, the conformance
bitstream LTRP_A_ERICSSON_3 has one such instance. FFmpeg gets away
with decoding it currently because the LTRP precedes the current picture
in the DPB, but this is by chance rather than design.
The commit addresses this issue by skipping over the current picture
inside the main loop of find_ref_idx. It's also necessary to add a
special case for if scheme 2b is used to reference the current picture,
which is syntactically possible but forbidden by clause 8.3.2. For the
same reason, the handling of `ref == fc->ref` at the callsite remains in
place.
---
Test bitstream for FATE is attached below.
>From 65de55f9703b986e32f56dad752fa8e0282200e1 Mon Sep 17 00:00:00 2001
From: Frank Plowman <[email protected]>
Date: Sat, 29 Aug 2026 09:46:56 +0100
Subject: [PATCH 1/2] avcodec/vvc: Skip over the current picture in
find_ref_idx
In VVC, there are two schemes for addressing reference pictures in
the DPB:
1. Normally, the POC of the reference picture is signaled using a delta
from the current picture POC.
2. In the case of long-term reference pictures (LTRPs), which may be far
away from the current POC, the absolute value of the LTRP POC is
signalled. This scheme has two sub-schemes:
a. Signalling the entirety of the absolute value of the LTRP
POC would be expensive, so in most cases we only signal the
sps_log2_max_pic_order_cnt_lsb_minus4 least-significant bits of the
LTRP POC.
b. In the case of a collision in the least-significant bits,
the full POC is signalled.
Both schemes are implemented in find_ref_idx. The use_msb parameter
of find_ref_idx is nonzero in the case of schemes 1 and 2b, and zero in
the case of scheme 2a.
This commit fixes a bug in scheme 2a. The issue arises from the fact
that the current picture is stored in the DPB at the time find_ref_idx
is invoked in FFmpeg's implementation, whereas in the specification the
current picture is not yet considered part of the DPB. The current
picture may have a POC which shares least-significant bits with an LTRP.
The specification does not identify this as a collision however (see the
condition in delta_poc_msb_cycle_present_flag), and so scheme 2a is used
rather than scheme 2b. If the current frame precedes the correct LTRP
in the DPB, FFmpeg's implementation then interprets the signalling to
mean a frame which references itself. The problematic callsite
identifies this as an issue and returns an AVERROR_INVALIDDATA, but this
scenario can occur in valid bitstreams. For example, the conformance
bitstream LTRP_A_ERICSSON_3 has one such instance. FFmpeg gets away
with decoding it currently because the LTRP precedes the current picture
in the DPB, but this is by chance rather than design.
The commit addresses this issue by skipping over the current picture
inside the main loop of find_ref_idx. It's also necessary to add a
special case for if scheme 2b is used to reference the current picture,
which is syntactically possible but forbidden by clause 8.3.2. For the
same reason, the handling of `ref == fc->ref` at the callsite remains in
place.
Signed-off-by: Frank Plowman <[email protected]>
---
libavcodec/vvc/refs.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/libavcodec/vvc/refs.c b/libavcodec/vvc/refs.c
index 15024d7b68..e9541a5f6f 100644
--- a/libavcodec/vvc/refs.c
+++ b/libavcodec/vvc/refs.c
@@ -378,8 +378,18 @@ static VVCFrame *find_ref_idx(VVCContext *s,
VVCFrameContext *fc, int poc, uint8
{
const unsigned mask = use_msb ? ~0 : fc->ps.sps->max_pic_order_cnt_lsb - 1;
+ // Special case for an (illegal) unambiguous reference to the current
picture
+ if (use_msb && poc == fc->ref->poc)
+ return fc->ref;
+
for (int i = 0; i < FF_ARRAY_ELEMS(fc->DPB); i++) {
VVCFrame *ref = &fc->DPB[i];
+
+ // In the specification, the current frame is not considered part of
+ // the DPB at this stage, therefore we treat it as if it's not there.
+ if (ref == fc->ref)
+ continue;
+
if (ref->frame->buf[0] && ref->sequence == s->seq_decode) {
if ((ref->poc & mask) == poc)
return ref;
--
2.52.0
>From 751c2267d67a7c68995dc4c4bbe03893f3a767d2 Mon Sep 17 00:00:00 2001
From: Frank Plowman <[email protected]>
Date: Sun, 30 Aug 2026 17:20:00 +0100
Subject: [PATCH 2/2] tests/fate/vvc: Add fate-vvc-curr-ltrp-alias
Regression test for 65de55f9703b986e32f56dad752fa8e0282200e1
Signed-off-by: Frank Plowman <[email protected]>
---
tests/fate/vvc.mak | 3 ++-
tests/ref/fate/vvc-curr-ltrp-alias | 35 ++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 1 deletion(-)
create mode 100644 tests/ref/fate/vvc-curr-ltrp-alias
diff --git a/tests/fate/vvc.mak b/tests/fate/vvc.mak
index 6d7873f6e4..3ba32c896a 100644
--- a/tests/fate/vvc.mak
+++ b/tests/fate/vvc.mak
@@ -51,8 +51,9 @@ fate-vvc-conformance-%: CMD = framecrc -c:v vvc -i
$(TARGET_SAMPLES)/vvc-conform
fate-vvc-output-ref: CMD = framecrc -c:v vvc -i
$(TARGET_SAMPLES)/vvc/Hierarchical.bit $(SCALE_OPTS)
fate-vvc-frames-with-ltr: CMD = framecrc -c:v vvc -i
$(TARGET_SAMPLES)/vvc/vvc_frames_with_ltr.vvc -pix_fmt yuv420p10le -vf scale
fate-vvc-wpp-single-slice-pic: CMD = framecrc -c:v vvc -i
$(TARGET_SAMPLES)/vvc/wpp-single-slice-pic.vvc -pix_fmt yuv420p10le -vf scale
+fate-vvc-curr-ltrp-alias: CMD = framecrc -c:v vvc -i
$(TARGET_SAMPLES)/vvc/curr-ltrp-alias.vvc
-FATE_VVC-$(call FRAMECRC, VVC, VVC, VVC_PARSER) += $(VVC_TESTS_8BIT)
fate-vvc-output-ref
+FATE_VVC-$(call FRAMECRC, VVC, VVC, VVC_PARSER) += $(VVC_TESTS_8BIT)
fate-vvc-output-ref fate-vvc-curr-ltrp-alias
FATE_VVC-$(call FRAMECRC, VVC, VVC, VVC_PARSER SCALE_FILTER) +=
\
$(VVC_TESTS_10BIT)
\
$(VVC_TESTS_422_10BIT)
\
diff --git a/tests/ref/fate/vvc-curr-ltrp-alias
b/tests/ref/fate/vvc-curr-ltrp-alias
new file mode 100644
index 0000000000..4bfc4f5992
--- /dev/null
+++ b/tests/ref/fate/vvc-curr-ltrp-alias
@@ -0,0 +1,35 @@
+#tb 0: 1/25
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 32x32
+#sar 0: 0/1
+0, 0, 0, 1, 1536, 0x071e002d
+0, 1, 1, 1, 1536, 0x071e002d
+0, 2, 2, 1, 1536, 0x071e002d
+0, 3, 3, 1, 1536, 0x071e002d
+0, 4, 4, 1, 1536, 0x071e002d
+0, 5, 5, 1, 1536, 0x071e002d
+0, 6, 6, 1, 1536, 0x071e002d
+0, 7, 7, 1, 1536, 0x071e002d
+0, 8, 8, 1, 1536, 0x071e002d
+0, 9, 9, 1, 1536, 0x071e002d
+0, 10, 10, 1, 1536, 0x071e002d
+0, 11, 11, 1, 1536, 0x071e002d
+0, 12, 12, 1, 1536, 0x071e002d
+0, 13, 13, 1, 1536, 0x071e002d
+0, 14, 14, 1, 1536, 0x071e002d
+0, 15, 15, 1, 1536, 0x071e002d
+0, 16, 16, 1, 1536, 0x071e002d
+0, 17, 17, 1, 1536, 0x071e002d
+0, 18, 18, 1, 1536, 0x071e002d
+0, 19, 19, 1, 1536, 0x071e002d
+0, 20, 20, 1, 1536, 0x071e002d
+0, 21, 21, 1, 1536, 0x071e002d
+0, 22, 22, 1, 1536, 0x071e002d
+0, 23, 23, 1, 1536, 0x071e002d
+0, 24, 24, 1, 1536, 0x071e002d
+0, 25, 25, 1, 1536, 0x071e002d
+0, 26, 26, 1, 1536, 0x071e002d
+0, 27, 27, 1, 1536, 0x071e002d
+0, 28, 28, 1, 1536, 0x071e002d
+0, 29, 29, 1, 1536, 0x071e002d
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]