You've already forked FFmpeg
mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2025-11-23 21:54:53 +02:00
This patch adds support for the texture array feature used by AMD boards in the D3D12 HEVC encoder. In texture array mode, a single texture array is shared for all reference and reconstructed pictures using different subresources. The implementation ensures compatibility and has been successfully tested on AMD, Intel, and NVIDIA GPUs.
795 lines
32 KiB
C
795 lines
32 KiB
C
/*
|
|
* Direct3D 12 HW acceleration video encoder
|
|
*
|
|
* Copyright (c) 2024 Intel Corporation
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/common.h"
|
|
#include "libavutil/mem.h"
|
|
#include "libavutil/pixdesc.h"
|
|
#include "libavutil/hwcontext_d3d12va_internal.h"
|
|
|
|
#include "avcodec.h"
|
|
#include "cbs.h"
|
|
#include "cbs_h265.h"
|
|
#include "hw_base_encode_h265.h"
|
|
#include "h2645data.h"
|
|
#include "h265_profile_level.h"
|
|
#include "codec_internal.h"
|
|
#include "d3d12va_encode.h"
|
|
|
|
typedef struct D3D12VAEncodeHEVCPicture {
|
|
int pic_order_cnt;
|
|
int64_t last_idr_frame;
|
|
} D3D12VAEncodeHEVCPicture;
|
|
|
|
typedef struct D3D12VAEncodeHEVCContext {
|
|
D3D12VAEncodeContext common;
|
|
|
|
// User options.
|
|
int qp;
|
|
int profile;
|
|
int level;
|
|
|
|
// Writer structures.
|
|
FFHWBaseEncodeH265 units;
|
|
FFHWBaseEncodeH265Opts unit_opts;
|
|
|
|
CodedBitstreamContext *cbc;
|
|
CodedBitstreamFragment current_access_unit;
|
|
} D3D12VAEncodeHEVCContext;
|
|
|
|
typedef struct D3D12VAEncodeHEVCLevel {
|
|
int level;
|
|
D3D12_VIDEO_ENCODER_LEVELS_HEVC d3d12_level;
|
|
} D3D12VAEncodeHEVCLevel;
|
|
|
|
static const D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC hevc_config_support_sets[] =
|
|
{
|
|
{
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_NONE,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_8x8,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_32x32,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_4x4,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_32x32,
|
|
3,
|
|
3,
|
|
},
|
|
{
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_NONE,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_8x8,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_32x32,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_4x4,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_32x32,
|
|
0,
|
|
0,
|
|
},
|
|
{
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_NONE,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_8x8,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_32x32,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_4x4,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_32x32,
|
|
2,
|
|
2,
|
|
},
|
|
{
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_NONE,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_8x8,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_64x64,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_4x4,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_32x32,
|
|
2,
|
|
2,
|
|
},
|
|
{
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_NONE,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_8x8,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_64x64,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_4x4,
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_32x32,
|
|
4,
|
|
4,
|
|
},
|
|
};
|
|
|
|
static const D3D12VAEncodeHEVCLevel hevc_levels[] = {
|
|
{ 30, D3D12_VIDEO_ENCODER_LEVELS_HEVC_1 },
|
|
{ 60, D3D12_VIDEO_ENCODER_LEVELS_HEVC_2 },
|
|
{ 63, D3D12_VIDEO_ENCODER_LEVELS_HEVC_21 },
|
|
{ 90, D3D12_VIDEO_ENCODER_LEVELS_HEVC_3 },
|
|
{ 93, D3D12_VIDEO_ENCODER_LEVELS_HEVC_31 },
|
|
{ 120, D3D12_VIDEO_ENCODER_LEVELS_HEVC_4 },
|
|
{ 123, D3D12_VIDEO_ENCODER_LEVELS_HEVC_41 },
|
|
{ 150, D3D12_VIDEO_ENCODER_LEVELS_HEVC_5 },
|
|
{ 153, D3D12_VIDEO_ENCODER_LEVELS_HEVC_51 },
|
|
{ 156, D3D12_VIDEO_ENCODER_LEVELS_HEVC_52 },
|
|
{ 180, D3D12_VIDEO_ENCODER_LEVELS_HEVC_6 },
|
|
{ 183, D3D12_VIDEO_ENCODER_LEVELS_HEVC_61 },
|
|
{ 186, D3D12_VIDEO_ENCODER_LEVELS_HEVC_62 },
|
|
};
|
|
|
|
static const D3D12_VIDEO_ENCODER_PROFILE_HEVC profile_main = D3D12_VIDEO_ENCODER_PROFILE_HEVC_MAIN;
|
|
static const D3D12_VIDEO_ENCODER_PROFILE_HEVC profile_main10 = D3D12_VIDEO_ENCODER_PROFILE_HEVC_MAIN10;
|
|
|
|
#define D3D_PROFILE_DESC(name) \
|
|
{ sizeof(D3D12_VIDEO_ENCODER_PROFILE_HEVC), { .pHEVCProfile = (D3D12_VIDEO_ENCODER_PROFILE_HEVC *)&profile_ ## name } }
|
|
static const D3D12VAEncodeProfile d3d12va_encode_hevc_profiles[] = {
|
|
{ AV_PROFILE_HEVC_MAIN, 8, 3, 1, 1, D3D_PROFILE_DESC(main) },
|
|
{ AV_PROFILE_HEVC_MAIN_10, 10, 3, 1, 1, D3D_PROFILE_DESC(main10) },
|
|
{ AV_PROFILE_UNKNOWN },
|
|
};
|
|
|
|
static uint8_t d3d12va_encode_hevc_map_cusize(D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE cusize)
|
|
{
|
|
switch (cusize) {
|
|
case D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_8x8: return 8;
|
|
case D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_16x16: return 16;
|
|
case D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_32x32: return 32;
|
|
case D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_CUSIZE_64x64: return 64;
|
|
default: av_assert0(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t d3d12va_encode_hevc_map_tusize(D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE tusize)
|
|
{
|
|
switch (tusize) {
|
|
case D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_4x4: return 4;
|
|
case D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_8x8: return 8;
|
|
case D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_16x16: return 16;
|
|
case D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_TUSIZE_32x32: return 32;
|
|
default: av_assert0(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int d3d12va_encode_hevc_write_access_unit(AVCodecContext *avctx,
|
|
char *data, size_t *data_len,
|
|
CodedBitstreamFragment *au)
|
|
{
|
|
D3D12VAEncodeHEVCContext *priv = avctx->priv_data;
|
|
int err;
|
|
|
|
err = ff_cbs_write_fragment_data(priv->cbc, au);
|
|
if (err < 0) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to write packed header.\n");
|
|
return err;
|
|
}
|
|
|
|
if (*data_len < 8 * au->data_size - au->data_bit_padding) {
|
|
av_log(avctx, AV_LOG_ERROR, "Access unit too large: "
|
|
"%zu < %zu.\n", *data_len,
|
|
8 * au->data_size - au->data_bit_padding);
|
|
return AVERROR(ENOSPC);
|
|
}
|
|
|
|
memcpy(data, au->data, au->data_size);
|
|
*data_len = 8 * au->data_size - au->data_bit_padding;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int d3d12va_encode_hevc_add_nal(AVCodecContext *avctx,
|
|
CodedBitstreamFragment *au,
|
|
void *nal_unit)
|
|
{
|
|
H265RawNALUnitHeader *header = nal_unit;
|
|
int err;
|
|
|
|
err = ff_cbs_insert_unit_content(au, -1,
|
|
header->nal_unit_type, nal_unit, NULL);
|
|
if (err < 0) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to add NAL unit: "
|
|
"type = %d.\n", header->nal_unit_type);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int d3d12va_encode_hevc_write_sequence_header(AVCodecContext *avctx,
|
|
char *data, size_t *data_len)
|
|
{
|
|
D3D12VAEncodeHEVCContext *priv = avctx->priv_data;
|
|
CodedBitstreamFragment *au = &priv->current_access_unit;
|
|
int err;
|
|
|
|
err = d3d12va_encode_hevc_add_nal(avctx, au, &priv->units.raw_vps);
|
|
if (err < 0)
|
|
goto fail;
|
|
|
|
err = d3d12va_encode_hevc_add_nal(avctx, au, &priv->units.raw_sps);
|
|
if (err < 0)
|
|
goto fail;
|
|
|
|
err = d3d12va_encode_hevc_add_nal(avctx, au, &priv->units.raw_pps);
|
|
if (err < 0)
|
|
goto fail;
|
|
|
|
err = d3d12va_encode_hevc_write_access_unit(avctx, data, data_len, au);
|
|
fail:
|
|
ff_cbs_fragment_reset(au);
|
|
return err;
|
|
|
|
}
|
|
|
|
static int d3d12va_encode_hevc_init_sequence_params(AVCodecContext *avctx)
|
|
{
|
|
FFHWBaseEncodeContext *base_ctx = avctx->priv_data;
|
|
D3D12VAEncodeContext *ctx = avctx->priv_data;
|
|
D3D12VAEncodeHEVCContext *priv = avctx->priv_data;
|
|
AVD3D12VAFramesContext *hwctx = base_ctx->input_frames->hwctx;
|
|
H265RawSPS *sps = &priv->units.raw_sps;
|
|
H265RawPPS *pps = &priv->units.raw_pps;
|
|
D3D12_VIDEO_ENCODER_PROFILE_HEVC profile = D3D12_VIDEO_ENCODER_PROFILE_HEVC_MAIN;
|
|
D3D12_VIDEO_ENCODER_LEVEL_TIER_CONSTRAINTS_HEVC level = { 0 };
|
|
const AVPixFmtDescriptor *desc;
|
|
uint8_t min_cu_size, max_cu_size, min_tu_size, max_tu_size;
|
|
HRESULT hr;
|
|
int err;
|
|
|
|
D3D12_FEATURE_DATA_VIDEO_ENCODER_SUPPORT support = {
|
|
.NodeIndex = 0,
|
|
.Codec = D3D12_VIDEO_ENCODER_CODEC_HEVC,
|
|
.InputFormat = hwctx->format,
|
|
.RateControl = ctx->rc,
|
|
.IntraRefresh = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE,
|
|
.SubregionFrameEncoding = D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME,
|
|
.ResolutionsListCount = 1,
|
|
.pResolutionList = &ctx->resolution,
|
|
.CodecGopSequence = ctx->gop,
|
|
.MaxReferenceFramesInDPB = MAX_DPB_SIZE - 1,
|
|
.CodecConfiguration = ctx->codec_conf,
|
|
.SuggestedProfile.DataSize = sizeof(D3D12_VIDEO_ENCODER_PROFILE_HEVC),
|
|
.SuggestedProfile.pHEVCProfile = &profile,
|
|
.SuggestedLevel.DataSize = sizeof(D3D12_VIDEO_ENCODER_LEVEL_TIER_CONSTRAINTS_HEVC),
|
|
.SuggestedLevel.pHEVCLevelSetting = &level,
|
|
.pResolutionDependentSupport = &ctx->res_limits,
|
|
};
|
|
|
|
hr = ID3D12VideoDevice3_CheckFeatureSupport(ctx->video_device3, D3D12_FEATURE_VIDEO_ENCODER_SUPPORT,
|
|
&support, sizeof(support));
|
|
|
|
if (FAILED(hr)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to check encoder support(%lx).\n", (long)hr);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
if (!(support.SupportFlags & D3D12_VIDEO_ENCODER_SUPPORT_FLAG_GENERAL_SUPPORT_OK)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Driver does not support some request features. %#x\n",
|
|
support.ValidationFlags);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
if (support.SupportFlags & D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RECONSTRUCTED_FRAMES_REQUIRE_TEXTURE_ARRAYS) {
|
|
ctx->is_texture_array = 1;
|
|
av_log(avctx, AV_LOG_DEBUG, "D3D12 video encode on this device uses texture array mode.\n");
|
|
}
|
|
|
|
desc = av_pix_fmt_desc_get(base_ctx->input_frames->sw_format);
|
|
av_assert0(desc);
|
|
|
|
min_cu_size = d3d12va_encode_hevc_map_cusize(ctx->codec_conf.pHEVCConfig->MinLumaCodingUnitSize);
|
|
max_cu_size = d3d12va_encode_hevc_map_cusize(ctx->codec_conf.pHEVCConfig->MaxLumaCodingUnitSize);
|
|
min_tu_size = d3d12va_encode_hevc_map_tusize(ctx->codec_conf.pHEVCConfig->MinLumaTransformUnitSize);
|
|
max_tu_size = d3d12va_encode_hevc_map_tusize(ctx->codec_conf.pHEVCConfig->MaxLumaTransformUnitSize);
|
|
|
|
// cu_qp_delta always required to be 1 in https://github.com/microsoft/DirectX-Specs/blob/master/d3d/D3D12VideoEncoding.md
|
|
priv->unit_opts.cu_qp_delta_enabled_flag = 1;
|
|
priv->unit_opts.nb_slices = 1;
|
|
|
|
err = ff_hw_base_encode_init_params_h265(base_ctx, avctx,
|
|
&priv->units, &priv->unit_opts);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
avctx->level = priv->units.raw_vps.profile_tier_level.general_level_idc;
|
|
|
|
av_assert0(ctx->res_limits.SubregionBlockPixelsSize % min_cu_size == 0);
|
|
|
|
sps->pic_width_in_luma_samples = FFALIGN(base_ctx->surface_width,
|
|
ctx->res_limits.SubregionBlockPixelsSize);
|
|
sps->pic_height_in_luma_samples = FFALIGN(base_ctx->surface_height,
|
|
ctx->res_limits.SubregionBlockPixelsSize);
|
|
|
|
if (avctx->width != sps->pic_width_in_luma_samples ||
|
|
avctx->height != sps->pic_height_in_luma_samples) {
|
|
sps->conformance_window_flag = 1;
|
|
sps->conf_win_left_offset = 0;
|
|
sps->conf_win_right_offset =
|
|
(sps->pic_width_in_luma_samples - avctx->width) >> desc->log2_chroma_w;
|
|
sps->conf_win_top_offset = 0;
|
|
sps->conf_win_bottom_offset =
|
|
(sps->pic_height_in_luma_samples - avctx->height) >> desc->log2_chroma_h;
|
|
} else {
|
|
sps->conformance_window_flag = 0;
|
|
}
|
|
|
|
sps->log2_max_pic_order_cnt_lsb_minus4 = ctx->gop.pHEVCGroupOfPictures->log2_max_pic_order_cnt_lsb_minus4;
|
|
|
|
sps->log2_min_luma_coding_block_size_minus3 = (uint8_t)(av_log2(min_cu_size) - 3);
|
|
sps->log2_diff_max_min_luma_coding_block_size = (uint8_t)(av_log2(max_cu_size) - av_log2(min_cu_size));
|
|
sps->log2_min_luma_transform_block_size_minus2 = (uint8_t)(av_log2(min_tu_size) - 2);
|
|
sps->log2_diff_max_min_luma_transform_block_size = (uint8_t)(av_log2(max_tu_size) - av_log2(min_tu_size));
|
|
|
|
sps->max_transform_hierarchy_depth_inter = ctx->codec_conf.pHEVCConfig->max_transform_hierarchy_depth_inter;
|
|
sps->max_transform_hierarchy_depth_intra = ctx->codec_conf.pHEVCConfig->max_transform_hierarchy_depth_intra;
|
|
|
|
sps->amp_enabled_flag = !!(ctx->codec_conf.pHEVCConfig->ConfigurationFlags &
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_USE_ASYMETRIC_MOTION_PARTITION);
|
|
sps->sample_adaptive_offset_enabled_flag = !!(ctx->codec_conf.pHEVCConfig->ConfigurationFlags &
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_ENABLE_SAO_FILTER);
|
|
|
|
pps->cabac_init_present_flag = 1;
|
|
|
|
pps->init_qp_minus26 = 0;
|
|
|
|
pps->transform_skip_enabled_flag = !!(ctx->codec_conf.pHEVCConfig->ConfigurationFlags &
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_ENABLE_TRANSFORM_SKIPPING);
|
|
|
|
pps->pps_slice_chroma_qp_offsets_present_flag = 1;
|
|
|
|
pps->tiles_enabled_flag = 0; // no tiling in D3D12
|
|
|
|
pps->pps_loop_filter_across_slices_enabled_flag = !(ctx->codec_conf.pHEVCConfig->ConfigurationFlags &
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_DISABLE_LOOP_FILTER_ACROSS_SLICES);
|
|
|
|
pps->deblocking_filter_control_present_flag = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int d3d12va_encode_hevc_get_encoder_caps(AVCodecContext *avctx)
|
|
{
|
|
int i;
|
|
HRESULT hr;
|
|
uint8_t min_cu_size, max_cu_size;
|
|
FFHWBaseEncodeContext *base_ctx = avctx->priv_data;
|
|
D3D12VAEncodeContext *ctx = avctx->priv_data;
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC *config;
|
|
D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC hevc_caps;
|
|
|
|
D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT codec_caps = {
|
|
.NodeIndex = 0,
|
|
.Codec = D3D12_VIDEO_ENCODER_CODEC_HEVC,
|
|
.Profile = ctx->profile->d3d12_profile,
|
|
.CodecSupportLimits.DataSize = sizeof(D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC),
|
|
};
|
|
|
|
for (i = 0; i < FF_ARRAY_ELEMS(hevc_config_support_sets); i++) {
|
|
hevc_caps = hevc_config_support_sets[i];
|
|
codec_caps.CodecSupportLimits.pHEVCSupport = &hevc_caps;
|
|
hr = ID3D12VideoDevice3_CheckFeatureSupport(ctx->video_device3, D3D12_FEATURE_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT,
|
|
&codec_caps, sizeof(codec_caps));
|
|
if (SUCCEEDED(hr) && codec_caps.IsSupported)
|
|
break;
|
|
}
|
|
|
|
if (i == FF_ARRAY_ELEMS(hevc_config_support_sets)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unsupported codec configuration\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
ctx->codec_conf.DataSize = sizeof(D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC);
|
|
ctx->codec_conf.pHEVCConfig = av_mallocz(ctx->codec_conf.DataSize);
|
|
if (!ctx->codec_conf.pHEVCConfig)
|
|
return AVERROR(ENOMEM);
|
|
|
|
config = ctx->codec_conf.pHEVCConfig;
|
|
|
|
config->ConfigurationFlags = D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_NONE;
|
|
config->MinLumaCodingUnitSize = hevc_caps.MinLumaCodingUnitSize;
|
|
config->MaxLumaCodingUnitSize = hevc_caps.MaxLumaCodingUnitSize;
|
|
config->MinLumaTransformUnitSize = hevc_caps.MinLumaTransformUnitSize;
|
|
config->MaxLumaTransformUnitSize = hevc_caps.MaxLumaTransformUnitSize;
|
|
config->max_transform_hierarchy_depth_inter = hevc_caps.max_transform_hierarchy_depth_inter;
|
|
config->max_transform_hierarchy_depth_intra = hevc_caps.max_transform_hierarchy_depth_intra;
|
|
|
|
if (hevc_caps.SupportFlags & D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_ASYMETRIC_MOTION_PARTITION_SUPPORT ||
|
|
hevc_caps.SupportFlags & D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_ASYMETRIC_MOTION_PARTITION_REQUIRED)
|
|
config->ConfigurationFlags |= D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_USE_ASYMETRIC_MOTION_PARTITION;
|
|
|
|
if (hevc_caps.SupportFlags & D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_SAO_FILTER_SUPPORT)
|
|
config->ConfigurationFlags |= D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_ENABLE_SAO_FILTER;
|
|
|
|
if (hevc_caps.SupportFlags & D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_DISABLING_LOOP_FILTER_ACROSS_SLICES_SUPPORT)
|
|
config->ConfigurationFlags |= D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_DISABLE_LOOP_FILTER_ACROSS_SLICES;
|
|
|
|
if (hevc_caps.SupportFlags & D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_TRANSFORM_SKIP_SUPPORT)
|
|
config->ConfigurationFlags |= D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_ENABLE_TRANSFORM_SKIPPING;
|
|
|
|
if (hevc_caps.SupportFlags & D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_P_FRAMES_IMPLEMENTED_AS_LOW_DELAY_B_FRAMES)
|
|
ctx->bi_not_empty = 1;
|
|
|
|
// block sizes
|
|
min_cu_size = d3d12va_encode_hevc_map_cusize(hevc_caps.MinLumaCodingUnitSize);
|
|
max_cu_size = d3d12va_encode_hevc_map_cusize(hevc_caps.MaxLumaCodingUnitSize);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, "Using CTU size %dx%d, "
|
|
"min CB size %dx%d.\n", max_cu_size, max_cu_size,
|
|
min_cu_size, min_cu_size);
|
|
|
|
base_ctx->surface_width = FFALIGN(avctx->width, min_cu_size);
|
|
base_ctx->surface_height = FFALIGN(avctx->height, min_cu_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int d3d12va_encode_hevc_configure(AVCodecContext *avctx)
|
|
{
|
|
FFHWBaseEncodeContext *base_ctx = avctx->priv_data;
|
|
D3D12VAEncodeContext *ctx = avctx->priv_data;
|
|
D3D12VAEncodeHEVCContext *priv = avctx->priv_data;
|
|
int fixed_qp_idr, fixed_qp_p, fixed_qp_b;
|
|
int err;
|
|
|
|
err = ff_cbs_init(&priv->cbc, AV_CODEC_ID_HEVC, avctx);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
// Rate control
|
|
if (ctx->rc.Mode == D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP) {
|
|
D3D12_VIDEO_ENCODER_RATE_CONTROL_CQP *cqp_ctl;
|
|
fixed_qp_p = av_clip(ctx->rc_quality, 1, 51);
|
|
if (avctx->i_quant_factor > 0.0)
|
|
fixed_qp_idr = av_clip((avctx->i_quant_factor * fixed_qp_p +
|
|
avctx->i_quant_offset) + 0.5, 1, 51);
|
|
else
|
|
fixed_qp_idr = fixed_qp_p;
|
|
if (avctx->b_quant_factor > 0.0)
|
|
fixed_qp_b = av_clip((avctx->b_quant_factor * fixed_qp_p +
|
|
avctx->b_quant_offset) + 0.5, 1, 51);
|
|
else
|
|
fixed_qp_b = fixed_qp_p;
|
|
|
|
av_log(avctx, AV_LOG_DEBUG, "Using fixed QP = "
|
|
"%d / %d / %d for IDR- / P- / B-frames.\n",
|
|
fixed_qp_idr, fixed_qp_p, fixed_qp_b);
|
|
|
|
ctx->rc.ConfigParams.DataSize = sizeof(D3D12_VIDEO_ENCODER_RATE_CONTROL_CQP);
|
|
cqp_ctl = av_mallocz(ctx->rc.ConfigParams.DataSize);
|
|
if (!cqp_ctl)
|
|
return AVERROR(ENOMEM);
|
|
|
|
cqp_ctl->ConstantQP_FullIntracodedFrame = fixed_qp_idr;
|
|
cqp_ctl->ConstantQP_InterPredictedFrame_PrevRefOnly = fixed_qp_p;
|
|
cqp_ctl->ConstantQP_InterPredictedFrame_BiDirectionalRef = fixed_qp_b;
|
|
|
|
ctx->rc.ConfigParams.pConfiguration_CQP = cqp_ctl;
|
|
}
|
|
|
|
// GOP
|
|
ctx->gop.DataSize = sizeof(D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_HEVC);
|
|
ctx->gop.pHEVCGroupOfPictures = av_mallocz(ctx->gop.DataSize);
|
|
if (!ctx->gop.pHEVCGroupOfPictures)
|
|
return AVERROR(ENOMEM);
|
|
|
|
ctx->gop.pHEVCGroupOfPictures->GOPLength = base_ctx->gop_size;
|
|
ctx->gop.pHEVCGroupOfPictures->PPicturePeriod = base_ctx->b_per_p + 1;
|
|
// Power of 2
|
|
if (base_ctx->gop_size & base_ctx->gop_size - 1 == 0)
|
|
ctx->gop.pHEVCGroupOfPictures->log2_max_pic_order_cnt_lsb_minus4 =
|
|
FFMAX(av_log2(base_ctx->gop_size) - 4, 0);
|
|
else
|
|
ctx->gop.pHEVCGroupOfPictures->log2_max_pic_order_cnt_lsb_minus4 =
|
|
FFMAX(av_log2(base_ctx->gop_size) - 3, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int d3d12va_encode_hevc_set_level(AVCodecContext *avctx)
|
|
{
|
|
D3D12VAEncodeContext *ctx = avctx->priv_data;
|
|
D3D12VAEncodeHEVCContext *priv = avctx->priv_data;
|
|
int i;
|
|
|
|
ctx->level.DataSize = sizeof(D3D12_VIDEO_ENCODER_LEVEL_TIER_CONSTRAINTS_HEVC);
|
|
ctx->level.pHEVCLevelSetting = av_mallocz(ctx->level.DataSize);
|
|
if (!ctx->level.pHEVCLevelSetting)
|
|
return AVERROR(ENOMEM);
|
|
|
|
for (i = 0; i < FF_ARRAY_ELEMS(hevc_levels); i++) {
|
|
if (avctx->level == hevc_levels[i].level) {
|
|
ctx->level.pHEVCLevelSetting->Level = hevc_levels[i].d3d12_level;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == FF_ARRAY_ELEMS(hevc_levels)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Invalid level %d.\n", avctx->level);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
ctx->level.pHEVCLevelSetting->Tier = priv->units.raw_vps.profile_tier_level.general_tier_flag == 0 ?
|
|
D3D12_VIDEO_ENCODER_TIER_HEVC_MAIN :
|
|
D3D12_VIDEO_ENCODER_TIER_HEVC_HIGH;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void d3d12va_encode_hevc_free_picture_params(D3D12VAEncodePicture *pic)
|
|
{
|
|
if (!pic->pic_ctl.pHEVCPicData)
|
|
return;
|
|
|
|
av_freep(&pic->pic_ctl.pHEVCPicData->pList0ReferenceFrames);
|
|
av_freep(&pic->pic_ctl.pHEVCPicData->pList1ReferenceFrames);
|
|
av_freep(&pic->pic_ctl.pHEVCPicData->pReferenceFramesReconPictureDescriptors);
|
|
av_freep(&pic->pic_ctl.pHEVCPicData);
|
|
}
|
|
|
|
static int d3d12va_encode_hevc_init_picture_params(AVCodecContext *avctx,
|
|
FFHWBaseEncodePicture *base_pic)
|
|
{
|
|
D3D12VAEncodePicture *pic = base_pic->priv;
|
|
D3D12VAEncodeHEVCPicture *hpic = base_pic->codec_priv;
|
|
FFHWBaseEncodePicture *prev = base_pic->prev;
|
|
D3D12VAEncodeHEVCPicture *hprev = prev ? prev->codec_priv : NULL;
|
|
D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_HEVC *pd = NULL;
|
|
UINT *ref_list0 = NULL, *ref_list1 = NULL;
|
|
int i, idx = 0;
|
|
|
|
pic->pic_ctl.DataSize = sizeof(D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_HEVC);
|
|
pic->pic_ctl.pHEVCPicData = av_mallocz(pic->pic_ctl.DataSize);
|
|
if (!pic->pic_ctl.pHEVCPicData)
|
|
return AVERROR(ENOMEM);
|
|
|
|
if (base_pic->type == FF_HW_PICTURE_TYPE_IDR) {
|
|
av_assert0(base_pic->display_order == base_pic->encode_order);
|
|
hpic->last_idr_frame = base_pic->display_order;
|
|
} else {
|
|
av_assert0(prev);
|
|
hpic->last_idr_frame = hprev->last_idr_frame;
|
|
}
|
|
hpic->pic_order_cnt = base_pic->display_order - hpic->last_idr_frame;
|
|
|
|
switch(base_pic->type) {
|
|
case FF_HW_PICTURE_TYPE_IDR:
|
|
pic->pic_ctl.pHEVCPicData->FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_HEVC_IDR_FRAME;
|
|
break;
|
|
case FF_HW_PICTURE_TYPE_I:
|
|
pic->pic_ctl.pHEVCPicData->FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_HEVC_I_FRAME;
|
|
break;
|
|
case FF_HW_PICTURE_TYPE_P:
|
|
pic->pic_ctl.pHEVCPicData->FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_HEVC_P_FRAME;
|
|
break;
|
|
case FF_HW_PICTURE_TYPE_B:
|
|
pic->pic_ctl.pHEVCPicData->FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_HEVC_B_FRAME;
|
|
break;
|
|
default:
|
|
av_assert0(0 && "invalid picture type");
|
|
}
|
|
|
|
pic->pic_ctl.pHEVCPicData->slice_pic_parameter_set_id = 0;
|
|
pic->pic_ctl.pHEVCPicData->PictureOrderCountNumber = hpic->pic_order_cnt;
|
|
|
|
if (base_pic->type == FF_HW_PICTURE_TYPE_P || base_pic->type == FF_HW_PICTURE_TYPE_B) {
|
|
pd = av_calloc(MAX_PICTURE_REFERENCES, sizeof(*pd));
|
|
if (!pd)
|
|
return AVERROR(ENOMEM);
|
|
|
|
ref_list0 = av_calloc(MAX_PICTURE_REFERENCES, sizeof(*ref_list0));
|
|
if (!ref_list0)
|
|
return AVERROR(ENOMEM);
|
|
|
|
pic->pic_ctl.pHEVCPicData->List0ReferenceFramesCount = base_pic->nb_refs[0];
|
|
for (i = 0; i < base_pic->nb_refs[0]; i++) {
|
|
FFHWBaseEncodePicture *ref = base_pic->refs[0][i];
|
|
D3D12VAEncodeHEVCPicture *href;
|
|
|
|
av_assert0(ref && ref->encode_order < base_pic->encode_order);
|
|
href = ref->codec_priv;
|
|
|
|
ref_list0[i] = idx;
|
|
pd[idx].ReconstructedPictureResourceIndex = idx;
|
|
pd[idx].IsRefUsedByCurrentPic = TRUE;
|
|
pd[idx].PictureOrderCountNumber = href->pic_order_cnt;
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
if (base_pic->type == FF_HW_PICTURE_TYPE_B) {
|
|
ref_list1 = av_calloc(MAX_PICTURE_REFERENCES, sizeof(*ref_list1));
|
|
if (!ref_list1)
|
|
return AVERROR(ENOMEM);
|
|
|
|
pic->pic_ctl.pHEVCPicData->List1ReferenceFramesCount = base_pic->nb_refs[1];
|
|
for (i = 0; i < base_pic->nb_refs[1]; i++) {
|
|
FFHWBaseEncodePicture *ref = base_pic->refs[1][i];
|
|
D3D12VAEncodeHEVCPicture *href;
|
|
|
|
av_assert0(ref && ref->encode_order < base_pic->encode_order);
|
|
href = ref->codec_priv;
|
|
|
|
ref_list1[i] = idx;
|
|
pd[idx].ReconstructedPictureResourceIndex = idx;
|
|
pd[idx].IsRefUsedByCurrentPic = TRUE;
|
|
pd[idx].PictureOrderCountNumber = href->pic_order_cnt;
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
pic->pic_ctl.pHEVCPicData->pList0ReferenceFrames = ref_list0;
|
|
pic->pic_ctl.pHEVCPicData->pList1ReferenceFrames = ref_list1;
|
|
pic->pic_ctl.pHEVCPicData->ReferenceFramesReconPictureDescriptorsCount = idx;
|
|
pic->pic_ctl.pHEVCPicData->pReferenceFramesReconPictureDescriptors = pd;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const D3D12VAEncodeType d3d12va_encode_type_hevc = {
|
|
.profiles = d3d12va_encode_hevc_profiles,
|
|
|
|
.d3d12_codec = D3D12_VIDEO_ENCODER_CODEC_HEVC,
|
|
|
|
.flags = FF_HW_FLAG_B_PICTURES |
|
|
FF_HW_FLAG_B_PICTURE_REFERENCES |
|
|
FF_HW_FLAG_NON_IDR_KEY_PICTURES,
|
|
|
|
.default_quality = 25,
|
|
|
|
.get_encoder_caps = &d3d12va_encode_hevc_get_encoder_caps,
|
|
|
|
.configure = &d3d12va_encode_hevc_configure,
|
|
|
|
.set_level = &d3d12va_encode_hevc_set_level,
|
|
|
|
.picture_priv_data_size = sizeof(D3D12VAEncodeHEVCPicture),
|
|
|
|
.init_sequence_params = &d3d12va_encode_hevc_init_sequence_params,
|
|
|
|
.init_picture_params = &d3d12va_encode_hevc_init_picture_params,
|
|
|
|
.free_picture_params = &d3d12va_encode_hevc_free_picture_params,
|
|
|
|
.write_sequence_header = &d3d12va_encode_hevc_write_sequence_header,
|
|
};
|
|
|
|
static int d3d12va_encode_hevc_init(AVCodecContext *avctx)
|
|
{
|
|
D3D12VAEncodeContext *ctx = avctx->priv_data;
|
|
D3D12VAEncodeHEVCContext *priv = avctx->priv_data;
|
|
|
|
ctx->codec = &d3d12va_encode_type_hevc;
|
|
|
|
if (avctx->profile == AV_PROFILE_UNKNOWN)
|
|
avctx->profile = priv->profile;
|
|
if (avctx->level == AV_LEVEL_UNKNOWN)
|
|
avctx->level = priv->level;
|
|
|
|
if (avctx->level != AV_LEVEL_UNKNOWN && avctx->level & ~0xff) {
|
|
av_log(avctx, AV_LOG_ERROR, "Invalid level %d: must fit "
|
|
"in 8-bit unsigned integer.\n", avctx->level);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
if (priv->qp > 0)
|
|
ctx->explicit_qp = priv->qp;
|
|
|
|
return ff_d3d12va_encode_init(avctx);
|
|
}
|
|
|
|
static int d3d12va_encode_hevc_close(AVCodecContext *avctx)
|
|
{
|
|
D3D12VAEncodeHEVCContext *priv = avctx->priv_data;
|
|
|
|
ff_cbs_fragment_free(&priv->current_access_unit);
|
|
ff_cbs_close(&priv->cbc);
|
|
|
|
av_freep(&priv->common.codec_conf.pHEVCConfig);
|
|
av_freep(&priv->common.gop.pHEVCGroupOfPictures);
|
|
av_freep(&priv->common.level.pHEVCLevelSetting);
|
|
|
|
return ff_d3d12va_encode_close(avctx);
|
|
}
|
|
|
|
#define OFFSET(x) offsetof(D3D12VAEncodeHEVCContext, x)
|
|
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM)
|
|
static const AVOption d3d12va_encode_hevc_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)",
|
|
OFFSET(qp), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 52, FLAGS },
|
|
|
|
{ "profile", "Set profile (general_profile_idc)",
|
|
OFFSET(profile), AV_OPT_TYPE_INT,
|
|
{ .i64 = AV_PROFILE_UNKNOWN }, AV_PROFILE_UNKNOWN, 0xff, FLAGS, "profile" },
|
|
|
|
#define PROFILE(name, value) name, NULL, 0, AV_OPT_TYPE_CONST, \
|
|
{ .i64 = value }, 0, 0, FLAGS, "profile"
|
|
{ PROFILE("main", AV_PROFILE_HEVC_MAIN) },
|
|
{ PROFILE("main10", AV_PROFILE_HEVC_MAIN_10) },
|
|
#undef PROFILE
|
|
|
|
{ "tier", "Set tier (general_tier_flag)",
|
|
OFFSET(unit_opts.tier), AV_OPT_TYPE_INT,
|
|
{ .i64 = 0 }, 0, 1, FLAGS, "tier" },
|
|
{ "main", NULL, 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = 0 }, 0, 0, FLAGS, "tier" },
|
|
{ "high", NULL, 0, AV_OPT_TYPE_CONST,
|
|
{ .i64 = 1 }, 0, 0, FLAGS, "tier" },
|
|
|
|
{ "level", "Set level (general_level_idc)",
|
|
OFFSET(level), AV_OPT_TYPE_INT,
|
|
{ .i64 = AV_LEVEL_UNKNOWN }, AV_LEVEL_UNKNOWN, 0xff, FLAGS, "level" },
|
|
|
|
#define LEVEL(name, value) name, NULL, 0, AV_OPT_TYPE_CONST, \
|
|
{ .i64 = value }, 0, 0, FLAGS, "level"
|
|
{ LEVEL("1", 30) },
|
|
{ LEVEL("2", 60) },
|
|
{ LEVEL("2.1", 63) },
|
|
{ LEVEL("3", 90) },
|
|
{ LEVEL("3.1", 93) },
|
|
{ LEVEL("4", 120) },
|
|
{ LEVEL("4.1", 123) },
|
|
{ LEVEL("5", 150) },
|
|
{ LEVEL("5.1", 153) },
|
|
{ LEVEL("5.2", 156) },
|
|
{ LEVEL("6", 180) },
|
|
{ LEVEL("6.1", 183) },
|
|
{ LEVEL("6.2", 186) },
|
|
#undef LEVEL
|
|
|
|
{ NULL },
|
|
};
|
|
|
|
static const FFCodecDefault d3d12va_encode_hevc_defaults[] = {
|
|
{ "b", "0" },
|
|
{ "bf", "2" },
|
|
{ "g", "120" },
|
|
{ "i_qfactor", "1" },
|
|
{ "i_qoffset", "0" },
|
|
{ "b_qfactor", "1" },
|
|
{ "b_qoffset", "0" },
|
|
{ "qmin", "-1" },
|
|
{ "qmax", "-1" },
|
|
{ "refs", "0" },
|
|
{ NULL },
|
|
};
|
|
|
|
static const AVClass d3d12va_encode_hevc_class = {
|
|
.class_name = "hevc_d3d12va",
|
|
.item_name = av_default_item_name,
|
|
.option = d3d12va_encode_hevc_options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
};
|
|
|
|
const FFCodec ff_hevc_d3d12va_encoder = {
|
|
.p.name = "hevc_d3d12va",
|
|
CODEC_LONG_NAME("D3D12VA hevc encoder"),
|
|
.p.type = AVMEDIA_TYPE_VIDEO,
|
|
.p.id = AV_CODEC_ID_HEVC,
|
|
.priv_data_size = sizeof(D3D12VAEncodeHEVCContext),
|
|
.init = &d3d12va_encode_hevc_init,
|
|
FF_CODEC_RECEIVE_PACKET_CB(&ff_d3d12va_encode_receive_packet),
|
|
.close = &d3d12va_encode_hevc_close,
|
|
.p.priv_class = &d3d12va_encode_hevc_class,
|
|
.p.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_HARDWARE |
|
|
AV_CODEC_CAP_DR1 | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
|
|
.caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE |
|
|
FF_CODEC_CAP_INIT_CLEANUP,
|
|
.defaults = d3d12va_encode_hevc_defaults,
|
|
CODEC_PIXFMTS(AV_PIX_FMT_D3D12),
|
|
.hw_configs = ff_d3d12va_encode_hw_configs,
|
|
.p.wrapper_name = "d3d12va",
|
|
};
|