mirror of
https://github.com/k4yt3x/video2x.git
synced 2024-12-27 14:39:09 +00:00
feat: complete motion interpolation and add scene detection
Signed-off-by: k4yt3x <i@k4yt3x.com>
This commit is contained in:
parent
f41efb3bac
commit
5a59907493
@ -14,4 +14,6 @@ int64_t get_video_frame_count(AVFormatContext *ifmt_ctx, int in_vstream_idx);
|
|||||||
enum AVPixelFormat
|
enum AVPixelFormat
|
||||||
get_encoder_default_pix_fmt(const AVCodec *encoder, AVPixelFormat target_pix_fmt);
|
get_encoder_default_pix_fmt(const AVCodec *encoder, AVPixelFormat target_pix_fmt);
|
||||||
|
|
||||||
|
float get_frame_diff(AVFrame *frame1, AVFrame *frame2);
|
||||||
|
|
||||||
#endif // AVUTILS_H
|
#endif // AVUTILS_H
|
||||||
|
@ -76,7 +76,8 @@ struct ProcessorConfig {
|
|||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
int scaling_factor;
|
int scaling_factor;
|
||||||
int frame_rate_multiplier;
|
int frm_rate_mul;
|
||||||
|
float scn_det_thresh;
|
||||||
union {
|
union {
|
||||||
struct LibplaceboConfig libplacebo;
|
struct LibplaceboConfig libplacebo;
|
||||||
struct RealESRGANConfig realesrgan;
|
struct RealESRGANConfig realesrgan;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#include "avutils.h"
|
#include "avutils.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
#include <libavutil/pixdesc.h>
|
#include <libavutil/pixdesc.h>
|
||||||
@ -7,6 +9,8 @@ extern "C" {
|
|||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include "conversions.h"
|
||||||
|
|
||||||
AVRational get_video_frame_rate(AVFormatContext *ifmt_ctx, int in_vstream_idx) {
|
AVRational get_video_frame_rate(AVFormatContext *ifmt_ctx, int in_vstream_idx) {
|
||||||
AVRational frame_rate = ifmt_ctx->streams[in_vstream_idx]->avg_frame_rate;
|
AVRational frame_rate = ifmt_ctx->streams[in_vstream_idx]->avg_frame_rate;
|
||||||
if (frame_rate.num == 0 && frame_rate.den == 0) {
|
if (frame_rate.num == 0 && frame_rate.den == 0) {
|
||||||
@ -127,3 +131,58 @@ get_encoder_default_pix_fmt(const AVCodec *encoder, AVPixelFormat target_pix_fmt
|
|||||||
|
|
||||||
return best_pix_fmt;
|
return best_pix_fmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float get_frame_diff(AVFrame *frame1, AVFrame *frame2) {
|
||||||
|
if (!frame1 || !frame2) {
|
||||||
|
spdlog::error("Invalid frame(s) provided for comparison");
|
||||||
|
return -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame1->width != frame2->width || frame1->height != frame2->height) {
|
||||||
|
spdlog::error("Frame dimensions do not match");
|
||||||
|
return -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = frame1->width;
|
||||||
|
int height = frame1->height;
|
||||||
|
|
||||||
|
// Convert both frames to the target pixel format using the provided function
|
||||||
|
AVPixelFormat target_pix_fmt = AV_PIX_FMT_RGB24;
|
||||||
|
AVFrame *rgb_frame1 = convert_avframe_pix_fmt(frame1, target_pix_fmt);
|
||||||
|
AVFrame *rgb_frame2 = convert_avframe_pix_fmt(frame2, target_pix_fmt);
|
||||||
|
|
||||||
|
if (!rgb_frame1 || !rgb_frame2) {
|
||||||
|
spdlog::error("Failed to convert frames to target pixel format");
|
||||||
|
if (rgb_frame1) {
|
||||||
|
av_frame_free(&rgb_frame1);
|
||||||
|
}
|
||||||
|
if (rgb_frame2) {
|
||||||
|
av_frame_free(&rgb_frame2);
|
||||||
|
}
|
||||||
|
return -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t sum_diff = 0;
|
||||||
|
uint64_t max_diff = 0;
|
||||||
|
|
||||||
|
// Calculate difference pixel by pixel
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
uint8_t *ptr1 = rgb_frame1->data[0] + y * rgb_frame1->linesize[0];
|
||||||
|
uint8_t *ptr2 = rgb_frame2->data[0] + y * rgb_frame2->linesize[0];
|
||||||
|
for (int x = 0; x < width * 3; x++) {
|
||||||
|
sum_diff += static_cast<uint64_t>(
|
||||||
|
std::abs(static_cast<int>(ptr1[x]) - static_cast<int>(ptr2[x]))
|
||||||
|
);
|
||||||
|
max_diff += 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
av_frame_free(&rgb_frame1);
|
||||||
|
av_frame_free(&rgb_frame2);
|
||||||
|
|
||||||
|
// Calculate percentage difference
|
||||||
|
float percent_diff = (static_cast<float>(sum_diff) / static_cast<float>(max_diff)) * 100.0f;
|
||||||
|
|
||||||
|
return percent_diff;
|
||||||
|
}
|
||||||
|
@ -122,10 +122,10 @@ int Encoder::init(
|
|||||||
spdlog::debug("Auto-selected pixel format: {}", av_get_pix_fmt_name(enc_ctx_->pix_fmt));
|
spdlog::debug("Auto-selected pixel format: {}", av_get_pix_fmt_name(enc_ctx_->pix_fmt));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processor_config->frame_rate_multiplier > 0) {
|
if (processor_config->frm_rate_mul > 0) {
|
||||||
AVRational in_frame_rate = get_video_frame_rate(ifmt_ctx, in_vstream_idx);
|
AVRational in_frame_rate = get_video_frame_rate(ifmt_ctx, in_vstream_idx);
|
||||||
enc_ctx_->framerate = {
|
enc_ctx_->framerate = {
|
||||||
in_frame_rate.num * processor_config->frame_rate_multiplier, in_frame_rate.den
|
in_frame_rate.num * processor_config->frm_rate_mul, in_frame_rate.den
|
||||||
};
|
};
|
||||||
enc_ctx_->time_base = av_inv_q(enc_ctx_->framerate);
|
enc_ctx_->time_base = av_inv_q(enc_ctx_->framerate);
|
||||||
} else {
|
} else {
|
||||||
|
@ -78,15 +78,12 @@ int InterpolatorRIFE::interpolate(
|
|||||||
) {
|
) {
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/*
|
|
||||||
ncnn::Mat in_mat1 = avframe_to_ncnn_mat(prev_frame);
|
ncnn::Mat in_mat1 = avframe_to_ncnn_mat(prev_frame);
|
||||||
if (in_mat1.empty()) {
|
if (in_mat1.empty()) {
|
||||||
spdlog::error("Failed to convert AVFrame to ncnn::Mat");
|
spdlog::error("Failed to convert AVFrame to ncnn::Mat");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// Convert the input frame to RGB24
|
|
||||||
ncnn::Mat in_mat2 = avframe_to_ncnn_mat(in_frame);
|
ncnn::Mat in_mat2 = avframe_to_ncnn_mat(in_frame);
|
||||||
if (in_mat2.empty()) {
|
if (in_mat2.empty()) {
|
||||||
spdlog::error("Failed to convert AVFrame to ncnn::Mat");
|
spdlog::error("Failed to convert AVFrame to ncnn::Mat");
|
||||||
@ -96,9 +93,7 @@ int InterpolatorRIFE::interpolate(
|
|||||||
// Allocate space for output ncnn::Mat
|
// Allocate space for output ncnn::Mat
|
||||||
ncnn::Mat out_mat = ncnn::Mat(in_mat2.w, in_mat2.h, static_cast<size_t>(3), 3);
|
ncnn::Mat out_mat = ncnn::Mat(in_mat2.w, in_mat2.h, static_cast<size_t>(3), 3);
|
||||||
|
|
||||||
// TODO: handle frames properly
|
ret = rife_->process(in_mat1, in_mat2, time_step, out_mat);
|
||||||
// ret = rife_->process(in_mat1, in_mat2, time_step, out_mat);
|
|
||||||
ret = rife_->process(in_mat2, in_mat2, time_step, out_mat);
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
spdlog::error("RIFE processing failed");
|
spdlog::error("RIFE processing failed");
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -150,17 +150,35 @@ static int process_frames(
|
|||||||
case PROCESSING_MODE_INTERPOLATE: {
|
case PROCESSING_MODE_INTERPOLATE: {
|
||||||
Interpolator *interpolator = dynamic_cast<Interpolator *>(processor);
|
Interpolator *interpolator = dynamic_cast<Interpolator *>(processor);
|
||||||
|
|
||||||
float time_step =
|
float time_step = 1.0f / static_cast<float>(processor_config->frm_rate_mul);
|
||||||
1.0f / static_cast<float>(processor_config->frame_rate_multiplier);
|
|
||||||
float current_time_step = time_step;
|
float current_time_step = time_step;
|
||||||
|
|
||||||
while (current_time_step < 1.0f) {
|
bool skip_frame = false;
|
||||||
ret = interpolator->interpolate(
|
if (prev_frame != nullptr) {
|
||||||
prev_frame.get(),
|
float frame_diff = get_frame_diff(prev_frame.get(), frame.get());
|
||||||
frame.get(),
|
if (frame_diff > processor_config->scn_det_thresh) {
|
||||||
&raw_processed_frame,
|
spdlog::debug(
|
||||||
current_time_step
|
"Scene change detected ({:.2f}%), skipping frame {}",
|
||||||
);
|
frame_diff,
|
||||||
|
proc_ctx->processed_frames
|
||||||
|
);
|
||||||
|
skip_frame = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (prev_frame != nullptr && current_time_step < 1.0f) {
|
||||||
|
if (!skip_frame) {
|
||||||
|
ret = interpolator->interpolate(
|
||||||
|
prev_frame.get(),
|
||||||
|
frame.get(),
|
||||||
|
&raw_processed_frame,
|
||||||
|
current_time_step
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ret = 0;
|
||||||
|
raw_processed_frame = av_frame_clone(prev_frame.get());
|
||||||
|
}
|
||||||
|
|
||||||
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||||
av_strerror(ret, errbuf, sizeof(errbuf));
|
av_strerror(ret, errbuf, sizeof(errbuf));
|
||||||
return ret;
|
return ret;
|
||||||
@ -197,6 +215,7 @@ static int process_frames(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
prev_frame.reset(av_frame_clone(frame.get()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@ -64,8 +63,8 @@ struct Arguments {
|
|||||||
std::filesystem::path in_fname;
|
std::filesystem::path in_fname;
|
||||||
std::filesystem::path out_fname;
|
std::filesystem::path out_fname;
|
||||||
StringType processor_type;
|
StringType processor_type;
|
||||||
uint32_t gpu_id = 0;
|
|
||||||
StringType hwaccel = STR("none");
|
StringType hwaccel = STR("none");
|
||||||
|
uint32_t gpu_id = 0;
|
||||||
bool no_copy_streams = false;
|
bool no_copy_streams = false;
|
||||||
bool benchmark = false;
|
bool benchmark = false;
|
||||||
|
|
||||||
@ -90,7 +89,8 @@ struct Arguments {
|
|||||||
int width = 0;
|
int width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
int scaling_factor = 0;
|
int scaling_factor = 0;
|
||||||
int frame_rate_multiplier = 2;
|
int frm_rate_mul = 2;
|
||||||
|
float scn_det_thresh = 0.0f;
|
||||||
|
|
||||||
// libplacebo options
|
// libplacebo options
|
||||||
std::filesystem::path libplacebo_shader_path;
|
std::filesystem::path libplacebo_shader_path;
|
||||||
@ -387,10 +387,10 @@ int main(int argc, char **argv) {
|
|||||||
("output,o", PO_STR_VALUE<StringType>(), "Output video file path")
|
("output,o", PO_STR_VALUE<StringType>(), "Output video file path")
|
||||||
("processor,p", PO_STR_VALUE<StringType>(&arguments.processor_type),
|
("processor,p", PO_STR_VALUE<StringType>(&arguments.processor_type),
|
||||||
"Processor to use: 'libplacebo', 'realesrgan', or 'rife'")
|
"Processor to use: 'libplacebo', 'realesrgan', or 'rife'")
|
||||||
("gpu,g", po::value<uint32_t>(&arguments.gpu_id)->default_value(0),
|
|
||||||
"GPU ID (Vulkan device index)")
|
|
||||||
("hwaccel,a", PO_STR_VALUE<StringType>(&arguments.hwaccel)->default_value(STR("none"),
|
("hwaccel,a", PO_STR_VALUE<StringType>(&arguments.hwaccel)->default_value(STR("none"),
|
||||||
"none"), "Hardware acceleration method (mostly for decoding)")
|
"none"), "Hardware acceleration method (mostly for decoding)")
|
||||||
|
("gpu,g", po::value<uint32_t>(&arguments.gpu_id)->default_value(0),
|
||||||
|
"GPU ID (Vulkan device index)")
|
||||||
("benchmark,b", po::bool_switch(&arguments.benchmark),
|
("benchmark,b", po::bool_switch(&arguments.benchmark),
|
||||||
"Discard processed frames and calculate average FPS; "
|
"Discard processed frames and calculate average FPS; "
|
||||||
"useful for detecting encoder bottlenecks")
|
"useful for detecting encoder bottlenecks")
|
||||||
@ -432,14 +432,20 @@ int main(int argc, char **argv) {
|
|||||||
"Additional AVOption(s) for the encoder (format: -e key=value)")
|
"Additional AVOption(s) for the encoder (format: -e key=value)")
|
||||||
;
|
;
|
||||||
|
|
||||||
po::options_description common_opts("Common aprocessing options");
|
po::options_description upscale_opts("Upscaling options");
|
||||||
common_opts.add_options()
|
upscale_opts.add_options()
|
||||||
("width,w", po::value<int>(&arguments.width), "Output width")
|
("width,w", po::value<int>(&arguments.width), "Output width")
|
||||||
("height,h", po::value<int>(&arguments.height), "Output height")
|
("height,h", po::value<int>(&arguments.height), "Output height")
|
||||||
("scaling-factor,s", po::value<int>(&arguments.scaling_factor), "Scaling factor")
|
("scaling-factor,s", po::value<int>(&arguments.scaling_factor), "Scaling factor")
|
||||||
("frame-rate-multiplier,r",
|
;
|
||||||
po::value<int>(&arguments.frame_rate_multiplier)->default_value(0),
|
|
||||||
"Output frame rate")
|
po::options_description interp_opts("Frame interpolation options");
|
||||||
|
interp_opts.add_options()
|
||||||
|
("frame-rate-mul,f",
|
||||||
|
po::value<int>(&arguments.frm_rate_mul)->default_value(0),
|
||||||
|
"Frame rate multiplier")
|
||||||
|
("scene-thresh,d", po::value<float>(&arguments.scn_det_thresh)->default_value(10.0f),
|
||||||
|
"Scene detection threshold")
|
||||||
;
|
;
|
||||||
|
|
||||||
po::options_description libplacebo_opts("libplacebo options");
|
po::options_description libplacebo_opts("libplacebo options");
|
||||||
@ -467,7 +473,8 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
// Combine all options
|
// Combine all options
|
||||||
all_opts.add(encoder_opts)
|
all_opts.add(encoder_opts)
|
||||||
.add(common_opts)
|
.add(upscale_opts)
|
||||||
|
.add(interp_opts)
|
||||||
.add(libplacebo_opts)
|
.add(libplacebo_opts)
|
||||||
.add(realesrgan_opts)
|
.add(realesrgan_opts)
|
||||||
.add(rife_opts);
|
.add(rife_opts);
|
||||||
@ -581,10 +588,14 @@ int main(int argc, char **argv) {
|
|||||||
spdlog::critical("Invalid scaling factor specified.");
|
spdlog::critical("Invalid scaling factor specified.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (arguments.frame_rate_multiplier < 0) {
|
if (arguments.frm_rate_mul < 0) {
|
||||||
spdlog::critical("Invalid target frame rate specified.");
|
spdlog::critical("Invalid target frame rate specified.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (arguments.scn_det_thresh < 0.0f || arguments.scn_det_thresh > 100.0f) {
|
||||||
|
spdlog::critical("Invalid scene detection threshold specified.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (arguments.processor_type == STR("libplacebo")) {
|
if (arguments.processor_type == STR("libplacebo")) {
|
||||||
if (arguments.libplacebo_shader_path.empty() || arguments.width == 0 ||
|
if (arguments.libplacebo_shader_path.empty() || arguments.width == 0 ||
|
||||||
@ -681,7 +692,8 @@ int main(int argc, char **argv) {
|
|||||||
processor_config.width = arguments.width;
|
processor_config.width = arguments.width;
|
||||||
processor_config.height = arguments.height;
|
processor_config.height = arguments.height;
|
||||||
processor_config.scaling_factor = arguments.scaling_factor;
|
processor_config.scaling_factor = arguments.scaling_factor;
|
||||||
processor_config.frame_rate_multiplier = arguments.frame_rate_multiplier;
|
processor_config.frm_rate_mul = arguments.frm_rate_mul;
|
||||||
|
processor_config.scn_det_thresh = arguments.scn_det_thresh;
|
||||||
|
|
||||||
if (arguments.processor_type == STR("libplacebo")) {
|
if (arguments.processor_type == STR("libplacebo")) {
|
||||||
processor_config.processor_type = PROCESSOR_LIBPLACEBO;
|
processor_config.processor_type = PROCESSOR_LIBPLACEBO;
|
||||||
|
Loading…
Reference in New Issue
Block a user