577 lines
16 KiB
C++
577 lines
16 KiB
C++
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <chrono>
|
|
#include <future>
|
|
#include <cstring>
|
|
|
|
#include <ffnvcodec/nvEncodeAPI.h>
|
|
#include <ffnvcodec/dynlink_loader.h>
|
|
|
|
/*
|
|
* Utility to check for NVENC support and capabilities.
|
|
* Will check all GPUs and return INI-formatted results based on highest capability of all devices.
|
|
*/
|
|
|
|
using namespace std;
|
|
using namespace std::chrono_literals;
|
|
|
|
static CudaFunctions *cu = nullptr;
|
|
static NvencFunctions *nvenc = nullptr;
|
|
|
|
NV_ENCODE_API_FUNCTION_LIST nv = {NV_ENCODE_API_FUNCTION_LIST_VER};
|
|
static constexpr uint32_t NVENC_CONFIGURED_VERSION = (NVENCAPI_MAJOR_VERSION << 4) | NVENCAPI_MINOR_VERSION;
|
|
|
|
/* NVML stuff */
|
|
#define NVML_SUCCESS 0
|
|
#define NVML_DEVICE_UUID_V2_BUFFER_SIZE 96
|
|
#define NVML_DEVICE_NAME_V2_BUFFER_SIZE 96
|
|
#define NVML_SYSTEM_DRIVER_VERSION_BUFFER_SIZE 80
|
|
|
|
typedef int nvmlReturn_t;
|
|
typedef struct nvmlDevice *nvmlDevice_t;
|
|
|
|
typedef enum nvmlEncoderType {
|
|
NVML_ENCODER_QUERY_H264,
|
|
NVML_ENCODER_QUERY_HEVC,
|
|
NVML_ENCODER_QUERY_AV1,
|
|
NVML_ENCODER_QUERY_UNKNOWN
|
|
} nvmlEncoderType_t;
|
|
|
|
typedef nvmlReturn_t (*NVML_GET_DRIVER_VER_FUNC)(char *, unsigned int);
|
|
typedef nvmlReturn_t (*NVML_INIT_V2)();
|
|
typedef nvmlReturn_t (*NVML_SHUTDOWN)();
|
|
typedef nvmlReturn_t (*NVML_GET_HANDLE_BY_BUS_ID)(const char *, nvmlDevice_t *);
|
|
typedef nvmlReturn_t (*NVML_GET_DEVICE_UUID)(nvmlDevice_t, char *, unsigned);
|
|
typedef nvmlReturn_t (*NVML_GET_DEVICE_NAME)(nvmlDevice_t, char *, unsigned);
|
|
typedef nvmlReturn_t (*NVML_GET_DEVICE_PCIE_GEN)(nvmlDevice_t, unsigned *);
|
|
typedef nvmlReturn_t (*NVML_GET_DEVICE_PCIE_WIDTH)(nvmlDevice_t, unsigned *);
|
|
typedef nvmlReturn_t (*NVML_GET_DEVICE_NAME)(nvmlDevice_t, char *, unsigned);
|
|
typedef nvmlReturn_t (*NVML_GET_DEVICE_ARCHITECTURE)(nvmlDevice_t, unsigned *);
|
|
typedef nvmlReturn_t (*NVML_GET_ENCODER_SESSIONS)(nvmlDevice_t, unsigned *, void *);
|
|
typedef nvmlReturn_t (*NVML_GET_ENCODER_CAPACITY)(nvmlDevice_t, nvmlEncoderType, unsigned *);
|
|
typedef nvmlReturn_t (*NVML_GET_ENCODER_UTILISATION)(nvmlDevice_t, unsigned *, unsigned *);
|
|
|
|
/* Only Kepler is defined in NVIDIA's documentation, but it's also the main one we care about. */
|
|
constexpr uint32_t NVML_DEVICE_ARCH_KEPLER = 2;
|
|
|
|
const unordered_map<uint32_t, const string_view> arch_to_name = {
|
|
{NVML_DEVICE_ARCH_KEPLER, "Kepler"},
|
|
{3, "Kepler"},
|
|
{4, "Maxwell"},
|
|
{5, "Volta"},
|
|
{6, "Turing"},
|
|
{7, "Ampere"},
|
|
{8, "Ada"},
|
|
{9, "Hopper"},
|
|
{10, "Blackwell"},
|
|
};
|
|
|
|
/* List of capabilities to be queried per codec */
|
|
static const vector<pair<NV_ENC_CAPS, string>> capabilities = {
|
|
{NV_ENC_CAPS_NUM_MAX_BFRAMES, "bframes"},
|
|
{NV_ENC_CAPS_SUPPORT_LOSSLESS_ENCODE, "lossless"},
|
|
{NV_ENC_CAPS_SUPPORT_LOOKAHEAD, "lookahead"},
|
|
{NV_ENC_CAPS_SUPPORT_TEMPORAL_AQ, "temporal_aq"},
|
|
{NV_ENC_CAPS_SUPPORT_DYN_BITRATE_CHANGE, "dynamic_bitrate"},
|
|
{NV_ENC_CAPS_SUPPORT_10BIT_ENCODE, "10bit"},
|
|
{NV_ENC_CAPS_SUPPORT_BFRAME_REF_MODE, "bref"},
|
|
{NV_ENC_CAPS_NUM_ENCODER_ENGINES, "engines"},
|
|
{NV_ENC_CAPS_SUPPORT_YUV444_ENCODE, "yuv_444"},
|
|
{NV_ENC_CAPS_WIDTH_MAX, "max_width"},
|
|
{NV_ENC_CAPS_HEIGHT_MAX, "max_height"},
|
|
#if NVENCAPI_MAJOR_VERSION > 12 || NVENCAPI_MINOR_VERSION >= 2
|
|
/* SDK 12.2+ features */
|
|
{NV_ENC_CAPS_SUPPORT_TEMPORAL_FILTER, "temporal_filter"},
|
|
{NV_ENC_CAPS_SUPPORT_LOOKAHEAD_LEVEL, "lookahead_level"},
|
|
{NV_ENC_CAPS_SUPPORT_UNIDIRECTIONAL_B, "unidirectional_b"},
|
|
#endif
|
|
#if NVENCAPI_MAJOR_VERSION >= 13
|
|
/* SDK 13.0+ features */
|
|
{NV_ENC_CAPS_SUPPORT_YUV422_ENCODE, "yuv_422"},
|
|
#endif
|
|
};
|
|
|
|
static const vector<pair<string_view, GUID>> codecs = {{"h264", NV_ENC_CODEC_H264_GUID},
|
|
{"hevc", NV_ENC_CODEC_HEVC_GUID},
|
|
{"av1", NV_ENC_CODEC_AV1_GUID}};
|
|
|
|
typedef unordered_map<string, unordered_map<string, int>> codec_caps_map;
|
|
|
|
struct device_info {
|
|
string pci_id;
|
|
string nvml_uuid;
|
|
string cuda_uuid;
|
|
string name;
|
|
|
|
uint32_t architecture;
|
|
uint32_t pcie_gen;
|
|
uint32_t pcie_width;
|
|
|
|
uint32_t encoder_sessions;
|
|
uint32_t utilisation;
|
|
uint32_t sample_period;
|
|
uint32_t capacity_h264;
|
|
uint32_t capacity_hevc;
|
|
uint32_t capacity_av1;
|
|
|
|
codec_caps_map caps;
|
|
};
|
|
|
|
/* RAII wrappers to make my life a little easier. */
|
|
struct NVML {
|
|
NVML_INIT_V2 init;
|
|
NVML_SHUTDOWN shutdown;
|
|
NVML_GET_DRIVER_VER_FUNC getDriverVersion;
|
|
NVML_GET_HANDLE_BY_BUS_ID getDeviceHandleByPCIBusId;
|
|
NVML_GET_DEVICE_UUID getDeviceUUID;
|
|
NVML_GET_DEVICE_NAME getDeviceName;
|
|
NVML_GET_DEVICE_PCIE_GEN getDevicePCIeGen;
|
|
NVML_GET_DEVICE_PCIE_WIDTH getDevicePCIeWidth;
|
|
NVML_GET_DEVICE_ARCHITECTURE getDeviceArchitecture;
|
|
NVML_GET_ENCODER_SESSIONS getEncoderSessions;
|
|
NVML_GET_ENCODER_CAPACITY getEncoderCapacity;
|
|
NVML_GET_ENCODER_UTILISATION getEncoderUtilisation;
|
|
|
|
NVML() = default;
|
|
|
|
~NVML()
|
|
{
|
|
if (initialised && shutdown) {
|
|
shutdown();
|
|
}
|
|
}
|
|
|
|
bool Init()
|
|
{
|
|
if (!load_nvml_lib()) {
|
|
printf("reason=nvml_lib\n");
|
|
return false;
|
|
}
|
|
|
|
init = (NVML_INIT_V2)load_nvml_func("nvmlInit_v2");
|
|
shutdown = (NVML_SHUTDOWN)load_nvml_func("nvmlShutdown");
|
|
getDriverVersion = (NVML_GET_DRIVER_VER_FUNC)load_nvml_func("nvmlSystemGetDriverVersion");
|
|
getDeviceHandleByPCIBusId =
|
|
(NVML_GET_HANDLE_BY_BUS_ID)load_nvml_func("nvmlDeviceGetHandleByPciBusId_v2");
|
|
getDeviceUUID = (NVML_GET_DEVICE_UUID)load_nvml_func("nvmlDeviceGetUUID");
|
|
getDeviceName = (NVML_GET_DEVICE_NAME)load_nvml_func("nvmlDeviceGetName");
|
|
getDevicePCIeGen = (NVML_GET_DEVICE_PCIE_GEN)load_nvml_func("nvmlDeviceGetCurrPcieLinkGeneration");
|
|
getDevicePCIeWidth = (NVML_GET_DEVICE_PCIE_WIDTH)load_nvml_func("nvmlDeviceGetCurrPcieLinkWidth");
|
|
getDeviceArchitecture = (NVML_GET_DEVICE_ARCHITECTURE)load_nvml_func("nvmlDeviceGetArchitecture");
|
|
getEncoderSessions = (NVML_GET_ENCODER_SESSIONS)load_nvml_func("nvmlDeviceGetEncoderSessions");
|
|
getEncoderCapacity = (NVML_GET_ENCODER_CAPACITY)load_nvml_func("nvmlDeviceGetEncoderCapacity");
|
|
getEncoderUtilisation = (NVML_GET_ENCODER_UTILISATION)load_nvml_func("nvmlDeviceGetEncoderUtilization");
|
|
|
|
if (!init || !shutdown || !getDriverVersion || !getDeviceHandleByPCIBusId || !getDeviceUUID ||
|
|
!getDeviceName || !getDevicePCIeGen || !getDevicePCIeWidth || !getEncoderSessions ||
|
|
!getEncoderCapacity || !getEncoderUtilisation || !getDeviceArchitecture) {
|
|
return false;
|
|
}
|
|
|
|
nvmlReturn_t res = init();
|
|
if (res != 0) {
|
|
printf("reason=nvml_init_%d\n", res);
|
|
return false;
|
|
}
|
|
|
|
initialised = true;
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
bool initialised = false;
|
|
static inline void *nvml_lib = nullptr;
|
|
|
|
bool load_nvml_lib()
|
|
{
|
|
#ifdef _WIN32
|
|
nvml_lib = LoadLibraryA("nvml.dll");
|
|
#else
|
|
nvml_lib = dlopen("libnvidia-ml.so.1", RTLD_LAZY);
|
|
#endif
|
|
return nvml_lib != nullptr;
|
|
}
|
|
|
|
static void *load_nvml_func(const char *func)
|
|
{
|
|
#ifdef _WIN32
|
|
void *func_ptr = (void *)GetProcAddress((HMODULE)nvml_lib, func);
|
|
#else
|
|
void *func_ptr = dlsym(nvml_lib, func);
|
|
#endif
|
|
return func_ptr;
|
|
}
|
|
};
|
|
|
|
struct CUDACtx {
|
|
CUcontext ctx;
|
|
|
|
CUDACtx() = default;
|
|
|
|
~CUDACtx() { cu->cuCtxDestroy(ctx); }
|
|
|
|
bool Init(int adapter_idx)
|
|
{
|
|
CUdevice dev;
|
|
if (cu->cuDeviceGet(&dev, adapter_idx) != CUDA_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
return cu->cuCtxCreate(&ctx, 0, dev) == CUDA_SUCCESS;
|
|
}
|
|
|
|
string GetPCIBusId()
|
|
{
|
|
CUdevice dev;
|
|
string bus_id;
|
|
bus_id.resize(16);
|
|
|
|
cu->cuCtxGetDevice(&dev);
|
|
cu->cuDeviceGetPCIBusId(bus_id.data(), (int)bus_id.capacity(), dev);
|
|
return bus_id;
|
|
}
|
|
|
|
string GetUUID()
|
|
{
|
|
CUdevice dev;
|
|
CUuuid uuid;
|
|
string uuid_str;
|
|
|
|
cu->cuCtxGetDevice(&dev);
|
|
cu->cuDeviceGetUuid_v2(&uuid, dev);
|
|
|
|
uuid_str.resize(32);
|
|
for (size_t idx = 0; idx < 16; idx++) {
|
|
sprintf(uuid_str.data() + idx * 2, "%02x", uuid.bytes[idx] & 0xFF);
|
|
}
|
|
|
|
return uuid_str;
|
|
}
|
|
};
|
|
|
|
struct NVSession {
|
|
void *ptr = nullptr;
|
|
|
|
NVSession() = default;
|
|
|
|
~NVSession() { nv.nvEncDestroyEncoder(ptr); }
|
|
|
|
NVENCSTATUS OpenSession(const CUDACtx &ctx)
|
|
{
|
|
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS params = {};
|
|
params.version = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER;
|
|
params.apiVersion = NVENCAPI_VERSION;
|
|
params.device = ctx.ctx;
|
|
params.deviceType = NV_ENC_DEVICE_TYPE_CUDA;
|
|
|
|
return nv.nvEncOpenEncodeSessionEx(¶ms, &ptr);
|
|
}
|
|
};
|
|
|
|
static bool init_nvenc()
|
|
{
|
|
if (nvenc_load_functions(&nvenc, nullptr)) {
|
|
printf("reason=nvenc_lib\n");
|
|
return false;
|
|
}
|
|
|
|
NVENCSTATUS res = nvenc->NvEncodeAPICreateInstance(&nv);
|
|
if (res != NV_ENC_SUCCESS) {
|
|
printf("reason=nvenc_init_%d\n", res);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool init_cuda()
|
|
{
|
|
if (cuda_load_functions(&cu, nullptr)) {
|
|
printf("reason=cuda_lib\n");
|
|
return false;
|
|
}
|
|
|
|
CUresult res = cu->cuInit(0);
|
|
if (res != CUDA_SUCCESS) {
|
|
printf("reason=cuda_init_%d\n", res);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool get_adapter_caps(int adapter_idx, codec_caps_map &caps, device_info &device_info, NVML &nvml,
|
|
bool &session_limit)
|
|
{
|
|
CUDACtx cudaCtx;
|
|
NVSession nvSession;
|
|
|
|
if (!cudaCtx.Init(adapter_idx)) {
|
|
return false;
|
|
}
|
|
|
|
device_info.pci_id = cudaCtx.GetPCIBusId();
|
|
device_info.cuda_uuid = cudaCtx.GetUUID();
|
|
|
|
nvmlDevice_t dev;
|
|
if (nvml.getDeviceHandleByPCIBusId(device_info.pci_id.data(), &dev) == NVML_SUCCESS) {
|
|
char uuid[NVML_DEVICE_UUID_V2_BUFFER_SIZE];
|
|
nvml.getDeviceUUID(dev, uuid, sizeof(uuid));
|
|
device_info.nvml_uuid = uuid;
|
|
|
|
char name[NVML_DEVICE_NAME_V2_BUFFER_SIZE];
|
|
nvml.getDeviceName(dev, name, sizeof(name));
|
|
device_info.name = name;
|
|
|
|
nvml.getDevicePCIeGen(dev, &device_info.pcie_gen);
|
|
nvml.getDevicePCIeWidth(dev, &device_info.pcie_width);
|
|
nvml.getEncoderSessions(dev, &device_info.encoder_sessions, nullptr);
|
|
nvml.getDeviceArchitecture(dev, &device_info.architecture);
|
|
nvml.getEncoderUtilisation(dev, &device_info.utilisation, &device_info.sample_period);
|
|
nvml.getEncoderCapacity(dev, NVML_ENCODER_QUERY_H264, &device_info.capacity_h264);
|
|
nvml.getEncoderCapacity(dev, NVML_ENCODER_QUERY_HEVC, &device_info.capacity_hevc);
|
|
nvml.getEncoderCapacity(dev, NVML_ENCODER_QUERY_AV1, &device_info.capacity_av1);
|
|
}
|
|
|
|
auto res = nvSession.OpenSession(cudaCtx);
|
|
session_limit = session_limit || res == NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY;
|
|
if (res != NV_ENC_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t guid_count = 0;
|
|
if (nv.nvEncGetEncodeGUIDCount(nvSession.ptr, &guid_count) != NV_ENC_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
vector<GUID> guids;
|
|
guids.resize(guid_count);
|
|
NVENCSTATUS stat = nv.nvEncGetEncodeGUIDs(nvSession.ptr, guids.data(), guid_count, &guid_count);
|
|
if (stat != NV_ENC_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
NV_ENC_CAPS_PARAM param = {NV_ENC_CAPS_PARAM_VER};
|
|
|
|
for (uint32_t i = 0; i < guid_count; i++) {
|
|
GUID *guid = &guids[i];
|
|
|
|
std::string codec_name = "unknown";
|
|
for (const auto &[name, codec_guid] : codecs) {
|
|
if (memcmp(&codec_guid, guid, sizeof(GUID)) == 0) {
|
|
codec_name = name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
caps[codec_name]["codec_supported"] = 1;
|
|
device_info.caps[codec_name]["codec_supported"] = 1;
|
|
|
|
for (const auto &[cap, name] : capabilities) {
|
|
int v;
|
|
param.capsToQuery = cap;
|
|
if (nv.nvEncGetEncodeCaps(nvSession.ptr, *guid, ¶m, &v) != NV_ENC_SUCCESS) {
|
|
continue;
|
|
}
|
|
|
|
device_info.caps[codec_name][name] = v;
|
|
caps[codec_name][name] = std::max(v, caps[codec_name][name]);
|
|
}
|
|
|
|
#if NVENCAPI_MAJOR_VERSION > 12 || NVENCAPI_MINOR_VERSION >= 2
|
|
/* Explicitly check if UHQ tuning is supported since temporal filtering query is true for all codecs. */
|
|
NV_ENC_PRESET_CONFIG preset_config = {};
|
|
preset_config.version = NV_ENC_PRESET_CONFIG_VER;
|
|
preset_config.presetCfg.version = NV_ENC_CONFIG_VER;
|
|
|
|
NVENCSTATUS res = nv.nvEncGetEncodePresetConfigEx(nvSession.ptr, *guid, NV_ENC_PRESET_P7_GUID,
|
|
NV_ENC_TUNING_INFO_ULTRA_HIGH_QUALITY,
|
|
&preset_config);
|
|
|
|
device_info.caps[codec_name]["uhq"] = res == NV_ENC_SUCCESS ? 1 : 0;
|
|
caps[codec_name]["uhq"] = std::max(device_info.caps[codec_name]["uhq"], caps[codec_name]["uhq"]);
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool nvenc_checks(codec_caps_map &caps, vector<device_info> &device_infos)
|
|
{
|
|
/* NVENC API init */
|
|
if (!init_nvenc()) {
|
|
return false;
|
|
}
|
|
|
|
/* CUDA init */
|
|
if (!init_cuda()) {
|
|
return false;
|
|
}
|
|
|
|
NVML nvml;
|
|
if (!nvml.Init()) {
|
|
return false;
|
|
}
|
|
|
|
/* --------------------------------------------------------- */
|
|
/* obtain adapter compatibility information */
|
|
|
|
uint32_t nvenc_ver;
|
|
int cuda_driver_ver;
|
|
int cuda_devices = 0;
|
|
int nvenc_devices = 0;
|
|
char driver_ver[NVML_SYSTEM_DRIVER_VERSION_BUFFER_SIZE];
|
|
bool session_limit = false;
|
|
|
|
/* NVIDIA driver version */
|
|
if (nvml.getDriverVersion(driver_ver, sizeof(driver_ver)) == NVML_SUCCESS) {
|
|
printf("driver_ver=%s\n", driver_ver);
|
|
} else {
|
|
// Treat this as a non-fatal failure
|
|
printf("driver_ver=0.0\n");
|
|
}
|
|
|
|
/* CUDA driver version and devices */
|
|
if (cu->cuDriverGetVersion(&cuda_driver_ver) == CUDA_SUCCESS) {
|
|
printf("cuda_ver=%d.%d\n", cuda_driver_ver / 1000, cuda_driver_ver % 1000);
|
|
} else {
|
|
printf("reason=no_cuda_version\n");
|
|
return false;
|
|
}
|
|
|
|
if (cu->cuDeviceGetCount(&cuda_devices) == CUDA_SUCCESS && cuda_devices) {
|
|
printf("cuda_devices=%d\n", cuda_devices);
|
|
} else {
|
|
printf("reason=no_devices\n");
|
|
return false;
|
|
}
|
|
|
|
/* NVENC API version */
|
|
if (nvenc->NvEncodeAPIGetMaxSupportedVersion(&nvenc_ver) == NV_ENC_SUCCESS) {
|
|
printf("nvenc_ver=%d.%d\n", nvenc_ver >> 4, nvenc_ver & 0xf);
|
|
} else {
|
|
printf("reason=no_nvenc_version\n");
|
|
return false;
|
|
}
|
|
|
|
device_infos.resize(cuda_devices);
|
|
for (int idx = 0; idx < cuda_devices; idx++) {
|
|
if (get_adapter_caps(idx, caps, device_infos[idx], nvml, session_limit)) {
|
|
nvenc_devices++;
|
|
}
|
|
}
|
|
|
|
if (session_limit) {
|
|
printf("reason=session_limit\n");
|
|
return false;
|
|
}
|
|
|
|
if (nvenc_ver < NVENC_CONFIGURED_VERSION) {
|
|
printf("reason=outdated_driver\n");
|
|
return false;
|
|
}
|
|
|
|
printf("nvenc_devices=%d\n", nvenc_devices);
|
|
if (!nvenc_devices) {
|
|
printf("reason=no_supported_devices\n");
|
|
return false;
|
|
}
|
|
|
|
uint32_t latest_architecture = 0;
|
|
string_view architecture = "Unknown";
|
|
|
|
for (auto &info : device_infos) {
|
|
latest_architecture = std::max(info.architecture, latest_architecture);
|
|
}
|
|
|
|
if (arch_to_name.count(latest_architecture)) {
|
|
architecture = arch_to_name.at(latest_architecture);
|
|
}
|
|
|
|
printf("latest_architecture=%u\n"
|
|
"latest_architecture_name=%s\n",
|
|
latest_architecture, architecture.data());
|
|
|
|
return true;
|
|
}
|
|
|
|
int check_thread()
|
|
{
|
|
int ret = 0;
|
|
codec_caps_map caps;
|
|
vector<device_info> device_infos;
|
|
|
|
caps["h264"]["codec_supported"] = 0;
|
|
caps["hevc"]["codec_supported"] = 0;
|
|
caps["av1"]["codec_supported"] = 0;
|
|
|
|
printf("[general]\n");
|
|
|
|
if (nvenc_checks(caps, device_infos)) {
|
|
printf("nvenc_supported=true\n");
|
|
} else {
|
|
printf("nvenc_supported=false\n");
|
|
ret = 1;
|
|
}
|
|
|
|
/* Global capabilities, based on highest supported across all devices */
|
|
for (const auto &[codec, codec_caps] : caps) {
|
|
printf("\n[%s]\n", codec.c_str());
|
|
|
|
for (const auto &[name, value] : codec_caps) {
|
|
printf("%s=%d\n", name.c_str(), value);
|
|
}
|
|
}
|
|
|
|
/* Per-device info (mostly for debugging) */
|
|
for (size_t idx = 0; idx < device_infos.size(); idx++) {
|
|
const auto &info = device_infos[idx];
|
|
string_view architecture = "Unknown";
|
|
if (arch_to_name.count(info.architecture)) {
|
|
architecture = arch_to_name.at(info.architecture);
|
|
}
|
|
|
|
printf("\n[device.%zu]\n"
|
|
"pci_id=%s\n"
|
|
"nvml_uuid=%s\n"
|
|
"cuda_uuid=%s\n"
|
|
"name=%s\n"
|
|
"architecture=%u\n"
|
|
"architecture_name=%s\n"
|
|
"pcie_link_width=%d\n"
|
|
"pcie_link_gen=%d\n"
|
|
"encoder_sessions=%u\n"
|
|
"utilisation=%u\n"
|
|
"sample_period=%u\n"
|
|
"capacity_h264=%u\n"
|
|
"capacity_hevc=%u\n"
|
|
"capacity_av1=%u\n",
|
|
idx, info.pci_id.c_str(), info.nvml_uuid.c_str(), info.cuda_uuid.c_str(), info.name.c_str(),
|
|
info.architecture, architecture.data(), info.pcie_width, info.pcie_gen, info.encoder_sessions,
|
|
info.utilisation, info.sample_period, info.capacity_h264, info.capacity_hevc, info.capacity_av1);
|
|
|
|
for (const auto &[codec, codec_caps] : info.caps) {
|
|
printf("\n[device.%zu.%s]\n", idx, codec.c_str());
|
|
|
|
for (const auto &[name, value] : codec_caps) {
|
|
printf("%s=%d\n", name.c_str(), value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int main(int, char **)
|
|
{
|
|
future<int> f = async(launch::async, check_thread);
|
|
future_status status = f.wait_for(2.5s);
|
|
|
|
if (status == future_status::timeout) {
|
|
exit(1);
|
|
}
|
|
|
|
return f.get();
|
|
}
|