diff --git a/src/common/nv12_scaler.cpp b/src/common/nv12_scaler.cpp new file mode 100644 index 0000000..db3ce23 --- /dev/null +++ b/src/common/nv12_scaler.cpp @@ -0,0 +1,62 @@ +#include "nv12_scaler.h" + +#include +#include + +#include "libyuv/planar_functions.h" + +namespace crossdesk { + +int ScaleNv12ViaI420(const uint8_t* src_y, int src_stride_y, + const uint8_t* src_uv, int src_stride_uv, int src_width, + int src_height, uint8_t* dst_y, int dst_stride_y, + uint8_t* dst_uv, int dst_stride_uv, int dst_width, + int dst_height, libyuv::FilterMode filtering, + std::vector* scratch_buffer) { + if (!src_y || !src_uv || !dst_y || !dst_uv || !scratch_buffer || + src_width <= 0 || src_height <= 0 || dst_width <= 0 || dst_height <= 0 || + (src_width & 1) != 0 || (src_height & 1) != 0 || (dst_width & 1) != 0 || + (dst_height & 1) != 0) { + return -1; + } + + const int src_chroma_width = src_width / 2; + const int src_chroma_height = src_height / 2; + const int dst_chroma_width = dst_width / 2; + const int dst_chroma_height = dst_height / 2; + const size_t src_chroma_size = + static_cast(src_chroma_width) * src_chroma_height; + const size_t dst_chroma_size = + static_cast(dst_chroma_width) * dst_chroma_height; + const size_t max_size = std::numeric_limits::max(); + if (src_chroma_size > max_size / 2 || dst_chroma_size > max_size / 2) { + return -1; + } + + const size_t src_planes_size = 2 * src_chroma_size; + const size_t dst_planes_size = 2 * dst_chroma_size; + if (src_planes_size > max_size - dst_planes_size) { + return -1; + } + scratch_buffer->resize(src_planes_size + dst_planes_size); + uint8_t* src_u = scratch_buffer->data(); + uint8_t* src_v = src_u + src_chroma_size; + uint8_t* dst_u = src_v + src_chroma_size; + uint8_t* dst_v = dst_u + dst_chroma_size; + + libyuv::SplitUVPlane(src_uv, src_stride_uv, src_u, src_chroma_width, src_v, + src_chroma_width, src_chroma_width, src_chroma_height); + const int result = libyuv::I420Scale( + src_y, src_stride_y, src_u, src_chroma_width, src_v, src_chroma_width, + src_width, src_height, dst_y, dst_stride_y, dst_u, dst_chroma_width, + dst_v, dst_chroma_width, dst_width, dst_height, filtering); + if (result != 0) { + return result; + } + + libyuv::MergeUVPlane(dst_u, dst_chroma_width, dst_v, dst_chroma_width, dst_uv, + dst_stride_uv, dst_chroma_width, dst_chroma_height); + return 0; +} + +} // namespace crossdesk diff --git a/src/common/nv12_scaler.h b/src/common/nv12_scaler.h new file mode 100644 index 0000000..15d04c7 --- /dev/null +++ b/src/common/nv12_scaler.h @@ -0,0 +1,23 @@ +#ifndef CROSSDESK_COMMON_NV12_SCALER_H_ +#define CROSSDESK_COMMON_NV12_SCALER_H_ + +#include +#include + +#include "libyuv/scale.h" + +namespace crossdesk { + +// Scales NV12 by separating the interleaved chroma plane before filtering. +// This avoids the downward chroma rounding bias in libyuv::NV12Scale while +// preserving the requested filtering quality. +int ScaleNv12ViaI420(const uint8_t* src_y, int src_stride_y, + const uint8_t* src_uv, int src_stride_uv, int src_width, + int src_height, uint8_t* dst_y, int dst_stride_y, + uint8_t* dst_uv, int dst_stride_uv, int dst_width, + int dst_height, libyuv::FilterMode filtering, + std::vector* scratch_buffer); + +} // namespace crossdesk + +#endif // CROSSDESK_COMMON_NV12_SCALER_H_ diff --git a/src/gui/application/gui_application.cpp b/src/gui/application/gui_application.cpp index f077881..163b05d 100644 --- a/src/gui/application/gui_application.cpp +++ b/src/gui/application/gui_application.cpp @@ -29,6 +29,7 @@ #include "crossdesk_ui.h" #include "fa_solid_900.h" #include "localization.h" +#include "nv12_scaler.h" #include "platform.h" #if _WIN32 #include @@ -866,6 +867,7 @@ struct GuiApplication::SlintUi { std::make_shared>(); std::unordered_map displayed_frame_sequence; std::vector scaled_video_frame; + std::vector scaled_video_frame_scratch; #if !defined(__APPLE__) std::mutex video_gl_mutex; std::vector video_gl_conversion_frame; @@ -2651,10 +2653,11 @@ void GuiApplication::SyncStreamVideoFrame() { uint8_t* scaled_uv = scaled_y + static_cast(render_size.width) * render_size.height; - if (libyuv::NV12Scale(y_plane, width, uv_plane, width, width, height, - scaled_y, render_size.width, scaled_uv, - render_size.width, render_size.width, - render_size.height, libyuv::kFilterBox) == 0) { + if (ScaleNv12ViaI420( + y_plane, width, uv_plane, width, width, height, scaled_y, + render_size.width, scaled_uv, render_size.width, render_size.width, + render_size.height, libyuv::kFilterBox, + &ui_->scaled_video_frame_scratch) == 0) { y_plane = scaled_y; uv_plane = scaled_uv; output_width = render_size.width; diff --git a/submodules/minirtc b/submodules/minirtc index 9eafddd..c2cda31 160000 --- a/submodules/minirtc +++ b/submodules/minirtc @@ -1 +1 @@ -Subproject commit 9eafddd2fe2ad017390913661328be2d4484a510 +Subproject commit c2cda31219b82462f546f86e1f21313d7b33e57f diff --git a/xmake/targets.lua b/xmake/targets.lua index f14575f..3e445d5 100644 --- a/xmake/targets.lua +++ b/xmake/targets.lua @@ -33,6 +33,7 @@ function setup_targets() target("common") set_kind("object") add_deps("rd_log") + add_packages("libyuv") add_files("src/common/*.cpp") remove_files("src/common/rounded_corner_button.cpp") if is_os("macosx") then