mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-08-07 10:20:46 +00:00
fix(nvenc): dynamic nv codec sdk selection (#5451)
This commit is contained in:
@@ -38,6 +38,10 @@ endif()
|
||||
# set the module path, used for includes
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
# CPM
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpm/CPM.cmake)
|
||||
CPMUsePackageLock(package-lock.cmake)
|
||||
|
||||
# export compile_commands.json
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
|
||||
@@ -59,11 +59,53 @@ elseif(UNIX)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
BEFORE SYSTEM
|
||||
"${CMAKE_SOURCE_DIR}/third-party/build-deps/third-party/FFmpeg/nv-codec-headers/include"
|
||||
set(NVENC_PUBLIC_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_config.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_d3d11_interface.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_dynamic_factory.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_dynamic_factory.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_dynamic_factory_versions.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_encoded_frame.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_encoder.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_shared_dll.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_version.h"
|
||||
)
|
||||
file(GLOB NVENC_SOURCES CONFIGURE_DEPENDS "src/nvenc/*.cpp" "src/nvenc/*.h")
|
||||
set(NVENC_SOURCES ${NVENC_PUBLIC_SOURCES})
|
||||
|
||||
if(WIN32)
|
||||
set(NVENC_IMPLEMENTATION_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_base.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_d3d11.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_d3d11_native.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_d3d11_on_cuda.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_dynamic_factory_impl.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_utils.cpp"
|
||||
)
|
||||
|
||||
# Add a version-isolated NVENC implementation object library.
|
||||
# add_nvenc_sdk_implementation: args = `target_name`, `sdk_version`, `sdk_include_dir`
|
||||
function(add_nvenc_sdk_implementation target_name sdk_version sdk_include_dir)
|
||||
add_library(${target_name} OBJECT ${NVENC_IMPLEMENTATION_SOURCES})
|
||||
target_include_directories(${target_name} BEFORE PRIVATE "${sdk_include_dir}")
|
||||
target_compile_definitions(${target_name} PRIVATE
|
||||
NVENC_FACTORY_SUFFIX=${sdk_version}
|
||||
NVENC_NAMESPACE=nvenc_${sdk_version}
|
||||
NVENC_SDK_VERSION=${sdk_version}
|
||||
)
|
||||
target_compile_options(${target_name} PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
|
||||
endfunction()
|
||||
|
||||
add_nvenc_sdk_implementation(nvenc_sdk_1100 1100 "${NV_CODEC_HEADERS_11_INCLUDE_DIR}")
|
||||
add_nvenc_sdk_implementation(nvenc_sdk_1200 1200 "${NV_CODEC_HEADERS_12_INCLUDE_DIR}")
|
||||
add_nvenc_sdk_implementation(nvenc_sdk_1300 1300 "${NV_CODEC_HEADERS_13_INCLUDE_DIR}")
|
||||
|
||||
list(APPEND NVENC_SOURCES
|
||||
$<TARGET_OBJECTS:nvenc_sdk_1100>
|
||||
$<TARGET_OBJECTS:nvenc_sdk_1200>
|
||||
$<TARGET_OBJECTS:nvenc_sdk_1300>
|
||||
)
|
||||
endif()
|
||||
|
||||
list(APPEND PLATFORM_TARGET_FILES ${NVENC_SOURCES})
|
||||
|
||||
set(SUNSHINE_TARGET_FILES
|
||||
@@ -155,6 +197,10 @@ include_directories(
|
||||
${Boost_INCLUDE_DIRS} # has to be the last, or we get runtime error on macOS ffmpeg encoder
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
include_directories(BEFORE SYSTEM "${NV_CODEC_HEADERS_13_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
${MINIUPNP_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
|
||||
+1379
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@ if(SUNSHINE_ENABLE_TRAY)
|
||||
endif()
|
||||
|
||||
# common dependencies
|
||||
include("${CMAKE_MODULE_PATH}/dependencies/nv_codec_headers.cmake")
|
||||
include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake")
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
if(WIN32)
|
||||
CPMGetPackage(nv_codec_headers_13)
|
||||
CPMGetPackage(nv_codec_headers_11)
|
||||
CPMGetPackage(nv_codec_headers_12)
|
||||
|
||||
set(NV_CODEC_HEADERS_11_INCLUDE_DIR "${nv_codec_headers_11_SOURCE_DIR}/include")
|
||||
set(NV_CODEC_HEADERS_12_INCLUDE_DIR "${nv_codec_headers_12_SOURCE_DIR}/include")
|
||||
set(NV_CODEC_HEADERS_13_INCLUDE_DIR "${nv_codec_headers_13_SOURCE_DIR}/include")
|
||||
endif()
|
||||
@@ -0,0 +1,67 @@
|
||||
# CPM Package Lock
|
||||
# This file should be committed to version control
|
||||
|
||||
# The first argument of CPMDeclarePackage can be freely chosen and is used as argument in CPMGetPackage.
|
||||
# The NAME argument should be package name that would also be used in a find_package call.
|
||||
# Ideally, both are the same, which might not always be possible: https://github.com/cpm-cmake/CPM.cmake/issues/603
|
||||
# This is needed to support CPM_USE_LOCAL_PACKAGES
|
||||
|
||||
# Renovate-bot will update the versions and hashes in this file when a new version of a dependency is released.
|
||||
# The comments above each dependency are used by renovate to identify the dependencies and extract the version numbers.
|
||||
# See https://github.com/LizardByte/.github/blob/master/renovate-config.json5 for the configuration of renovate.
|
||||
#
|
||||
# Expected dependency structure for new entries:
|
||||
# - Start each block with a human-readable comment, for example `# Example dependency`.
|
||||
# - Follow it with consecutive renovate metadata comments.
|
||||
# - The first metadata line must start with `# renovate:` and include `datasource=` and `depName=`.
|
||||
# - Optional metadata keys are `packageName=`, `versioning=`, `extractVersion=`, and `registryUrl=`.
|
||||
# - Optional metadata may stay on the `# renovate:` line or continue on the next consecutive `#` lines.
|
||||
# - Keep metadata keys in this order: `datasource`, `depName`, `packageName`, `versioning`,
|
||||
# `extractVersion`, `registryUrl`.
|
||||
# - After metadata, declare the tracked value with `set(NAME_VERSION ...)` or `set(NAME_TAG ...)`.
|
||||
# - If the dependency also tracks a SHA256, keep `set(NAME_SHA256 ...)` immediately after the
|
||||
# matching `NAME_VERSION` or `NAME_TAG` line with no unrelated lines between them.
|
||||
# - Keep `CPMDeclarePackage(...)` below the tracked values.
|
||||
#
|
||||
# Example layout:
|
||||
# - `# Example dependency`
|
||||
# - `# renovate: datasource=github-tags depName=owner/repo`
|
||||
# - `# versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)$`
|
||||
# - `set(EXAMPLE_TAG v1.2.3)`
|
||||
# - `set(EXAMPLE_SHA256 <sha256>)`
|
||||
# - `CPMDeclarePackage(...)`
|
||||
|
||||
set(PATCH_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/patches")
|
||||
|
||||
# NVENC SDK 11.0 headers
|
||||
# renovate: datasource=github-tags depName=FFmpeg/nv-codec-headers
|
||||
# versioning=regex:^n(?<major>11)\.(?<minor>0)\.(?<patch>\d+)\.(?<build>\d+)$
|
||||
set(NV_CODEC_HEADERS_11_TAG n11.0.10.3)
|
||||
CPMDeclarePackage(nv_codec_headers_11
|
||||
NAME nv_codec_headers_11
|
||||
GIT_REPOSITORY https://github.com/FFmpeg/nv-codec-headers.git
|
||||
GIT_TAG ${NV_CODEC_HEADERS_11_TAG}
|
||||
DOWNLOAD_ONLY YES
|
||||
)
|
||||
|
||||
# NVENC SDK 12.0 headers
|
||||
# renovate: datasource=github-tags depName=FFmpeg/nv-codec-headers
|
||||
# versioning=regex:^n(?<major>12)\.(?<minor>0)\.(?<patch>\d+)\.(?<build>\d+)$
|
||||
set(NV_CODEC_HEADERS_12_TAG n12.0.16.2)
|
||||
CPMDeclarePackage(nv_codec_headers_12
|
||||
NAME nv_codec_headers_12
|
||||
GIT_REPOSITORY https://github.com/FFmpeg/nv-codec-headers.git
|
||||
GIT_TAG ${NV_CODEC_HEADERS_12_TAG}
|
||||
DOWNLOAD_ONLY YES
|
||||
)
|
||||
|
||||
# NVENC SDK 13.0 headers
|
||||
# renovate: datasource=github-tags depName=FFmpeg/nv-codec-headers
|
||||
# versioning=regex:^n(?<major>13)\.(?<minor>0)\.(?<patch>\d+)\.(?<build>\d+)$
|
||||
set(NV_CODEC_HEADERS_13_TAG n13.0.19.1)
|
||||
CPMDeclarePackage(nv_codec_headers_13
|
||||
NAME nv_codec_headers_13
|
||||
GIT_REPOSITORY https://github.com/FFmpeg/nv-codec-headers.git
|
||||
GIT_TAG ${NV_CODEC_HEADERS_13_TAG}
|
||||
DOWNLOAD_ONLY YES
|
||||
)
|
||||
+463
-319
@@ -6,30 +6,121 @@
|
||||
#include "nvenc_base.h"
|
||||
|
||||
// standard includes
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_utils.h"
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
/**
|
||||
* @def MAKE_NVENC_VER(major, minor)
|
||||
* @brief Macro for MAKE NVENC VER.
|
||||
*/
|
||||
#define MAKE_NVENC_VER(major, minor) ((major) | ((minor) << 24))
|
||||
|
||||
// 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_VERSION != MAKE_NVENC_VER(13U, 0U)
|
||||
#error Check and update NVENC code for backwards compatibility!
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace NVENC_NAMESPACE;
|
||||
|
||||
/**
|
||||
* @brief Determine whether an NVENC buffer format stores 10-bit samples.
|
||||
*
|
||||
* @param buffer_format NVENC input buffer format.
|
||||
* @return `true` for a 10-bit format, otherwise `false`.
|
||||
*/
|
||||
bool buffer_is_10bit(NV_ENC_BUFFER_FORMAT buffer_format) {
|
||||
return buffer_format == NV_ENC_BUFFER_FORMAT_YUV420_10BIT || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determine whether an NVENC buffer format stores YUV 4:4:4 samples.
|
||||
*
|
||||
* @param buffer_format NVENC input buffer format.
|
||||
* @return `true` for a YUV 4:4:4 format, otherwise `false`.
|
||||
*/
|
||||
bool buffer_is_yuv444(NV_ENC_BUFFER_FORMAT buffer_format) {
|
||||
return buffer_format == NV_ENC_BUFFER_FORMAT_AYUV ||
|
||||
buffer_format == NV_ENC_BUFFER_FORMAT_YUV444 ||
|
||||
buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determine whether a codec GUID appears in the driver-provided list.
|
||||
*
|
||||
* @param encode_guids Driver-provided codec GUIDs.
|
||||
* @param encode_guid Codec GUID to locate.
|
||||
* @return `true` when the codec is supported, otherwise `false`.
|
||||
*/
|
||||
bool contains_guid(const std::vector<GUID> &encode_guids, const GUID &encode_guid) {
|
||||
return std::ranges::any_of(encode_guids, [&](const GUID &guid) {
|
||||
return std::memcmp(&encode_guid, &guid, sizeof(GUID)) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the display name for a Sunshine video format.
|
||||
*
|
||||
* @param video_format Sunshine video format identifier.
|
||||
* @return Display name including its trailing separator.
|
||||
*/
|
||||
std::string_view video_format_name(int video_format) {
|
||||
switch (video_format) {
|
||||
case 0:
|
||||
return "H.264 ";
|
||||
case 1:
|
||||
return "HEVC ";
|
||||
case 2:
|
||||
return "AV1 ";
|
||||
default:
|
||||
return " ";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert an NVENC status value to its symbolic name.
|
||||
*
|
||||
* @param status NVENC status value.
|
||||
* @return Symbolic name when known, otherwise its numeric value.
|
||||
*/
|
||||
std::string nvenc_status_string(NVENCSTATUS status) {
|
||||
static constexpr auto names = std::to_array<std::pair<NVENCSTATUS, std::string_view>>({
|
||||
{NV_ENC_SUCCESS, "NV_ENC_SUCCESS"},
|
||||
{NV_ENC_ERR_NO_ENCODE_DEVICE, "NV_ENC_ERR_NO_ENCODE_DEVICE"},
|
||||
{NV_ENC_ERR_UNSUPPORTED_DEVICE, "NV_ENC_ERR_UNSUPPORTED_DEVICE"},
|
||||
{NV_ENC_ERR_INVALID_ENCODERDEVICE, "NV_ENC_ERR_INVALID_ENCODERDEVICE"},
|
||||
{NV_ENC_ERR_INVALID_DEVICE, "NV_ENC_ERR_INVALID_DEVICE"},
|
||||
{NV_ENC_ERR_DEVICE_NOT_EXIST, "NV_ENC_ERR_DEVICE_NOT_EXIST"},
|
||||
{NV_ENC_ERR_INVALID_PTR, "NV_ENC_ERR_INVALID_PTR"},
|
||||
{NV_ENC_ERR_INVALID_EVENT, "NV_ENC_ERR_INVALID_EVENT"},
|
||||
{NV_ENC_ERR_INVALID_PARAM, "NV_ENC_ERR_INVALID_PARAM"},
|
||||
{NV_ENC_ERR_INVALID_CALL, "NV_ENC_ERR_INVALID_CALL"},
|
||||
{NV_ENC_ERR_OUT_OF_MEMORY, "NV_ENC_ERR_OUT_OF_MEMORY"},
|
||||
{NV_ENC_ERR_ENCODER_NOT_INITIALIZED, "NV_ENC_ERR_ENCODER_NOT_INITIALIZED"},
|
||||
{NV_ENC_ERR_UNSUPPORTED_PARAM, "NV_ENC_ERR_UNSUPPORTED_PARAM"},
|
||||
{NV_ENC_ERR_LOCK_BUSY, "NV_ENC_ERR_LOCK_BUSY"},
|
||||
{NV_ENC_ERR_NOT_ENOUGH_BUFFER, "NV_ENC_ERR_NOT_ENOUGH_BUFFER"},
|
||||
{NV_ENC_ERR_INVALID_VERSION, "NV_ENC_ERR_INVALID_VERSION"},
|
||||
{NV_ENC_ERR_MAP_FAILED, "NV_ENC_ERR_MAP_FAILED"},
|
||||
{NV_ENC_ERR_NEED_MORE_INPUT, "NV_ENC_ERR_NEED_MORE_INPUT"},
|
||||
{NV_ENC_ERR_ENCODER_BUSY, "NV_ENC_ERR_ENCODER_BUSY"},
|
||||
{NV_ENC_ERR_EVENT_NOT_REGISTERD, "NV_ENC_ERR_EVENT_NOT_REGISTERD"},
|
||||
{NV_ENC_ERR_GENERIC, "NV_ENC_ERR_GENERIC"},
|
||||
{NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY, "NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY"},
|
||||
{NV_ENC_ERR_UNIMPLEMENTED, "NV_ENC_ERR_UNIMPLEMENTED"},
|
||||
{NV_ENC_ERR_RESOURCE_REGISTER_FAILED, "NV_ENC_ERR_RESOURCE_REGISTER_FAILED"},
|
||||
{NV_ENC_ERR_RESOURCE_NOT_REGISTERED, "NV_ENC_ERR_RESOURCE_NOT_REGISTERED"},
|
||||
{NV_ENC_ERR_RESOURCE_NOT_MAPPED, "NV_ENC_ERR_RESOURCE_NOT_MAPPED"},
|
||||
});
|
||||
|
||||
const auto item = std::ranges::find_if(names, [status](const auto &entry) {
|
||||
return entry.first == status;
|
||||
});
|
||||
return item == names.end() ? std::to_string(status) : std::string {item->second};
|
||||
}
|
||||
|
||||
GUID quality_preset_guid_from_number(unsigned number) {
|
||||
if (number > 7) {
|
||||
number = 7;
|
||||
@@ -91,7 +182,7 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
nvenc_base::nvenc_base(NV_ENC_DEVICE_TYPE device_type):
|
||||
device_type(device_type) {
|
||||
@@ -101,332 +192,305 @@ namespace nvenc {
|
||||
// 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) {
|
||||
if (!nvenc && !init_library()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (encoder) {
|
||||
destroy_encoder();
|
||||
}
|
||||
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.rfi = true;
|
||||
|
||||
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = {NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER};
|
||||
session_params.device = device;
|
||||
session_params.deviceType = device_type;
|
||||
session_params.apiVersion = NVENCAPI_VERSION;
|
||||
if (nvenc_failed(nvenc->nvEncOpenEncodeSessionEx(&session_params, &encoder))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncOpenEncodeSessionEx() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t encode_guid_count = 0;
|
||||
if (nvenc_failed(nvenc->nvEncGetEncodeGUIDCount(encoder, &encode_guid_count))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDCount() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
};
|
||||
|
||||
std::vector<GUID> encode_guids(encode_guid_count);
|
||||
if (nvenc_failed(nvenc->nvEncGetEncodeGUIDs(encoder, encode_guids.data(), (uint32_t) encode_guids.size(), &encode_guid_count))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDs() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
NV_ENC_INITIALIZE_PARAMS init_params = {NV_ENC_INITIALIZE_PARAMS_VER};
|
||||
|
||||
switch (client_config.videoFormat) {
|
||||
case 0:
|
||||
// H.264
|
||||
init_params.encodeGUID = NV_ENC_CODEC_H264_GUID;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// HEVC
|
||||
init_params.encodeGUID = NV_ENC_CODEC_HEVC_GUID;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// AV1
|
||||
init_params.encodeGUID = NV_ENC_CODEC_AV1_GUID;
|
||||
break;
|
||||
|
||||
default:
|
||||
BOOST_LOG(error) << "NvEnc: unknown video format " << client_config.videoFormat;
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
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) {
|
||||
int nvenc_base::get_encoder_cap(const GUID &encode_guid, NV_ENC_CAPS cap) const {
|
||||
NV_ENC_CAPS_PARAM param = {NV_ENC_CAPS_PARAM_VER};
|
||||
param.capsToQuery = cap;
|
||||
int value = 0;
|
||||
if (int ret = nvenc->nvEncGetEncodeCaps(encoder, init_params.encodeGUID, ¶m, &value); ret == NV_ENC_SUCCESS) {
|
||||
if (nvenc->nvEncGetEncodeCaps(encoder, encode_guid, ¶m, &value) == NV_ENC_SUCCESS) {
|
||||
return value;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
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 || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
|
||||
};
|
||||
|
||||
{
|
||||
auto supported_width = get_encoder_cap(NV_ENC_CAPS_WIDTH_MAX);
|
||||
auto supported_height = get_encoder_cap(NV_ENC_CAPS_HEIGHT_MAX);
|
||||
bool nvenc_base::validate_encoder_capabilities(const GUID &encode_guid, NV_ENC_BUFFER_FORMAT buffer_format) {
|
||||
const auto supported_width = get_encoder_cap(encode_guid, NV_ENC_CAPS_WIDTH_MAX);
|
||||
const auto supported_height = get_encoder_cap(encode_guid, NV_ENC_CAPS_HEIGHT_MAX);
|
||||
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;
|
||||
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() && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_10BIT_ENCODE)) {
|
||||
if (buffer_is_10bit(buffer_format) && !get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_10BIT_ENCODE)) {
|
||||
BOOST_LOG(error) << "NvEnc: gpu doesn't support 10-bit encode";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer_is_yuv444() && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_YUV444_ENCODE)) {
|
||||
if (buffer_is_yuv444(buffer_format) && !get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_YUV444_ENCODE)) {
|
||||
BOOST_LOG(error) << "NvEnc: gpu doesn't support YUV444 encode";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (async_event_handle && !get_encoder_cap(NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT)) {
|
||||
if (async_event_handle && !get_encoder_cap(encode_guid, NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT)) {
|
||||
BOOST_LOG(warning) << "NvEnc: gpu doesn't support async encode";
|
||||
async_event_handle = nullptr;
|
||||
}
|
||||
encoder_params.rfi = get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_REF_PIC_INVALIDATION);
|
||||
return true;
|
||||
}
|
||||
|
||||
encoder_params.rfi = get_encoder_cap(NV_ENC_CAPS_SUPPORT_REF_PIC_INVALIDATION);
|
||||
void nvenc_base::configure_split_frame(
|
||||
NV_ENC_INITIALIZE_PARAMS &init_params,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config
|
||||
) const {
|
||||
#if NVENC_SDK_VERSION >= 1300
|
||||
if (client_config.videoFormat <= 0 || get_encoder_cap(init_params.encodeGUID, NV_ENC_CAPS_NUM_ENCODER_ENGINES) <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_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;
|
||||
const AVRational fps = video::framerate_to_rational(client_config);
|
||||
init_params.frameRateNum = fps.num;
|
||||
init_params.frameRateDen = fps.den;
|
||||
|
||||
if (client_config.videoFormat > 0 && get_encoder_cap(NV_ENC_CAPS_NUM_ENCODER_ENGINES) > 1) {
|
||||
// SFE supports HEVC/AV1 if you have more than 1 nvenc block
|
||||
using enum nvenc_split_frame_encoding;
|
||||
NV_ENC_SPLIT_ENCODE_MODE split_mode;
|
||||
using enum ::nvenc::nvenc_split_frame_encoding;
|
||||
if (config.split_frame_encoding == disabled) {
|
||||
split_mode = NV_ENC_SPLIT_DISABLE_MODE;
|
||||
init_params.splitEncodeMode = NV_ENC_SPLIT_DISABLE_MODE;
|
||||
} else if (config.split_frame_encoding == force_enabled) {
|
||||
split_mode = NV_ENC_SPLIT_AUTO_FORCED_MODE;
|
||||
init_params.splitEncodeMode = NV_ENC_SPLIT_AUTO_FORCED_MODE;
|
||||
} else {
|
||||
split_mode = NV_ENC_SPLIT_AUTO_MODE;
|
||||
init_params.splitEncodeMode = NV_ENC_SPLIT_AUTO_MODE;
|
||||
}
|
||||
init_params.splitEncodeMode = split_mode;
|
||||
#else
|
||||
if (config.split_frame_encoding == ::nvenc::nvenc_split_frame_encoding::force_enabled) {
|
||||
BOOST_LOG(warning) << "NvEnc: split-frame encoding requires NVENC API 13.0; ignoring forced mode";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
NV_ENC_PRESET_CONFIG preset_config = {
|
||||
.version = NV_ENC_PRESET_CONFIG_VER,
|
||||
.presetCfg = {.version = NV_ENC_CONFIG_VER},
|
||||
};
|
||||
if (nvenc_failed(nvenc->nvEncGetEncodePresetConfigEx(encoder, init_params.encodeGUID, init_params.presetGUID, init_params.tuningInfo, &preset_config))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfigEx() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
NV_ENC_CONFIG enc_config = preset_config.presetCfg;
|
||||
enc_config.profileGUID = NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID;
|
||||
void nvenc_base::configure_rate_control(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const GUID &encode_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;
|
||||
|
||||
using enum ::nvenc::nvenc_two_pass;
|
||||
if (config.two_pass == quarter_resolution) {
|
||||
enc_config.rcParams.multiPass = NV_ENC_TWO_PASS_QUARTER_RESOLUTION;
|
||||
} else if (config.two_pass == full_resolution) {
|
||||
enc_config.rcParams.multiPass = NV_ENC_TWO_PASS_FULL_RESOLUTION;
|
||||
} else {
|
||||
enc_config.rcParams.multiPass = NV_ENC_MULTI_PASS_DISABLED;
|
||||
}
|
||||
|
||||
enc_config.rcParams.enableAQ = config.adaptive_quantization;
|
||||
enc_config.rcParams.averageBitRate = client_config.bitrate * 1000;
|
||||
|
||||
if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) {
|
||||
if (get_encoder_cap(encode_guid, 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;
|
||||
if (buffer_is_yuv444()) {
|
||||
format_config.chromaFormatIDC = 3;
|
||||
}
|
||||
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)) {
|
||||
void nvenc_base::configure_reference_frames(
|
||||
std::uint32_t &ref_frames_option,
|
||||
NV_ENC_NUM_REF_FRAMES &list0_option,
|
||||
std::uint32_t default_count,
|
||||
int requested_count,
|
||||
const GUID &encode_guid
|
||||
) {
|
||||
ref_frames_option = requested_count > 0 ? static_cast<std::uint32_t>(requested_count) : default_count;
|
||||
if (ref_frames_option > 0U && !get_encoder_cap(encode_guid, 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;
|
||||
// Limit each frame to one reference while keeping a larger DPB for RFI fallback.
|
||||
list0_option = NV_ENC_NUM_REF_FRAMES_1;
|
||||
}
|
||||
|
||||
template<typename FormatConfig>
|
||||
void nvenc_base::configure_h264_hevc_metadata(
|
||||
FormatConfig &format_config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
) {
|
||||
const auto configure_vui = [&](auto &vui) {
|
||||
vui.videoSignalTypePresentFlag = 1;
|
||||
vui.videoFormat = NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED;
|
||||
vui.videoFullRangeFlag = colorspace.full_range;
|
||||
vui.colourDescriptionPresentFlag = 1;
|
||||
vui.colourPrimaries = colorspace.primaries;
|
||||
vui.transferCharacteristics = colorspace.tranfer_function;
|
||||
vui.colourMatrix = colorspace.matrix;
|
||||
vui.chromaSampleLocationFlag = buffer_is_yuv444(buffer_format) ? 0 : 1;
|
||||
vui.chromaSampleLocationTop = 0;
|
||||
vui.chromaSampleLocationBot = 0;
|
||||
vui.bitstreamRestrictionFlag = 1;
|
||||
};
|
||||
|
||||
auto set_minqp_if_enabled = [&](int value) {
|
||||
if constexpr (requires { format_config.h264VUIParameters; }) {
|
||||
configure_vui(format_config.h264VUIParameters);
|
||||
} else {
|
||||
configure_vui(format_config.hevcVUIParameters);
|
||||
}
|
||||
|
||||
if (client_config.enableIntraRefresh != 1) {
|
||||
return;
|
||||
}
|
||||
if (!get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) {
|
||||
BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh";
|
||||
return;
|
||||
}
|
||||
format_config.enableIntraRefresh = 1;
|
||||
format_config.intraRefreshPeriod = 300;
|
||||
format_config.intraRefreshCnt = 299;
|
||||
if constexpr (requires { format_config.outputRecoveryPointSEI; }) {
|
||||
format_config.outputRecoveryPointSEI = 1;
|
||||
}
|
||||
#if NVENC_SDK_VERSION >= 1200
|
||||
if (get_encoder_cap(encode_guid, NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) {
|
||||
format_config.singleSliceIntraRefresh = 1;
|
||||
} else {
|
||||
BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void nvenc_base::configure_h264(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
) {
|
||||
enc_config.profileGUID = buffer_is_yuv444(buffer_format) ? NV_ENC_H264_PROFILE_HIGH_444_GUID : NV_ENC_H264_PROFILE_HIGH_GUID;
|
||||
auto &format_config = enc_config.encodeCodecConfig.h264Config;
|
||||
format_config.repeatSPSPPS = 1;
|
||||
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
|
||||
format_config.sliceMode = 3;
|
||||
format_config.sliceModeData = client_config.slicesPerFrame;
|
||||
if (buffer_is_yuv444(buffer_format)) {
|
||||
format_config.chromaFormatIDC = 3;
|
||||
}
|
||||
format_config.enableFillerDataInsertion = config.insert_filler_data;
|
||||
format_config.entropyCodingMode = config.h264_cavlc || !get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_CABAC) ?
|
||||
NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC :
|
||||
NV_ENC_H264_ENTROPY_CODING_MODE_CABAC;
|
||||
configure_reference_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5, client_config.numRefFrames, encode_guid);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
enc_config.rcParams.minQP.qpInterP = config.min_qp_h264;
|
||||
enc_config.rcParams.minQP.qpIntra = config.min_qp_h264;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
// HEVC
|
||||
configure_h264_hevc_metadata(format_config, client_config, colorspace, buffer_format, encode_guid);
|
||||
}
|
||||
|
||||
void nvenc_base::configure_hevc(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
) {
|
||||
auto &format_config = enc_config.encodeCodecConfig.hevcConfig;
|
||||
set_h264_hevc_common_format_config(format_config);
|
||||
if (buffer_is_10bit()) {
|
||||
format_config.repeatSPSPPS = 1;
|
||||
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
|
||||
format_config.sliceMode = 3;
|
||||
format_config.sliceModeData = client_config.slicesPerFrame;
|
||||
if (buffer_is_yuv444(buffer_format)) {
|
||||
format_config.chromaFormatIDC = 3;
|
||||
}
|
||||
format_config.enableFillerDataInsertion = config.insert_filler_data;
|
||||
if (buffer_is_10bit(buffer_format)) {
|
||||
#if NVENC_SDK_VERSION >= 1300
|
||||
format_config.inputBitDepth = NV_ENC_BIT_DEPTH_10;
|
||||
format_config.outputBitDepth = NV_ENC_BIT_DEPTH_10;
|
||||
#else
|
||||
format_config.pixelBitDepthMinus8 = 2;
|
||||
#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 (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;
|
||||
configure_reference_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, 5, client_config.numRefFrames, encode_guid);
|
||||
|
||||
if (config.enable_min_qp) {
|
||||
enc_config.rcParams.enableMinQP = 1;
|
||||
enc_config.rcParams.minQP.qpInterP = config.min_qp_hevc;
|
||||
enc_config.rcParams.minQP.qpIntra = config.min_qp_hevc;
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
// AV1
|
||||
configure_h264_hevc_metadata(format_config, client_config, colorspace, buffer_format, encode_guid);
|
||||
}
|
||||
|
||||
#if NVENC_SDK_VERSION >= 1200
|
||||
void nvenc_base::configure_av1(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
) {
|
||||
auto &format_config = enc_config.encodeCodecConfig.av1Config;
|
||||
format_config.repeatSeqHdr = 1;
|
||||
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
|
||||
if (buffer_is_yuv444()) {
|
||||
if (buffer_is_yuv444(buffer_format)) {
|
||||
format_config.chromaFormatIDC = 3;
|
||||
}
|
||||
format_config.enableBitstreamPadding = config.insert_filler_data;
|
||||
if (buffer_is_10bit()) {
|
||||
if (buffer_is_10bit(buffer_format)) {
|
||||
#if NVENC_SDK_VERSION >= 1300
|
||||
format_config.inputBitDepth = NV_ENC_BIT_DEPTH_10;
|
||||
format_config.outputBitDepth = NV_ENC_BIT_DEPTH_10;
|
||||
#else
|
||||
format_config.inputPixelBitDepthMinus8 = 2;
|
||||
format_config.pixelBitDepthMinus8 = 2;
|
||||
#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;
|
||||
set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numFwdRefs, 8);
|
||||
set_minqp_if_enabled(config.min_qp_av1);
|
||||
format_config.chromaSamplePosition = buffer_is_yuv444(buffer_format) ? 0 : 1;
|
||||
configure_reference_frames(format_config.maxNumRefFramesInDPB, format_config.numFwdRefs, 8, client_config.numRefFrames, encode_guid);
|
||||
|
||||
if (config.enable_min_qp) {
|
||||
enc_config.rcParams.enableMinQP = 1;
|
||||
enc_config.rcParams.minQP.qpInterP = config.min_qp_av1;
|
||||
enc_config.rcParams.minQP.qpIntra = 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.
|
||||
// NVENC supports power-of-two tile counts, biased toward rows.
|
||||
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));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void nvenc_base::configure_codec(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
) {
|
||||
switch (client_config.videoFormat) {
|
||||
case 0:
|
||||
configure_h264(enc_config, config, client_config, colorspace, buffer_format, encode_guid);
|
||||
break;
|
||||
case 1:
|
||||
configure_hevc(enc_config, config, client_config, colorspace, buffer_format, encode_guid);
|
||||
break;
|
||||
#if NVENC_SDK_VERSION >= 1200
|
||||
case 2:
|
||||
configure_av1(enc_config, config, client_config, colorspace, buffer_format, encode_guid);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
init_params.encodeConfig = &enc_config;
|
||||
|
||||
bool nvenc_base::initialize_encoder_resources(NV_ENC_INITIALIZE_PARAMS &init_params) {
|
||||
if (nvenc_failed(nvenc->nvEncInitializeEncoder(encoder, &init_params))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncInitializeEncoder() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (async_event_handle) {
|
||||
NV_ENC_EVENT_PARAMS event_params = {NV_ENC_EVENT_PARAMS_VER};
|
||||
event_params.completionEvent = async_event_handle;
|
||||
@@ -442,35 +506,30 @@ namespace nvenc {
|
||||
return false;
|
||||
}
|
||||
output_bitstream = create_bitstream_buffer.bitstreamBuffer;
|
||||
|
||||
if (!create_and_register_input_buffer()) {
|
||||
return false;
|
||||
return create_and_register_input_buffer();
|
||||
}
|
||||
|
||||
{
|
||||
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";
|
||||
}
|
||||
|
||||
{
|
||||
auto video_format_string = client_config.videoFormat == 0 ? "H.264 " :
|
||||
client_config.videoFormat == 1 ? "HEVC " :
|
||||
client_config.videoFormat == 2 ? "AV1 " :
|
||||
" ";
|
||||
void nvenc_base::log_created_encoder(
|
||||
const NV_ENC_INITIALIZE_PARAMS &init_params,
|
||||
const NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format
|
||||
) const {
|
||||
std::string extra;
|
||||
if (init_params.enableEncodeAsync) {
|
||||
extra += " async";
|
||||
}
|
||||
if (buffer_is_yuv444()) {
|
||||
if (buffer_is_yuv444(buffer_format)) {
|
||||
extra += " yuv444";
|
||||
}
|
||||
if (buffer_is_10bit()) {
|
||||
if (buffer_is_10bit(buffer_format)) {
|
||||
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)) {
|
||||
if (config.vbv_percentage_increase > 0 && get_encoder_cap(init_params.encodeGUID, NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) {
|
||||
extra += std::format(" vbv+{}", config.vbv_percentage_increase);
|
||||
}
|
||||
if (encoder_params.rfi) {
|
||||
@@ -488,17 +547,142 @@ namespace nvenc {
|
||||
if (config.insert_filler_data) {
|
||||
extra += " filler-data";
|
||||
}
|
||||
if (client_config.videoFormat > 0 && get_encoder_cap(NV_ENC_CAPS_NUM_ENCODER_ENGINES) > 1) {
|
||||
#if NVENC_SDK_VERSION >= 1300
|
||||
if (client_config.videoFormat > 0 && get_encoder_cap(init_params.encodeGUID, NV_ENC_CAPS_NUM_ENCODER_ENGINES) > 1) {
|
||||
if (init_params.splitEncodeMode == NV_ENC_SPLIT_AUTO_MODE) {
|
||||
extra += " sfe-auto";
|
||||
} else if (init_params.splitEncodeMode == NV_ENC_SPLIT_AUTO_FORCED_MODE) {
|
||||
extra += " sfe";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOST_LOG(info) << "NvEnc: created encoder " << video_format_string << quality_preset_string_from_guid(init_params.presetGUID) << extra;
|
||||
BOOST_LOG(info) << "NvEnc: created encoder v" << NVENC_SDK_VERSION << " "
|
||||
<< video_format_name(client_config.videoFormat)
|
||||
<< quality_preset_string_from_guid(init_params.presetGUID) << extra;
|
||||
}
|
||||
|
||||
bool nvenc_base::create_encoder(
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const video::sunshine_colorspace_t &sunshine_colorspace,
|
||||
platf::pix_fmt_e sunshine_buffer_format
|
||||
) {
|
||||
if (!nvenc && !init_library()) {
|
||||
return false;
|
||||
}
|
||||
if (encoder) {
|
||||
destroy_encoder();
|
||||
}
|
||||
auto fail_guard = util::fail_guard([this] {
|
||||
destroy_encoder();
|
||||
});
|
||||
|
||||
const auto colorspace = nvenc_colorspace_from_sunshine_colorspace(sunshine_colorspace);
|
||||
const auto buffer_format = nvenc_format_from_sunshine_format(sunshine_buffer_format);
|
||||
if (buffer_format == NV_ENC_BUFFER_FORMAT_UNDEFINED) {
|
||||
BOOST_LOG(error) << "NvEnc: unsupported input pixel format";
|
||||
return false;
|
||||
}
|
||||
encoder_params.width = client_config.width;
|
||||
encoder_params.height = client_config.height;
|
||||
encoder_params.buffer_format = buffer_format;
|
||||
encoder_params.rfi = true;
|
||||
|
||||
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = {NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER};
|
||||
session_params.device = device;
|
||||
session_params.deviceType = device_type;
|
||||
session_params.apiVersion = NVENCAPI_VERSION;
|
||||
if (nvenc_failed(nvenc->nvEncOpenEncodeSessionEx(&session_params, &encoder))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncOpenEncodeSessionEx() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t encode_guid_count = 0;
|
||||
if (nvenc_failed(nvenc->nvEncGetEncodeGUIDCount(encoder, &encode_guid_count))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDCount() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
std::vector<GUID> encode_guids(encode_guid_count);
|
||||
if (nvenc_failed(nvenc->nvEncGetEncodeGUIDs(
|
||||
encoder,
|
||||
encode_guids.data(),
|
||||
static_cast<std::uint32_t>(encode_guids.size()),
|
||||
&encode_guid_count
|
||||
))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDs() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
NV_ENC_INITIALIZE_PARAMS init_params = {NV_ENC_INITIALIZE_PARAMS_VER};
|
||||
switch (client_config.videoFormat) {
|
||||
case 0:
|
||||
init_params.encodeGUID = NV_ENC_CODEC_H264_GUID;
|
||||
break;
|
||||
case 1:
|
||||
init_params.encodeGUID = NV_ENC_CODEC_HEVC_GUID;
|
||||
break;
|
||||
#if NVENC_SDK_VERSION >= 1200
|
||||
case 2:
|
||||
init_params.encodeGUID = NV_ENC_CODEC_AV1_GUID;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
BOOST_LOG(error) << "NvEnc: unknown video format " << client_config.videoFormat;
|
||||
return false;
|
||||
}
|
||||
if (!contains_guid(encode_guids, init_params.encodeGUID)) {
|
||||
BOOST_LOG(error) << "NvEnc: encoding format is not supported by the gpu";
|
||||
return false;
|
||||
}
|
||||
if (!validate_encoder_capabilities(init_params.encodeGUID, buffer_format)) {
|
||||
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 &&
|
||||
get_encoder_cap(init_params.encodeGUID, NV_ENC_CAPS_SUPPORT_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;
|
||||
const AVRational fps = video::framerate_to_rational(client_config);
|
||||
init_params.frameRateNum = fps.num;
|
||||
init_params.frameRateDen = fps.den;
|
||||
configure_split_frame(init_params, config, client_config);
|
||||
|
||||
NV_ENC_PRESET_CONFIG preset_config = {
|
||||
.version = NV_ENC_PRESET_CONFIG_VER,
|
||||
.presetCfg = {.version = NV_ENC_CONFIG_VER},
|
||||
};
|
||||
if (nvenc_failed(nvenc->nvEncGetEncodePresetConfigEx(
|
||||
encoder,
|
||||
init_params.encodeGUID,
|
||||
init_params.presetGUID,
|
||||
init_params.tuningInfo,
|
||||
&preset_config
|
||||
))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfigEx() failed: " << last_nvenc_error_string;
|
||||
return false;
|
||||
}
|
||||
|
||||
NV_ENC_CONFIG enc_config = preset_config.presetCfg;
|
||||
enc_config.profileGUID = NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID;
|
||||
configure_rate_control(enc_config, config, client_config, init_params.encodeGUID);
|
||||
configure_codec(enc_config, config, client_config, colorspace, buffer_format, init_params.encodeGUID);
|
||||
init_params.encodeConfig = &enc_config;
|
||||
if (!initialize_encoder_resources(init_params)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto frame_size_format = stat_trackers::two_digits_after_decimal();
|
||||
BOOST_LOG(debug) << "NvEnc: requested encoded frame size "
|
||||
<< frame_size_format % (client_config.bitrate / 8. / client_config.framerate) << " kB";
|
||||
log_created_encoder(init_params, enc_config, config, client_config, buffer_format);
|
||||
|
||||
encoder_state = {};
|
||||
fail_guard.disable();
|
||||
return true;
|
||||
@@ -535,7 +719,7 @@ namespace nvenc {
|
||||
encoder_params = {};
|
||||
}
|
||||
|
||||
nvenc_encoded_frame nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
|
||||
::nvenc::nvenc_encoded_frame nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
|
||||
if (!encoder) {
|
||||
return {};
|
||||
}
|
||||
@@ -592,7 +776,7 @@ namespace nvenc {
|
||||
}
|
||||
|
||||
auto data_pointer = (uint8_t *) lock_bitstream.bitstreamBufferPtr;
|
||||
nvenc_encoded_frame encoded_frame {
|
||||
::nvenc::nvenc_encoded_frame encoded_frame {
|
||||
{data_pointer, data_pointer + lock_bitstream.bitstreamSizeInBytes},
|
||||
lock_bitstream.outputTimeStamp,
|
||||
lock_bitstream.pictureType == NV_ENC_PIC_TYPE_IDR,
|
||||
@@ -657,46 +841,6 @@ namespace nvenc {
|
||||
}
|
||||
|
||||
bool nvenc_base::nvenc_failed(NVENCSTATUS status) {
|
||||
auto status_string = [](NVENCSTATUS status) -> std::string {
|
||||
switch (status) {
|
||||
#ifndef DOXYGEN
|
||||
#define nvenc_status_case(x) \
|
||||
case x: \
|
||||
return #x;
|
||||
#endif
|
||||
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);
|
||||
// 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
|
||||
@@ -705,11 +849,11 @@ namespace nvenc {
|
||||
if (!last_nvenc_error_string.empty()) last_nvenc_error_string += " ";
|
||||
}
|
||||
*/
|
||||
last_nvenc_error_string += status_string(status);
|
||||
last_nvenc_error_string += nvenc_status_string(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
|
||||
+202
-11
@@ -4,32 +4,33 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_colorspace.h"
|
||||
#include "nvenc_config.h"
|
||||
#include "nvenc_encoded_frame.h"
|
||||
#include "nvenc_encoder.h"
|
||||
#include "nvenc_sdk.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/video.h"
|
||||
#include "src/video_colorspace.h"
|
||||
|
||||
/**
|
||||
* @brief Standalone NVENC encoder
|
||||
*/
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
/**
|
||||
* @brief Abstract platform-agnostic base of standalone NVENC encoder.
|
||||
* Derived classes perform platform-specific operations.
|
||||
*/
|
||||
class nvenc_base {
|
||||
// Virtual inheritance is required because platform implementations also inherit their SDK-neutral interface.
|
||||
class nvenc_base: public virtual ::nvenc::nvenc_encoder { // NOSONAR(cpp:S1011)
|
||||
public:
|
||||
/**
|
||||
* @param device_type Underlying device type used by derived class.
|
||||
*/
|
||||
explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type);
|
||||
virtual ~nvenc_base();
|
||||
~nvenc_base() override;
|
||||
|
||||
nvenc_base(const nvenc_base &) = delete;
|
||||
nvenc_base &operator=(const nvenc_base &) = delete;
|
||||
@@ -42,13 +43,18 @@ namespace nvenc {
|
||||
* @param buffer_format Platform-agnostic input surface format.
|
||||
* @return `true` on success, `false` on error
|
||||
*/
|
||||
bool create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format);
|
||||
bool create_encoder(
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const video::sunshine_colorspace_t &colorspace,
|
||||
platf::pix_fmt_e buffer_format
|
||||
) override;
|
||||
|
||||
/**
|
||||
* @brief Destroy the encoder.
|
||||
* Derived classes classes call it in the destructor.
|
||||
*/
|
||||
void destroy_encoder();
|
||||
void destroy_encoder() override;
|
||||
|
||||
/**
|
||||
* @brief Encode the next frame using platform-specific input surface.
|
||||
@@ -58,7 +64,7 @@ namespace nvenc {
|
||||
* @param force_idr Whether to encode frame as forced IDR.
|
||||
* @return Encoded frame.
|
||||
*/
|
||||
nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr);
|
||||
::nvenc::nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr) override;
|
||||
|
||||
/**
|
||||
* @brief Perform reference frame invalidation (RFI) procedure.
|
||||
@@ -67,7 +73,7 @@ namespace nvenc {
|
||||
* @return `true` on success, `false` on error.
|
||||
* After error next frame must be encoded with `force_idr = true`.
|
||||
*/
|
||||
bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame);
|
||||
bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
@@ -137,6 +143,191 @@ namespace nvenc {
|
||||
///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`.
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Query one encoder capability.
|
||||
*
|
||||
* @param encode_guid Codec GUID to query.
|
||||
* @param cap Capability identifier.
|
||||
* @return Capability value, or zero when the query fails.
|
||||
*/
|
||||
int get_encoder_cap(const GUID &encode_guid, NV_ENC_CAPS cap) const;
|
||||
|
||||
/**
|
||||
* @brief Validate the requested input format and dimensions against encoder capabilities.
|
||||
*
|
||||
* @param encode_guid Selected codec GUID.
|
||||
* @param buffer_format Selected NVENC input format.
|
||||
* @return `true` when the request is supported, otherwise `false`.
|
||||
*/
|
||||
bool validate_encoder_capabilities(const GUID &encode_guid, NV_ENC_BUFFER_FORMAT buffer_format);
|
||||
|
||||
/**
|
||||
* @brief Configure split-frame encoding for the selected SDK.
|
||||
*
|
||||
* @param init_params Encoder initialization parameters to update.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
*/
|
||||
void configure_split_frame(
|
||||
NV_ENC_INITIALIZE_PARAMS &init_params,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config
|
||||
) const;
|
||||
|
||||
/**
|
||||
* @brief Configure rate control and VBV options.
|
||||
*
|
||||
* @param enc_config Encoder configuration to update.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param encode_guid Selected codec GUID.
|
||||
*/
|
||||
void configure_rate_control(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const GUID &encode_guid
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Configure the requested reference-frame count.
|
||||
*
|
||||
* @param ref_frames_option Codec-specific reference-frame option.
|
||||
* @param list0_option Codec-specific list-zero option.
|
||||
* @param default_count Default reference-frame count.
|
||||
* @param requested_count Client-requested reference-frame count.
|
||||
* @param encode_guid Selected codec GUID.
|
||||
*/
|
||||
void configure_reference_frames(
|
||||
std::uint32_t &ref_frames_option,
|
||||
NV_ENC_NUM_REF_FRAMES &list0_option,
|
||||
std::uint32_t default_count,
|
||||
int requested_count,
|
||||
const GUID &encode_guid
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Configure VUI metadata and intra-refresh options shared by H.264 and HEVC.
|
||||
*
|
||||
* @tparam FormatConfig Codec-specific NVENC configuration type.
|
||||
* @param format_config Codec-specific encoder configuration to update.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace NVENC colorspace metadata.
|
||||
* @param buffer_format Selected NVENC input format.
|
||||
* @param encode_guid Selected codec GUID.
|
||||
*/
|
||||
template<typename FormatConfig>
|
||||
void configure_h264_hevc_metadata(
|
||||
FormatConfig &format_config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Configure H.264 codec options.
|
||||
*
|
||||
* @param enc_config Encoder configuration to update.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace NVENC colorspace metadata.
|
||||
* @param buffer_format Selected NVENC input format.
|
||||
* @param encode_guid Selected codec GUID.
|
||||
*/
|
||||
void configure_h264(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Configure HEVC codec options.
|
||||
*
|
||||
* @param enc_config Encoder configuration to update.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace NVENC colorspace metadata.
|
||||
* @param buffer_format Selected NVENC input format.
|
||||
* @param encode_guid Selected codec GUID.
|
||||
*/
|
||||
void configure_hevc(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
);
|
||||
|
||||
#if NVENC_SDK_VERSION >= 1200
|
||||
/**
|
||||
* @brief Configure AV1 codec options.
|
||||
*
|
||||
* @param enc_config Encoder configuration to update.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace NVENC colorspace metadata.
|
||||
* @param buffer_format Selected NVENC input format.
|
||||
* @param encode_guid Selected codec GUID.
|
||||
*/
|
||||
void configure_av1(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Configure codec-specific encoder options.
|
||||
*
|
||||
* @param enc_config Encoder configuration to update.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace NVENC colorspace metadata.
|
||||
* @param buffer_format Selected NVENC input format.
|
||||
* @param encode_guid Selected codec GUID.
|
||||
*/
|
||||
void configure_codec(
|
||||
NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const nvenc_colorspace_t &colorspace,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format,
|
||||
const GUID &encode_guid
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Initialize the encoder and its registered input and output resources.
|
||||
*
|
||||
* @param init_params Completed encoder initialization parameters.
|
||||
* @return `true` on success, otherwise `false`.
|
||||
*/
|
||||
bool initialize_encoder_resources(NV_ENC_INITIALIZE_PARAMS &init_params);
|
||||
|
||||
/**
|
||||
* @brief Log the selected encoder configuration.
|
||||
*
|
||||
* @param init_params Encoder initialization parameters.
|
||||
* @param enc_config Encoder configuration.
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param buffer_format Selected NVENC input format.
|
||||
*/
|
||||
void log_created_encoder(
|
||||
const NV_ENC_INITIALIZE_PARAMS &init_params,
|
||||
const NV_ENC_CONFIG &enc_config,
|
||||
const ::nvenc::nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
NV_ENC_BUFFER_FORMAT buffer_format
|
||||
) const;
|
||||
|
||||
NV_ENC_OUTPUT_PTR output_bitstream = nullptr;
|
||||
|
||||
struct {
|
||||
@@ -147,4 +338,4 @@ namespace nvenc {
|
||||
} encoder_state;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// lib includes
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
// local includes
|
||||
#include "nvenc_sdk.h"
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
/**
|
||||
* @brief YUV colorspace and color range.
|
||||
@@ -19,4 +19,4 @@ namespace nvenc {
|
||||
bool full_range; ///< Whether the video range is full-range instead of limited.
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
|
||||
+16
-31
@@ -8,62 +8,47 @@
|
||||
#ifdef _WIN32
|
||||
#include "nvenc_d3d11.h"
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
nvenc_d3d11::nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type):
|
||||
nvenc_base(device_type) {
|
||||
nvenc_d3d11::nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type, ::nvenc::shared_dll dll):
|
||||
nvenc_base(device_type),
|
||||
dll(std::move(dll)) {
|
||||
async_event_handle = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
}
|
||||
|
||||
nvenc_d3d11::~nvenc_d3d11() {
|
||||
if (dll) {
|
||||
FreeLibrary(dll);
|
||||
dll = nullptr;
|
||||
}
|
||||
if (async_event_handle) {
|
||||
CloseHandle(async_event_handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool nvenc_d3d11::init_library() {
|
||||
if (dll) {
|
||||
if (nvenc) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
constexpr auto dll_name = "nvEncodeAPI64.dll";
|
||||
#else
|
||||
constexpr auto dll_name = "nvEncodeAPI.dll";
|
||||
#endif
|
||||
auto create_instance = reinterpret_cast<decltype(NvEncodeAPICreateInstance) *>(
|
||||
GetProcAddress(dll.get(), "NvEncodeAPICreateInstance")
|
||||
);
|
||||
if (!create_instance) {
|
||||
BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in NVENC driver library";
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((dll = LoadLibraryEx(dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
|
||||
if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) {
|
||||
auto new_nvenc = std::make_unique<NV_ENCODE_API_FUNCTION_LIST>();
|
||||
new_nvenc->version = NV_ENCODE_API_FUNCTION_LIST_VER;
|
||||
if (nvenc_failed(create_instance(new_nvenc.get()))) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
nvenc = std::move(new_nvenc);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in " << dll_name;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name;
|
||||
}
|
||||
|
||||
if (dll) {
|
||||
FreeLibrary(dll);
|
||||
dll = nullptr;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool nvenc_d3d11::wait_for_async_event(uint32_t timeout_ms) {
|
||||
return WaitForSingleObject(async_event_handle, timeout_ms) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
#endif
|
||||
|
||||
+10
-7
@@ -11,8 +11,10 @@
|
||||
|
||||
// local includes
|
||||
#include "nvenc_base.h"
|
||||
#include "nvenc_d3d11_interface.h"
|
||||
#include "nvenc_shared_dll.h"
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
#ifdef DOXYGEN
|
||||
/**
|
||||
@@ -42,29 +44,30 @@ namespace nvenc {
|
||||
* @brief Abstract Direct3D11 NVENC encoder.
|
||||
* Encapsulates common code used by native and interop implementations.
|
||||
*/
|
||||
class nvenc_d3d11: public nvenc_base {
|
||||
class nvenc_d3d11: public nvenc_base, public ::nvenc::nvenc_d3d11_interface {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize an NVENC session wrapper for D3D11 input textures.
|
||||
*
|
||||
* @param device_type NVENC device type used by the encoder session.
|
||||
* @param dll Shared NVENC driver module.
|
||||
*/
|
||||
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type);
|
||||
~nvenc_d3d11();
|
||||
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type, ::nvenc::shared_dll dll);
|
||||
~nvenc_d3d11() override;
|
||||
|
||||
/**
|
||||
* @brief Get input surface texture.
|
||||
* @return Input surface texture.
|
||||
*/
|
||||
virtual ID3D11Texture2D *get_input_texture() = 0;
|
||||
ID3D11Texture2D *get_input_texture() override = 0;
|
||||
|
||||
protected:
|
||||
bool init_library() override;
|
||||
bool wait_for_async_event(uint32_t timeout_ms) override;
|
||||
|
||||
private:
|
||||
HMODULE dll = nullptr;
|
||||
::nvenc::shared_dll dll;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_d3d11_interface.h
|
||||
* @brief Declarations for the SDK-neutral Direct3D11 NVENC interface.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
|
||||
// lib includes
|
||||
#include <d3d11.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_encoder.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief SDK-neutral Direct3D11 NVENC encoder interface.
|
||||
*/
|
||||
// Virtual inheritance is required because concrete encoders also inherit their SDK-specific implementation.
|
||||
class nvenc_d3d11_interface: public virtual nvenc_encoder { // NOSONAR(cpp:S1011)
|
||||
public:
|
||||
/**
|
||||
* @brief Destroy the SDK-neutral Direct3D11 encoder interface.
|
||||
*/
|
||||
~nvenc_d3d11_interface() override = default;
|
||||
|
||||
/**
|
||||
* @brief Get the input surface texture.
|
||||
*
|
||||
* @return Input surface texture.
|
||||
*/
|
||||
virtual ID3D11Texture2D *get_input_texture() = 0;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
@@ -9,10 +9,10 @@
|
||||
// local includes
|
||||
#include "nvenc_utils.h"
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device):
|
||||
nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX),
|
||||
nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device, ::nvenc::shared_dll dll):
|
||||
nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX, std::move(dll)),
|
||||
d3d_device(d3d_device) {
|
||||
device = d3d_device;
|
||||
}
|
||||
@@ -70,5 +70,5 @@ namespace nvenc {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
#endif
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// local includes
|
||||
#include "nvenc_d3d11.h"
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
/**
|
||||
* @brief Native Direct3D11 NVENC encoder.
|
||||
@@ -20,9 +20,10 @@ namespace nvenc {
|
||||
public:
|
||||
/**
|
||||
* @param d3d_device Direct3D11 device used for encoding.
|
||||
* @param dll Shared NVENC driver module.
|
||||
*/
|
||||
explicit nvenc_d3d11_native(ID3D11Device *d3d_device);
|
||||
~nvenc_d3d11_native();
|
||||
explicit nvenc_d3d11_native(ID3D11Device *d3d_device, ::nvenc::shared_dll dll);
|
||||
~nvenc_d3d11_native() override;
|
||||
|
||||
ID3D11Texture2D *get_input_texture() override;
|
||||
|
||||
@@ -33,5 +34,5 @@ namespace nvenc {
|
||||
ID3D11Texture2DPtr d3d_input_texture;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
#endif
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
// local includes
|
||||
#include "nvenc_utils.h"
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device):
|
||||
nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA),
|
||||
nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device, ::nvenc::shared_dll dll):
|
||||
nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA, std::move(dll)),
|
||||
d3d_device(d3d_device) {
|
||||
}
|
||||
|
||||
@@ -265,5 +265,5 @@ namespace nvenc {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
#endif
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
// lib includes
|
||||
#include <ffnvcodec/dynlink_cuda.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_d3d11.h"
|
||||
#include "nvenc_sdk.h"
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
/**
|
||||
* @brief Interop Direct3D11 on CUDA NVENC encoder.
|
||||
@@ -21,9 +19,10 @@ namespace nvenc {
|
||||
/**
|
||||
* @param d3d_device Direct3D11 device that will create input surface texture.
|
||||
* CUDA encoding device will be derived from it.
|
||||
* @param dll Shared NVENC driver module.
|
||||
*/
|
||||
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device);
|
||||
~nvenc_d3d11_on_cuda();
|
||||
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device, ::nvenc::shared_dll dll);
|
||||
~nvenc_d3d11_on_cuda() override;
|
||||
|
||||
ID3D11Texture2D *get_input_texture() override;
|
||||
|
||||
@@ -84,5 +83,5 @@ namespace nvenc {
|
||||
size_t cuda_surface_pitch = 0;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_dynamic_factory.cpp
|
||||
* @brief Definitions for runtime NVENC SDK selection on Windows.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
|
||||
// this include
|
||||
#include "nvenc_dynamic_factory.h"
|
||||
|
||||
// standard includes
|
||||
#include <bit>
|
||||
#include <utility>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_dynamic_factory_versions.h"
|
||||
#include "src/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef _WIN64
|
||||
constexpr auto nvenc_dll_name = "nvEncodeAPI64.dll";
|
||||
#else
|
||||
constexpr auto nvenc_dll_name = "nvEncodeAPI.dll";
|
||||
#endif
|
||||
|
||||
constexpr auto minimum_driver_version = "456.71";
|
||||
|
||||
using get_max_supported_version_fn = std::uint32_t(WINAPI *)(std::uint32_t *);
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
nvenc_dynamic_factory::nvenc_dynamic_factory(
|
||||
shared_dll dll,
|
||||
nvenc_sdk_version sdk_version,
|
||||
create_encoder_fn create_native,
|
||||
create_encoder_fn create_on_cuda
|
||||
):
|
||||
dll(std::move(dll)),
|
||||
selected_sdk_version(sdk_version),
|
||||
create_native(std::move(create_native)),
|
||||
create_on_cuda(std::move(create_on_cuda)) {
|
||||
}
|
||||
|
||||
std::shared_ptr<nvenc_dynamic_factory> nvenc_dynamic_factory::get() {
|
||||
return get({
|
||||
[]() {
|
||||
return make_shared_dll(LoadLibraryEx(nvenc_dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
|
||||
},
|
||||
[](HMODULE dll, const char *symbol) {
|
||||
return GetProcAddress(dll, symbol);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<nvenc_dynamic_factory> nvenc_dynamic_factory::get(const nvenc_runtime_api &runtime_api) {
|
||||
auto dll = runtime_api.load_driver();
|
||||
if (!dll) {
|
||||
BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << nvenc_dll_name;
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto get_max_version = std::bit_cast<get_max_supported_version_fn>(
|
||||
runtime_api.get_symbol(dll.get(), "NvEncodeAPIGetMaxSupportedVersion")
|
||||
);
|
||||
if (!get_max_version) {
|
||||
BOOST_LOG(error) << "NvEnc: No NvEncodeAPIGetMaxSupportedVersion() in " << nvenc_dll_name;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::uint32_t packed_max_version = 0;
|
||||
if (get_max_version(&packed_max_version) != 0U) {
|
||||
BOOST_LOG(error) << "NvEnc: NvEncodeAPIGetMaxSupportedVersion() failed";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto max_version = decode_nvenc_driver_version(packed_max_version);
|
||||
const auto sdk_version = select_nvenc_sdk_version(max_version);
|
||||
switch (sdk_version) {
|
||||
case nvenc_sdk_version::sdk_13_0:
|
||||
return std::make_shared<nvenc_dynamic_factory>(
|
||||
std::move(dll),
|
||||
sdk_version,
|
||||
detail::create_nvenc_d3d11_native_1300,
|
||||
detail::create_nvenc_d3d11_on_cuda_1300
|
||||
);
|
||||
|
||||
case nvenc_sdk_version::sdk_12_0:
|
||||
return std::make_shared<nvenc_dynamic_factory>(
|
||||
std::move(dll),
|
||||
sdk_version,
|
||||
detail::create_nvenc_d3d11_native_1200,
|
||||
detail::create_nvenc_d3d11_on_cuda_1200
|
||||
);
|
||||
|
||||
case nvenc_sdk_version::sdk_11_0:
|
||||
return std::make_shared<nvenc_dynamic_factory>(
|
||||
std::move(dll),
|
||||
sdk_version,
|
||||
detail::create_nvenc_d3d11_native_1100,
|
||||
detail::create_nvenc_d3d11_on_cuda_1100
|
||||
);
|
||||
|
||||
case nvenc_sdk_version::unsupported:
|
||||
default:
|
||||
BOOST_LOG(error) << "NvEnc: minimum required driver version is " << minimum_driver_version;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<nvenc_d3d11_interface> nvenc_dynamic_factory::create_nvenc_d3d11_native(
|
||||
ID3D11Device *d3d_device
|
||||
) const {
|
||||
return create_native(d3d_device, dll);
|
||||
}
|
||||
|
||||
std::unique_ptr<nvenc_d3d11_interface> nvenc_dynamic_factory::create_nvenc_d3d11_on_cuda(
|
||||
ID3D11Device *d3d_device
|
||||
) const {
|
||||
return create_on_cuda(d3d_device, dll);
|
||||
}
|
||||
|
||||
nvenc_sdk_version nvenc_dynamic_factory::sdk_version() const {
|
||||
return selected_sdk_version;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_dynamic_factory.h
|
||||
* @brief Declarations for runtime NVENC SDK selection on Windows.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
|
||||
// standard includes
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_d3d11_interface.h"
|
||||
#include "nvenc_shared_dll.h"
|
||||
#include "nvenc_version.h"
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Windows runtime operations used to discover the installed NVENC API.
|
||||
*/
|
||||
struct nvenc_runtime_api {
|
||||
using load_driver_fn = std::function<shared_dll()>; ///< Load the NVENC driver module.
|
||||
|
||||
/**
|
||||
* @brief Resolve an export from the loaded NVENC driver module.
|
||||
*/
|
||||
using get_symbol_fn = std::function<FARPROC(HMODULE, const char *)>;
|
||||
|
||||
load_driver_fn load_driver; ///< Function that loads the NVENC driver module.
|
||||
get_symbol_fn get_symbol; ///< Resolve a driver export.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Factory bound to the newest NVENC SDK supported by the installed driver.
|
||||
*/
|
||||
class nvenc_dynamic_factory {
|
||||
public:
|
||||
/**
|
||||
* @brief SDK-specific encoder constructor.
|
||||
*/
|
||||
using create_encoder_fn =
|
||||
std::function<std::unique_ptr<nvenc_d3d11_interface>(ID3D11Device *, shared_dll)>;
|
||||
|
||||
/**
|
||||
* @brief Construct a factory bound to one NVENC SDK implementation.
|
||||
*
|
||||
* @param dll Shared NVENC driver module.
|
||||
* @param sdk_version Selected SDK version.
|
||||
* @param create_native Native Direct3D11 encoder constructor.
|
||||
* @param create_on_cuda CUDA-interoperability encoder constructor.
|
||||
*/
|
||||
nvenc_dynamic_factory(
|
||||
shared_dll dll,
|
||||
nvenc_sdk_version sdk_version,
|
||||
create_encoder_fn create_native,
|
||||
create_encoder_fn create_on_cuda
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Load the NVENC driver and select a compatible SDK implementation.
|
||||
*
|
||||
* @return Initialized factory, or an empty pointer when NVENC is unavailable.
|
||||
*/
|
||||
static std::shared_ptr<nvenc_dynamic_factory> get();
|
||||
|
||||
/**
|
||||
* @brief Select a compatible SDK implementation using the supplied runtime operations.
|
||||
*
|
||||
* @param runtime_api Runtime operations used to load and query the NVENC driver.
|
||||
* @return Initialized factory, or an empty pointer when NVENC is unavailable.
|
||||
*/
|
||||
static std::shared_ptr<nvenc_dynamic_factory> get(const nvenc_runtime_api &runtime_api);
|
||||
|
||||
/**
|
||||
* @brief Create a native Direct3D11 NVENC encoder.
|
||||
*
|
||||
* @param d3d_device Direct3D11 device used for encoding.
|
||||
* @return SDK-neutral encoder instance.
|
||||
*/
|
||||
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_native(ID3D11Device *d3d_device) const;
|
||||
|
||||
/**
|
||||
* @brief Create a CUDA NVENC encoder with Direct3D11 input surfaces.
|
||||
*
|
||||
* @param d3d_device Direct3D11 device used to create input textures.
|
||||
* @return SDK-neutral encoder instance.
|
||||
*/
|
||||
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_on_cuda(ID3D11Device *d3d_device) const;
|
||||
|
||||
/**
|
||||
* @brief Get the SDK implementation selected for this factory.
|
||||
*
|
||||
* @return Selected NVENC SDK version.
|
||||
*/
|
||||
nvenc_sdk_version sdk_version() const;
|
||||
|
||||
private:
|
||||
shared_dll dll;
|
||||
nvenc_sdk_version selected_sdk_version;
|
||||
create_encoder_fn create_native;
|
||||
create_encoder_fn create_on_cuda;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_dynamic_factory_impl.cpp
|
||||
* @brief SDK-specific constructors used by the runtime NVENC factory.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
|
||||
#ifndef NVENC_NAMESPACE
|
||||
#error NVENC_NAMESPACE must identify the version-specific implementation namespace
|
||||
#endif
|
||||
|
||||
#ifndef NVENC_FACTORY_SUFFIX
|
||||
#error NVENC_FACTORY_SUFFIX must identify the version-specific constructor suffix
|
||||
#endif
|
||||
|
||||
// standard includes
|
||||
#include <utility>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_d3d11_native.h"
|
||||
#include "nvenc_d3d11_on_cuda.h"
|
||||
#include "nvenc_dynamic_factory_versions.h"
|
||||
|
||||
#ifndef DOXYGEN
|
||||
#define NVENC_CONCAT_IMPL(left, right) left##right
|
||||
#define NVENC_CONCAT(left, right) NVENC_CONCAT_IMPL(left, right)
|
||||
|
||||
namespace nvenc::detail {
|
||||
|
||||
std::unique_ptr<nvenc_d3d11_interface> NVENC_CONCAT(create_nvenc_d3d11_native_, NVENC_FACTORY_SUFFIX)(
|
||||
ID3D11Device *device,
|
||||
shared_dll dll
|
||||
) {
|
||||
return std::make_unique<NVENC_NAMESPACE::nvenc_d3d11_native>(device, std::move(dll));
|
||||
}
|
||||
|
||||
std::unique_ptr<nvenc_d3d11_interface> NVENC_CONCAT(create_nvenc_d3d11_on_cuda_, NVENC_FACTORY_SUFFIX)(
|
||||
ID3D11Device *device,
|
||||
shared_dll dll
|
||||
) {
|
||||
return std::make_unique<NVENC_NAMESPACE::nvenc_d3d11_on_cuda>(device, std::move(dll));
|
||||
}
|
||||
|
||||
} // namespace nvenc::detail
|
||||
|
||||
#undef NVENC_CONCAT
|
||||
#undef NVENC_CONCAT_IMPL
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_dynamic_factory_versions.h
|
||||
* @brief Declarations for SDK-specific NVENC encoder constructors.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
|
||||
// standard includes
|
||||
#include <memory>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_d3d11_interface.h"
|
||||
#include "nvenc_shared_dll.h"
|
||||
|
||||
namespace nvenc::detail {
|
||||
|
||||
/**
|
||||
* @brief Create an SDK 11.0 native Direct3D11 encoder.
|
||||
*
|
||||
* @param device Direct3D11 device used for encoding.
|
||||
* @param dll Shared NVENC driver module.
|
||||
* @return SDK-neutral encoder instance.
|
||||
*/
|
||||
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_native_1100(ID3D11Device *device, shared_dll dll);
|
||||
/**
|
||||
* @brief Create an SDK 11.0 CUDA-interoperability encoder.
|
||||
*
|
||||
* @param device Direct3D11 device used for input surfaces.
|
||||
* @param dll Shared NVENC driver module.
|
||||
* @return SDK-neutral encoder instance.
|
||||
*/
|
||||
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_on_cuda_1100(ID3D11Device *device, shared_dll dll);
|
||||
/**
|
||||
* @brief Create an SDK 12.0 native Direct3D11 encoder.
|
||||
*
|
||||
* @param device Direct3D11 device used for encoding.
|
||||
* @param dll Shared NVENC driver module.
|
||||
* @return SDK-neutral encoder instance.
|
||||
*/
|
||||
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_native_1200(ID3D11Device *device, shared_dll dll);
|
||||
/**
|
||||
* @brief Create an SDK 12.0 CUDA-interoperability encoder.
|
||||
*
|
||||
* @param device Direct3D11 device used for input surfaces.
|
||||
* @param dll Shared NVENC driver module.
|
||||
* @return SDK-neutral encoder instance.
|
||||
*/
|
||||
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_on_cuda_1200(ID3D11Device *device, shared_dll dll);
|
||||
/**
|
||||
* @brief Create an SDK 13.0 native Direct3D11 encoder.
|
||||
*
|
||||
* @param device Direct3D11 device used for encoding.
|
||||
* @param dll Shared NVENC driver module.
|
||||
* @return SDK-neutral encoder instance.
|
||||
*/
|
||||
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_native_1300(ID3D11Device *device, shared_dll dll);
|
||||
/**
|
||||
* @brief Create an SDK 13.0 CUDA-interoperability encoder.
|
||||
*
|
||||
* @param device Direct3D11 device used for input surfaces.
|
||||
* @param dll Shared NVENC driver module.
|
||||
* @return SDK-neutral encoder instance.
|
||||
*/
|
||||
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_on_cuda_1300(ID3D11Device *device, shared_dll dll);
|
||||
|
||||
} // namespace nvenc::detail
|
||||
#endif
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_encoder.h
|
||||
* @brief Declarations for the SDK-neutral NVENC encoder interface.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <cstdint>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_config.h"
|
||||
#include "nvenc_encoded_frame.h"
|
||||
|
||||
namespace platf {
|
||||
enum class pix_fmt_e;
|
||||
}
|
||||
|
||||
namespace video {
|
||||
struct config_t;
|
||||
struct sunshine_colorspace_t;
|
||||
} // namespace video
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief SDK-neutral standalone NVENC encoder interface.
|
||||
*/
|
||||
class nvenc_encoder {
|
||||
public:
|
||||
/**
|
||||
* @brief Destroy the SDK-neutral encoder interface.
|
||||
*/
|
||||
virtual ~nvenc_encoder() = default;
|
||||
|
||||
/**
|
||||
* @brief Create the encoder.
|
||||
*
|
||||
* @param config NVENC encoder configuration.
|
||||
* @param client_config Stream configuration requested by the client.
|
||||
* @param colorspace Sunshine colorspace metadata.
|
||||
* @param buffer_format Platform-agnostic input surface format.
|
||||
* @return `true` on success, `false` on error.
|
||||
*/
|
||||
virtual bool create_encoder(
|
||||
const nvenc_config &config,
|
||||
const video::config_t &client_config,
|
||||
const video::sunshine_colorspace_t &colorspace,
|
||||
platf::pix_fmt_e buffer_format
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* @brief Destroy the encoder.
|
||||
*/
|
||||
virtual void destroy_encoder() = 0;
|
||||
|
||||
/**
|
||||
* @brief Encode the next frame using the platform-specific input surface.
|
||||
*
|
||||
* @param frame_index Frame index that uniquely identifies the frame.
|
||||
* @param force_idr Whether to encode the frame as a forced IDR.
|
||||
* @return Encoded frame.
|
||||
*/
|
||||
virtual nvenc_encoded_frame encode_frame(std::uint64_t frame_index, bool force_idr) = 0;
|
||||
|
||||
/**
|
||||
* @brief Invalidate reference frames in the requested range.
|
||||
*
|
||||
* @param first_frame First frame index of the invalidation range.
|
||||
* @param last_frame Last frame index of the invalidation range.
|
||||
* @return `true` on success, `false` on error.
|
||||
*/
|
||||
virtual bool invalidate_ref_frames(std::uint64_t first_frame, std::uint64_t last_frame) = 0;
|
||||
};
|
||||
|
||||
} // namespace nvenc
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_sdk.h
|
||||
* @brief Imports one NVENC SDK into its version-specific implementation namespace.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef DOXYGEN
|
||||
/**
|
||||
* @def NVENC_NAMESPACE
|
||||
* @brief Namespace used for the documented NVENC implementation.
|
||||
*/
|
||||
#define NVENC_NAMESPACE nvenc
|
||||
|
||||
/**
|
||||
* @def NVENC_SDK_VERSION
|
||||
* @brief NVENC SDK version used while generating documentation.
|
||||
*/
|
||||
// Doxygen must select the same preprocessor interface used by versioned build targets.
|
||||
#define NVENC_SDK_VERSION 1300 // NOSONAR(cpp:S5028)
|
||||
#endif
|
||||
|
||||
#ifndef NVENC_NAMESPACE
|
||||
#error NVENC_NAMESPACE must identify the version-specific implementation namespace
|
||||
#endif
|
||||
|
||||
#ifndef NVENC_SDK_VERSION
|
||||
#error NVENC_SDK_VERSION must identify the version-specific NVENC SDK
|
||||
#endif
|
||||
|
||||
// Include common platform and integer declarations outside the SDK namespace.
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
// The version-specific namespace is populated by the SDK headers included below.
|
||||
namespace NVENC_NAMESPACE { // NOSONAR(cpp:S3261)
|
||||
#ifndef DOXYGEN
|
||||
#include <ffnvcodec/dynlink_cuda.h>
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
#if NVENCAPI_MAJOR_VERSION * 100 + NVENCAPI_MINOR_VERSION != NVENC_SDK_VERSION
|
||||
#error NVENC_SDK_VERSION does not match the selected nv-codec-headers package
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if NVENC_SDK_VERSION < 1200
|
||||
/**
|
||||
* @brief Video format values added as named types after SDK 11.0.
|
||||
*/
|
||||
enum NV_ENC_VUI_VIDEO_FORMAT {
|
||||
NV_ENC_VUI_VIDEO_FORMAT_COMPONENT = 0,
|
||||
NV_ENC_VUI_VIDEO_FORMAT_PAL = 1,
|
||||
NV_ENC_VUI_VIDEO_FORMAT_NTSC = 2,
|
||||
NV_ENC_VUI_VIDEO_FORMAT_SECAM = 3,
|
||||
NV_ENC_VUI_VIDEO_FORMAT_MAC = 4,
|
||||
NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED = 5,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Color-primary values added as named types after SDK 11.0.
|
||||
*/
|
||||
enum NV_ENC_VUI_COLOR_PRIMARIES {
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_UNDEFINED = 0,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_BT709 = 1,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_UNSPECIFIED = 2,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_RESERVED = 3,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_BT470M = 4,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_BT470BG = 5,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_SMPTE170M = 6,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_SMPTE240M = 7,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_FILM = 8,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_BT2020 = 9,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_SMPTE428 = 10,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_SMPTE431 = 11,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_SMPTE432 = 12,
|
||||
NV_ENC_VUI_COLOR_PRIMARIES_JEDEC_P22 = 22,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Transfer-characteristic values added as named types after SDK 11.0.
|
||||
*/
|
||||
enum NV_ENC_VUI_TRANSFER_CHARACTERISTIC {
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_UNDEFINED = 0,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT709 = 1,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_UNSPECIFIED = 2,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_RESERVED = 3,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT470M = 4,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT470BG = 5,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE170M = 6,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE240M = 7,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_LINEAR = 8,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_LOG = 9,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_LOG_SQRT = 10,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_IEC61966_2_4 = 11,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT1361_ECG = 12,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SRGB = 13,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_10 = 14,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_12 = 15,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084 = 16,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE428 = 17,
|
||||
NV_ENC_VUI_TRANSFER_CHARACTERISTIC_ARIB_STD_B67 = 18,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Matrix-coefficient values added as named types after SDK 11.0.
|
||||
*/
|
||||
enum NV_ENC_VUI_MATRIX_COEFFS {
|
||||
NV_ENC_VUI_MATRIX_COEFFS_RGB = 0,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_BT709 = 1,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_UNSPECIFIED = 2,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_RESERVED = 3,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_FCC = 4,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_BT470BG = 5,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_SMPTE170M = 6,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_SMPTE240M = 7,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_YCGCO = 8,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL = 9,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_BT2020_CL = 10,
|
||||
NV_ENC_VUI_MATRIX_COEFFS_SMPTE2085 = 11,
|
||||
};
|
||||
#endif
|
||||
} // namespace NVENC_NAMESPACE
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_shared_dll.h
|
||||
* @brief Windows module lifetime helpers shared by NVENC factories and encoders.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
|
||||
// standard includes
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
// platform includes
|
||||
#include <Windows.h>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief Shared ownership wrapper for a loaded Windows module.
|
||||
*/
|
||||
using shared_dll = std::shared_ptr<std::remove_pointer_t<HMODULE>>;
|
||||
|
||||
/**
|
||||
* @brief Release a Windows module when its final shared owner is destroyed.
|
||||
*/
|
||||
struct shared_dll_deleter {
|
||||
/**
|
||||
* @brief Release the module.
|
||||
*
|
||||
* @param dll Module handle to release.
|
||||
*/
|
||||
void operator()(HMODULE dll) const {
|
||||
if (dll) {
|
||||
FreeLibrary(dll);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wrap a Windows module handle in shared ownership.
|
||||
*
|
||||
* @param dll Module handle returned by `LoadLibraryEx()`.
|
||||
* @return Shared module handle.
|
||||
*/
|
||||
inline shared_dll make_shared_dll(HMODULE dll) {
|
||||
return shared_dll(dll, shared_dll_deleter {});
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
#endif
|
||||
@@ -8,7 +8,7 @@
|
||||
// local includes
|
||||
#include "nvenc_utils.h"
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
@@ -102,4 +102,4 @@ namespace nvenc {
|
||||
return colorspace;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
|
||||
@@ -9,15 +9,13 @@
|
||||
#include <dxgiformat.h>
|
||||
#endif
|
||||
|
||||
// lib includes
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
|
||||
// local includes
|
||||
#include "nvenc_colorspace.h"
|
||||
#include "nvenc_sdk.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/video_colorspace.h"
|
||||
|
||||
namespace nvenc {
|
||||
namespace NVENC_NAMESPACE {
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
@@ -45,4 +43,4 @@ namespace nvenc {
|
||||
*/
|
||||
nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace);
|
||||
|
||||
} // namespace nvenc
|
||||
} // namespace NVENC_NAMESPACE
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file src/nvenc/nvenc_version.h
|
||||
* @brief NVENC SDK version selection helpers.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
namespace nvenc {
|
||||
|
||||
/**
|
||||
* @brief NVENC SDK implementations compiled into Sunshine.
|
||||
*/
|
||||
enum class nvenc_sdk_version : std::uint32_t {
|
||||
unsupported = 0U, ///< No compatible SDK implementation is available.
|
||||
sdk_11_0 = 1100U, ///< Video Codec SDK 11.0.
|
||||
sdk_12_0 = 1200U, ///< Video Codec SDK 12.0.
|
||||
sdk_13_0 = 1300U, ///< Video Codec SDK 13.0.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Convert the packed driver API version to a comparable integer.
|
||||
*
|
||||
* @param version Version returned by `NvEncodeAPIGetMaxSupportedVersion()`.
|
||||
* @return Version encoded as `major * 100 + minor`.
|
||||
*/
|
||||
constexpr std::uint32_t decode_nvenc_driver_version(std::uint32_t version) {
|
||||
return (version >> 4U) * 100U + (version & 0x0FU);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Select the newest compiled SDK supported by the installed driver.
|
||||
*
|
||||
* @param max_version Maximum driver API version encoded as `major * 100 + minor`.
|
||||
* @return Selected SDK implementation, or `unsupported` when the driver is too old.
|
||||
*/
|
||||
constexpr nvenc_sdk_version select_nvenc_sdk_version(std::uint32_t max_version) {
|
||||
using enum nvenc_sdk_version;
|
||||
if (max_version >= std::to_underlying(sdk_13_0)) {
|
||||
return sdk_13_0;
|
||||
}
|
||||
if (max_version >= std::to_underlying(sdk_12_0)) {
|
||||
return sdk_12_0;
|
||||
}
|
||||
if (max_version >= std::to_underlying(sdk_11_0)) {
|
||||
return sdk_11_0;
|
||||
}
|
||||
return unsupported;
|
||||
}
|
||||
|
||||
} // namespace nvenc
|
||||
@@ -69,7 +69,7 @@ namespace video {
|
||||
} // namespace video
|
||||
|
||||
namespace nvenc {
|
||||
class nvenc_base;
|
||||
class nvenc_encoder;
|
||||
}
|
||||
|
||||
namespace platf {
|
||||
@@ -638,7 +638,7 @@ namespace platf {
|
||||
*/
|
||||
virtual bool init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0;
|
||||
|
||||
nvenc::nvenc_base *nvenc = nullptr; ///< NVENC encoder instance owned by the encode device.
|
||||
nvenc::nvenc_encoder *nvenc = nullptr; ///< NVENC encoder instance owned by the encode device.
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,9 +24,7 @@ extern "C" {
|
||||
#include "src/config.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/nvenc/nvenc_config.h"
|
||||
#include "src/nvenc/nvenc_d3d11_native.h"
|
||||
#include "src/nvenc/nvenc_d3d11_on_cuda.h"
|
||||
#include "src/nvenc/nvenc_utils.h"
|
||||
#include "src/nvenc/nvenc_dynamic_factory.h"
|
||||
#include "src/video.h"
|
||||
#include "utf_utils.h"
|
||||
|
||||
@@ -1240,21 +1238,25 @@ namespace platf::dxgi {
|
||||
* @return True when the D3D11 device resources are initialized.
|
||||
*/
|
||||
bool init_device(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
|
||||
buffer_format = nvenc::nvenc_format_from_sunshine_format(pix_fmt);
|
||||
if (buffer_format == NV_ENC_BUFFER_FORMAT_UNDEFINED) {
|
||||
BOOST_LOG(error) << "Unexpected pixel format for NvENC ["sv << from_pix_fmt(pix_fmt) << ']';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (base.init(display, adapter_p, pix_fmt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pix_fmt == pix_fmt_e::yuv444p16) {
|
||||
nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_on_cuda>(base.device.get());
|
||||
} else {
|
||||
nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_native>(base.device.get());
|
||||
auto factory = nvenc::nvenc_dynamic_factory::get();
|
||||
if (!factory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pix_fmt == pix_fmt_e::yuv444p16) {
|
||||
nvenc_d3d = factory->create_nvenc_d3d11_on_cuda(base.device.get());
|
||||
} else {
|
||||
nvenc_d3d = factory->create_nvenc_d3d11_native(base.device.get());
|
||||
}
|
||||
if (!nvenc_d3d) {
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer_format = pix_fmt;
|
||||
nvenc = nvenc_d3d.get();
|
||||
|
||||
return true;
|
||||
@@ -1272,8 +1274,7 @@ namespace platf::dxgi {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nvenc_colorspace = nvenc::nvenc_colorspace_from_sunshine_colorspace(colorspace);
|
||||
if (!nvenc_d3d->create_encoder(config::video.nv, client_config, nvenc_colorspace, buffer_format)) {
|
||||
if (!nvenc_d3d->create_encoder(config::video.nv, client_config, colorspace, buffer_format)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1293,8 +1294,8 @@ namespace platf::dxgi {
|
||||
|
||||
private:
|
||||
d3d_base_encode_device base;
|
||||
std::unique_ptr<nvenc::nvenc_d3d11> nvenc_d3d;
|
||||
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
|
||||
std::unique_ptr<nvenc::nvenc_d3d11_interface> nvenc_d3d;
|
||||
platf::pix_fmt_e buffer_format = platf::pix_fmt_e::unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+4
-1
@@ -18,6 +18,9 @@ extern "C" {
|
||||
#include <libavutil/mastering_display_metadata.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
#include <ffnvcodec/nvEncodeAPI.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
// local includes
|
||||
@@ -27,7 +30,7 @@ extern "C" {
|
||||
#include "globals.h"
|
||||
#include "input.h"
|
||||
#include "logging.h"
|
||||
#include "nvenc/nvenc_base.h"
|
||||
#include "nvenc/nvenc_encoder.h"
|
||||
#include "platform/common.h"
|
||||
#include "sync.h"
|
||||
#include "video.h"
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* @file tests/unit/test_nvenc_dynamic_factory.cpp
|
||||
* @brief Tests for the Windows runtime NVENC SDK factory.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
|
||||
// standard includes
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
// lib includes
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
// local includes
|
||||
#include "src/nvenc/nvenc_dynamic_factory.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::uint32_t reported_version; ///< Version returned by the fake NVENC driver.
|
||||
std::uint32_t reported_status; ///< Status returned by the fake NVENC driver.
|
||||
|
||||
/**
|
||||
* @brief Fake implementation of `NvEncodeAPIGetMaxSupportedVersion()`.
|
||||
*
|
||||
* @param version Receives the configured packed API version.
|
||||
* @return Configured NVENC status code.
|
||||
*/
|
||||
std::uint32_t WINAPI get_fake_max_supported_version(std::uint32_t *version) {
|
||||
*version = reported_version;
|
||||
return reported_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Minimal SDK-neutral encoder used to verify factory callbacks.
|
||||
*/
|
||||
class fake_nvenc_encoder final: public nvenc::nvenc_d3d11_interface {
|
||||
public:
|
||||
bool create_encoder(
|
||||
const nvenc::nvenc_config &,
|
||||
const video::config_t &,
|
||||
const video::sunshine_colorspace_t &,
|
||||
platf::pix_fmt_e
|
||||
) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy_encoder() override {
|
||||
}
|
||||
|
||||
nvenc::nvenc_encoded_frame encode_frame(std::uint64_t frame_index, bool force_idr) override {
|
||||
return {{}, frame_index, force_idr, false};
|
||||
}
|
||||
|
||||
bool invalidate_ref_frames(std::uint64_t, std::uint64_t) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
ID3D11Texture2D *get_input_texture() override {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Make a non-owning fake Windows module handle for factory discovery tests.
|
||||
*
|
||||
* @return Shared fake module handle.
|
||||
*/
|
||||
nvenc::shared_dll make_fake_dll() {
|
||||
constexpr std::uintptr_t fake_handle_value = 1U;
|
||||
return {
|
||||
reinterpret_cast<HMODULE>(fake_handle_value),
|
||||
[](HMODULE) {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Make runtime operations that expose the fake version query function.
|
||||
*
|
||||
* @return Runtime operations for a successfully loaded fake NVENC driver.
|
||||
*/
|
||||
nvenc::nvenc_runtime_api make_fake_runtime_api() {
|
||||
return {
|
||||
[]() {
|
||||
return make_fake_dll();
|
||||
},
|
||||
[](HMODULE dll, const char *symbol) {
|
||||
EXPECT_EQ(dll, make_fake_dll().get());
|
||||
EXPECT_EQ(std::string_view {symbol}, "NvEncodeAPIGetMaxSupportedVersion");
|
||||
return std::bit_cast<FARPROC>(&get_fake_max_supported_version);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
TEST(NvencDynamicFactoryTest, HandlesUnavailableDriver) {
|
||||
bool resolved_symbol = false;
|
||||
const nvenc::nvenc_runtime_api runtime_api {
|
||||
[]() {
|
||||
return nvenc::shared_dll {};
|
||||
},
|
||||
[&resolved_symbol](HMODULE, const char *) {
|
||||
resolved_symbol = true;
|
||||
return FARPROC {};
|
||||
},
|
||||
};
|
||||
|
||||
EXPECT_FALSE(nvenc::nvenc_dynamic_factory::get(runtime_api));
|
||||
EXPECT_FALSE(resolved_symbol);
|
||||
}
|
||||
|
||||
TEST(NvencDynamicFactoryTest, HandlesMissingVersionQuery) {
|
||||
const nvenc::nvenc_runtime_api runtime_api {
|
||||
[]() {
|
||||
return make_fake_dll();
|
||||
},
|
||||
[](HMODULE, const char *) {
|
||||
return FARPROC {};
|
||||
},
|
||||
};
|
||||
|
||||
EXPECT_FALSE(nvenc::nvenc_dynamic_factory::get(runtime_api));
|
||||
}
|
||||
|
||||
TEST(NvencDynamicFactoryTest, HandlesFailedVersionQuery) {
|
||||
reported_version = 1300U;
|
||||
reported_status = 1U;
|
||||
|
||||
EXPECT_FALSE(nvenc::nvenc_dynamic_factory::get(make_fake_runtime_api()));
|
||||
}
|
||||
|
||||
TEST(NvencDynamicFactoryTest, RejectsUnsupportedDriver) {
|
||||
reported_version = 10U << 4U;
|
||||
reported_status = 0U;
|
||||
|
||||
EXPECT_FALSE(nvenc::nvenc_dynamic_factory::get(make_fake_runtime_api()));
|
||||
}
|
||||
|
||||
TEST(NvencDynamicFactoryTest, SelectsSupportedSdkImplementations) {
|
||||
using enum nvenc::nvenc_sdk_version;
|
||||
constexpr std::array test_cases {
|
||||
std::pair {11U << 4U, sdk_11_0},
|
||||
std::pair {12U << 4U, sdk_12_0},
|
||||
std::pair {13U << 4U, sdk_13_0},
|
||||
std::pair {14U << 4U, sdk_13_0},
|
||||
};
|
||||
reported_status = 0U;
|
||||
|
||||
for (const auto &[version, expected] : test_cases) {
|
||||
reported_version = version;
|
||||
const auto factory = nvenc::nvenc_dynamic_factory::get(make_fake_runtime_api());
|
||||
ASSERT_TRUE(factory);
|
||||
EXPECT_EQ(factory->sdk_version(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NvencDynamicFactoryTest, UsesConfiguredEncoderConstructors) {
|
||||
bool created_native = false;
|
||||
bool created_on_cuda = false;
|
||||
const auto dll = make_fake_dll();
|
||||
nvenc::nvenc_dynamic_factory factory {
|
||||
dll,
|
||||
nvenc::nvenc_sdk_version::sdk_13_0,
|
||||
[&created_native, &dll](ID3D11Device *, nvenc::shared_dll callback_dll) {
|
||||
created_native = true;
|
||||
EXPECT_EQ(callback_dll, dll);
|
||||
return std::make_unique<fake_nvenc_encoder>();
|
||||
},
|
||||
[&created_on_cuda, &dll](ID3D11Device *, nvenc::shared_dll callback_dll) {
|
||||
created_on_cuda = true;
|
||||
EXPECT_EQ(callback_dll, dll);
|
||||
return std::make_unique<fake_nvenc_encoder>();
|
||||
},
|
||||
};
|
||||
|
||||
EXPECT_EQ(factory.sdk_version(), nvenc::nvenc_sdk_version::sdk_13_0);
|
||||
EXPECT_TRUE(factory.create_nvenc_d3d11_native(nullptr));
|
||||
EXPECT_TRUE(factory.create_nvenc_d3d11_on_cuda(nullptr));
|
||||
EXPECT_TRUE(created_native);
|
||||
EXPECT_TRUE(created_on_cuda);
|
||||
}
|
||||
|
||||
TEST(NvencDynamicFactoryTest, UsesDefaultWindowsRuntime) {
|
||||
const auto factory = nvenc::nvenc_dynamic_factory::get();
|
||||
EXPECT_TRUE(!factory || factory->sdk_version() != nvenc::nvenc_sdk_version::unsupported);
|
||||
}
|
||||
|
||||
TEST(NvencSharedDllTest, OwnsLoadedModuleUntilLastReference) {
|
||||
const auto handle = LoadLibraryEx("version.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
|
||||
auto dll = nvenc::make_shared_dll(handle);
|
||||
EXPECT_EQ(dll.get(), handle);
|
||||
auto copy = dll;
|
||||
EXPECT_EQ(copy.use_count(), 2);
|
||||
|
||||
dll.reset();
|
||||
EXPECT_EQ(copy.use_count(), 1);
|
||||
copy.reset();
|
||||
}
|
||||
|
||||
TEST(NvencSharedDllTest, AcceptsNullModule) {
|
||||
auto dll = nvenc::make_shared_dll(nullptr);
|
||||
EXPECT_FALSE(dll);
|
||||
dll.reset();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @file tests/unit/test_nvenc_version.cpp
|
||||
* @brief Tests for runtime NVENC SDK version selection.
|
||||
*/
|
||||
|
||||
// standard includes
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
// lib includes
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
// local includes
|
||||
#include "src/nvenc/nvenc_version.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief Expected SDK selection for a driver API version.
|
||||
*/
|
||||
struct nvenc_version_test_case {
|
||||
std::uint32_t max_version; ///< Maximum API version reported by the driver.
|
||||
nvenc::nvenc_sdk_version expected; ///< SDK implementation Sunshine should select.
|
||||
};
|
||||
|
||||
TEST(NvencVersionTest, DecodesPackedDriverVersion) {
|
||||
EXPECT_EQ(nvenc::decode_nvenc_driver_version((11U << 4U) | 0U), 1100U);
|
||||
EXPECT_EQ(nvenc::decode_nvenc_driver_version((13U << 4U) | 1U), 1301U);
|
||||
}
|
||||
|
||||
TEST(NvencVersionTest, SelectsNewestCompatibleSdk) {
|
||||
using enum nvenc::nvenc_sdk_version;
|
||||
constexpr std::array test_cases {
|
||||
nvenc_version_test_case {1000U, unsupported},
|
||||
nvenc_version_test_case {1099U, unsupported},
|
||||
nvenc_version_test_case {1100U, sdk_11_0},
|
||||
nvenc_version_test_case {1101U, sdk_11_0},
|
||||
nvenc_version_test_case {1199U, sdk_11_0},
|
||||
nvenc_version_test_case {1200U, sdk_12_0},
|
||||
nvenc_version_test_case {1201U, sdk_12_0},
|
||||
nvenc_version_test_case {1299U, sdk_12_0},
|
||||
nvenc_version_test_case {1300U, sdk_13_0},
|
||||
nvenc_version_test_case {1301U, sdk_13_0},
|
||||
nvenc_version_test_case {1400U, sdk_13_0},
|
||||
};
|
||||
|
||||
for (const auto &[max_version, expected] : test_cases) {
|
||||
EXPECT_EQ(nvenc::select_nvenc_sdk_version(max_version), expected);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user