feat(video2x): added the --listgpus option and GPU ID validation

Signed-off-by: k4yt3x <i@k4yt3x.com>
This commit is contained in:
k4yt3x 2024-11-04 00:00:00 +00:00
parent 850e0fde9c
commit bcbe33d5dc
No known key found for this signature in database
5 changed files with 191 additions and 51 deletions

View File

@ -282,6 +282,11 @@ else()
endif()
set(BOOST_LIB Boost::program_options)
if (BUILD_VIDEO2X_CLI)
find_package(Vulkan REQUIRED)
set(VULKAN_LIB Vulkan::Vulkan)
endif()
# Include ExternalProject module
include(ExternalProject)
@ -374,7 +379,13 @@ if (BUILD_VIDEO2X_CLI)
target_compile_options(video2x PRIVATE $<$<CONFIG:Debug>:-g -DDEBUG>)
# Link the executable with the shared library
target_link_libraries(video2x PRIVATE libvideo2x ${FFMPEG_LIB} ${SPDLOG_LIB} ${BOOST_LIB})
target_link_libraries(video2x PRIVATE
libvideo2x
${FFMPEG_LIB}
${SPDLOG_LIB}
${BOOST_LIB}
${VULKAN_LIB}
)
endif()
# Define the default installation directories

View File

