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

A couple of fixes relating to the handling of large motion vectors in the VVC 
decoder, plus a regression test:

---
_**lavc/vvc: Use correct wrapping arithmetic for MVs**_

In the VVC spec, MVs are computed using 18-bit signed arithmetic with
normative wrapping.  In H.266(V3), the wrapping behaviour is defined
in Eqs. (465)--(468) for block-wide MVs, Eqs. (658)--(661) for affine
CPMVs and (1086)--(1089) for IBC BVs.  This wrapping was only
partially implemented in the VVC decoder before this patch.
Specifically, only eqs. (1087) and (1089) were implemented. This patch
implements the full wrapping as its own function `ff_vvc_wrap_mv`, and
uses it for all sorts of MVs.

Fixes mismatches and signed integer overflows in bitstreams with
unusually large MVs.

---
_**lavc/vvc: Re-clip MVs after adding MMVD MVD**_

Per Eqs. (484) and (485) in H.266(V3), merge MVs need to be clipped if
MMVD is used.

---
Crafted regression test bitstream for FATE attached below.


>From 27e06f8a0e07775d07e841bd7e37850c7101190b Mon Sep 17 00:00:00 2001
From: Frank Plowman <[email protected]>
Date: Sat, 5 Sep 2026 14:24:10 +0100
Subject: [PATCH 1/3] lavc/vvc: Use correct wrapping arithmetic for MVs

In the VVC spec, MVs are computed using 18-bit signed arithmetic with
normative wrapping.  In H.266(V3), the wrapping behaviour is defined
in Eqs. (465)--(468) for block-wide MVs, Eqs. (658)--(661) for affine
CPMVs and (1086)--(1089) for IBC BVs.  This wrapping was only
partially implemented in the VVC decoder before this patch.
Specifically, only eqs. (1087) and (1089) were implemented. This patch
implements the full wrapping as its own function `ff_vvc_wrap_mv`, and
uses it for all sorts of MVs.

Fixes mismatches and signed integer overflows in bitstreams with
unusually large MVs.

Signed-off-by: Frank Plowman <[email protected]>
---
 libavcodec/vvc/ctu.c |  1 +
 libavcodec/vvc/mvs.c | 24 +++++++++++++++++-------
 libavcodec/vvc/mvs.h |  1 +
 3 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/libavcodec/vvc/ctu.c b/libavcodec/vvc/ctu.c
index e50ac592bd..25aebee4df 100644
--- a/libavcodec/vvc/ctu.c
+++ b/libavcodec/vvc/ctu.c
@@ -1616,6 +1616,7 @@ static void mvp_add_difference(MotionInfo *mi, const int 
num_cp_mv,
                 const Mv *mvd = &mvds[i][j];
                 mi->mv[i][j].x += mvd->x * (1 << amvr_shift);
                 mi->mv[i][j].y += mvd->y * (1 << amvr_shift);
+                ff_vvc_wrap_mv(&mi->mv[i][j]);
             }
         }
     }
diff --git a/libavcodec/vvc/mvs.c b/libavcodec/vvc/mvs.c
index 955fe4b9fd..fe32b96555 100644
--- a/libavcodec/vvc/mvs.c
+++ b/libavcodec/vvc/mvs.c
@@ -1689,15 +1689,13 @@ static int ibc_history_candidates(const VVCLocalContext 
*lc,
     return 0;
 }
 
-#define MV_BITS 18
-#define IBC_SHIFT(v) ((v) >= (1 << (MV_BITS - 1)) ? ((v) - (1 << MV_BITS)) : 
(v))
-
 static inline void ibc_add_mvp(Mv *mv, Mv *mvp, const int amvr_shift)
 {
     ff_vvc_round_mv(mv, amvr_shift, 0);
     ff_vvc_round_mv(mvp, amvr_shift, amvr_shift);
-    mv->x = IBC_SHIFT(mv->x + mvp->x);
-    mv->y = IBC_SHIFT(mv->y + mvp->y);
+    mv->x += mvp->x;
+    mv->y += mvp->y;
+    ff_vvc_wrap_mv(mv);
 }
 
 static void ibc_merge_candidates(VVCLocalContext *lc, const int merge_idx, Mv 
*mv)
@@ -1897,10 +1895,22 @@ void ff_vvc_round_mv(Mv *mv, const int lshift, const 
int rshift)
     }
 }
 
+#define MV_BITS 18
+#define MV_MIN  (-(1 << (MV_BITS - 1)))
+#define MV_MAX  ((1 << (MV_BITS - 1)) - 1)
+
 void ff_vvc_clip_mv(Mv *mv)
 {
-    mv->x = av_clip(mv->x, -(1 << 17), (1 << 17) - 1);
-    mv->y = av_clip(mv->y, -(1 << 17), (1 << 17) - 1);
+    mv->x = av_clip(mv->x, MV_MIN, MV_MAX);
+    mv->y = av_clip(mv->y, MV_MIN, MV_MAX);
+}
+
+#define MV_WRAP(v) ((((v) & ((1 << MV_BITS) - 1)) ^ (1 << (MV_BITS - 1))) + 
MV_MIN)
+
+void ff_vvc_wrap_mv(Mv *mv)
+{
+    mv->x = MV_WRAP(mv->x);
+    mv->y = MV_WRAP(mv->y);
 }
 
 //8.5.2.1 Derivation process for motion vector components and reference indices
