Skip to content

Commit

Permalink
avcodec/nvenc: add AV1 encoding support
Browse files Browse the repository at this point in the history
The encoder seems to be trading blows with hevc_nvenc.
In terms of quality at low bitrate cbr settings, it seems to
outperform it even. It produces fewer artifacts and the ones it
does produce are less jarring to my perception.

At higher bitrates I had a hard time finding differences between
the two encoders in terms of subjective visual quality.

Using the 'slow' preset, av1_nvenc outperformed hevc_nvenc in terms
of encoding speed by 75% to 100% while performing above tests.

Needless to say, it always massively outperformed h264_nvenc in terms
of quality for a given bitrate, while also being slightly faster.
  • Loading branch information
BtbN committed Nov 5, 2022
1 parent 28680e2 commit 5c288a4
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 14 deletions.
14 changes: 10 additions & 4 deletions configure
Expand Up @@ -3184,6 +3184,8 @@ nvenc_deps_any="libdl LoadLibrary"
aac_mf_encoder_deps="mediafoundation"
ac3_mf_encoder_deps="mediafoundation"
av1_cuvid_decoder_deps="cuvid CUVIDAV1PICPARAMS"
av1_nvenc_encoder_deps="nvenc NV_ENC_PIC_PARAMS_AV1"
av1_nvenc_encoder_select="atsc_a53"
h263_v4l2m2m_decoder_deps="v4l2_m2m h263_v4l2_m2m"
h263_v4l2m2m_encoder_deps="v4l2_m2m h263_v4l2_m2m"
h264_amf_encoder_deps="amf"
Expand Down Expand Up @@ -6466,10 +6468,10 @@ fi

if ! disabled ffnvcodec; then
ffnv_hdr_list="ffnvcodec/nvEncodeAPI.h ffnvcodec/dynlink_cuda.h ffnvcodec/dynlink_cuviddec.h ffnvcodec/dynlink_nvcuvid.h"
check_pkg_config ffnvcodec "ffnvcodec >= 9.1.23.1" "$ffnv_hdr_list" "" || \
check_pkg_config ffnvcodec "ffnvcodec >= 9.0.18.3 ffnvcodec < 9.1" "$ffnv_hdr_list" "" || \
check_pkg_config ffnvcodec "ffnvcodec >= 8.2.15.10 ffnvcodec < 8.3" "$ffnv_hdr_list" "" || \
check_pkg_config ffnvcodec "ffnvcodec >= 8.1.24.11 ffnvcodec < 8.2" "$ffnv_hdr_list" ""
check_pkg_config ffnvcodec "ffnvcodec >= 12.0.11.0" "$ffnv_hdr_list" "" || \
check_pkg_config ffnvcodec "ffnvcodec >= 11.1.5.2 ffnvcodec < 12.0" "$ffnv_hdr_list" "" || \
check_pkg_config ffnvcodec "ffnvcodec >= 11.0.10.2 ffnvcodec < 11.1" "$ffnv_hdr_list" "" || \
check_pkg_config ffnvcodec "ffnvcodec >= 8.1.24.14 ffnvcodec < 8.2" "$ffnv_hdr_list" ""
fi

