mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
feat: 新增安卓平台支持
This commit is contained in:
@@ -8,4 +8,4 @@ license = "BSD-3-Clause"
|
||||
[dependencies]
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.59"
|
||||
bindgen = "0.70.1"
|
||||
|
||||
@@ -19,9 +19,19 @@ fn main() {
|
||||
|
||||
fn generate_bindings(cpp_dir: &Path) {
|
||||
let ffi_header = cpp_dir.join("yuv_ffi.h");
|
||||
let mut builder = bindgen::builder().header(ffi_header.to_string_lossy().to_string());
|
||||
|
||||
bindgen::builder()
|
||||
.header(ffi_header.to_string_lossy().to_string())
|
||||
if env::var("CARGO_CFG_TARGET_OS").ok().as_deref() == Some("android") {
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_NDK_HOME");
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_NDK_ROOT");
|
||||
println!("cargo:rerun-if-env-changed=NDK_HOME");
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_HOME");
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_SDK_ROOT");
|
||||
println!("cargo:rerun-if-env-changed=CARGO_NDK_PLATFORM");
|
||||
builder = builder.clang_args(android_clang_args());
|
||||
}
|
||||
|
||||
builder
|
||||
// YUYV conversions
|
||||
.allowlist_function("YUY2ToI420")
|
||||
.allowlist_function("YUY2ToNV12")
|
||||
@@ -38,6 +48,7 @@ fn generate_bindings(cpp_dir: &Path) {
|
||||
// NV12/NV21 conversions
|
||||
.allowlist_function("NV12ToI420")
|
||||
.allowlist_function("NV21ToI420")
|
||||
.allowlist_function("NV21ToNV12")
|
||||
.allowlist_function("NV12Copy")
|
||||
.allowlist_function("SplitUVPlane")
|
||||
// ARGB/BGRA conversions
|
||||
@@ -49,6 +60,7 @@ fn generate_bindings(cpp_dir: &Path) {
|
||||
.allowlist_function("ABGRToARGB")
|
||||
// RGB24/BGR24 conversions
|
||||
.allowlist_function("RGB24ToI420")
|
||||
.allowlist_function("RGB24ToNV12")
|
||||
.allowlist_function("RAWToI420")
|
||||
.allowlist_function("RGB24ToARGB")
|
||||
.allowlist_function("RAWToARGB")
|
||||
@@ -62,6 +74,9 @@ fn generate_bindings(cpp_dir: &Path) {
|
||||
.allowlist_function("UYVYToARGB")
|
||||
.allowlist_function("ARGBToRGB24")
|
||||
.allowlist_function("ARGBToRAW")
|
||||
// MJPEG decoding
|
||||
.allowlist_function("MJPGToNV12")
|
||||
.allowlist_function("MJPGSize")
|
||||
// Scaling
|
||||
.allowlist_function("I420Scale")
|
||||
.allowlist_function("NV12Scale")
|
||||
@@ -81,6 +96,30 @@ fn generate_bindings(cpp_dir: &Path) {
|
||||
}
|
||||
|
||||
fn link_libyuv() {
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
|
||||
if target_os == "android" {
|
||||
if link_android_libyuv() {
|
||||
return;
|
||||
}
|
||||
if let Some(vcpkg_installed) = vcpkg_installed_root() {
|
||||
if link_vcpkg(vcpkg_installed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
panic!(
|
||||
"Android libyuv not found!\n\
|
||||
\n\
|
||||
Build it with scripts/build-android-libyuv.sh and set:\n\
|
||||
export ONE_KVM_ANDROID_LIBYUV_ROOT=/path/to/android-libyuv\n\
|
||||
\n\
|
||||
Expected layout:\n\
|
||||
$ONE_KVM_ANDROID_LIBYUV_ROOT/<abi>/include\n\
|
||||
$ONE_KVM_ANDROID_LIBYUV_ROOT/<abi>/lib/libyuv.a"
|
||||
);
|
||||
}
|
||||
|
||||
// Try vcpkg first
|
||||
if let Some(vcpkg_installed) = vcpkg_installed_root() {
|
||||
if link_vcpkg(vcpkg_installed) {
|
||||
@@ -109,6 +148,217 @@ fn link_libyuv() {
|
||||
);
|
||||
}
|
||||
|
||||
fn link_android_libyuv() -> bool {
|
||||
println!("cargo:rerun-if-env-changed=ONE_KVM_ANDROID_LIBYUV_ROOT");
|
||||
println!("cargo:rerun-if-env-changed=ONE_KVM_ANDROID_LIBYUV_STATIC");
|
||||
|
||||
let root = match env::var("ONE_KVM_ANDROID_LIBYUV_ROOT")
|
||||
.ok()
|
||||
.filter(|path| !path.trim().is_empty())
|
||||
{
|
||||
Some(path) => PathBuf::from(path),
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||
let abi = android_abi(&target_arch);
|
||||
let abi_root = root.join(abi);
|
||||
let lib_dir = if abi_root.join("lib").exists() {
|
||||
abi_root.join("lib")
|
||||
} else {
|
||||
root.join("lib")
|
||||
};
|
||||
let include_dir = if abi_root.join("include").exists() {
|
||||
abi_root.join("include")
|
||||
} else {
|
||||
root.join("include")
|
||||
};
|
||||
|
||||
let static_lib = lib_dir.join("libyuv.a");
|
||||
let shared_lib = lib_dir.join("libyuv.so");
|
||||
let use_static = env::var("ONE_KVM_ANDROID_LIBYUV_STATIC")
|
||||
.or_else(|_| env::var("LIBYUV_STATIC"))
|
||||
.map(|value| value != "0")
|
||||
.unwrap_or(true);
|
||||
|
||||
if use_static && static_lib.exists() {
|
||||
println!("cargo:rustc-link-search=native={}", lib_dir.display());
|
||||
println!("cargo:rustc-link-lib=static=yuv");
|
||||
link_android_libjpeg(&root, abi);
|
||||
println!("cargo:rustc-link-lib=c++_shared");
|
||||
println!(
|
||||
"cargo:info=Using Android libyuv from {} (static linking)",
|
||||
root.display()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
if shared_lib.exists() {
|
||||
println!("cargo:rustc-link-search=native={}", lib_dir.display());
|
||||
println!("cargo:rustc-link-lib=yuv");
|
||||
println!("cargo:rustc-link-lib=c++_shared");
|
||||
println!(
|
||||
"cargo:info=Using Android libyuv from {} (dynamic linking)",
|
||||
root.display()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:warning=Android libyuv not found under {} for ABI {} (checked {}, {})",
|
||||
root.display(),
|
||||
abi,
|
||||
static_lib.display(),
|
||||
shared_lib.display()
|
||||
);
|
||||
if !include_dir.exists() {
|
||||
println!(
|
||||
"cargo:warning=Android libyuv include directory not found: {}",
|
||||
include_dir.display()
|
||||
);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn link_android_libjpeg(libyuv_root: &Path, abi: &str) {
|
||||
println!("cargo:rerun-if-env-changed=ONE_KVM_ANDROID_TURBOJPEG_ROOT");
|
||||
|
||||
let mut roots = Vec::new();
|
||||
if let Ok(root) = env::var("ONE_KVM_ANDROID_TURBOJPEG_ROOT") {
|
||||
if !root.trim().is_empty() {
|
||||
roots.push(PathBuf::from(root));
|
||||
}
|
||||
}
|
||||
roots.push(libyuv_root.with_file_name("android-turbojpeg"));
|
||||
|
||||
for root in roots {
|
||||
let abi_lib_dir = root.join(abi).join("lib");
|
||||
let lib_dir = if abi_lib_dir.exists() {
|
||||
abi_lib_dir
|
||||
} else {
|
||||
root.join("lib")
|
||||
};
|
||||
let jpeg_lib = lib_dir.join("libjpeg.a");
|
||||
if jpeg_lib.exists() {
|
||||
println!("cargo:rustc-link-search=native={}", lib_dir.display());
|
||||
println!("cargo:rustc-link-lib=static=jpeg");
|
||||
println!(
|
||||
"cargo:info=Using Android libjpeg for libyuv MJPEG from {}",
|
||||
root.display()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
println!("cargo:warning=Android libjpeg.a not found; libyuv MJPEG symbols may fail to link");
|
||||
}
|
||||
|
||||
fn android_abi(target_arch: &str) -> &'static str {
|
||||
match target_arch {
|
||||
"aarch64" => "arm64-v8a",
|
||||
"arm" => "armeabi-v7a",
|
||||
"x86" => "x86",
|
||||
"x86_64" => "x86_64",
|
||||
_ => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn android_clang_args() -> Vec<String> {
|
||||
let ndk = android_ndk_home();
|
||||
let target = env::var("TARGET").unwrap_or_default();
|
||||
let toolchain = ndk.join("toolchains/llvm/prebuilt").join(host_tag());
|
||||
let sysroot = toolchain.join("sysroot");
|
||||
let clang_include = toolchain
|
||||
.join("lib/clang")
|
||||
.join(clang_version(&toolchain))
|
||||
.join("include");
|
||||
let api = env::var("CARGO_NDK_PLATFORM")
|
||||
.ok()
|
||||
.and_then(|value| value.parse::<u32>().ok())
|
||||
.unwrap_or(21);
|
||||
let clang_target = android_clang_target(&target);
|
||||
|
||||
vec![
|
||||
format!("--target={clang_target}"),
|
||||
format!("--sysroot={}", sysroot.display()),
|
||||
format!("-D__ANDROID_API__={api}"),
|
||||
format!("-isystem{}", clang_include.display()),
|
||||
format!("-isystem{}", sysroot.join("usr/include").display()),
|
||||
format!(
|
||||
"-isystem{}",
|
||||
sysroot.join("usr/include").join(clang_target).display()
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn android_clang_target(target: &str) -> &'static str {
|
||||
match target {
|
||||
"aarch64-linux-android" => "aarch64-linux-android",
|
||||
"armv7-linux-androideabi" => "armv7a-linux-androideabi",
|
||||
"i686-linux-android" => "i686-linux-android",
|
||||
"x86_64-linux-android" => "x86_64-linux-android",
|
||||
other => panic!("unsupported Android target for libyuv bindgen: {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn android_ndk_home() -> PathBuf {
|
||||
for key in ["ANDROID_NDK_HOME", "ANDROID_NDK_ROOT", "NDK_HOME"] {
|
||||
if let Ok(value) = env::var(key) {
|
||||
return PathBuf::from(value);
|
||||
}
|
||||
}
|
||||
|
||||
for key in ["ANDROID_HOME", "ANDROID_SDK_ROOT"] {
|
||||
if let Ok(value) = env::var(key) {
|
||||
let ndk_dir = PathBuf::from(value).join("ndk");
|
||||
if let Some(newest) = newest_child_dir(&ndk_dir) {
|
||||
return newest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!(
|
||||
"libyuv Android bindgen requires ANDROID_NDK_HOME, ANDROID_NDK_ROOT, NDK_HOME, \
|
||||
or ANDROID_HOME/ANDROID_SDK_ROOT with an ndk directory"
|
||||
);
|
||||
}
|
||||
|
||||
fn newest_child_dir(path: &Path) -> Option<PathBuf> {
|
||||
let mut entries = std::fs::read_dir(path)
|
||||
.ok()?
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.path())
|
||||
.filter(|path| path.is_dir())
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort();
|
||||
entries.pop()
|
||||
}
|
||||
|
||||
fn host_tag() -> &'static str {
|
||||
if cfg!(target_os = "linux") {
|
||||
"linux-x86_64"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"darwin-x86_64"
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"windows-x86_64"
|
||||
} else {
|
||||
panic!("unsupported host OS for Android NDK");
|
||||
}
|
||||
}
|
||||
|
||||
fn clang_version(toolchain: &Path) -> String {
|
||||
let clang_dir = toolchain.join("lib/clang");
|
||||
let mut entries = std::fs::read_dir(&clang_dir)
|
||||
.unwrap_or_else(|_| panic!("missing NDK clang directory: {}", clang_dir.display()))
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.file_name().to_string_lossy().into_owned())
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort();
|
||||
entries
|
||||
.pop()
|
||||
.unwrap_or_else(|| panic!("no clang versions found under: {}", clang_dir.display()))
|
||||
}
|
||||
|
||||
fn vcpkg_installed_root() -> Option<PathBuf> {
|
||||
println!("cargo:rerun-if-env-changed=VCPKG_INSTALLED_DIR");
|
||||
println!("cargo:rerun-if-env-changed=VCPKG_ROOT");
|
||||
@@ -133,6 +383,10 @@ fn link_vcpkg(mut path: PathBuf) -> bool {
|
||||
("linux", "x86_64") => "x64-linux",
|
||||
("linux", "aarch64") => "arm64-linux",
|
||||
("linux", "arm") => "arm-linux",
|
||||
("android", "x86_64") => "x64-android",
|
||||
("android", "x86") => "x86-android",
|
||||
("android", "aarch64") => "arm64-android",
|
||||
("android", "arm") => "arm-neon-android",
|
||||
("windows", "x86_64") => "x64-windows-static",
|
||||
("windows", "x86") => "x86-windows-static",
|
||||
("macos", "x86_64") => "x64-osx",
|
||||
@@ -169,14 +423,21 @@ fn link_vcpkg(mut path: PathBuf) -> bool {
|
||||
if use_static && static_lib.exists() {
|
||||
// Static linking (for deb packaging)
|
||||
println!("cargo:rustc-link-lib=static=yuv");
|
||||
#[cfg(target_os = "linux")]
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
link_libjpeg_for_static_libyuv(&[lib_path.clone()], &target_os);
|
||||
if target_os == "linux" {
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
} else if target_os == "android" {
|
||||
println!("cargo:rustc-link-lib=c++_shared");
|
||||
}
|
||||
println!("cargo:info=Using libyuv from vcpkg (static linking)");
|
||||
} else {
|
||||
// Dynamic linking (default for development)
|
||||
println!("cargo:rustc-link-lib=yuv");
|
||||
#[cfg(target_os = "linux")]
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
if target_os == "linux" {
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
} else if target_os == "android" {
|
||||
println!("cargo:rustc-link-lib=c++_shared");
|
||||
}
|
||||
println!("cargo:info=Using libyuv from vcpkg (dynamic linking)");
|
||||
}
|
||||
|
||||
@@ -268,6 +529,7 @@ fn link_system() -> bool {
|
||||
if use_static && libyuv_static.exists() {
|
||||
println!("cargo:rustc-link-search=native={}", path);
|
||||
println!("cargo:rustc-link-lib=static=yuv");
|
||||
link_libjpeg_for_static_libyuv(&[lib_path.to_path_buf()], "linux");
|
||||
println!("cargo:rustc-link-lib=stdc++");
|
||||
println!(
|
||||
"cargo:info=Using system libyuv from {} (static linking)",
|
||||
@@ -294,3 +556,58 @@ fn link_system() -> bool {
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn link_libjpeg_for_static_libyuv(preferred_lib_dirs: &[PathBuf], target_os: &str) {
|
||||
if target_os != "linux" {
|
||||
return;
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-env-changed=ONE_KVM_LIBJPEG_DIR");
|
||||
|
||||
let mut lib_dirs = Vec::new();
|
||||
if let Ok(path) = env::var("ONE_KVM_LIBJPEG_DIR") {
|
||||
if !path.trim().is_empty() {
|
||||
lib_dirs.push(PathBuf::from(path));
|
||||
}
|
||||
}
|
||||
lib_dirs.extend(preferred_lib_dirs.iter().cloned());
|
||||
lib_dirs.extend(
|
||||
[
|
||||
"/usr/local/lib",
|
||||
"/usr/local/lib64",
|
||||
"/usr/lib",
|
||||
"/usr/lib64",
|
||||
"/usr/lib/x86_64-linux-gnu",
|
||||
"/usr/lib/aarch64-linux-gnu",
|
||||
"/usr/lib/arm-linux-gnueabihf",
|
||||
"/usr/aarch64-linux-gnu/lib",
|
||||
"/usr/arm-linux-gnueabihf/lib",
|
||||
]
|
||||
.iter()
|
||||
.map(PathBuf::from),
|
||||
);
|
||||
|
||||
for lib_dir in dedupe_paths(lib_dirs) {
|
||||
if lib_dir.join("libjpeg.a").exists() {
|
||||
println!("cargo:rustc-link-search=native={}", lib_dir.display());
|
||||
println!("cargo:rustc-link-lib=static=jpeg");
|
||||
println!(
|
||||
"cargo:info=Using libjpeg for static libyuv MJPEG from {}",
|
||||
lib_dir.display()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
println!("cargo:warning=libjpeg.a not found; static libyuv built with MJPEG may fail to link");
|
||||
}
|
||||
|
||||
fn dedupe_paths(paths: Vec<PathBuf>) -> Vec<PathBuf> {
|
||||
let mut deduped = Vec::new();
|
||||
for path in paths {
|
||||
if !deduped.iter().any(|existing| existing == &path) {
|
||||
deduped.push(path);
|
||||
}
|
||||
}
|
||||
deduped
|
||||
}
|
||||
|
||||
@@ -103,6 +103,13 @@ int NV21ToI420(const uint8_t* src_y, int src_stride_y,
|
||||
uint8_t* dst_v, int dst_stride_v,
|
||||
int width, int height);
|
||||
|
||||
// NV21 -> NV12
|
||||
int NV21ToNV12(const uint8_t* src_y, int src_stride_y,
|
||||
const uint8_t* src_vu, int src_stride_vu,
|
||||
uint8_t* dst_y, int dst_stride_y,
|
||||
uint8_t* dst_uv, int dst_stride_uv,
|
||||
int width, int height);
|
||||
|
||||
// Split interleaved UV plane into separate U and V planes
|
||||
void SplitUVPlane(const uint8_t* src_uv, int src_stride_uv,
|
||||
uint8_t* dst_u, int dst_stride_u,
|
||||
@@ -167,6 +174,12 @@ int RAWToI420(const uint8_t* src_raw, int src_stride_raw,
|
||||
uint8_t* dst_v, int dst_stride_v,
|
||||
int width, int height);
|
||||
|
||||
// BGR24 -> NV12
|
||||
int RGB24ToNV12(const uint8_t* src_rgb24, int src_stride_rgb24,
|
||||
uint8_t* dst_y, int dst_stride_y,
|
||||
uint8_t* dst_uv, int dst_stride_uv,
|
||||
int width, int height);
|
||||
|
||||
// RGB24 -> ARGB
|
||||
int RGB24ToARGB(const uint8_t* src_rgb24, int src_stride_rgb24,
|
||||
uint8_t* dst_argb, int dst_stride_argb,
|
||||
@@ -253,12 +266,6 @@ int MJPGToNV12(const uint8_t* sample, size_t sample_size,
|
||||
int src_width, int src_height,
|
||||
int dst_width, int dst_height);
|
||||
|
||||
// MJPEG -> ARGB
|
||||
int MJPGToARGB(const uint8_t* sample, size_t sample_size,
|
||||
uint8_t* dst_argb, int dst_stride_argb,
|
||||
int src_width, int src_height,
|
||||
int dst_width, int dst_height);
|
||||
|
||||
// Get MJPEG dimensions without decoding
|
||||
int MJPGSize(const uint8_t* sample, size_t sample_size,
|
||||
int* width, int* height);
|
||||
|
||||
@@ -32,17 +32,9 @@ use std::fmt;
|
||||
// Include auto-generated FFI bindings
|
||||
include!(concat!(env!("OUT_DIR"), "/yuv_ffi.rs"));
|
||||
|
||||
// Type alias for C's size_t - adapts to platform pointer width
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
type SizeT = u32;
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
type SizeT = u64;
|
||||
|
||||
// Helper function to convert usize to C's size_t type
|
||||
#[inline]
|
||||
fn usize_to_size_t(val: usize) -> SizeT {
|
||||
val as SizeT
|
||||
fn usize_to_size_t(val: usize) -> usize {
|
||||
val
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -522,6 +514,34 @@ pub fn nv21_to_i420(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Resu
|
||||
))
|
||||
}
|
||||
|
||||
/// Convert NV21 to NV12 by swapping interleaved chroma bytes.
|
||||
pub fn nv21_to_nv12(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
||||
if width % 2 != 0 || height % 2 != 0 {
|
||||
return Err(YuvError::InvalidDimensions);
|
||||
}
|
||||
|
||||
let w = width as usize;
|
||||
let h = height as usize;
|
||||
let y_size = w * h;
|
||||
|
||||
if src.len() < nv12_size(w, h) || dst.len() < nv12_size(w, h) {
|
||||
return Err(YuvError::BufferTooSmall);
|
||||
}
|
||||
|
||||
call_yuv!(NV21ToNV12(
|
||||
src.as_ptr(),
|
||||
width,
|
||||
src[y_size..].as_ptr(),
|
||||
width,
|
||||
dst.as_mut_ptr(),
|
||||
width,
|
||||
dst[y_size..].as_mut_ptr(),
|
||||
width,
|
||||
width,
|
||||
height,
|
||||
))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ARGB/BGRA conversions (32-bit)
|
||||
// Note: libyuv ARGB = BGRA in memory on little-endian systems
|
||||
@@ -1046,7 +1066,7 @@ pub fn rgb24_to_nv12(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Res
|
||||
i420_to_nv12(&i420_buffer, dst, width, height)
|
||||
}
|
||||
|
||||
/// Convert BGR24 to NV12 (via two-step conversion: BGR24 → I420 → NV12)
|
||||
/// Convert BGR24 to NV12.
|
||||
pub fn bgr24_to_nv12(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
||||
if width % 2 != 0 || height % 2 != 0 {
|
||||
return Err(YuvError::InvalidDimensions);
|
||||
@@ -1059,10 +1079,71 @@ pub fn bgr24_to_nv12(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Res
|
||||
return Err(YuvError::BufferTooSmall);
|
||||
}
|
||||
|
||||
// Two-step conversion: BGR24 → I420 → NV12
|
||||
let mut i420_buffer = vec![0u8; i420_size(w, h)];
|
||||
bgr24_to_i420(src, &mut i420_buffer, width, height)?;
|
||||
i420_to_nv12(&i420_buffer, dst, width, height)
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut i420_buffer = vec![0u8; i420_size(w, h)];
|
||||
bgr24_to_i420(src, &mut i420_buffer, width, height)?;
|
||||
return i420_to_nv12(&i420_buffer, dst, width, height);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let y_size = w * h;
|
||||
call_yuv!(RGB24ToNV12(
|
||||
src.as_ptr(),
|
||||
width * 3,
|
||||
dst.as_mut_ptr(),
|
||||
width,
|
||||
dst[y_size..].as_mut_ptr(),
|
||||
width,
|
||||
width,
|
||||
height,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Read MJPEG dimensions without decoding the frame.
|
||||
pub fn mjpg_size(src: &[u8]) -> Result<(i32, i32)> {
|
||||
let mut width = 0;
|
||||
let mut height = 0;
|
||||
|
||||
call_yuv!(MJPGSize(
|
||||
src.as_ptr(),
|
||||
usize_to_size_t(src.len()),
|
||||
&mut width,
|
||||
&mut height,
|
||||
))?;
|
||||
|
||||
Ok((width, height))
|
||||
}
|
||||
|
||||
/// Decode MJPEG directly to NV12.
|
||||
pub fn mjpg_to_nv12(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
||||
if width % 2 != 0 || height % 2 != 0 {
|
||||
return Err(YuvError::InvalidDimensions);
|
||||
}
|
||||
|
||||
let w = width as usize;
|
||||
let h = height as usize;
|
||||
if dst.len() < nv12_size(w, h) {
|
||||
return Err(YuvError::BufferTooSmall);
|
||||
}
|
||||
|
||||
let y_size = w * h;
|
||||
let (dst_y, dst_uv) = dst.split_at_mut(y_size);
|
||||
|
||||
call_yuv!(MJPGToNV12(
|
||||
src.as_ptr(),
|
||||
usize_to_size_t(src.len()),
|
||||
dst_y.as_mut_ptr(),
|
||||
width,
|
||||
dst_uv.as_mut_ptr(),
|
||||
width,
|
||||
width,
|
||||
height,
|
||||
width,
|
||||
height,
|
||||
))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user