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]
