From 7b2ae2ccf79954fb7d3e3a668e0bd50bd512040d Mon Sep 17 00:00:00 2001 From: stevxiao Date: Fri, 21 Nov 2025 20:39:22 -0500 Subject: [PATCH] avcodec/d3d12va_encode: add intra refresh support for d3d12va encode Intra refresh is a technique that gradually refreshes the video by encoding rows or regions as intra macroblocks/CTUs spread over multiple frames, rather than using periodic I-frames. This provides better error resilience for video streaming while maintaining more consistent bitrate. Disable Intra Refresh (This is the default) ffmpeg -init_hw_device d3d12va -hwaccel d3d12va -hwaccel_output_format d3d12 \ -i input.mp4 \ -c:v h264_d3d12va \ -intra_refresh_mode none \ -intra_refresh_duration 30 \ -g 60 \ output.h264 Enable Intra Refresh ffmpeg -init_hw_device d3d12va -hwaccel d3d12va -hwaccel_output_format d3d12 \ -i input.mp4 \ -c:v h264_d3d12va \ -intra_refresh_mode row_based \ -intra_refresh_duration 30 \ -g 60 \ output.h264 Parameters - `-intra_refresh_mode`: Set to `row_based` to enable row-based intra refresh, or `NONE` to disable - `-intra_refresh_duration`: Number of frames over which to spread the intra refresh (default: 0 = use GOP size) - `-g`: GOP size (should typically be larger than intra refresh duration) --- configure | 2 + libavcodec/d3d12va_encode.c | 98 +++++++++++++++++++++++++++++++- libavcodec/d3d12va_encode.h | 29 +++++++++- libavcodec/d3d12va_encode_av1.c | 3 +- libavcodec/d3d12va_encode_h264.c | 2 +- libavcodec/d3d12va_encode_hevc.c | 2 +- 6 files changed, 129 insertions(+), 7 deletions(-) diff --git a/configure b/configure index 883539e361..04e086c32a 100755 --- a/configure +++ b/configure @@ -2615,6 +2615,7 @@ CONFIG_EXTRA=" cbs_vp8 cbs_vp9 celp_math + d3d12_intra_refresh d3d12va_encode deflate_wrapper dirac_parse @@ -6964,6 +6965,7 @@ check_type "windows.h d3d12video.h" "ID3D12VideoEncoder" test_code cc "windows.h d3d12video.h" "D3D12_FEATURE_VIDEO feature = D3D12_FEATURE_VIDEO_ENCODER_CODEC" && \ test_code cc "windows.h d3d12video.h" "D3D12_FEATURE_DATA_VIDEO_ENCODER_RESOURCE_REQUIREMENTS req" && enable d3d12_encoder_feature test_code cc "windows.h d3d12video.h" "D3D12_VIDEO_ENCODER_CODEC c = D3D12_VIDEO_ENCODER_CODEC_AV1; (void)c;" && enable d3d12va_av1_headers +test_code cc "windows.h d3d12video.h" "D3D12_FEATURE_DATA_VIDEO_ENCODER_INTRA_REFRESH_MODE check = { 0 };" && enable d3d12_intra_refresh check_type "windows.h" "DPI_AWARENESS_CONTEXT" -D_WIN32_WINNT=0x0A00 check_type "windows.h security.h schnlsp.h" SecPkgContext_KeyingMaterialInfo -DSECURITY_WIN32 check_type "d3d9.h dxva2api.h" DXVA2_ConfigPictureDecode -D_WIN32_WINNT=0x0602 diff --git a/libavcodec/d3d12va_encode.c b/libavcodec/d3d12va_encode.c index 5a82a42c5f..f15df37b96 100644 --- a/libavcodec/d3d12va_encode.c +++ b/libavcodec/d3d12va_encode.c @@ -211,10 +211,17 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, int barriers_ref_index = 0; D3D12_RESOURCE_BARRIER *barriers_ref = NULL; + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS seq_flags = D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE; + + // Request intra refresh if enabled + if (ctx->intra_refresh.Mode != D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE) { + seq_flags |= D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_REQUEST_INTRA_REFRESH; + } + D3D12_VIDEO_ENCODER_ENCODEFRAME_INPUT_ARGUMENTS input_args = { .SequenceControlDesc = { - .Flags = D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE, - .IntraRefreshConfig = { 0 }, + .Flags = seq_flags, + .IntraRefreshConfig = ctx->intra_refresh, .RateControl = ctx->rc, .PictureTargetResolution = ctx->resolution, .SelectedLayoutMode = D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME, @@ -359,7 +366,7 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, } } - input_args.PictureControlDesc.IntraRefreshFrameIndex = 0; + input_args.PictureControlDesc.IntraRefreshFrameIndex = ctx->intra_refresh_frame_index; if (base_pic->is_reference) input_args.PictureControlDesc.Flags |= D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE; @@ -547,6 +554,12 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, pic->fence_value = ctx->sync_ctx.fence_value; + // Update intra refresh frame index for next frame + if (ctx->intra_refresh.Mode != D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE) { + ctx->intra_refresh_frame_index = + (ctx->intra_refresh_frame_index + 1) % ctx->intra_refresh.IntraRefreshDuration; + } + if (d3d12_refs.ppTexture2Ds) av_freep(&d3d12_refs.ppTexture2Ds); @@ -1220,6 +1233,81 @@ static int d3d12va_encode_init_gop_structure(AVCodecContext *avctx) return 0; } +static int d3d12va_encode_init_intra_refresh(AVCodecContext *avctx) +{ + FFHWBaseEncodeContext *base_ctx = avctx->priv_data; + D3D12VAEncodeContext *ctx = avctx->priv_data; + + if (ctx->intra_refresh.Mode == D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE) + return 0; + + // Check for SDK API availability +#if CONFIG_D3D12_INTRA_REFRESH + HRESULT hr; + D3D12_VIDEO_ENCODER_LEVEL_SETTING level = { 0 }; + D3D12_VIDEO_ENCODER_LEVELS_H264 h264_level = { 0 }; + D3D12_VIDEO_ENCODER_LEVEL_TIER_CONSTRAINTS_HEVC hevc_level = { 0 }; +#if CONFIG_AV1_D3D12VA_ENCODER + D3D12_VIDEO_ENCODER_AV1_LEVEL_TIER_CONSTRAINTS av1_level = { 0 }; +#endif + + switch (ctx->codec->d3d12_codec) { + case D3D12_VIDEO_ENCODER_CODEC_H264: + level.DataSize = sizeof(D3D12_VIDEO_ENCODER_LEVELS_H264); + level.pH264LevelSetting = &h264_level; + break; + case D3D12_VIDEO_ENCODER_CODEC_HEVC: + level.DataSize = sizeof(D3D12_VIDEO_ENCODER_LEVEL_TIER_CONSTRAINTS_HEVC); + level.pHEVCLevelSetting = &hevc_level; + break; +#if CONFIG_AV1_D3D12VA_ENCODER + case D3D12_VIDEO_ENCODER_CODEC_AV1: + level.DataSize = sizeof(D3D12_VIDEO_ENCODER_AV1_LEVEL_TIER_CONSTRAINTS); + level.pAV1LevelSetting = &av1_level; + break; +#endif + default: + av_assert0(0); + } + + D3D12_FEATURE_DATA_VIDEO_ENCODER_INTRA_REFRESH_MODE intra_refresh_support = { + .NodeIndex = 0, + .Codec = ctx->codec->d3d12_codec, + .Profile = ctx->profile->d3d12_profile, + .Level = level, + .IntraRefreshMode = ctx->intra_refresh.Mode, + }; + + hr = ID3D12VideoDevice3_CheckFeatureSupport(ctx->video_device3, + D3D12_FEATURE_VIDEO_ENCODER_INTRA_REFRESH_MODE, + &intra_refresh_support, sizeof(intra_refresh_support)); + + if (FAILED(hr) || !intra_refresh_support.IsSupported) { + av_log(avctx, AV_LOG_ERROR, "Requested intra refresh mode not supported by driver.\n"); + return AVERROR(ENOTSUP); + } +#else + // Older SDK - validation will occur in init_sequence_params via D3D12_FEATURE_VIDEO_ENCODER_SUPPORT + av_log(avctx, AV_LOG_VERBOSE, "Intra refresh explicit check not available in this SDK.\n" + "Support will be validated during encoder initialization.\n"); +#endif + + // Set duration: use GOP size if not specified + if (ctx->intra_refresh.IntraRefreshDuration == 0) { + ctx->intra_refresh.IntraRefreshDuration = base_ctx->gop_size; + av_log(avctx, AV_LOG_VERBOSE, "Intra refresh duration set to GOP size: %d\n", + ctx->intra_refresh.IntraRefreshDuration); + } + + // Initialize frame index + ctx->intra_refresh_frame_index = 0; + + av_log(avctx, AV_LOG_VERBOSE, "Intra refresh: mode=%d, duration=%d frames\n", + ctx->intra_refresh.Mode, ctx->intra_refresh.IntraRefreshDuration); + + return 0; +} + static int d3d12va_create_encoder(AVCodecContext *avctx) { FFHWBaseEncodeContext *base_ctx = avctx->priv_data; @@ -1570,6 +1658,10 @@ int ff_d3d12va_encode_init(AVCodecContext *avctx) if (err < 0) goto fail; + err = d3d12va_encode_init_intra_refresh(avctx); + if (err < 0) + goto fail; + if (!(ctx->codec->flags & FF_HW_FLAG_SLICE_CONTROL) && avctx->slices > 0) { av_log(avctx, AV_LOG_WARNING, "Multiple slices were requested " "but this codec does not support controlling slices.\n"); diff --git a/libavcodec/d3d12va_encode.h b/libavcodec/d3d12va_encode.h index b7cfea0582..699d8d2077 100644 --- a/libavcodec/d3d12va_encode.h +++ b/libavcodec/d3d12va_encode.h @@ -266,6 +266,17 @@ typedef struct D3D12VAEncodeContext { D3D12_VIDEO_ENCODER_LEVEL_SETTING level; D3D12_VIDEO_ENCODER_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA subregions_layout; + + /** + * Intra refresh configuration + */ + D3D12_VIDEO_ENCODER_INTRA_REFRESH intra_refresh; + + /** + * Current frame index within intra refresh cycle + */ + UINT intra_refresh_frame_index; + } D3D12VAEncodeContext; typedef struct D3D12VAEncodeType { @@ -347,11 +358,27 @@ int ff_d3d12va_encode_receive_packet(AVCodecContext *avctx, AVPacket *pkt); int ff_d3d12va_encode_init(AVCodecContext *avctx); int ff_d3d12va_encode_close(AVCodecContext *avctx); +#define D3D12VA_ENCODE_INTRA_REFRESH_MODE(name, mode, desc) \ + { #name, desc, 0, AV_OPT_TYPE_CONST, { .i64 = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_ ## mode }, \ + 0, 0, FLAGS, .unit = "intra_refresh_mode" } + #define D3D12VA_ENCODE_COMMON_OPTIONS \ { "max_frame_size", \ "Maximum frame size (in bytes)",\ OFFSET(common.max_frame_size), AV_OPT_TYPE_INT, \ - { .i64 = 0 }, 0, INT_MAX / 8, FLAGS } + { .i64 = 0 }, 0, INT_MAX / 8, FLAGS }, \ + { "intra_refresh_mode", \ + "Set intra refresh mode", \ + OFFSET(common.intra_refresh.Mode), AV_OPT_TYPE_INT, \ + { .i64 = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE }, \ + D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE, \ + D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_ROW_BASED, FLAGS, .unit = "intra_refresh_mode" }, \ + D3D12VA_ENCODE_INTRA_REFRESH_MODE(none, NONE, "Disable intra refresh"), \ + D3D12VA_ENCODE_INTRA_REFRESH_MODE(row_based, ROW_BASED, "Row-based intra refresh"), \ + { "intra_refresh_duration", \ + "Number of frames over which to spread intra refresh (0 = GOP size)", \ + OFFSET(common.intra_refresh.IntraRefreshDuration), AV_OPT_TYPE_INT, \ + { .i64 = 0 }, 0, INT_MAX, FLAGS } #define D3D12VA_ENCODE_RC_MODE(name, desc) \ { #name, desc, 0, AV_OPT_TYPE_CONST, { .i64 = RC_MODE_ ## name }, \ diff --git a/libavcodec/d3d12va_encode_av1.c b/libavcodec/d3d12va_encode_av1.c index 34e803db98..31d3df33bd 100644 --- a/libavcodec/d3d12va_encode_av1.c +++ b/libavcodec/d3d12va_encode_av1.c @@ -559,7 +559,7 @@ static int d3d12va_encode_av1_init_sequence_params(AVCodecContext *avctx) .Codec = D3D12_VIDEO_ENCODER_CODEC_AV1, .InputFormat = hwctx->format, .RateControl = ctx->rc, - .IntraRefresh = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE, + .IntraRefresh = ctx->intra_refresh.Mode, .SubregionFrameEncoding = D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME, .ResolutionsListCount = 1, .pResolutionList = &ctx->resolution, @@ -1082,6 +1082,7 @@ static int d3d12va_encode_av1_close(AVCodecContext *avctx) #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM) static const AVOption d3d12va_encode_av1_options[] = { HW_BASE_ENCODE_COMMON_OPTIONS, + D3D12VA_ENCODE_COMMON_OPTIONS, D3D12VA_ENCODE_RC_OPTIONS, { "qp", "Constant QP (for P-frames; scaled by qfactor/qoffset for I/B)", diff --git a/libavcodec/d3d12va_encode_h264.c b/libavcodec/d3d12va_encode_h264.c index 967544ea24..bcf5a326e5 100644 --- a/libavcodec/d3d12va_encode_h264.c +++ b/libavcodec/d3d12va_encode_h264.c @@ -178,7 +178,7 @@ static int d3d12va_encode_h264_init_sequence_params(AVCodecContext *avctx) .Codec = D3D12_VIDEO_ENCODER_CODEC_H264, .InputFormat = hwctx->format, .RateControl = ctx->rc, - .IntraRefresh = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE, + .IntraRefresh = ctx->intra_refresh.Mode, .SubregionFrameEncoding = D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME, .ResolutionsListCount = 1, .pResolutionList = &ctx->resolution, diff --git a/libavcodec/d3d12va_encode_hevc.c b/libavcodec/d3d12va_encode_hevc.c index 01e5b4cb4c..e00ecbb4de 100644 --- a/libavcodec/d3d12va_encode_hevc.c +++ b/libavcodec/d3d12va_encode_hevc.c @@ -250,7 +250,7 @@ static int d3d12va_encode_hevc_init_sequence_params(AVCodecContext *avctx) .Codec = D3D12_VIDEO_ENCODER_CODEC_HEVC, .InputFormat = hwctx->format, .RateControl = ctx->rc, - .IntraRefresh = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE, + .IntraRefresh = ctx->intra_refresh.Mode, .SubregionFrameEncoding = D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME, .ResolutionsListCount = 1, .pResolutionList = &ctx->resolution,