PR #25853 opened by Enrique Wood (EnriqueWood) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25853 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25853.patch
# Summary of changes Fixes trac #9771 (also reported as #10374). The DXVA2, D3D11VA and D3D12VA H.264 hwaccels keep the slice control entries in fixed arrays of MAX_SLICES (32) per picture and refuse any further slice. With the default error handling the extra slices are dropped and the bottom of the picture is left undecoded; with AV_EF_EXPLODE the whole frame fails. The PlayStation 5 hardware encoder on system software 10.x sends 1080p H.264 as 34 slices per frame (720p as 45), which the software decoder and the Linux hwaccels handle, but these three don't. This is what Moonlight users streaming from such a console see on Windows. The entries now go in a buffer owned by the hwaccel context that grows with av_fast_realloc(), as nvdec_h264 does for its slice offsets. These hwaccels are not thread-safe, so only one frame is between start_frame and end_frame at a time and the buffer is reused from frame to frame; the per-picture context keeps a pointer to it. Tested on Windows 11 with 214 1080p IDR frames from that encoder (34 slices each), decoding with `-err_detect explode`: - AMD Radeon integrated GPU: before, 0/214 frames decode on d3d11va, dxva2 and d3d12va; after, 214/214, and every frame is byte-identical (yuv420p) to the software decoder's output. - NVIDIA GTX 1650 and Intel UHD 630 (27 of those frames, d3d11va and d3d12va on each adapter, dxva2 on the default adapter): before 0/27, after 27/27. - The same change on top of FFmpeg 9.0.2 in Moonlight 6.2.0 (AMD): live 1080p and 720p H.264 streams decode in hardware with no errors. >From af62a12f81070f2737512888124f7d6c2cf2b752 Mon Sep 17 00:00:00 2001 From: Enrique Wood <[email protected]> Date: Sat, 7 Nov 2026 19:10:35 -0300 Subject: [PATCH] avcodec/dxva2_h264, d3d12va_h264: allow more than 32 slices per frame The DXVA2, D3D11VA and D3D12VA H.264 hwaccels keep the slice control entries in fixed arrays of MAX_SLICES (32) per picture and refuse any further slice. With the default error handling the extra slices are dropped and the bottom of the picture is left undecoded; with AV_EF_EXPLODE the whole frame fails. Some encoders cut every frame into more slices than that. The PlayStation 5 hardware encoder on system software 10.x sends 1080p H.264 as 34 slices per frame, which the software decoder handles but these hwaccels do not. Keep the entries in a buffer owned by the hwaccel context that grows with av_fast_realloc(), as nvdec_h264 does for its slice offsets. These hwaccels are not thread-safe, so only one frame is between start_frame and end_frame at a time and the buffer is reused from frame to frame. The per-picture context keeps a pointer to it. Signed-off-by: Enrique Wood <[email protected]> --- libavcodec/d3d12va_decode.c | 2 ++ libavcodec/d3d12va_decode.h | 8 ++++++++ libavcodec/d3d12va_h264.c | 14 ++++++++++++-- libavcodec/dxva2.c | 2 ++ libavcodec/dxva2_h264.c | 22 ++++++++++++++++++---- libavcodec/dxva2_internal.h | 8 ++++++++ 6 files changed, 50 insertions(+), 6 deletions(-) diff --git a/libavcodec/d3d12va_decode.c b/libavcodec/d3d12va_decode.c index 39feb3a623b..cbd7f72b1c3 100644 --- a/libavcodec/d3d12va_decode.c +++ b/libavcodec/d3d12va_decode.c @@ -516,6 +516,8 @@ av_cold int ff_d3d12va_decode_uninit(AVCodecContext *avctx) av_freep(&ctx->ref_resources); av_freep(&ctx->ref_subresources); + av_freep(&ctx->slice_ctrl); + ctx->slice_ctrl_size = 0; D3D12_OBJECT_RELEASE(ctx->command_list); D3D12_OBJECT_RELEASE(ctx->command_queue); diff --git a/libavcodec/d3d12va_decode.h b/libavcodec/d3d12va_decode.h index 00340e860c4..e9665a12235 100644 --- a/libavcodec/d3d12va_decode.h +++ b/libavcodec/d3d12va_decode.h @@ -127,6 +127,14 @@ typedef struct D3D12VADecodeContext { void *reference_only_map; ID3D12Resource **ref_only_resources; + /** + * Slice control entries of the frame being decoded, for codecs whose + * frames can have any number of slices. The hwaccel is not thread-safe, + * so only one frame is between start_frame and end_frame. + */ + void *slice_ctrl; + unsigned int slice_ctrl_size; + } D3D12VADecodeContext; /** diff --git a/libavcodec/d3d12va_h264.c b/libavcodec/d3d12va_h264.c index a3031f67b34..28038275308 100644 --- a/libavcodec/d3d12va_h264.c +++ b/libavcodec/d3d12va_h264.c @@ -22,6 +22,7 @@ #include "config_components.h" #include "libavutil/avassert.h" +#include "libavutil/mem.h" #include "h264dec.h" #include "h264data.h" #include "h264_ps.h" @@ -35,7 +36,7 @@ typedef struct H264DecodePictureContext { DXVA_PicParams_H264 pp; DXVA_Qmatrix_H264 qm; unsigned slice_count; - DXVA_Slice_H264_Short slice_short[MAX_SLICES]; + DXVA_Slice_H264_Short *slice_short; ///< in D3D12VADecodeContext.slice_ctrl const uint8_t *bitstream; unsigned bitstream_size; } H264DecodePictureContext; @@ -70,6 +71,7 @@ static int d3d12va_h264_start_frame(AVCodecContext *avctx, ff_dxva2_h264_fill_scaling_lists(avctx, (AVDXVAContext *)ctx, &ctx_pic->qm); ctx_pic->slice_count = 0; + ctx_pic->slice_short = NULL; ctx_pic->bitstream_size = 0; ctx_pic->bitstream = NULL; @@ -83,9 +85,17 @@ static int d3d12va_h264_decode_slice(AVCodecContext *avctx, const uint8_t *buffe const H264SliceContext *sl = &h->slice_ctx[0]; const H264Picture *current_picture = h->cur_pic_ptr; H264DecodePictureContext *ctx_pic = current_picture->hwaccel_picture_private; + D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx); + DXVA_Slice_H264_Short *slice_ctrl; - if (ctx_pic->slice_count >= MAX_SLICES) + if (ctx_pic->slice_count >= UINT_MAX / sizeof(*slice_ctrl) - 1) return AVERROR(ERANGE); + slice_ctrl = av_fast_realloc(ctx->slice_ctrl, &ctx->slice_ctrl_size, + (ctx_pic->slice_count + 1) * sizeof(*slice_ctrl)); + if (!slice_ctrl) + return AVERROR(ENOMEM); + ctx->slice_ctrl = slice_ctrl; + ctx_pic->slice_short = slice_ctrl; if (!ctx_pic->bitstream) ctx_pic->bitstream = buffer; diff --git a/libavcodec/dxva2.c b/libavcodec/dxva2.c index 6919d52680a..0abf09c0f48 100644 --- a/libavcodec/dxva2.c +++ b/libavcodec/dxva2.c @@ -773,6 +773,8 @@ int ff_dxva2_decode_uninit(AVCodecContext *avctx) int i; av_buffer_unref(&sctx->decoder_ref); + av_freep(&sctx->slice_ctrl); + sctx->slice_ctrl_size = 0; #if CONFIG_D3D11VA for (i = 0; i < sctx->nb_d3d11_views; i++) { diff --git a/libavcodec/dxva2_h264.c b/libavcodec/dxva2_h264.c index dd9dccbf8c0..0e5de744151 100644 --- a/libavcodec/dxva2_h264.c +++ b/libavcodec/dxva2_h264.c @@ -23,6 +23,7 @@ #include "config_components.h" #include "libavutil/avassert.h" +#include "libavutil/mem.h" #include "dxva2_internal.h" #include "h264dec.h" @@ -35,8 +36,8 @@ struct dxva2_picture_context { DXVA_PicParams_H264 pp; DXVA_Qmatrix_H264 qm; unsigned slice_count; - DXVA_Slice_H264_Short slice_short[MAX_SLICES]; - DXVA_Slice_H264_Long slice_long[MAX_SLICES]; + DXVA_Slice_H264_Short *slice_short; ///< in FFDXVASharedContext.slice_ctrl + DXVA_Slice_H264_Long *slice_long; ///< same buffer, for the long format const uint8_t *bitstream; unsigned bitstream_size; }; @@ -463,6 +464,8 @@ static int dxva2_h264_start_frame(AVCodecContext *avctx, ff_dxva2_h264_fill_scaling_lists(avctx, ctx, &ctx_pic->qm); ctx_pic->slice_count = 0; + ctx_pic->slice_short = NULL; + ctx_pic->slice_long = NULL; ctx_pic->bitstream_size = 0; ctx_pic->bitstream = NULL; return 0; @@ -477,10 +480,21 @@ static int dxva2_h264_decode_slice(AVCodecContext *avctx, AVDXVAContext *ctx = DXVA_CONTEXT(avctx); const H264Picture *current_picture = h->cur_pic_ptr; struct dxva2_picture_context *ctx_pic = current_picture->hwaccel_picture_private; + FFDXVASharedContext *sctx = DXVA_SHARED_CONTEXT(avctx); + const size_t entry_size = is_slice_short(avctx, ctx) ? sizeof(DXVA_Slice_H264_Short) + : sizeof(DXVA_Slice_H264_Long); unsigned position; + void *slice_ctrl; - if (ctx_pic->slice_count >= MAX_SLICES) - return -1; + if (ctx_pic->slice_count >= UINT_MAX / entry_size - 1) + return AVERROR(ERANGE); + slice_ctrl = av_fast_realloc(sctx->slice_ctrl, &sctx->slice_ctrl_size, + (ctx_pic->slice_count + 1) * entry_size); + if (!slice_ctrl) + return AVERROR(ENOMEM); + sctx->slice_ctrl = slice_ctrl; + ctx_pic->slice_short = slice_ctrl; + ctx_pic->slice_long = slice_ctrl; if (!ctx_pic->bitstream) ctx_pic->bitstream = buffer; diff --git a/libavcodec/dxva2_internal.h b/libavcodec/dxva2_internal.h index 6cbd0e42d77..74f34a49325 100644 --- a/libavcodec/dxva2_internal.h +++ b/libavcodec/dxva2_internal.h @@ -94,6 +94,14 @@ typedef struct FFDXVASharedContext { AVHWDeviceContext *device_ctx; + /** + * Slice control entries of the frame being decoded, for codecs whose + * frames can have any number of slices. The DXVA hwaccels are not + * thread-safe, so only one frame is between start_frame and end_frame. + */ + void *slice_ctrl; + unsigned int slice_ctrl_size; + #if CONFIG_D3D11VA ID3D11VideoDecoder *d3d11_decoder; D3D11_VIDEO_DECODER_CONFIG d3d11_config; -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