diff --git a/libavcodec/vvc/mvs.h b/libavcodec/vvc/mvs.h
index 7150c0b8cf..056f7c1ab3 100644
--- a/libavcodec/vvc/mvs.h
+++ b/libavcodec/vvc/mvs.h
@@ -27,6 +27,7 @@
 
 void ff_vvc_round_mv(Mv *mv, int lshift, int rshift);
 void ff_vvc_clip_mv(Mv *mv);
+void ff_vvc_wrap_mv(Mv *mv);
 void ff_vvc_mv_scale(Mv *dst, const Mv *src, int td, int tb);
 void ff_vvc_luma_mv_merge_mode(VVCLocalContext *lc, int merge_idx, int 
ciip_flag, MvField *mv);
 void ff_vvc_luma_mv_merge_gpm(VVCLocalContext *lc, const int merge_gpm_idx[2], 
MvField *mv);
-- 
2.52.0


>From 296dc133f498d3dce8e08799f213059cdc856b6a Mon Sep 17 00:00:00 2001
From: Frank Plowman <[email protected]>
Date: Sat, 5 Sep 2026 14:45:00 +0100
Subject: [PATCH 2/3] lavc/vvc: Re-clip MVs after adding MMVD MVD

Per Eqs. (484) and (485) in H.266(V3), merge MVs need to be clipped if
MMVD is used.

Signed-off-by: Frank Plowman <[email protected]>
---
 libavcodec/vvc/ctu.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/libavcodec/vvc/ctu.c b/libavcodec/vvc/ctu.c
index 25aebee4df..9dc67e5d27 100644
--- a/libavcodec/vvc/ctu.c
+++ b/libavcodec/vvc/ctu.c
@@ -1315,10 +1315,13 @@ static void derive_mmvd(const VVCLocalContext *lc, 
MvField *mvf, const Mv *mmvd_
         mvf->mv[0].y += mmvd[0].y;
         mvf->mv[1].x += mmvd[1].x;
         mvf->mv[1].y += mmvd[1].y;
+        ff_vvc_clip_mv(&mvf->mv[0]);
+        ff_vvc_clip_mv(&mvf->mv[1]);
     } else {
         const int idx = mvf->pred_flag - PF_L0;
         mvf->mv[idx].x += mmvd_offset->x;
         mvf->mv[idx].y += mmvd_offset->y;
+        ff_vvc_clip_mv(&mvf->mv[idx]);
     }
 
 }
-- 
2.52.0


>From 7bf0fda28b52173a73b888ebe3f9e3d9afa10e4d Mon Sep 17 00:00:00 2001
From: Frank Plowman <[email protected]>
Date: Sat, 5 Sep 2026 14:49:54 +0100
Subject: [PATCH 3/3] tests/fate/vvc: Add fate-vvc-mv-clip-wrap

Regression test for 27e06f8a0e07775d07e841bd7e37850c7101190b and
296dc133f498d3dce8e08799f213059cdc856b6a.

Includes a variety of large regular MVs, including some signalled
by MMVD MVD, as well as some large affine CMVPs.

Signed-off-by: Frank Plowman <[email protected]>
---
 tests/fate/vvc.mak              | 4 +++-
 tests/ref/fate/vvc-mv-clip-wrap | 7 +++++++
 2 files changed, 10 insertions(+), 1 deletion(-)
 create mode 100644 tests/ref/fate/vvc-mv-clip-wrap

diff --git a/tests/fate/vvc.mak b/tests/fate/vvc.mak
index 6d7873f6e4..7da50eac37 100644
--- a/tests/fate/vvc.mak
+++ b/tests/fate/vvc.mak
@@ -51,8 +51,10 @@ 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-mv-clip-wrap: CMD = framecrc -c:v vvc -i 
$(TARGET_SAMPLES)/vvc/mv-clip-wrap.vvc -pix_fmt yuv420p
 
-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-mv-clip-wrap
 FATE_VVC-$(call FRAMECRC, VVC, VVC, VVC_PARSER SCALE_FILTER) +=                
   \
                                                     $(VVC_TESTS_10BIT)         
   \
                                                     $(VVC_TESTS_422_10BIT)     
   \
diff --git a/tests/ref/fate/vvc-mv-clip-wrap b/tests/ref/fate/vvc-mv-clip-wrap
new file mode 100644
index 0000000000..9546739329
--- /dev/null
+++ b/tests/ref/fate/vvc-mv-clip-wrap
@@ -0,0 +1,7 @@
+#tb 0: 1/25
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 64x96
+#sar 0: 0/1
+0,          0,          0,        1,     9216, 0x330ae2c3
+0,          1,          1,        1,     9216, 0xd643c60c
-- 
2.52.0

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

Reply via email to