/** * @file src/nvenc/nvenc_base.cpp * @brief Definitions for abstract platform-agnostic base of standalone NVENC encoder. */ // this include #include "nvenc_base.h" #include "nvenc_api.h" // standard includes #include #include #include #include #include #include // Make sure we check backwards compatibility when bumping the Video Codec SDK version // Things to look out for: // - NV_ENC_*_VER definitions where the value inside NVENCAPI_STRUCT_VERSION() was increased // - Incompatible struct changes in nvEncodeAPI.h (fields removed, semantics changed, etc.) // - Test both old and new drivers with all supported codecs #if !((NVENCAPI_MAJOR_VERSION == 12 && NVENCAPI_MINOR_VERSION <= 2) || (NVENCAPI_MAJOR_VERSION == 13 && NVENCAPI_MINOR_VERSION == 0)) #error Check and update NVENC code for backwards compatibility! #endif // local includes #include "src/config.h" #include "src/logging.h" #include "src/utility.h" namespace { GUID quality_preset_guid_from_number(unsigned number) { if (number > 7) { number = 7; } switch (number) { case 1: default: return NV_ENC_PRESET_P1_GUID; case 2: return NV_ENC_PRESET_P2_GUID; case 3: return NV_ENC_PRESET_P3_GUID; case 4: return NV_ENC_PRESET_P4_GUID; case 5: return NV_ENC_PRESET_P5_GUID; case 6: return NV_ENC_PRESET_P6_GUID; case 7: return NV_ENC_PRESET_P7_GUID; } }; bool equal_guids(const GUID &guid1, const GUID &guid2) { return std::memcmp(&guid1, &guid2, sizeof(GUID)) == 0; } auto quality_preset_string_from_guid(const GUID &guid) { if (equal_guids(guid, NV_ENC_PRESET_P1_GUID)) { return "P1"; } if (equal_guids(guid, NV_ENC_PRESET_P2_GUID)) { return "P2"; } if (equal_guids(guid, NV_ENC_PRESET_P3_GUID)) { return "P3"; } if (equal_guids(guid, NV_ENC_PRESET_P4_GUID)) { return "P4"; } if (equal_guids(guid, NV_ENC_PRESET_P5_GUID)) { return "P5"; } if (equal_guids(guid, NV_ENC_PRESET_P6_GUID)) { return "P6"; } if (equal_guids(guid, NV_ENC_PRESET_P7_GUID)) { return "P7"; } return "Unknown"; } const char *codec_name_from_video_format(int video_format) { switch (video_format) { case 0: return "H.264"; case 1: return "HEVC"; case 2: return "AV1"; default: return "Unknown"; } } std::optional encode_guid_from_video_format(int video_format) { switch (video_format) { case 0: return NV_ENC_CODEC_H264_GUID; case 1: return NV_ENC_CODEC_HEVC_GUID; case 2: return NV_ENC_CODEC_AV1_GUID; default: return std::nullopt; } } } // namespace namespace nvenc { nvenc_base::nvenc_base(NV_ENC_DEVICE_TYPE device_type): device_type(device_type) { } nvenc_base::~nvenc_base() { // Use destroy_encoder() instead } bool nvenc_base::create_encoder( const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format, const SS_HDR_METADATA *initial_hdr_metadata ) { context_guard_t context_guard {*this}; if (!context_guard) { BOOST_LOG(error) << "NvEnc: couldn't enter the platform device context while creating the encoder"; return false; } const auto encode_guid = encode_guid_from_video_format(client_config.videoFormat); if (!encode_guid) { BOOST_LOG(error) << "NvEnc: unknown video format " << client_config.videoFormat; return false; } const auto api_candidates = api::codec_api_candidates(client_config.videoFormat); if (api_candidates.empty()) { BOOST_LOG(error) << "NvEnc: no API candidates available for video format " << client_config.videoFormat; return false; } if (encoder) { if (!destroy_encoder()) { BOOST_LOG(error) << "NvEnc: couldn't destroy the previous encoder before reinitialization"; return false; } } if (initial_hdr_metadata) { set_hdr_metadata(*initial_hdr_metadata); } else { hdr_metadata_valid = false; hdr_metadata = {}; } auto fail_guard = util::fail_guard([this] { destroy_encoder(); }); encoder_params.width = client_config.width; encoder_params.height = client_config.height; encoder_params.buffer_format = buffer_format; encoder_params.video_format = client_config.videoFormat; encoder_params.rfi = true; selected_api_version = 0; NV_ENC_INITIALIZE_PARAMS init_params {}; NV_ENC_PRESET_CONFIG preset_config {}; bool have_preset_config = false; auto destroy_api_attempt = [&]() { if (encoder) { if (nvenc_failed(nvenc->nvEncDestroyEncoder(encoder))) { BOOST_LOG(error) << "NvEnc: couldn't destroy the rejected API session: " << last_nvenc_error_string; return false; } encoder = nullptr; } selected_api_version = 0; return true; }; auto retry_with_next_api = [&](std::string_view operation, uint32_t api_version) { BOOST_LOG(debug) << "NvEnc: "sv << operation << " rejected API "sv << api::version_string(api_version); return destroy_api_attempt(); }; auto buffer_is_10bit = [&]() { return buffer_format == NV_ENC_BUFFER_FORMAT_YUV420_10BIT || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT; }; auto buffer_is_yuv444 = [&]() { return buffer_format == NV_ENC_BUFFER_FORMAT_AYUV || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT; }; for (const auto api_version : api_candidates) { if (!init_library(api_version)) { if (last_nvenc_status == NV_ENC_ERR_INVALID_VERSION) { continue; } return false; } NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = {api::open_encode_session_ex_params_version(api_version)}; session_params.device = device; session_params.deviceType = device_type; session_params.apiVersion = api_version; if (!nvenc_failed(nvenc->nvEncOpenEncodeSessionEx(&session_params, &encoder))) { selected_api_version = api_version; } else { if (last_nvenc_status != NV_ENC_ERR_INVALID_VERSION) { BOOST_LOG(error) << "NvEnc: NvEncOpenEncodeSessionEx() failed for API " << api::version_string(api_version) << ": " << last_nvenc_error_string; return false; } if (!retry_with_next_api("NvEncOpenEncodeSessionEx()"sv, api_version)) { return false; } continue; } uint32_t encode_guid_count = 0; if (nvenc_failed(nvenc->nvEncGetEncodeGUIDCount(encoder, &encode_guid_count))) { if (last_nvenc_status == NV_ENC_ERR_INVALID_VERSION) { if (!retry_with_next_api("NvEncGetEncodeGUIDCount()"sv, api_version)) { return false; } continue; } BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDCount() failed: " << last_nvenc_error_string; return false; }; std::vector encode_guids(encode_guid_count); if (nvenc_failed(nvenc->nvEncGetEncodeGUIDs(encoder, encode_guids.data(), (uint32_t) encode_guids.size(), &encode_guid_count))) { if (last_nvenc_status == NV_ENC_ERR_INVALID_VERSION) { if (!retry_with_next_api("NvEncGetEncodeGUIDs()"sv, api_version)) { return false; } continue; } BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDs() failed: " << last_nvenc_error_string; return false; } init_params = {api::initialize_params_version(selected_api_version)}; init_params.encodeGUID = *encode_guid; { auto search_predicate = [&](const GUID &guid) { return equal_guids(init_params.encodeGUID, guid); }; if (std::find_if(encode_guids.begin(), encode_guids.end(), search_predicate) == encode_guids.end()) { BOOST_LOG(error) << "NvEnc: encoding format is not supported by the gpu"; return false; } } auto get_encoder_cap = [&](NV_ENC_CAPS cap) -> std::optional { NV_ENC_CAPS_PARAM param = {api::caps_param_version(selected_api_version), cap}; int value = 0; if (nvenc_failed(nvenc->nvEncGetEncodeCaps(encoder, init_params.encodeGUID, ¶m, &value))) { return std::nullopt; } return value; }; auto require_encoder_cap = [&](NV_ENC_CAPS cap, std::string_view operation) -> std::optional { auto value = get_encoder_cap(cap); if (value) { return value; } if (last_nvenc_status == NV_ENC_ERR_INVALID_VERSION) { retry_with_next_api(operation, api_version); return std::nullopt; } BOOST_LOG(error) << "NvEnc: "sv << operation << " failed: "sv << last_nvenc_error_string; return std::nullopt; }; auto supported_width = require_encoder_cap(NV_ENC_CAPS_WIDTH_MAX, "NvEncGetEncodeCaps(width)"sv); if (!supported_width) { if (!encoder) { continue; } return false; } auto supported_height = require_encoder_cap(NV_ENC_CAPS_HEIGHT_MAX, "NvEncGetEncodeCaps(height)"sv); if (!supported_height) { if (!encoder) { continue; } return false; } if (encoder_params.width > *supported_width || encoder_params.height > *supported_height) { BOOST_LOG(error) << "NvEnc: gpu max encode resolution " << *supported_width << "x" << *supported_height << ", requested " << encoder_params.width << "x" << encoder_params.height; return false; } if (buffer_is_10bit()) { auto supports_10bit = require_encoder_cap(NV_ENC_CAPS_SUPPORT_10BIT_ENCODE, "NvEncGetEncodeCaps(10-bit)"sv); if (!supports_10bit) { if (!encoder) { continue; } return false; } if (!*supports_10bit) { BOOST_LOG(error) << "NvEnc: gpu doesn't support 10-bit encode"; return false; } } if (buffer_is_yuv444()) { auto supports_yuv444 = require_encoder_cap(NV_ENC_CAPS_SUPPORT_YUV444_ENCODE, "NvEncGetEncodeCaps(YUV444)"sv); if (!supports_yuv444) { if (!encoder) { continue; } return false; } if (!*supports_yuv444) { BOOST_LOG(error) << "NvEnc: gpu doesn't support YUV444 encode"; return false; } } if (async_event_handle) { auto supports_async = require_encoder_cap(NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT, "NvEncGetEncodeCaps(async)"sv); if (!supports_async) { if (!encoder) { continue; } return false; } if (!*supports_async) { BOOST_LOG(warning) << "NvEnc: gpu doesn't support async encode"; async_event_handle = nullptr; } } auto supports_rfi = require_encoder_cap(NV_ENC_CAPS_SUPPORT_REF_PIC_INVALIDATION, "NvEncGetEncodeCaps(rfi)"sv); if (!supports_rfi) { if (!encoder) { continue; } return false; } encoder_params.rfi = *supports_rfi; auto supports_weighted_prediction = require_encoder_cap(NV_ENC_CAPS_SUPPORT_WEIGHTED_PREDICTION, "NvEncGetEncodeCaps(weighted prediction)"sv); if (!supports_weighted_prediction) { if (!encoder) { continue; } return false; } init_params.presetGUID = quality_preset_guid_from_number(config.quality_preset); init_params.tuningInfo = NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY; init_params.enablePTD = 1; init_params.enableEncodeAsync = async_event_handle ? 1 : 0; init_params.enableWeightedPrediction = config.weighted_prediction && *supports_weighted_prediction; init_params.encodeWidth = encoder_params.width; init_params.darWidth = encoder_params.width; init_params.encodeHeight = encoder_params.height; init_params.darHeight = encoder_params.height; init_params.frameRateNum = client_config.framerate; init_params.frameRateDen = 1; if (client_config.framerateX100 > 0) { AVRational fps = video::framerateX100_to_rational(client_config.framerateX100); init_params.frameRateNum = fps.num; init_params.frameRateDen = fps.den; } preset_config = {}; preset_config.version = api::preset_config_version(selected_api_version); preset_config.presetCfg.version = api::config_version(selected_api_version); BOOST_LOG(debug) << "NvEnc: querying preset config for API " << api::version_string(api_version) << " (preset_config_ver=0x" << std::hex << api::preset_config_version(selected_api_version) << " config_ver=0x" << api::config_version(selected_api_version) << std::dec << ")"; if (nvenc_failed(nvenc->nvEncGetEncodePresetConfigEx(encoder, init_params.encodeGUID, init_params.presetGUID, init_params.tuningInfo, &preset_config))) { const auto preset_config_ex_status = last_nvenc_status; const auto preset_config_ex_error = last_nvenc_error_string; BOOST_LOG(debug) << "NvEnc: NvEncGetEncodePresetConfigEx() returned " << preset_config_ex_error << " for API " << api::version_string(api_version); if (client_config.videoFormat != 2 && (preset_config_ex_status == NV_ENC_ERR_INVALID_VERSION || preset_config_ex_status == NV_ENC_ERR_UNSUPPORTED_PARAM)) { preset_config = {}; preset_config.version = api::preset_config_version(selected_api_version); preset_config.presetCfg.version = api::config_version(selected_api_version); if (!nvenc_failed(nvenc->nvEncGetEncodePresetConfig(encoder, init_params.encodeGUID, init_params.presetGUID, &preset_config))) { BOOST_LOG(debug) << "NvEnc: falling back to NvEncGetEncodePresetConfig() for API " << api::version_string(api_version); have_preset_config = true; break; } BOOST_LOG(debug) << "NvEnc: NvEncGetEncodePresetConfig() returned " << last_nvenc_error_string << " for API " << api::version_string(api_version); if (last_nvenc_status == NV_ENC_ERR_INVALID_VERSION || last_nvenc_status == NV_ENC_ERR_UNSUPPORTED_PARAM) { BOOST_LOG(warning) << "NvEnc: preset config queries were rejected for API " << api::version_string(api_version) << "; will initialize with presetGUID + tuningInfo defaults"; break; } BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfigEx() failed: " << preset_config_ex_error; BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfig() failed: " << last_nvenc_error_string; return false; } if (preset_config_ex_status == NV_ENC_ERR_INVALID_VERSION || preset_config_ex_status == NV_ENC_ERR_UNSUPPORTED_PARAM) { BOOST_LOG(warning) << "NvEnc: NvEncGetEncodePresetConfigEx() was rejected for API " << api::version_string(api_version) << "; will initialize with presetGUID + tuningInfo defaults"; break; } BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfigEx() failed: " << preset_config_ex_error; return false; } have_preset_config = true; break; } if (!encoder || !selected_api_version) { BOOST_LOG(error) << "NvEnc: no compatible driver API found for " << codec_name_from_video_format(client_config.videoFormat); return false; } auto get_encoder_cap = [&](NV_ENC_CAPS cap) { NV_ENC_CAPS_PARAM param = {api::caps_param_version(selected_api_version), cap}; int value = 0; if (nvenc_failed(nvenc->nvEncGetEncodeCaps(encoder, init_params.encodeGUID, ¶m, &value))) { BOOST_LOG(warning) << "NvEnc: NvEncGetEncodeCaps() failed after API selection: " << last_nvenc_error_string; return 0; } return value; }; const auto encoder_engine_count = get_encoder_cap(NV_ENC_CAPS_NUM_ENCODER_ENGINES); NV_ENC_CONFIG enc_config {}; if (have_preset_config) { enc_config = preset_config.presetCfg; } else { enc_config.version = api::config_version(selected_api_version); } enc_config.profileGUID = NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID; enc_config.gopLength = NVENC_INFINITE_GOPLENGTH; enc_config.frameIntervalP = 1; enc_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR; enc_config.rcParams.zeroReorderDelay = 1; enc_config.rcParams.enableLookahead = 0; enc_config.rcParams.lowDelayKeyFrameScale = 1; enc_config.rcParams.multiPass = config.two_pass == nvenc_two_pass::quarter_resolution ? NV_ENC_TWO_PASS_QUARTER_RESOLUTION : config.two_pass == nvenc_two_pass::full_resolution ? NV_ENC_TWO_PASS_FULL_RESOLUTION : NV_ENC_MULTI_PASS_DISABLED; enc_config.rcParams.enableAQ = config.adaptive_quantization; enc_config.rcParams.enableTemporalAQ = config.temporal_aq && get_encoder_cap(NV_ENC_CAPS_SUPPORT_TEMPORAL_AQ); enc_config.rcParams.averageBitRate = client_config.bitrate * 1000; if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) { enc_config.rcParams.vbvBufferSize = client_config.bitrate * 1000 / client_config.framerate; if (config.vbv_percentage_increase > 0) { enc_config.rcParams.vbvBufferSize += enc_config.rcParams.vbvBufferSize * config.vbv_percentage_increase / 100; } } auto set_h264_hevc_common_format_config = [&](auto &format_config) { format_config.repeatSPSPPS = 1; format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH; format_config.sliceMode = 3; format_config.sliceModeData = client_config.slicesPerFrame; format_config.chromaFormatIDC = buffer_is_yuv444() ? 3 : 1; format_config.enableFillerDataInsertion = config.insert_filler_data; }; auto set_ref_frames = [&](uint32_t &ref_frames_option, NV_ENC_NUM_REF_FRAMES &L0_option, uint32_t ref_frames_default) { if (client_config.numRefFrames > 0) { ref_frames_option = client_config.numRefFrames; } else { ref_frames_option = ref_frames_default; } if (ref_frames_option > 0 && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES)) { ref_frames_option = 1; encoder_params.rfi = false; } encoder_params.ref_frames_in_dpb = ref_frames_option; // This limits ref frames any frame can use to 1, but allows larger buffer size for fallback if some frames are invalidated through rfi L0_option = NV_ENC_NUM_REF_FRAMES_1; }; auto set_minqp_if_enabled = [&](int value) { if (config.enable_min_qp) { enc_config.rcParams.enableMinQP = 1; enc_config.rcParams.minQP.qpInterP = value; enc_config.rcParams.minQP.qpIntra = value; } }; auto fill_h264_hevc_vui = [&](auto &vui_config) { vui_config.videoSignalTypePresentFlag = 1; vui_config.videoFormat = NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED; vui_config.videoFullRangeFlag = colorspace.full_range; vui_config.colourDescriptionPresentFlag = 1; vui_config.colourPrimaries = colorspace.primaries; vui_config.transferCharacteristics = colorspace.tranfer_function; vui_config.colourMatrix = colorspace.matrix; vui_config.chromaSampleLocationFlag = buffer_is_yuv444() ? 0 : 1; vui_config.chromaSampleLocationTop = 0; vui_config.chromaSampleLocationBot = 0; // This is critical for low decoding latency on certain devices vui_config.bitstreamRestrictionFlag = 1; }; const auto set_hevc_10bit_format = [&](auto &format_config) { #if NVENCAPI_MAJOR_VERSION > 12 || (NVENCAPI_MAJOR_VERSION == 12 && NVENCAPI_MINOR_VERSION >= 2) if (api::supports_separate_bit_depth_fields(selected_api_version)) { format_config.inputBitDepth = NV_ENC_BIT_DEPTH_10; format_config.outputBitDepth = NV_ENC_BIT_DEPTH_10; } else { // SDK 12.2 removed the old HEVC bit-depth field names, but the v11/v12.1 // layout still occupies these reserved bits. format_config.reserved3 = 2; } #else format_config.pixelBitDepthMinus8 = 2; #endif }; const auto set_av1_10bit_format = [&](auto &format_config) { #if NVENCAPI_MAJOR_VERSION > 12 || (NVENCAPI_MAJOR_VERSION == 12 && NVENCAPI_MINOR_VERSION >= 2) if (api::supports_separate_bit_depth_fields(selected_api_version)) { format_config.inputBitDepth = NV_ENC_BIT_DEPTH_10; format_config.outputBitDepth = NV_ENC_BIT_DEPTH_10; } else { // SDK 12.2 moved AV1 bit depth out of the packed bitfield, but API // 12.1 drivers still read the original input/output bit-depth bits. #if NVENCAPI_MAJOR_VERSION > 12 format_config.enableTemporalSVC = 1; format_config.reserved4 = 1; #else format_config.reserved4 = 18; #endif } #else format_config.inputPixelBitDepthMinus8 = 2; format_config.pixelBitDepthMinus8 = 2; #endif }; switch (client_config.videoFormat) { case 0: { // H.264 enc_config.profileGUID = buffer_is_yuv444() ? NV_ENC_H264_PROFILE_HIGH_444_GUID : NV_ENC_H264_PROFILE_HIGH_GUID; auto &format_config = enc_config.encodeCodecConfig.h264Config; set_h264_hevc_common_format_config(format_config); if (config.h264_cavlc || !get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC)) { format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC; } else { format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC; } #if NVENCAPI_MAJOR_VERSION > 12 || (NVENCAPI_MAJOR_VERSION == 12 && NVENCAPI_MINOR_VERSION >= 2) if (api::supports_separate_bit_depth_fields(selected_api_version)) { format_config.inputBitDepth = buffer_is_10bit() ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8; format_config.outputBitDepth = buffer_is_10bit() ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8; } #endif set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5); set_minqp_if_enabled(config.min_qp_h264); fill_h264_hevc_vui(format_config.h264VUIParameters); if (client_config.enableIntraRefresh == 1) { if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) { format_config.enableIntraRefresh = 1; format_config.intraRefreshPeriod = 300; format_config.intraRefreshCnt = 299; format_config.outputRecoveryPointSEI = 1; if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) { format_config.singleSliceIntraRefresh = 1; } else { BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported"; } } else { BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh"; } } break; } case 1: { // HEVC auto &format_config = enc_config.encodeCodecConfig.hevcConfig; set_h264_hevc_common_format_config(format_config); if (buffer_is_10bit()) { set_hevc_10bit_format(format_config); } #if NVENCAPI_MAJOR_VERSION > 12 || (NVENCAPI_MAJOR_VERSION == 12 && NVENCAPI_MINOR_VERSION >= 2) else if (api::supports_separate_bit_depth_fields(selected_api_version)) { format_config.inputBitDepth = NV_ENC_BIT_DEPTH_8; format_config.outputBitDepth = NV_ENC_BIT_DEPTH_8; } #endif set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, 5); set_minqp_if_enabled(config.min_qp_hevc); fill_h264_hevc_vui(format_config.hevcVUIParameters); #if NVENCAPI_MAJOR_VERSION >= 13 if (colorspace.tranfer_function == NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084 && hdr_metadata_valid && api::supports_hdr10_metadata(selected_api_version)) { format_config.outputMaxCll = hdr_metadata.maxContentLightLevel != 0 || hdr_metadata.maxFrameAverageLightLevel != 0; format_config.outputMasteringDisplay = hdr_metadata.maxDisplayLuminance != 0; } #endif if (client_config.enableIntraRefresh == 1) { if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) { format_config.enableIntraRefresh = 1; format_config.intraRefreshPeriod = 300; format_config.intraRefreshCnt = 299; format_config.outputRecoveryPointSEI = 1; if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) { format_config.singleSliceIntraRefresh = 1; } else { BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported"; } } else { BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh"; } } break; } case 2: { // AV1 enc_config.profileGUID = NV_ENC_AV1_PROFILE_MAIN_GUID; auto &format_config = enc_config.encodeCodecConfig.av1Config; format_config.level = NV_ENC_LEVEL_AV1_AUTOSELECT; format_config.repeatSeqHdr = 1; format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH; format_config.chromaFormatIDC = buffer_is_yuv444() ? 3 : 1; format_config.enableBitstreamPadding = config.insert_filler_data; if (buffer_is_10bit()) { set_av1_10bit_format(format_config); } #if NVENCAPI_MAJOR_VERSION > 12 || (NVENCAPI_MAJOR_VERSION == 12 && NVENCAPI_MINOR_VERSION >= 2) else if (api::supports_separate_bit_depth_fields(selected_api_version)) { format_config.inputBitDepth = NV_ENC_BIT_DEPTH_8; format_config.outputBitDepth = NV_ENC_BIT_DEPTH_8; } #endif format_config.colorPrimaries = colorspace.primaries; format_config.transferCharacteristics = colorspace.tranfer_function; format_config.matrixCoefficients = colorspace.matrix; format_config.colorRange = colorspace.full_range; format_config.chromaSamplePosition = buffer_is_yuv444() ? 0 : 1; #if NVENCAPI_MAJOR_VERSION >= 13 if (colorspace.tranfer_function == NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084 && hdr_metadata_valid && api::supports_hdr10_metadata(selected_api_version)) { format_config.outputMaxCll = hdr_metadata.maxContentLightLevel != 0 || hdr_metadata.maxFrameAverageLightLevel != 0; format_config.outputMasteringDisplay = hdr_metadata.maxDisplayLuminance != 0; } #endif set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numFwdRefs, 8); set_minqp_if_enabled(config.min_qp_av1); if (client_config.slicesPerFrame > 1) { // NVENC only supports slice counts that are powers of two, so we'll pick powers of two // with bias to rows due to hopefully more similar macroblocks with a row vs a column. format_config.numTileRows = std::pow(2, std::ceil(std::log2(client_config.slicesPerFrame) / 2)); format_config.numTileColumns = std::pow(2, std::floor(std::log2(client_config.slicesPerFrame) / 2)); } break; } } std::string split_frame_status = "split-frame=n/a"; if (client_config.videoFormat == 1 || client_config.videoFormat == 2) { if (!api::supports_implicit_split_frame(selected_api_version)) { split_frame_status = "split-frame=disabled(api<12.1)"; } else if (client_config.videoFormat == 1 && init_params.enableWeightedPrediction) { split_frame_status = "split-frame=disabled(weighted-prediction)"; } else { #if NVENCAPI_MAJOR_VERSION > 12 || (NVENCAPI_MAJOR_VERSION == 12 && NVENCAPI_MINOR_VERSION >= 1) switch (config.split_encode_mode) { case nvenc::split_encode_mode::enabled: init_params.splitEncodeMode = NV_ENC_SPLIT_AUTO_FORCED_MODE; split_frame_status = "split-frame=forced"; break; case nvenc::split_encode_mode::disabled: init_params.splitEncodeMode = NV_ENC_SPLIT_DISABLE_MODE; split_frame_status = "split-frame=disabled"; break; case nvenc::split_encode_mode::auto_mode: default: init_params.splitEncodeMode = NV_ENC_SPLIT_AUTO_MODE; split_frame_status = "split-frame=auto"; break; } #else split_frame_status = "split-frame=disabled(header<12.1)"; #endif } } init_params.encodeConfig = &enc_config; if (nvenc_failed(nvenc->nvEncInitializeEncoder(encoder, &init_params))) { auto init_error = last_nvenc_error_string; auto init_status = last_nvenc_status; // Log detailed error string from the driver if available if (nvenc->nvEncGetLastErrorString) { auto driver_msg = nvenc->nvEncGetLastErrorString(encoder); if (driver_msg && driver_msg[0]) { BOOST_LOG(error) << "NvEnc: driver error detail: " << driver_msg; } } const bool hdr_metadata_requires_explicit_config = colorspace.tranfer_function == NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084 && hdr_metadata_valid && api::supports_hdr10_metadata(selected_api_version); if (!have_preset_config && init_status == NV_ENC_ERR_INVALID_PARAM && client_config.videoFormat != 2 && !hdr_metadata_requires_explicit_config) { // Without preset config, our manual config may have missed required fields. // Try once more with encodeConfig=nullptr so the driver uses presetGUID+tuningInfo defaults. BOOST_LOG(debug) << "NvEnc: explicit config rejected (" << init_error << "); retrying NvEncInitializeEncoder() with driver defaults"; // Must destroy and re-open the session since NvEncInitializeEncoder can only be called once if (nvenc_failed(nvenc->nvEncDestroyEncoder(encoder))) { BOOST_LOG(error) << "NvEnc: couldn't destroy the rejected explicit-config session: " << last_nvenc_error_string; return false; } encoder = nullptr; NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = {api::open_encode_session_ex_params_version(selected_api_version)}; session_params.device = device; session_params.deviceType = device_type; session_params.apiVersion = selected_api_version; if (nvenc_failed(nvenc->nvEncOpenEncodeSessionEx(&session_params, &encoder))) { BOOST_LOG(error) << "NvEnc: NvEncInitializeEncoder() failed: " << init_error; return false; } init_params.encodeConfig = nullptr; if (nvenc_failed(nvenc->nvEncInitializeEncoder(encoder, &init_params))) { BOOST_LOG(error) << "NvEnc: NvEncInitializeEncoder() failed with both explicit config (" << init_error << ") and driver defaults (" << last_nvenc_error_string << ")"; return false; } BOOST_LOG(info) << "NvEnc: initialized with driver defaults (preset config was unavailable)"; } else if (!have_preset_config && init_status == NV_ENC_ERR_INVALID_PARAM && hdr_metadata_requires_explicit_config) { BOOST_LOG(error) << "NvEnc: NvEncInitializeEncoder() failed: " << init_error << " (driver-default retry disabled because it would drop HDR10 metadata)"; return false; } else if (!have_preset_config && init_status == NV_ENC_ERR_INVALID_PARAM && client_config.videoFormat == 2) { BOOST_LOG(error) << "NvEnc: NvEncInitializeEncoder() failed: " << init_error << " (AV1 preset fallback unavailable; skipping driver-default retry)"; return false; } else { BOOST_LOG(error) << "NvEnc: NvEncInitializeEncoder() failed: " << init_error; return false; } } // Preserve init params + config so set_bitrate() can reconfigure the live encoder later. // encodeConfig must point at our own storage that outlives this function. saved_init_params = init_params; current_enc_config = enc_config; saved_init_params.encodeConfig = ¤t_enc_config; if (async_event_handle) { NV_ENC_EVENT_PARAMS event_params = {api::event_params_version(selected_api_version)}; event_params.completionEvent = async_event_handle; if (nvenc_failed(nvenc->nvEncRegisterAsyncEvent(encoder, &event_params))) { BOOST_LOG(error) << "NvEnc: NvEncRegisterAsyncEvent() failed: " << last_nvenc_error_string; return false; } } NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer = {api::create_bitstream_buffer_version(selected_api_version)}; if (nvenc_failed(nvenc->nvEncCreateBitstreamBuffer(encoder, &create_bitstream_buffer))) { BOOST_LOG(error) << "NvEnc: NvEncCreateBitstreamBuffer() failed: " << last_nvenc_error_string; return false; } output_bitstream = create_bitstream_buffer.bitstreamBuffer; if (!create_and_register_input_buffer()) { return false; } { auto f = stat_trackers::two_digits_after_decimal(); BOOST_LOG(debug) << "NvEnc: requested encoded frame size " << f % (client_config.bitrate / 8. / client_config.framerate) << " kB"; } { std::string extra; extra += std::format(" api={}", api::version_string(selected_api_version)); if (init_params.enableEncodeAsync) { extra += " async"; } if (buffer_is_yuv444()) { extra += " yuv444"; } if (buffer_is_10bit()) { extra += " 10-bit"; } if (enc_config.rcParams.multiPass != NV_ENC_MULTI_PASS_DISABLED) { extra += " two-pass"; } if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) { extra += std::format(" vbv+{}", config.vbv_percentage_increase); } if (encoder_params.rfi) { extra += " rfi"; } if (init_params.enableWeightedPrediction) { extra += " weighted-prediction"; } if (enc_config.rcParams.enableAQ) { extra += " spatial-aq"; } if (enc_config.rcParams.enableTemporalAQ) { extra += " temporal-aq"; } if (enc_config.rcParams.enableMinQP) { extra += std::format(" qpmin={}", enc_config.rcParams.minQP.qpInterP); } if (config.insert_filler_data) { extra += " filler-data"; } if (encoder_engine_count > 0) { extra += std::format(" engines={}", encoder_engine_count); } extra += " " + split_frame_status; BOOST_LOG(info) << "NvEnc: created encoder " << codec_name_from_video_format(client_config.videoFormat) << " " << quality_preset_string_from_guid(init_params.presetGUID) << extra; } encoder_state = {}; fail_guard.disable(); return true; } bool nvenc_base::destroy_encoder() { context_guard_t context_guard {*this}; if (!context_guard) { BOOST_LOG(error) << "NvEnc: couldn't enter the platform device context while destroying the encoder"; return false; } if (output_bitstream) { if (nvenc_failed(nvenc->nvEncDestroyBitstreamBuffer(encoder, output_bitstream))) { BOOST_LOG(error) << "NvEnc: NvEncDestroyBitstreamBuffer() failed: " << last_nvenc_error_string; } output_bitstream = nullptr; } if (encoder && async_event_handle) { NV_ENC_EVENT_PARAMS event_params = {api::event_params_version(selected_api_version)}; event_params.completionEvent = async_event_handle; if (nvenc_failed(nvenc->nvEncUnregisterAsyncEvent(encoder, &event_params))) { BOOST_LOG(error) << "NvEnc: NvEncUnregisterAsyncEvent() failed: " << last_nvenc_error_string; } } if (registered_input_buffer) { if (nvenc_failed(nvenc->nvEncUnregisterResource(encoder, registered_input_buffer))) { BOOST_LOG(error) << "NvEnc: NvEncUnregisterResource() failed: " << last_nvenc_error_string; } registered_input_buffer = nullptr; } if (encoder) { if (nvenc_failed(nvenc->nvEncDestroyEncoder(encoder))) { BOOST_LOG(error) << "NvEnc: NvEncDestroyEncoder() failed: " << last_nvenc_error_string; if (!preserve_encoder_on_destroy_failure()) { encoder = nullptr; } return false; } encoder = nullptr; } encoder_state = {}; encoder_params = {}; hdr_metadata_valid = false; hdr_metadata = {}; selected_api_version = 0; return true; } void nvenc_base::set_hdr_metadata(const SS_HDR_METADATA &metadata) { hdr_metadata = metadata; hdr_metadata_valid = true; BOOST_LOG(info) << "NvEnc: HDR metadata set (mastering peak " << metadata.maxDisplayLuminance << " nits, MaxCLL " << metadata.maxContentLightLevel << " nits, MaxFALL " << metadata.maxFrameAverageLightLevel << " nits)"; } nvenc_encoded_frame nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) { const auto encode_started = std::chrono::steady_clock::now(); auto latency_guard = util::fail_guard([&] { encoder_state.encode_latency_logger.collect_and_log( std::chrono::duration(std::chrono::steady_clock::now() - encode_started).count() ); }); context_guard_t context_guard {*this}; if (!context_guard) { BOOST_LOG(error) << "NvEnc: couldn't enter the platform device context while encoding"; return {}; } if (!encoder) { return {}; } assert(registered_input_buffer); assert(output_bitstream); if (!synchronize_input_buffer()) { BOOST_LOG(error) << "NvEnc: failed to synchronize input buffer"; return {}; } NV_ENC_MAP_INPUT_RESOURCE mapped_input_buffer = {api::map_input_resource_version(selected_api_version)}; mapped_input_buffer.registeredResource = registered_input_buffer; if (nvenc_failed(nvenc->nvEncMapInputResource(encoder, &mapped_input_buffer))) { BOOST_LOG(error) << "NvEnc: NvEncMapInputResource() failed: " << last_nvenc_error_string; return {}; } auto unmap_guard = util::fail_guard([&] { if (nvenc_failed(nvenc->nvEncUnmapInputResource(encoder, mapped_input_buffer.mappedResource))) { BOOST_LOG(error) << "NvEnc: NvEncUnmapInputResource() failed: " << last_nvenc_error_string; } }); NV_ENC_PIC_PARAMS pic_params = {api::pic_params_version(selected_api_version)}; pic_params.inputWidth = encoder_params.width; pic_params.inputHeight = encoder_params.height; pic_params.encodePicFlags = force_idr ? NV_ENC_PIC_FLAG_FORCEIDR : 0; pic_params.inputTimeStamp = frame_index; pic_params.pictureStruct = NV_ENC_PIC_STRUCT_FRAME; pic_params.inputBuffer = mapped_input_buffer.mappedResource; pic_params.bufferFmt = mapped_input_buffer.mappedBufferFmt; pic_params.outputBitstream = output_bitstream; pic_params.completionEvent = async_event_handle; #if NVENCAPI_MAJOR_VERSION >= 13 CONTENT_LIGHT_LEVEL content_light_level {}; MASTERING_DISPLAY_INFO mastering_display {}; if (hdr_metadata_valid && api::supports_hdr10_metadata(selected_api_version)) { content_light_level.maxContentLightLevel = hdr_metadata.maxContentLightLevel; content_light_level.maxPicAverageLightLevel = hdr_metadata.maxFrameAverageLightLevel; const auto copy_hevc_chroma = [](CHROMA_POINTS &destination, const auto &source) { destination.x = source.x; destination.y = source.y; }; const auto copy_av1_chroma = [](CHROMA_POINTS &destination, const auto &source) { destination.x = static_cast(std::clamp(std::lround(source.x * (65536.0 / 50000.0)), 0l, 65535l)); destination.y = static_cast(std::clamp(std::lround(source.y * (65536.0 / 50000.0)), 0l, 65535l)); }; if (encoder_params.video_format == 1) { copy_hevc_chroma(mastering_display.r, hdr_metadata.displayPrimaries[0]); copy_hevc_chroma(mastering_display.g, hdr_metadata.displayPrimaries[1]); copy_hevc_chroma(mastering_display.b, hdr_metadata.displayPrimaries[2]); copy_hevc_chroma(mastering_display.whitePoint, hdr_metadata.whitePoint); mastering_display.maxLuma = static_cast(hdr_metadata.maxDisplayLuminance) * 10000u; mastering_display.minLuma = hdr_metadata.minDisplayLuminance; if (hdr_metadata.maxContentLightLevel != 0 || hdr_metadata.maxFrameAverageLightLevel != 0) { pic_params.codecPicParams.hevcPicParams.pMaxCll = &content_light_level; } if (hdr_metadata.maxDisplayLuminance != 0) { pic_params.codecPicParams.hevcPicParams.pMasteringDisplay = &mastering_display; } } else if (encoder_params.video_format == 2) { copy_av1_chroma(mastering_display.r, hdr_metadata.displayPrimaries[0]); copy_av1_chroma(mastering_display.g, hdr_metadata.displayPrimaries[1]); copy_av1_chroma(mastering_display.b, hdr_metadata.displayPrimaries[2]); copy_av1_chroma(mastering_display.whitePoint, hdr_metadata.whitePoint); mastering_display.maxLuma = static_cast(hdr_metadata.maxDisplayLuminance) * 256u; mastering_display.minLuma = static_cast( std::lround(static_cast(hdr_metadata.minDisplayLuminance) * (16384.0 / 10000.0)) ); if (hdr_metadata.maxContentLightLevel != 0 || hdr_metadata.maxFrameAverageLightLevel != 0) { pic_params.codecPicParams.av1PicParams.pMaxCll = &content_light_level; } if (hdr_metadata.maxDisplayLuminance != 0) { pic_params.codecPicParams.av1PicParams.pMasteringDisplay = &mastering_display; } } } #endif if (nvenc_failed(nvenc->nvEncEncodePicture(encoder, &pic_params))) { BOOST_LOG(error) << "NvEnc: NvEncEncodePicture() failed: " << last_nvenc_error_string; return {}; } NV_ENC_LOCK_BITSTREAM lock_bitstream = {api::lock_bitstream_version(selected_api_version)}; lock_bitstream.outputBitstream = output_bitstream; lock_bitstream.doNotWait = async_event_handle ? 1 : 0; if (async_event_handle && !wait_for_async_event(100)) { BOOST_LOG(error) << "NvEnc: frame " << frame_index << " encode wait timeout"; return {}; } if (nvenc_failed(nvenc->nvEncLockBitstream(encoder, &lock_bitstream))) { BOOST_LOG(error) << "NvEnc: NvEncLockBitstream() failed: " << last_nvenc_error_string; return {}; } auto data_pointer = (uint8_t *) lock_bitstream.bitstreamBufferPtr; nvenc_encoded_frame encoded_frame { {data_pointer, data_pointer + lock_bitstream.bitstreamSizeInBytes}, lock_bitstream.outputTimeStamp, lock_bitstream.pictureType == NV_ENC_PIC_TYPE_IDR, encoder_state.rfi_needs_confirmation, }; if (encoder_state.rfi_needs_confirmation) { // Invalidation request has been fulfilled, and video network packet will be marked as such encoder_state.rfi_needs_confirmation = false; } encoder_state.last_encoded_frame_index = frame_index; if (encoded_frame.idr) { BOOST_LOG(debug) << "NvEnc: idr frame " << encoded_frame.frame_index; } if (nvenc_failed(nvenc->nvEncUnlockBitstream(encoder, lock_bitstream.outputBitstream))) { BOOST_LOG(error) << "NvEnc: NvEncUnlockBitstream() failed: " << last_nvenc_error_string; } encoder_state.frame_size_logger.collect_and_log(encoded_frame.data.size() / 1000.); return encoded_frame; } bool nvenc_base::invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) { context_guard_t context_guard {*this}; if (!context_guard) { return false; } if (!encoder || !encoder_params.rfi) { return false; } if (first_frame >= encoder_state.last_rfi_range.first && last_frame <= encoder_state.last_rfi_range.second) { BOOST_LOG(debug) << "NvEnc: rfi request " << first_frame << "-" << last_frame << " already done"; return true; } encoder_state.rfi_needs_confirmation = true; if (last_frame < first_frame) { BOOST_LOG(error) << "NvEnc: invaid rfi request " << first_frame << "-" << last_frame << ", generating IDR"; return false; } BOOST_LOG(debug) << "NvEnc: rfi request " << first_frame << "-" << last_frame << " expanding to last encoded frame " << encoder_state.last_encoded_frame_index; last_frame = encoder_state.last_encoded_frame_index; encoder_state.last_rfi_range = {first_frame, last_frame}; if (last_frame - first_frame + 1 >= encoder_params.ref_frames_in_dpb) { BOOST_LOG(debug) << "NvEnc: rfi request too large, generating IDR"; return false; } for (auto i = first_frame; i <= last_frame; i++) { if (nvenc_failed(nvenc->nvEncInvalidateRefFrames(encoder, i))) { BOOST_LOG(error) << "NvEnc: NvEncInvalidateRefFrames() " << i << " failed: " << last_nvenc_error_string; return false; } } return true; } bool nvenc_base::set_bitrate(int bitrate_kbps) { context_guard_t context_guard {*this}; if (!context_guard) { return false; } if (!encoder || !nvenc) { BOOST_LOG(warning) << "NvEnc: encoder not initialized; cannot change bitrate"; return false; } // Reject non-positive and absurd values. The 800000 kbps ceiling keeps bitrate_kbps * 1000 // well inside uint32 so the rate-control fields below cannot overflow. if (bitrate_kbps <= 0 || bitrate_kbps > 800000) { BOOST_LOG(error) << "NvEnc: out-of-range bitrate " << bitrate_kbps << " kbps"; return false; } const bool is_hevc = equal_guids(saved_init_params.encodeGUID, NV_ENC_CODEC_HEVC_GUID); const uint32_t new_bitrate_bps = static_cast(bitrate_kbps) * 1000u; const uint32_t prev_bitrate_bps = current_enc_config.rcParams.averageBitRate; // Copy the saved config and apply the new bitrate. NV_ENC_CONFIG enc_config = current_enc_config; enc_config.rcParams.averageBitRate = new_bitrate_bps; enc_config.rcParams.maxBitRate = new_bitrate_bps; // Scale the VBV buffer with the bitrate so the rate controller keeps a comparable buffering // window (mirrors the create_encoder VBV sizing), with a floor to avoid an undersized buffer. if (enc_config.rcParams.vbvBufferSize > 0 && prev_bitrate_bps > 0) { const uint64_t scaled = static_cast(new_bitrate_bps) * current_enc_config.rcParams.vbvBufferSize / prev_bitrate_bps; enc_config.rcParams.vbvBufferSize = static_cast(std::max(scaled, 100u * 1000u)); } NV_ENC_RECONFIGURE_PARAMS reconfigure_params {api::reconfigure_params_version(selected_api_version)}; reconfigure_params.reInitEncodeParams = saved_init_params; reconfigure_params.reInitEncodeParams.encodeConfig = &enc_config; // When raising the ceiling, reset the rate controller and force an IDR so the higher bitrate // takes effect immediately instead of draining the old VBV first. if (new_bitrate_bps > prev_bitrate_bps) { reconfigure_params.resetEncoder = 1; reconfigure_params.forceIDR = 1; } if (nvenc_failed(nvenc->nvEncReconfigureEncoder(encoder, &reconfigure_params))) { BOOST_LOG(error) << "NvEnc: nvEncReconfigureEncoder() for " << bitrate_kbps << " kbps failed: " << last_nvenc_error_string; return false; } current_enc_config.rcParams.averageBitRate = new_bitrate_bps; current_enc_config.rcParams.maxBitRate = new_bitrate_bps; current_enc_config.rcParams.vbvBufferSize = enc_config.rcParams.vbvBufferSize; BOOST_LOG(info) << "NvEnc: " << (is_hevc ? "HEVC" : "H.264/AV1") << " bitrate reconfigured to " << bitrate_kbps << " kbps"; return true; } bool nvenc_base::nvenc_failed(NVENCSTATUS status) { last_nvenc_status = status; auto status_string = [](NVENCSTATUS status) -> std::string { switch (status) { #define nvenc_status_case(x) \ case x: \ return #x; nvenc_status_case(NV_ENC_SUCCESS); nvenc_status_case(NV_ENC_ERR_NO_ENCODE_DEVICE); nvenc_status_case(NV_ENC_ERR_UNSUPPORTED_DEVICE); nvenc_status_case(NV_ENC_ERR_INVALID_ENCODERDEVICE); nvenc_status_case(NV_ENC_ERR_INVALID_DEVICE); nvenc_status_case(NV_ENC_ERR_DEVICE_NOT_EXIST); nvenc_status_case(NV_ENC_ERR_INVALID_PTR); nvenc_status_case(NV_ENC_ERR_INVALID_EVENT); nvenc_status_case(NV_ENC_ERR_INVALID_PARAM); nvenc_status_case(NV_ENC_ERR_INVALID_CALL); nvenc_status_case(NV_ENC_ERR_OUT_OF_MEMORY); nvenc_status_case(NV_ENC_ERR_ENCODER_NOT_INITIALIZED); nvenc_status_case(NV_ENC_ERR_UNSUPPORTED_PARAM); nvenc_status_case(NV_ENC_ERR_LOCK_BUSY); nvenc_status_case(NV_ENC_ERR_NOT_ENOUGH_BUFFER); nvenc_status_case(NV_ENC_ERR_INVALID_VERSION); nvenc_status_case(NV_ENC_ERR_MAP_FAILED); nvenc_status_case(NV_ENC_ERR_NEED_MORE_INPUT); nvenc_status_case(NV_ENC_ERR_ENCODER_BUSY); nvenc_status_case(NV_ENC_ERR_EVENT_NOT_REGISTERD); nvenc_status_case(NV_ENC_ERR_GENERIC); nvenc_status_case(NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY); nvenc_status_case(NV_ENC_ERR_UNIMPLEMENTED); nvenc_status_case(NV_ENC_ERR_RESOURCE_REGISTER_FAILED); nvenc_status_case(NV_ENC_ERR_RESOURCE_NOT_REGISTERED); nvenc_status_case(NV_ENC_ERR_RESOURCE_NOT_MAPPED); #ifdef NV_ENC_ERR_NEED_MORE_OUTPUT nvenc_status_case(NV_ENC_ERR_NEED_MORE_OUTPUT); #endif // Newer versions of sdk may add more constants, look for them at the end of NVENCSTATUS enum #undef nvenc_status_case default: return std::to_string(status); } }; last_nvenc_error_string.clear(); if (status != NV_ENC_SUCCESS) { /* This API function gives broken strings more often than not if (nvenc && encoder) { last_nvenc_error_string = nvenc->nvEncGetLastErrorString(encoder); if (!last_nvenc_error_string.empty()) last_nvenc_error_string += " "; } */ last_nvenc_error_string += status_string(status); return true; } return false; } } // namespace nvenc