From f84dfbc74a4f881e80cbf467649c59b2bb628eaa Mon Sep 17 00:00:00 2001 From: Andrey Turkin Date: Wed, 25 May 2016 19:39:54 +0300 Subject: [PATCH] avcodec/nvenc: add rate control option Signed-off-by: Timo Rothenpieler --- libavcodec/nvenc.c | 278 +++++++++++++++++++++++----------------- libavcodec/nvenc.h | 1 + libavcodec/nvenc_h264.c | 8 ++ libavcodec/nvenc_hevc.c | 8 ++ 4 files changed, 174 insertions(+), 121 deletions(-) diff --git a/libavcodec/nvenc.c b/libavcodec/nvenc.c index d74960563d..0e8521655e 100644 --- a/libavcodec/nvenc.c +++ b/libavcodec/nvenc.c @@ -40,6 +40,10 @@ #include "libavutil/hwcontext_cuda.h" #endif +#define IS_CBR(rc) (rc == NV_ENC_PARAMS_RC_CBR || \ + rc == NV_ENC_PARAMS_RC_2_PASS_QUALITY || \ + rc == NV_ENC_PARAMS_RC_2_PASS_FRAMESIZE_CAP) + #if defined(_WIN32) #define LOAD_FUNC(l, s) GetProcAddress(l, s) #define DL_CLOSE_FUNC(l) FreeLibrary(l) @@ -485,45 +489,121 @@ static void nvenc_map_preset(NvencContext *ctx) static av_cold void set_constqp(AVCodecContext *avctx) { NvencContext *ctx = avctx->priv_data; + NV_ENC_RC_PARAMS *rc = &ctx->encode_config.rcParams; + + rc->rateControlMode = NV_ENC_PARAMS_RC_CONSTQP; + rc->constQP.qpInterB = avctx->global_quality; + rc->constQP.qpInterP = avctx->global_quality; + rc->constQP.qpIntra = avctx->global_quality; - ctx->encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CONSTQP; - ctx->encode_config.rcParams.constQP.qpInterB = avctx->global_quality; - ctx->encode_config.rcParams.constQP.qpInterP = avctx->global_quality; - ctx->encode_config.rcParams.constQP.qpIntra = avctx->global_quality; + avctx->qmin = -1; + avctx->qmax = -1; } 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; - ctx->encode_config.rcParams.enableMinQP = 1; - ctx->encode_config.rcParams.enableMaxQP = 1; + if (avctx->qmin >= 0 && avctx->qmax >= 0) { + rc->enableMinQP = 1; + rc->enableMaxQP = 1; - ctx->encode_config.rcParams.minQP.qpInterB = avctx->qmin; - ctx->encode_config.rcParams.minQP.qpInterP = avctx->qmin; - ctx->encode_config.rcParams.minQP.qpIntra = avctx->qmin; + rc->minQP.qpInterB = avctx->qmin; + rc->minQP.qpInterP = avctx->qmin; + rc->minQP.qpIntra = avctx->qmin; - ctx->encode_config.rcParams.maxQP.qpInterB = avctx->qmax; - ctx->encode_config.rcParams.maxQP.qpInterP = avctx->qmax; - ctx->encode_config.rcParams.maxQP.qpIntra = avctx->qmax; + rc->maxQP.qpInterB = avctx->qmax; + rc->maxQP.qpInterP = avctx->qmax; + rc->maxQP.qpIntra = avctx->qmax; + + qp_inter_p = (avctx->qmax + 3 * avctx->qmin) / 4; // biased towards Qmin + } else { + qp_inter_p = 26; // default to 26 + } + + rc->enableInitialRCQP = 1; + rc->initialRCQP.qpInterP = qp_inter_p; + + if (avctx->i_quant_factor != 0.0 && avctx->b_quant_factor != 0.0) { + rc->initialRCQP.qpIntra = av_clip( + qp_inter_p * fabs(avctx->i_quant_factor) + avctx->i_quant_offset, 0, 51); + rc->initialRCQP.qpInterB = av_clip( + qp_inter_p * fabs(avctx->b_quant_factor) + avctx->b_quant_offset, 0, 51); + } else { + rc->initialRCQP.qpIntra = qp_inter_p; + rc->initialRCQP.qpInterB = qp_inter_p; + } } static av_cold void set_lossless(AVCodecContext *avctx) { NvencContext *ctx = avctx->priv_data; + NV_ENC_RC_PARAMS *rc = &ctx->encode_config.rcParams; + + rc->rateControlMode = NV_ENC_PARAMS_RC_CONSTQP; + rc->constQP.qpInterB = 0; + rc->constQP.qpInterP = 0; + rc->constQP.qpIntra = 0; + + avctx->qmin = -1; + avctx->qmax = -1; +} + +static void nvenc_override_rate_control(AVCodecContext *avctx) +{ + NvencContext *ctx = avctx->priv_data; + NV_ENC_RC_PARAMS *rc = &ctx->encode_config.rcParams; + + switch (ctx->rc) { + case NV_ENC_PARAMS_RC_CONSTQP: + if (avctx->global_quality <= 0) { + av_log(avctx, AV_LOG_WARNING, + "The constant quality rate-control requires " + "the 'global_quality' option set.\n"); + return; + } + set_constqp(avctx); + return; + case NV_ENC_PARAMS_RC_2_PASS_VBR: + case NV_ENC_PARAMS_RC_VBR: + if (avctx->qmin < 0 && avctx->qmax < 0) { + av_log(avctx, AV_LOG_WARNING, + "The variable bitrate rate-control requires " + "the 'qmin' and/or 'qmax' option set.\n"); + set_vbr(avctx); + return; + } + case NV_ENC_PARAMS_RC_VBR_MINQP: + if (avctx->qmin < 0) { + av_log(avctx, AV_LOG_WARNING, + "The variable bitrate rate-control requires " + "the 'qmin' option set.\n"); + set_vbr(avctx); + return; + } + set_vbr(avctx); + break; + case NV_ENC_PARAMS_RC_CBR: + break; + case NV_ENC_PARAMS_RC_2_PASS_QUALITY: + case NV_ENC_PARAMS_RC_2_PASS_FRAMESIZE_CAP: + if (!(ctx->flags & NVENC_LOWLATENCY)) { + av_log(avctx, AV_LOG_WARNING, + "The multipass rate-control requires " + "a low-latency preset.\n"); + return; + } + } - ctx->encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CONSTQP; - ctx->encode_config.rcParams.constQP.qpInterB = 0; - ctx->encode_config.rcParams.constQP.qpInterP = 0; - ctx->encode_config.rcParams.constQP.qpIntra = 0; + rc->rateControlMode = ctx->rc; } static av_cold void nvenc_setup_rate_control(AVCodecContext *avctx) { NvencContext *ctx = avctx->priv_data; - int qp_inter_p; - if (avctx->bit_rate > 0) { ctx->encode_config.rcParams.averageBitRate = avctx->bit_rate; } else if (ctx->encode_config.rcParams.averageBitRate > 0) { @@ -533,72 +613,37 @@ static av_cold void nvenc_setup_rate_control(AVCodecContext *avctx) if (avctx->rc_max_rate > 0) ctx->encode_config.rcParams.maxBitRate = avctx->rc_max_rate; - if (ctx->flags & NVENC_LOSSLESS) { - set_lossless(avctx); + if (ctx->rc < 0) { + if (ctx->flags & NVENC_ONE_PASS) + ctx->twopass = 0; + if (ctx->flags & NVENC_TWO_PASSES) + ctx->twopass = 1; - avctx->qmin = -1; - avctx->qmax = -1; - } else if (ctx->cbr) { - if (!ctx->twopass) { - ctx->encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR; - } else { - ctx->encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_2_PASS_QUALITY; - - if (avctx->codec->id == AV_CODEC_ID_H264) { - ctx->encode_config.encodeCodecConfig.h264Config.adaptiveTransformMode = NV_ENC_H264_ADAPTIVE_TRANSFORM_ENABLE; - ctx->encode_config.encodeCodecConfig.h264Config.fmoMode = NV_ENC_H264_FMO_DISABLE; - } - } - - if (avctx->codec->id == AV_CODEC_ID_H264) { - ctx->encode_config.encodeCodecConfig.h264Config.outputBufferingPeriodSEI = 1; - ctx->encode_config.encodeCodecConfig.h264Config.outputPictureTimingSEI = 1; - } else if (avctx->codec->id == AV_CODEC_ID_H265) { - ctx->encode_config.encodeCodecConfig.hevcConfig.outputBufferingPeriodSEI = 1; - ctx->encode_config.encodeCodecConfig.hevcConfig.outputPictureTimingSEI = 1; - } - } else if (avctx->global_quality > 0) { - set_constqp(avctx); - - avctx->qmin = -1; - avctx->qmax = -1; - } else { - if (avctx->qmin >= 0 && avctx->qmax >= 0) { - set_vbr(avctx); - - qp_inter_p = (avctx->qmax + 3 * avctx->qmin) / 4; // biased towards Qmin + if (ctx->twopass < 0) + ctx->twopass = (ctx->flags & NVENC_LOWLATENCY) != 0; + if (ctx->cbr) { if (ctx->twopass) { - ctx->encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_2_PASS_VBR; - if (avctx->codec->id == AV_CODEC_ID_H264) { - ctx->encode_config.encodeCodecConfig.h264Config.adaptiveTransformMode = NV_ENC_H264_ADAPTIVE_TRANSFORM_ENABLE; - ctx->encode_config.encodeCodecConfig.h264Config.fmoMode = NV_ENC_H264_FMO_DISABLE; - } + ctx->rc = NV_ENC_PARAMS_RC_2_PASS_QUALITY; } else { - ctx->encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_VBR_MINQP; - } - } else { - qp_inter_p = 26; // default to 26 - - if (ctx->twopass) { - ctx->encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_2_PASS_VBR; - } else { - ctx->encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_VBR; + ctx->rc = NV_ENC_PARAMS_RC_CBR; } + } else if (avctx->global_quality > 0) { + ctx->rc = NV_ENC_PARAMS_RC_CONSTQP; + } else if (ctx->twopass) { + ctx->rc = NV_ENC_PARAMS_RC_2_PASS_VBR; + } else if (avctx->qmin >= 0 && avctx->qmax >= 0) { + ctx->rc = NV_ENC_PARAMS_RC_VBR_MINQP; } + } - ctx->encode_config.rcParams.enableInitialRCQP = 1; - ctx->encode_config.rcParams.initialRCQP.qpInterP = qp_inter_p; - - if (avctx->i_quant_factor != 0.0 && avctx->b_quant_factor != 0.0) { - ctx->encode_config.rcParams.initialRCQP.qpIntra = av_clip( - qp_inter_p * fabs(avctx->i_quant_factor) + avctx->i_quant_offset, 0, 51); - ctx->encode_config.rcParams.initialRCQP.qpInterB = av_clip( - qp_inter_p * fabs(avctx->b_quant_factor) + avctx->b_quant_offset, 0, 51); - } else { - ctx->encode_config.rcParams.initialRCQP.qpIntra = qp_inter_p; - ctx->encode_config.rcParams.initialRCQP.qpInterB = qp_inter_p; - } + if (ctx->flags & NVENC_LOSSLESS) { + set_lossless(avctx); + } else if (ctx->rc > 0) { + nvenc_override_rate_control(avctx); + } else { + ctx->encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_VBR; + set_vbr(avctx); } if (avctx->rc_buffer_size > 0) { @@ -633,9 +678,28 @@ static av_cold int nvenc_setup_h264_config(AVCodecContext *avctx) h264->sliceModeData = 1; h264->disableSPSPPS = (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) ? 1 : 0; - h264->repeatSPSPPS = (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) ? 0 : 1; + h264->repeatSPSPPS = (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) ? 0 : 1; + h264->outputAUD = 1; - h264->outputAUD = 1; + if (avctx->refs >= 0) { + /* 0 means "let the hardware decide" */ + h264->maxNumRefFrames = avctx->refs; + } + if (avctx->gop_size >= 0) { + h264->idrPeriod = cc->gopLength; + } + + if (IS_CBR(cc->rcParams.rateControlMode)) { + h264->outputBufferingPeriodSEI = 1; + h264->outputPictureTimingSEI = 1; + } + + if (cc->rcParams.rateControlMode == NV_ENC_PARAMS_RC_2_PASS_QUALITY || + cc->rcParams.rateControlMode == NV_ENC_PARAMS_RC_2_PASS_FRAMESIZE_CAP || + cc->rcParams.rateControlMode == NV_ENC_PARAMS_RC_2_PASS_VBR) { + h264->adaptiveTransformMode = NV_ENC_H264_ADAPTIVE_TRANSFORM_ENABLE; + h264->fmoMode = NV_ENC_H264_FMO_DISABLE; + } if (ctx->flags & NVENC_LOSSLESS) { h264->qpPrimeYZeroTransformBypassFlag = 1; @@ -698,9 +762,21 @@ static av_cold int nvenc_setup_hevc_config(AVCodecContext *avctx) hevc->sliceModeData = 1; hevc->disableSPSPPS = (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) ? 1 : 0; - hevc->repeatSPSPPS = (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) ? 0 : 1; + hevc->repeatSPSPPS = (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) ? 0 : 1; + hevc->outputAUD = 1; - hevc->outputAUD = 1; + if (avctx->refs >= 0) { + /* 0 means "let the hardware decide" */ + hevc->maxNumRefFramesInDPB = avctx->refs; + } + if (avctx->gop_size >= 0) { + hevc->idrPeriod = cc->gopLength; + } + + if (IS_CBR(cc->rcParams.rateControlMode)) { + hevc->outputBufferingPeriodSEI = 1; + hevc->outputPictureTimingSEI = 1; + } /* No other profile is supported in the current SDK version 5 */ cc->profileGUID = NV_ENC_HEVC_PROFILE_MAIN_GUID; @@ -751,15 +827,6 @@ static av_cold int nvenc_setup_encoder(AVCodecContext *avctx) nvenc_map_preset(ctx); - if (ctx->flags & NVENC_ONE_PASS) - ctx->twopass = 0; - if (ctx->flags & NVENC_TWO_PASSES) - ctx->twopass = 1; - - if (ctx->twopass < 0) { - ctx->twopass = (ctx->flags & NVENC_LOWLATENCY) != 0; - } - preset_config.version = NV_ENC_PRESET_CONFIG_VER; preset_config.presetCfg.version = NV_ENC_CONFIG_VER; @@ -811,19 +878,6 @@ static av_cold int nvenc_setup_encoder(AVCodecContext *avctx) ctx->init_encode_params.enableEncodeAsync = 0; ctx->init_encode_params.enablePTD = 1; - if (avctx->refs >= 0) { - /* 0 means "let the hardware decide" */ - switch (avctx->codec->id) { - case AV_CODEC_ID_H264: - ctx->encode_config.encodeCodecConfig.h264Config.maxNumRefFrames = avctx->refs; - break; - case AV_CODEC_ID_H265: - ctx->encode_config.encodeCodecConfig.hevcConfig.maxNumRefFramesInDPB = avctx->refs; - break; - /* Earlier switch/case will return if unknown codec is passed. */ - } - } - if (avctx->gop_size > 0) { if (avctx->max_b_frames >= 0) { /* 0 is intra-only, 1 is I/P only, 2 is one B Frame, 3 two B frames, and so on. */ @@ -831,27 +885,9 @@ static av_cold int nvenc_setup_encoder(AVCodecContext *avctx) } ctx->encode_config.gopLength = avctx->gop_size; - switch (avctx->codec->id) { - case AV_CODEC_ID_H264: - ctx->encode_config.encodeCodecConfig.h264Config.idrPeriod = avctx->gop_size; - break; - case AV_CODEC_ID_H265: - ctx->encode_config.encodeCodecConfig.hevcConfig.idrPeriod = avctx->gop_size; - break; - /* Earlier switch/case will return if unknown codec is passed. */ - } } else if (avctx->gop_size == 0) { ctx->encode_config.frameIntervalP = 0; ctx->encode_config.gopLength = 1; - switch (avctx->codec->id) { - case AV_CODEC_ID_H264: - ctx->encode_config.encodeCodecConfig.h264Config.idrPeriod = 1; - break; - case AV_CODEC_ID_H265: - ctx->encode_config.encodeCodecConfig.hevcConfig.idrPeriod = 1; - break; - /* Earlier switch/case will return if unknown codec is passed. */ - } } /* when there're b frames, set dts offset */ diff --git a/libavcodec/nvenc.h b/libavcodec/nvenc.h index 8007311da1..fd44ebc9af 100644 --- a/libavcodec/nvenc.h +++ b/libavcodec/nvenc.h @@ -170,6 +170,7 @@ typedef struct NvencContext int profile; int level; int tier; + int rc; int cbr; int twopass; int gpu; diff --git a/libavcodec/nvenc_h264.c b/libavcodec/nvenc_h264.c index 57304f1460..438aa2002f 100644 --- a/libavcodec/nvenc_h264.c +++ b/libavcodec/nvenc_h264.c @@ -68,6 +68,14 @@ static const AVOption options[] = { { "5", "", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_LEVEL_H264_5 }, 0, 0, VE, "level" }, { "5.0", "", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_LEVEL_H264_5 }, 0, 0, VE, "level" }, { "5.1", "", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_LEVEL_H264_51 }, 0, 0, VE, "level" }, + { "rc", "Override the preset rate-control", OFFSET(rc), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, VE, "rc" }, + { "constqp", "Constant QP mode", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_CONSTQP }, 0, 0, VE, "rc" }, + { "vbr", "Variable bitrate mode", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_VBR }, 0, 0, VE, "rc" }, + { "cbr", "Constant bitrate mode", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_CBR }, 0, 0, VE, "rc" }, + { "vbr_minqp", "Variable bitrate mode with MinQP", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_VBR_MINQP }, 0, 0, VE, "rc" }, + { "ll_2pass_quality", "Multi-pass optimized for image quality (only for low-latency presets)", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_2_PASS_QUALITY }, 0, 0, VE, "rc" }, + { "ll_2pass_size", "Multi-pass optimized for constant frame size (only for low-latency presets)", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_2_PASS_FRAMESIZE_CAP }, 0, 0, VE, "rc" }, + { "vbr_2pass", "Multi-pass variable bitrate mode", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_2_PASS_VBR }, 0, 0, VE, "rc" }, { "cbr", "Use cbr encoding mode", OFFSET(cbr), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, { "2pass", "Use 2pass encoding mode", OFFSET(twopass), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, VE }, { "gpu", "Selects which NVENC capable GPU to use. First GPU is 0, second is 1, and so on.", OFFSET(gpu), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, VE }, diff --git a/libavcodec/nvenc_hevc.c b/libavcodec/nvenc_hevc.c index 2bd6521c42..f98c60da13 100644 --- a/libavcodec/nvenc_hevc.c +++ b/libavcodec/nvenc_hevc.c @@ -65,6 +65,14 @@ static const AVOption options[] = { { "tier", "Set the encoding tier", OFFSET(tier), AV_OPT_TYPE_INT, { .i64 = NV_ENC_TIER_HEVC_MAIN }, NV_ENC_TIER_HEVC_MAIN, NV_ENC_TIER_HEVC_HIGH, VE, "tier"}, { "main", "", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_TIER_HEVC_MAIN }, 0, 0, VE, "tier" }, { "high", "", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_TIER_HEVC_HIGH }, 0, 0, VE, "tier" }, + { "rc", "Override the preset rate-control", OFFSET(rc), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, VE, "rc" }, + { "constqp", "Constant QP mode", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_CONSTQP }, 0, 0, VE, "rc" }, + { "vbr", "Variable bitrate mode", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_VBR }, 0, 0, VE, "rc" }, + { "cbr", "Constant bitrate mode", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_CBR }, 0, 0, VE, "rc" }, + { "vbr_minqp", "Variable bitrate mode with MinQP", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_VBR_MINQP }, 0, 0, VE, "rc" }, + { "ll_2pass_quality", "Multi-pass optimized for image quality (only for low-latency presets)", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_2_PASS_QUALITY }, 0, 0, VE, "rc" }, + { "ll_2pass_size", "Multi-pass optimized for constant frame size (only for low-latency presets)", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_2_PASS_FRAMESIZE_CAP }, 0, 0, VE, "rc" }, + { "vbr_2pass", "Multi-pass variable bitrate mode", 0, AV_OPT_TYPE_CONST, { .i64 = NV_ENC_PARAMS_RC_2_PASS_VBR }, 0, 0, VE, "rc" }, { "cbr", "Use cbr encoding mode", OFFSET(cbr), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, { "2pass", "Use 2pass encoding mode", OFFSET(twopass), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, VE }, { "gpu", "Selects which NVENC capable GPU to use. First GPU is 0, second is 1, and so on.", OFFSET(gpu), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, VE },