PR #24977 opened by Kilian (lusoris) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24977 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24977.patch
# Summary of changes hevc_vaapi produces corrupt streams when ROI side data is used with `-rc_mode CQP`. This PR enables `cu_qp_delta` in the PPS for that case. **Why.** In CQP mode `vaapi_encode_h265.c` writes the PPS with `cu_qp_delta_enabled_flag = 0` (line 273, and the feature query at 327-329 is skipped for CQP). However, `vaapi_encode.c:2046-2050` still allows ROI in CQP mode, and the ROI buffer is sent every frame. A driver that applies the ROI quantises those blocks at a different QP that the stream cannot signal. The decoder then uses the slice QP everywhere, and the error spreads through prediction to the whole picture. iHD also reports `cu_qp_delta` as `VA_FEATURE_REQUIRED` on `VAEntrypointEncSliceLP`, which FFmpeg ignores in CQP mode. **What changes.** In CQP mode, `cu_qp_delta` is now enabled when ROI is allowed, or when the driver reports the feature as required. Non-CQP modes are unchanged. **How it was verified.** Tested on an Arc A380 (DG2, iHD 26.3.5, low-power entrypoint) with 60 frames at 640x360, `-rc_mode CQP -qp 30`, and `addroi=x=0:y=0:w=iw:h=ih/2:qoffset=-0.15`: | | bytes | PSNR-Y | ROI half | other half | | --- | ---: | ---: | ---: | ---: | | master, no ROI | 81459 | 41.02 | 52.51 | 38.17 | | master, ROI | 84077 | 13.81 | 24.77 | 10.98 | | this PR, no ROI | 81577 | 41.02 | 52.51 | 38.17 | | this PR, ROI | 85443 | 41.12 | 54.83 | 38.20 | - The same result holds for qoffset −0.3, −0.1 and +0.1, for a full-frame ROI and for a 64x64 ROI. - The libavcodec HEVC decoder and libde265 give identical PSNR on all four streams. - h264_vaapi with the same ROI was already correct. - `make fate-source` passes. FATE has no VA-API encode coverage. **For the reviewer.** - Without ROI side data, CQP streams on drivers that advertise ROI now carry `cu_qp_delta` syntax. That cost 0.14% here, and the decoded pictures are bit-identical to master. - The `VA_FEATURE_REQUIRED` clause can be dropped if you prefer a ROI-only change. On iHD both conditions hold. Reproducer: ```sh ffmpeg -f lavfi -i "gradients=size=640x180:x0=0:y0=0:x1=0:y1=180:c0=0x060608:c1=0x2a3038:nb_colors=2:rate=25:seed=1" \ -f lavfi -i "mandelbrot=size=640x180:rate=25" \ -filter_complex "[0:v][1:v]vstack,format=yuv420p" -frames:v 60 -f rawvideo src.yuv ffmpeg -init_hw_device vaapi=va:/dev/dri/renderD128 -filter_hw_device va \ -f rawvideo -pix_fmt yuv420p -s 640x360 -framerate 25 -i src.yuv \ -vf "format=nv12,addroi=x=0:y=0:w=iw:h=ih/2:qoffset=-0.15,hwupload" \ -c:v hevc_vaapi -rc_mode CQP -qp 30 roi.hevc ffmpeg -i roi.hevc -f rawvideo -pix_fmt yuv420p -s 640x360 -i src.yuv -lavfi psnr -f null - ``` From 4cd3a47efd3ec4b6a4b5a13615311aebd4209723 Mon Sep 17 00:00:00 2001 From: lusoris <[email protected]> Date: Fri, 9 Oct 2026 19:47:34 +0200 Subject: [PATCH] avcodec/vaapi_encode_h265: enable cu_qp_delta for ROI in CQP mode In constant-QP mode the PPS is written with cu_qp_delta_enabled_flag set to 0, but ROI side data is still passed to the driver: ROI is allowed in CQP mode even without roi_rc_qp_delta_support. A driver that applies the ROI changes the QP of the affected CUs, and the stream has no syntax to signal that change, so a decoder reconstructs every CU with the slice QP. The mismatch then spreads through prediction to the rest of the picture. On an Arc A380 (DG2, iHD 26.3.5, VAEntrypointEncSliceLP) with -rc_mode CQP -qp 30 and one addroi region (top half, qoffset -0.15), PSNR-Y of the decoded stream drops from 41.02 dB without ROI to 13.81 dB with it, and the half outside the ROI is damaged too. The same ROI on h264_vaapi is fine, since H.264 always carries mb_qp_delta. Enable cu_qp_delta in CQP mode when ROI is allowed. Also enable it when the driver reports the feature as VA_FEATURE_REQUIRED, which iHD does for HEVC on VAEntrypointEncSliceLP. With this change the same command gives 41.12 dB, with the ROI half at 54.83 dB against 52.51 dB without ROI. Without ROI the decoded frames are bit-identical to before and the stream grows by 0.14% (81459 to 81577 bytes for 60 frames at 640x360). Signed-off-by: lusoris <[email protected]> --- libavcodec/vaapi_encode_h265.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libavcodec/vaapi_encode_h265.c b/libavcodec/vaapi_encode_h265.c index baf0d77c8a..1b69b6d136 100644 --- a/libavcodec/vaapi_encode_h265.c +++ b/libavcodec/vaapi_encode_h265.c @@ -270,7 +270,10 @@ static int vaapi_encode_h265_init_sequence_params(AVCodecContext *avctx) // priv->unit_opts.tier already set // priv->unit_opts.fixed_qp_idr already set - priv->unit_opts.cu_qp_delta_enabled_flag = (ctx->va_rc_mode != VA_RC_CQP); + // ROI in constant-QP mode changes the QP of individual CUs, which the + // bitstream can only signal with cu_qp_delta. + priv->unit_opts.cu_qp_delta_enabled_flag = ctx->va_rc_mode != VA_RC_CQP || + base_ctx->roi_allowed; priv->unit_opts.tile_rows = ctx->tile_rows; priv->unit_opts.tile_cols = ctx->tile_cols; priv->unit_opts.nb_slices = ctx->nb_slices; @@ -324,7 +327,8 @@ static int vaapi_encode_h265_init_sequence_params(AVCodecContext *avctx) // update pps setting according to queried result if (priv->va_features) { VAConfigAttribValEncHEVCFeatures features = { .value = priv->va_features }; - if (ctx->va_rc_mode != VA_RC_CQP) + if (pps->cu_qp_delta_enabled_flag || + features.bits.cu_qp_delta == VA_FEATURE_REQUIRED) pps->cu_qp_delta_enabled_flag = !!features.bits.cu_qp_delta; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