if enabled_all libglslang libshaderc; then
Expand Down Expand Up @@ -7050,6 +7052,10 @@ void f(void) { struct { const GUID guid; } s[] = { { NV_ENC_PRESET_HQ_GUID } };
int main(void) { return 0; }
EOF

if enabled nvenc; then
check_type "ffnvcodec/nvEncodeAPI.h" "NV_ENC_PIC_PARAMS_AV1"
fi

if enabled_any nvdec cuvid; then
check_type "ffnvcodec/dynlink_cuda.h ffnvcodec/dynlink_cuviddec.h" "CUVIDAV1PICPARAMS"
fi
Expand Down
1 change: 1 addition & 0 deletions libavcodec/Makefile
Expand Up @@ -244,6 +244,7 @@ OBJS-$(CONFIG_AURA_DECODER) += cyuv.o
OBJS-$(CONFIG_AURA2_DECODER) += aura.o
OBJS-$(CONFIG_AV1_DECODER) += av1dec.o
OBJS-$(CONFIG_AV1_CUVID_DECODER) += cuviddec.o
OBJS-$(CONFIG_AV1_NVENC_ENCODER) += nvenc_av1.o nvenc.o
OBJS-$(CONFIG_AV1_QSV_ENCODER) += qsvenc_av1.o
OBJS-$(CONFIG_AVRN_DECODER) += avrndec.o
OBJS-$(CONFIG_AVRP_DECODER) += r210dec.o
Expand Down
1 change: 1 addition & 0 deletions libavcodec/allcodecs.c
Expand Up @@ -827,6 +827,7 @@ extern const FFCodec ff_libaom_av1_decoder;
/* hwaccel hooks only, so prefer external decoders */
extern const FFCodec ff_av1_decoder;
extern const FFCodec ff_av1_cuvid_decoder;
extern const FFCodec ff_av1_nvenc_encoder;
extern const FFCodec ff_av1_qsv_decoder;
extern const FFCodec ff_av1_qsv_encoder;
extern const FFCodec ff_libopenh264_encoder;
Expand Down
134 changes: 126 additions & 8 deletions libavcodec/nvenc.c
@@ -1,5 +1,5 @@
/*
* H.264/HEVC hardware encoding using nvidia nvenc
* H.264/HEVC/AV1 hardware encoding using nvidia nvenc
* Copyright (c) 2016 Timo Rothenpieler <timo@rothenpieler.org>
*
* This file is part of FFmpeg.
Expand Down Expand Up @@ -222,8 +222,14 @@ static void nvenc_map_preset(NvencContext *ctx)

static void nvenc_print_driver_requirement(AVCodecContext *avctx, int level)
{
#if NVENCAPI_CHECK_VERSION(11, 2)
#if NVENCAPI_CHECK_VERSION(12, 1)
const char *minver = "(unknown)";
#elif NVENCAPI_CHECK_VERSION(12, 0)
# if defined(_WIN32) || defined(__CYGWIN__)
const char *minver = "522.25";
# else
const char *minver = "520.56.06";
# endif
#elif NVENCAPI_CHECK_VERSION(11, 1)
# if defined(_WIN32) || defined(__CYGWIN__)
const char *minver = "471.41";
Expand Down Expand Up @@ -658,6 +664,11 @@ static av_cold int nvenc_setup_device(AVCodecContext *avctx)
case AV_CODEC_ID_HEVC:
ctx->init_encode_params.encodeGUID = NV_ENC_CODEC_HEVC_GUID;
break;
#if CONFIG_AV1_NVENC_ENCODER
case AV_CODEC_ID_AV1:
ctx->init_encode_params.encodeGUID = NV_ENC_CODEC_AV1_GUID;
break;
#endif
default:
return AVERROR_BUG;
}
Expand Down Expand Up @@ -761,6 +772,11 @@ static av_cold void set_constqp(AVCodecContext *avctx)
{
NvencContext *ctx = avctx->priv_data;
NV_ENC_RC_PARAMS *rc = &ctx->encode_config.rcParams;
#if CONFIG_AV1_NVENC_ENCODER
int qmax = avctx->codec->id == AV_CODEC_ID_AV1 ? 255 : 51;
#else
int qmax = 51;
#endif

rc->rateControlMode = NV_ENC_PARAMS_RC_CONSTQP;

Expand All @@ -771,19 +787,19 @@ static av_cold void set_constqp(AVCodecContext *avctx)
rc->constQP.qpInterB = ctx->init_qp_b;
} else if (avctx->i_quant_factor != 0.0 && avctx->b_quant_factor != 0.0) {
rc->constQP.qpIntra = av_clip(
rc->constQP.qpInterP * fabs(avctx->i_quant_factor) + avctx->i_quant_offset + 0.5, 0, 51);
rc->constQP.qpInterP * fabs(avctx->i_quant_factor) + avctx->i_quant_offset + 0.5, 0, qmax);
rc->constQP.qpInterB = av_clip(
rc->constQP.qpInterP * fabs(avctx->b_quant_factor) + avctx->b_quant_offset + 0.5, 0, 51);
rc->constQP.qpInterP * fabs(avctx->b_quant_factor) + avctx->b_quant_offset + 0.5, 0, qmax);
} else {
rc->constQP.qpIntra = rc->constQP.qpInterP;
rc->constQP.qpInterB = rc->constQP.qpInterP;
}
} else if (ctx->cqp >= 0) {
rc->constQP.qpInterP = rc->constQP.qpInterB = rc->constQP.qpIntra = ctx->cqp;
if (avctx->b_quant_factor != 0.0)
rc->constQP.qpInterB = av_clip(ctx->cqp * fabs(avctx->b_quant_factor) + avctx->b_quant_offset + 0.5, 0, 51);
rc->constQP.qpInterB = av_clip(ctx->cqp * fabs(avctx->b_quant_factor) + avctx->b_quant_offset + 0.5, 0, qmax);
if (avctx->i_quant_factor != 0.0)
rc->constQP.qpIntra = av_clip(ctx->cqp * fabs(avctx->i_quant_factor) + avctx->i_quant_offset + 0.5, 0, 51);
rc->constQP.qpIntra = av_clip(ctx->cqp * fabs(avctx->i_quant_factor) + avctx->i_quant_offset + 0.5, 0, qmax);
}

avctx->qmin = -1;
Expand All @@ -795,6 +811,11 @@ static av_cold void set_vbr(AVCodecContext *avctx)
NvencContext *ctx = avctx->priv_data;
NV_ENC_RC_PARAMS *rc = &ctx->encode_config.rcParams;
int qp_inter_p;
#if CONFIG_AV1_NVENC_ENCODER
int qmax = avctx->codec->id == AV_CODEC_ID_AV1 ? 255 : 51;
#else
int qmax = 51;
#endif

if (avctx->qmin >= 0 && avctx->qmax >= 0) {
rc->enableMinQP = 1;
Expand Down Expand Up @@ -832,7 +853,7 @@ static av_cold void set_vbr(AVCodecContext *avctx)
if (ctx->init_qp_i < 0) {
if (avctx->i_quant_factor != 0.0 && avctx->b_quant_factor != 0.0) {
rc->initialRCQP.qpIntra = av_clip(
rc->initialRCQP.qpInterP * fabs(avctx->i_quant_factor) + avctx->i_quant_offset + 0.5, 0, 51);
rc->initialRCQP.qpInterP * fabs(avctx->i_quant_factor) + avctx->i_quant_offset + 0.5, 0, qmax);
} else {
rc->initialRCQP.qpIntra = rc->initialRCQP.qpInterP;
}
Expand All @@ -843,7 +864,7 @@ static av_cold void set_vbr(AVCodecContext *avctx)
if (ctx->init_qp_b < 0) {
if (avctx->i_quant_factor != 0.0 && avctx->b_quant_factor != 0.0) {
rc->initialRCQP.qpInterB = av_clip(
rc->initialRCQP.qpInterP * fabs(avctx->b_quant_factor) + avctx->b_quant_offset + 0.5, 0, 51);
rc->initialRCQP.qpInterP * fabs(avctx->b_quant_factor) + avctx->b_quant_offset + 0.5, 0, qmax);
} else {
rc->initialRCQP.qpInterB = rc->initialRCQP.qpInterP;
}
Expand Down Expand Up @@ -1327,13 +1348,97 @@ static av_cold int nvenc_setup_hevc_config(AVCodecContext *avctx)
return 0;
}

#if CONFIG_AV1_NVENC_ENCODER
static av_cold int nvenc_setup_av1_config(AVCodecContext *avctx)
{
NvencContext *ctx = avctx->priv_data;
NV_ENC_CONFIG *cc = &ctx->encode_config;
NV_ENC_CONFIG_AV1 *av1 = &cc->encodeCodecConfig.av1Config;

const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(ctx->data_pix_fmt);

if ((pixdesc->flags & AV_PIX_FMT_FLAG_RGB) && !IS_GBRP(ctx->data_pix_fmt)) {
av1->matrixCoefficients = AVCOL_SPC_BT470BG;
av1->colorPrimaries = avctx->color_primaries;
av1->transferCharacteristics = avctx->color_trc;
av1->colorRange = 0;
} else {
av1->matrixCoefficients = IS_GBRP(ctx->data_pix_fmt) ? AVCOL_SPC_RGB : avctx->colorspace;
av1->colorPrimaries = avctx->color_primaries;
av1->transferCharacteristics = avctx->color_trc;
av1->colorRange = (avctx->color_range == AVCOL_RANGE_JPEG
|| ctx->data_pix_fmt == AV_PIX_FMT_YUVJ420P || ctx->data_pix_fmt == AV_PIX_FMT_YUVJ422P || ctx->data_pix_fmt == AV_PIX_FMT_YUVJ444P);
}

if (IS_YUV444(ctx->data_pix_fmt)) {
cc->profileGUID = NV_ENC_AV1_PROFILE_HIGH_GUID;
avctx->profile = FF_PROFILE_AV1_HIGH;
} else {
cc->profileGUID = NV_ENC_AV1_PROFILE_MAIN_GUID;
avctx->profile = FF_PROFILE_AV1_MAIN;
}

if (ctx->dpb_size >= 0) {
/* 0 means "let the hardware decide" */
av1->maxNumRefFramesInDPB = ctx->dpb_size;
}