@ -56,7 +56,6 @@ struct LibplaceboConfig {
// Configuration for RealESRGAN filter
struct RealESRGANConfig {
int gpuid;
bool tta_mode;
int scaling_factor;
const CharType *model_name;
@ -100,6 +99,7 @@ struct VideoProcessingContext {
* @param[in] out_fname Path to the output video file
* @param[in] log_level Log level
* @param[in] benchmark Flag to enable benchmarking mode
* @param[in] vk_device_index Vulkan device index
* @param[in] hw_type Hardware device type
* @param[in] filter_config Filter configurations
* @param[in] encoder_config Encoder configurations
@ -111,6 +111,7 @@ LIBVIDEO2X_API int process_video(
const CharType *out_fname,
enum Libvideo2xLogLevel log_level,
bool benchmark,
uint32_t vk_device_index,
enum AVHWDeviceType hw_device_type,
const struct FilterConfig *filter_config,
struct EncoderConfig *encoder_config,

View File

@ -92,7 +92,7 @@ static int process_frames(
AVPacket *packet = av_packet_alloc();
if (packet == nullptr) {
spdlog::error("Could not allocate AVPacket");
spdlog::critical("Could not allocate AVPacket");
av_frame_free(&frame);
return AVERROR(ENOMEM);
}
@ -118,7 +118,7 @@ static int process_frames(
break;
}
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error reading packet: {}", errbuf);
spdlog::critical("Error reading packet: {}", errbuf);
cleanup();
return ret;
}
@ -127,7 +127,7 @@ static int process_frames(
ret = avcodec_send_packet(dec_ctx, packet);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error sending packet to decoder: {}", errbuf);
spdlog::critical("Error sending packet to decoder: {}", errbuf);
av_packet_unref(packet);
cleanup();
return ret;
@ -145,7 +145,7 @@ static int process_frames(
break;
} else if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error decoding video frame: {}", errbuf);
spdlog::critical("Error decoding video frame: {}", errbuf);
av_packet_unref(packet);
cleanup();
return ret;
@ -164,7 +164,7 @@ static int process_frames(
ret = write_frame(processed_frame, enc_ctx, ofmt_ctx, vstream_idx);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error encoding/writing frame: {}", errbuf);
spdlog::critical("Error encoding/writing frame: {}", errbuf);
av_frame_free(&processed_frame);
av_packet_unref(packet);
cleanup();
@ -204,7 +204,7 @@ static int process_frames(
ret = filter->flush(flushed_frames);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error flushing filter: {}", errbuf);
spdlog::critical("Error flushing filter: {}", errbuf);
cleanup();
return ret;
}
@ -214,7 +214,7 @@ static int process_frames(
ret = write_frame(flushed_frame, enc_ctx, ofmt_ctx, vstream_idx);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error encoding/writing flushed frame: {}", errbuf);
spdlog::critical("Error encoding/writing flushed frame: {}", errbuf);
av_frame_free(&flushed_frame);
flushed_frame = nullptr;
cleanup();
@ -229,7 +229,7 @@ static int process_frames(
ret = flush_encoder(enc_ctx, ofmt_ctx);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error flushing encoder: {}", errbuf);
spdlog::critical("Error flushing encoder: {}", errbuf);
cleanup();
return ret;
}
@ -243,6 +243,7 @@ extern "C" int process_video(
const CharType *out_fname,
Libvideo2xLogLevel log_level,
bool benchmark,
uint32_t vk_device_index,
AVHWDeviceType hw_type,
const FilterConfig *filter_config,
EncoderConfig *encoder_config,
@ -340,7 +341,7 @@ extern "C" int process_video(
ret = av_hwdevice_ctx_create(&hw_ctx, hw_type, NULL, NULL, 0);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error initializing hardware device context: {}", errbuf);
spdlog::critical("Error initializing hardware device context: {}", errbuf);
cleanup();
return ret;
}
@ -350,7 +351,7 @@ extern "C" int process_video(
ret = init_decoder(hw_type, hw_ctx, in_fpath, &ifmt_ctx, &dec_ctx, &vstream_idx);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Failed to initialize decoder: {}", errbuf);
spdlog::critical("Failed to initialize decoder: {}", errbuf);
cleanup();
return ret;
}
@ -367,7 +368,7 @@ extern "C" int process_video(
output_height = dec_ctx->height * filter_config->config.realesrgan.scaling_factor;
break;
default:
spdlog::error("Unknown filter type");
spdlog::critical("Unknown filter type");
cleanup();
return -1;
}
@ -389,7 +390,7 @@ extern "C" int process_video(
);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Failed to initialize encoder: {}", errbuf);
spdlog::critical("Failed to initialize encoder: {}", errbuf);
cleanup();
return ret;
}
@ -398,7 +399,7 @@ extern "C" int process_video(
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error occurred when opening output file: {}", errbuf);
spdlog::critical("Error occurred when opening output file: {}", errbuf);
cleanup();
return ret;
}
@ -407,7 +408,7 @@ extern "C" int process_video(
if (filter_config->filter_type == FILTER_LIBPLACEBO) {
const auto &config = filter_config->config.libplacebo;
if (!config.shader_path) {
spdlog::error("Shader path must be provided for the libplacebo filter");
spdlog::critical("Shader path must be provided for the libplacebo filter");
cleanup();
return -1;
}
@ -417,22 +418,25 @@ extern "C" int process_video(
} else if (filter_config->filter_type == FILTER_REALESRGAN) {
const auto &config = filter_config->config.realesrgan;
if (!config.model_name) {
spdlog::error("Model name must be provided for the RealESRGAN filter");
spdlog::critical("Model name must be provided for the RealESRGAN filter");
cleanup();
return -1;
}
filter = new RealesrganFilter{
config.gpuid, config.tta_mode, config.scaling_factor, config.model_name
static_cast<int>(vk_device_index),
config.tta_mode,
config.scaling_factor,
config.model_name
};
} else {
spdlog::error("Unknown filter type");
spdlog::critical("Unknown filter type");
cleanup();
return -1;
}
// Check if the filter instance was created successfully
if (filter == nullptr) {
spdlog::error("Failed to create filter instance");
spdlog::critical("Failed to create filter instance");
cleanup();
return -1;
}
@ -440,7 +444,7 @@ extern "C" int process_video(
// Initialize the filter
ret = filter->init(dec_ctx, enc_ctx, hw_ctx);
if (ret < 0) {
spdlog::error("Failed to initialize filter");
spdlog::critical("Failed to initialize filter");
cleanup();
return ret;
}
@ -460,7 +464,7 @@ extern "C" int process_video(
);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error processing frames: {}", errbuf);
spdlog::critical("Error processing frames: {}", errbuf);
cleanup();
return ret;
}
@ -473,7 +477,7 @@ extern "C" int process_video(
if (ret < 0 && ret != AVERROR_EOF) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error occurred: {}", errbuf);
spdlog::critical("Error occurred: {}", errbuf);
return ret;
}
return 0;

View File

@ -3,7 +3,6 @@
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <string>
#include <spdlog/spdlog.h>

View File

@ -33,6 +33,7 @@ extern "C" {
}
#include <spdlog/spdlog.h>
#include <vulkan/vulkan.h>
#ifdef _WIN32
#define BOOST_PROGRAM_OPTIONS_WCHAR_T
@ -49,17 +50,22 @@ namespace po = boost::program_options;
// Indicate if a newline needs to be printed before the next output
std::atomic<bool> newline_required = false;
// Mutex for synchronizing access to VideoProcessingContext
std::mutex proc_ctx_mutex;
// Structure to hold parsed arguments
struct Arguments {
StringType loglevel = STR("info");
bool noprogress = false;
// General options
std::filesystem::path in_fname;
std::filesystem::path out_fname;
StringType filter_type;
uint32_t gpuid = 0;
StringType hwaccel = STR("none");
bool nocopystreams = false;
bool benchmark = false;
StringType loglevel = STR("info");
bool noprogress = false;
// Encoder options
StringType codec = STR("libx264");
@ -74,7 +80,6 @@ struct Arguments {
int out_height = 0;
// RealESRGAN options
int gpuid = 0;
StringType model_name;
int scaling_factor = 0;
};
@ -161,8 +166,112 @@ enum Libvideo2xLogLevel parse_log_level(const StringType &level_name) {
}
}
// Mutex for synchronizing access to VideoProcessingContext
std::mutex proc_ctx_mutex;
int list_gpus() {
// Create a Vulkan instance
VkInstance instance;
VkInstanceCreateInfo create_info{};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
if (vkCreateInstance(&create_info, nullptr, &instance) != VK_SUCCESS) {
spdlog::critical("Failed to create Vulkan instance.");
return -1;
}
// Enumerate physical devices
uint32_t device_count = 0;
VkResult result = vkEnumeratePhysicalDevices(instance, &device_count, nullptr);
if (result != VK_SUCCESS) {
spdlog::critical("Failed to enumerate Vulkan physical devices.");
vkDestroyInstance(instance, nullptr);
return -1;
}
// Check if any devices are found
if (device_count == 0) {
spdlog::critical("No Vulkan physical devices found.");
vkDestroyInstance(instance, nullptr);
return -1;
}
// Get physical device properties
std::vector<VkPhysicalDevice> physical_devices(device_count);
result = vkEnumeratePhysicalDevices(instance, &device_count, physical_devices.data());
if (result != VK_SUCCESS) {
spdlog::critical("Failed to enumerate Vulkan physical devices.");
vkDestroyInstance(instance, nullptr);
return -1;
}
// List GPU information
for (uint32_t i = 0; i < device_count; i++) {
VkPhysicalDevice device = physical_devices[i];
VkPhysicalDeviceProperties device_properties;
vkGetPhysicalDeviceProperties(device, &device_properties);
// Print GPU ID and name
std::cout << i << ". " << device_properties.deviceName << std::endl;
std::cout << "\tType: ";
switch (device_properties.deviceType) {
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
std::cout << "Integrated GPU";
break;
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
std::cout << "Discrete GPU";
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
std::cout << "Virtual GPU";
break;
case VK_PHYSICAL_DEVICE_TYPE_CPU:
std::cout << "CPU";
break;
default:
std::cout << "Unknown";
break;
}
std::cout << std::endl;
// Print Vulkan API version
std::cout << "\tVulkan API Version: " << VK_VERSION_MAJOR(device_properties.apiVersion)
<< "." << VK_VERSION_MINOR(device_properties.apiVersion) << "."
<< VK_VERSION_PATCH(device_properties.apiVersion) << std::endl;
// Print driver version
std::cout << "\tDriver Version: " << VK_VERSION_MAJOR(device_properties.driverVersion)
<< "." << VK_VERSION_MINOR(device_properties.driverVersion) << "."
<< VK_VERSION_PATCH(device_properties.driverVersion) << std::endl;
}
// Clean up Vulkan instance
vkDestroyInstance(instance, nullptr);
return 0;
}
int is_valid_gpu_id(uint32_t gpu_id) {
// Create a Vulkan instance
VkInstance instance;
VkInstanceCreateInfo create_info{};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
if (vkCreateInstance(&create_info, nullptr, &instance) != VK_SUCCESS) {
spdlog::error("Failed to create Vulkan instance.");
return -1;
}
// Enumerate physical devices
uint32_t device_count = 0;
VkResult result = vkEnumeratePhysicalDevices(instance, &device_count, nullptr);
if (result != VK_SUCCESS) {
spdlog::error("Failed to enumerate Vulkan physical devices.");
vkDestroyInstance(instance, nullptr);
return -1;
}
// Clean up Vulkan instance
vkDestroyInstance(instance, nullptr);
if (gpu_id >= device_count) {
return 0;
}
return 1;
}
// Wrapper function for video processing thread
void process_video_thread(
@ -194,6 +303,7 @@ void process_video_thread(
out_fname,
log_level,
arguments->benchmark,
arguments->gpuid,
hw_device_type,
filter_config,
encoder_config,
@ -224,11 +334,13 @@ int main(int argc, char **argv) {
("version,v", "Print program version")
("loglevel", PO_STR_VALUE<StringType>(&arguments.loglevel)->default_value(STR("info"), "info"), "Set log level (trace, debug, info, warn, error, critical, none)")
("noprogress", po::bool_switch(&arguments.noprogress), "Do not display the progress bar")
("listgpus", "List the available GPUs")
// General Processing Options
("input,i", PO_STR_VALUE<StringType>(), "Input video file path")
("output,o", PO_STR_VALUE<StringType>(), "Output video file path")
("filter,f", PO_STR_VALUE<StringType>(&arguments.filter_type), "Filter to use: 'libplacebo' or 'realesrgan'")
("gpuid,g", po::value<uint32_t>(&arguments.gpuid)->default_value(0), "Vulkan GPU ID (default: 0)")
("hwaccel,a", PO_STR_VALUE<StringType>(&arguments.hwaccel)->default_value(STR("none"), "none"), "Hardware acceleration method (default: none)")
("nocopystreams", po::bool_switch(&arguments.nocopystreams), "Do not copy audio and subtitle streams")
("benchmark", po::bool_switch(&arguments.benchmark), "Discard processed frames and calculate average FPS")
@ -246,7 +358,6 @@ int main(int argc, char **argv) {
("height,h", po::value<int>(&arguments.out_height), "Output height")
// RealESRGAN options
("gpuid,g", po::value<int>(&arguments.gpuid)->default_value(0), "Vulkan GPU ID (default: 0)")
("model,m", PO_STR_VALUE<StringType>(&arguments.model_name), "Name of the model to use")
("scale,r", po::value<int>(&arguments.scaling_factor), "Scaling factor (2, 3, or 4)")
;
@ -274,23 +385,27 @@ int main(int argc, char **argv) {
return 0;
}
if (vm.count("listgpus")) {
return list_gpus();
}
// Assign positional arguments
if (vm.count("input")) {
arguments.in_fname = std::filesystem::path(vm["input"].as<StringType>());
} else {
spdlog::error("Error: Input file path is required.");
spdlog::critical("Input file path is required.");
return 1;
}
if (vm.count("output")) {
arguments.out_fname = std::filesystem::path(vm["output"].as<StringType>());
} else if (!arguments.benchmark) {
spdlog::error("Error: Output file path is required.");
spdlog::critical("Output file path is required.");
return 1;
}
if (!vm.count("filter")) {
spdlog::error("Error: Filter type is required (libplacebo or realesrgan).");
spdlog::critical("Filter type is required (libplacebo or realesrgan).");
return 1;
}
@ -300,18 +415,18 @@ int main(int argc, char **argv) {
if (vm.count("model")) {
if (!is_valid_realesrgan_model(vm["model"].as<StringType>())) {
spdlog::error(
"Error: Invalid model specified. Must be 'realesrgan-plus', "
spdlog::critical(
"Invalid model specified. Must be 'realesrgan-plus', "
"'realesrgan-plus-anime', or 'realesr-animevideov3'."
);
return 1;
}
}
} catch (const po::error &e) {
spdlog::error("Error parsing options: {}", e.what());
spdlog::critical("Error parsing options: {}", e.what());
return 1;
} catch (const std::exception &e) {
spdlog::error("Unexpected exception caught while parsing options: {}", e.what());
spdlog::critical("Unexpected exception caught while parsing options: {}", e.what());
return 1;
}
@ -319,45 +434,52 @@ int main(int argc, char **argv) {
if (arguments.filter_type == STR("libplacebo")) {
if (arguments.shader_path.empty() || arguments.out_width == 0 ||
arguments.out_height == 0) {
spdlog::error(
"Error: For libplacebo, shader name/path (-s), width (-w), "
spdlog::critical(
"For libplacebo, shader name/path (-s), width (-w), "
"and height (-h) are required."
);
return 1;
}
} else if (arguments.filter_type == STR("realesrgan")) {
if (arguments.scaling_factor == 0 || arguments.model_name.empty()) {
spdlog::error("Error: For realesrgan, scaling factor (-r) and model (-m) are required."
);
spdlog::critical("For realesrgan, scaling factor (-r) and model (-m) are required.");
return 1;
}
if (arguments.scaling_factor != 2 && arguments.scaling_factor != 3 &&
arguments.scaling_factor != 4) {
spdlog::error("Error: Scaling factor must be 2, 3, or 4.");
spdlog::critical("Scaling factor must be 2, 3, or 4.");
return 1;
}
} else {
spdlog::error("Error: Invalid filter type specified. Must be 'libplacebo' or 'realesrgan'."
);
spdlog::critical("Invalid filter type specified. Must be 'libplacebo' or 'realesrgan'.");
return 1;
}
// Validate GPU ID
int gpu_status = is_valid_gpu_id(arguments.gpuid);
if (gpu_status < 0) {
spdlog::warn("Unable to validate GPU ID.");
} else if (arguments.gpuid > 0 && gpu_status == 0) {
spdlog::critical("Invalid GPU ID specified.");
return 1;
}
// Validate bitrate
if (arguments.bitrate < 0) {
spdlog::error("Error: Invalid bitrate specified.");
spdlog::critical("Invalid bitrate specified.");
return 1;
}
// Validate CRF
if (arguments.crf < 0.0f || arguments.crf > 51.0f) {
spdlog::error("Error: CRF must be between 0 and 51.");
spdlog::critical("CRF must be between 0 and 51.");
return 1;
}
// Parse codec to AVCodec
const AVCodec *codec = avcodec_find_encoder_by_name(wstring_to_utf8(arguments.codec).c_str());
if (!codec) {
spdlog::error("Error: Codec '{}' not found.", wstring_to_utf8(arguments.codec));
spdlog::critical("Codec '{}' not found.", wstring_to_utf8(arguments.codec));
return 1;
}
@ -366,7 +488,7 @@ int main(int argc, char **argv) {
if (!arguments.pix_fmt.empty()) {
pix_fmt = av_get_pix_fmt(wstring_to_utf8(arguments.pix_fmt).c_str());
if (pix_fmt == AV_PIX_FMT_NONE) {
spdlog::error("Error: Invalid pixel format '{}'.", wstring_to_utf8(arguments.pix_fmt));
spdlog::critical("Invalid pixel format '{}'.", wstring_to_utf8(arguments.pix_fmt));
return 1;
}
}
@ -400,6 +522,10 @@ int main(int argc, char **argv) {
break;
}
// Print program version and processing information
spdlog::info("Video2X version {}", LIBVIDEO2X_VERSION_STRING);
spdlog::info("Processing file: {}", arguments.in_fname.u8string());
#ifdef _WIN32
std::wstring shader_path_str = arguments.shader_path.wstring();
#else
@ -415,7 +541,6 @@ int main(int argc, char **argv) {
filter_config.config.libplacebo.shader_path = shader_path_str.c_str();
} else if (arguments.filter_type == STR("realesrgan")) {
filter_config.filter_type = FILTER_REALESRGAN;
filter_config.config.realesrgan.gpuid = arguments.gpuid;
filter_config.config.realesrgan.tta_mode = false;
filter_config.config.realesrgan.scaling_factor = arguments.scaling_factor;
filter_config.config.realesrgan.model_name = arguments.model_name.c_str();
@ -439,8 +564,8 @@ int main(int argc, char **argv) {
if (arguments.hwaccel != STR("none")) {
hw_device_type = av_hwdevice_find_type_by_name(wstring_to_utf8(arguments.hwaccel).c_str());
if (hw_device_type == AV_HWDEVICE_TYPE_NONE) {
spdlog::error(
"Error: Invalid hardware device type '{}'.", wstring_to_utf8(arguments.hwaccel)
spdlog::critical(
"Invalid hardware device type '{}'.", wstring_to_utf8(arguments.hwaccel)
);
return 1;
}