if (ctx->intra_refresh) {
av1->enableIntraRefresh = 1;
av1->intraRefreshPeriod = avctx->gop_size;
av1->intraRefreshCnt = avctx->gop_size - 1;

av1->idrPeriod = NVENC_INFINITE_GOPLENGTH;
} else if (avctx->gop_size >= 0) {
av1->idrPeriod = avctx->gop_size;
}

if (IS_CBR(cc->rcParams.rateControlMode)) {
av1->enableBitstreamPadding = 1;
}

if (ctx->tile_cols >= 0)
av1->numTileColumns = ctx->tile_cols;
if (ctx->tile_rows >= 0)
av1->numTileRows = ctx->tile_rows;

av1->outputAnnexBFormat = 0;

av1->level = ctx->level;
av1->tier = ctx->tier;

av1->enableTimingInfo = ctx->timing_info;

/* mp4 encapsulation requires sequence headers to be present on all keyframes for AV1 */
av1->disableSeqHdr = 0;
av1->repeatSeqHdr = 1;

av1->chromaFormatIDC = IS_YUV444(ctx->data_pix_fmt) ? 3 : 1;

av1->inputPixelBitDepthMinus8 = IS_10BIT(ctx->data_pix_fmt) ? 2 : 0;
av1->pixelBitDepthMinus8 = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? 2 : 0;

if (ctx->b_ref_mode >= 0)
av1->useBFramesAsRef = ctx->b_ref_mode;

av1->numFwdRefs = avctx->refs;
av1->numBwdRefs = avctx->refs;

return 0;
}
#endif

static av_cold int nvenc_setup_codec_config(AVCodecContext *avctx)
{
switch (avctx->codec->id) {
case AV_CODEC_ID_H264:
return nvenc_setup_h264_config(avctx);
case AV_CODEC_ID_HEVC:
return nvenc_setup_hevc_config(avctx);
#if CONFIG_AV1_NVENC_ENCODER
case AV_CODEC_ID_AV1:
return nvenc_setup_av1_config(avctx);
#endif
/* Earlier switch/case will return if unknown codec is passed. */
}

Expand Down Expand Up @@ -2025,6 +2130,19 @@ static void nvenc_codec_specific_pic_params(AVCodecContext *avctx,
}

break;
#if CONFIG_AV1_NVENC_ENCODER
case AV_CODEC_ID_AV1:
params->codecPicParams.av1PicParams.numTileColumns =
ctx->encode_config.encodeCodecConfig.av1Config.numTileColumns;
params->codecPicParams.av1PicParams.numTileRows =
ctx->encode_config.encodeCodecConfig.av1Config.numTileRows;
if (sei_count > 0) {
params->codecPicParams.av1PicParams.obuPayloadArray = sei_data;
params->codecPicParams.av1PicParams.obuPayloadArrayCnt = sei_count;
}

break;
#endif
}
}

Expand Down
4 changes: 4 additions & 0 deletions libavcodec/nvenc.h
Expand Up @@ -199,6 +199,8 @@ typedef struct NvencContext
int tier;
int rc;
int cbr;
int tile_rows;
int tile_cols;
int twopass;
int device;
int flags;
Expand Down Expand Up @@ -236,6 +238,8 @@ typedef struct NvencContext
int single_slice_intra_refresh;
int constrained_encoding;
int udu_sei;
int timing_info;
int highbitdepth;
} NvencContext;

int ff_nvenc_encode_init(AVCodecContext *avctx);
Expand Down

0 comments on commit 5c288a4

Please sign in to comment.