mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-04-29 17:36:35 +08:00
1314 lines
34 KiB
Rust
1314 lines
34 KiB
Rust
//! libyuv bindings for high-performance pixel format conversion
|
|
//!
|
|
//! This crate provides safe Rust bindings to Google's libyuv library,
|
|
//! which offers SIMD-accelerated (SSE/AVX/NEON) color space conversion.
|
|
//!
|
|
//! # Features
|
|
//!
|
|
//! - Zero-copy conversion (operates directly on slices)
|
|
//! - SIMD acceleration on x86_64 (SSE2/AVX2) and ARM (NEON)
|
|
//! - Support for common video formats: YUYV, UYVY, NV12, I420, RGB, MJPEG
|
|
//! - Scaling and rotation support
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! ```ignore
|
|
//! use libyuv::{yuy2_to_nv12, nv12_size};
|
|
//!
|
|
//! let width = 1920;
|
|
//! let height = 1080;
|
|
//! let yuyv_data: &[u8] = &[/* YUYV data from capture */];
|
|
//! let mut nv12_data = vec![0u8; nv12_size(width, height)];
|
|
//!
|
|
//! yuy2_to_nv12(yuyv_data, &mut nv12_data, width as i32, height as i32).unwrap();
|
|
//! ```
|
|
|
|
#![allow(non_camel_case_types)]
|
|
#![allow(non_snake_case)]
|
|
#![allow(non_upper_case_globals)]
|
|
#![allow(dead_code)]
|
|
|
|
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
|
|
}
|
|
|
|
// ============================================================================
|
|
// Error types
|
|
// ============================================================================
|
|
|
|
/// Error type for libyuv operations
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum YuvError {
|
|
/// Dimensions must be even numbers
|
|
InvalidDimensions,
|
|
/// Input or output buffer is too small
|
|
BufferTooSmall,
|
|
/// libyuv function returned an error code
|
|
ConversionFailed(i32),
|
|
}
|
|
|
|
impl fmt::Display for YuvError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
YuvError::InvalidDimensions => write!(f, "Invalid dimensions (must be even)"),
|
|
YuvError::BufferTooSmall => write!(f, "Buffer too small"),
|
|
YuvError::ConversionFailed(code) => write!(f, "Conversion failed with code {}", code),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for YuvError {}
|
|
|
|
pub type Result<T> = std::result::Result<T, YuvError>;
|
|
|
|
/// Macro to call libyuv functions and check return value
|
|
macro_rules! call_yuv {
|
|
($func:expr) => {{
|
|
let ret = unsafe { $func };
|
|
if ret != 0 {
|
|
return Err(YuvError::ConversionFailed(ret));
|
|
}
|
|
Ok(())
|
|
}};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Buffer size calculations
|
|
// ============================================================================
|
|
|
|
/// Calculate I420 (YUV420P) buffer size
|
|
#[inline]
|
|
pub const fn i420_size(width: usize, height: usize) -> usize {
|
|
width * height + (width / 2) * (height / 2) * 2
|
|
}
|
|
|
|
/// Calculate NV12 buffer size
|
|
#[inline]
|
|
pub const fn nv12_size(width: usize, height: usize) -> usize {
|
|
width * height * 3 / 2
|
|
}
|
|
|
|
/// Calculate YUYV/UYVY buffer size
|
|
#[inline]
|
|
pub const fn yuyv_size(width: usize, height: usize) -> usize {
|
|
width * height * 2
|
|
}
|
|
|
|
/// Calculate RGB24/BGR24 buffer size
|
|
#[inline]
|
|
pub const fn rgb24_size(width: usize, height: usize) -> usize {
|
|
width * height * 3
|
|
}
|
|
|
|
/// Calculate ARGB/BGRA buffer size
|
|
#[inline]
|
|
pub const fn argb_size(width: usize, height: usize) -> usize {
|
|
width * height * 4
|
|
}
|
|
|
|
// ============================================================================
|
|
// YUYV (YUY2) conversions
|
|
// ============================================================================
|
|
|
|
/// Convert YUYV (YUY2) to I420 (YUV420P)
|
|
///
|
|
/// # Arguments
|
|
/// * `src` - Source YUYV data
|
|
/// * `dst` - Destination I420 buffer
|
|
/// * `width` - Frame width (must be even)
|
|
/// * `height` - Frame height (must be even)
|
|
pub fn yuy2_to_i420(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < yuyv_size(w, h) || dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(YUY2ToI420(
|
|
src.as_ptr(),
|
|
width * 2,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert YUYV (YUY2) to NV12 (optimal for VAAPI)
|
|
///
|
|
/// # Arguments
|
|
/// * `src` - Source YUYV data
|
|
/// * `dst` - Destination NV12 buffer
|
|
/// * `width` - Frame width (must be even)
|
|
/// * `height` - Frame height (must be even)
|
|
pub fn yuy2_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() < yuyv_size(w, h) || dst.len() < nv12_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(YUY2ToNV12(
|
|
src.as_ptr(),
|
|
width * 2,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
// ============================================================================
|
|
// UYVY conversions
|
|
// ============================================================================
|
|
|
|
/// Convert UYVY to I420
|
|
pub fn uyvy_to_i420(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < yuyv_size(w, h) || dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(UYVYToI420(
|
|
src.as_ptr(),
|
|
width * 2,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert UYVY to NV12
|
|
pub fn uyvy_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() < yuyv_size(w, h) || dst.len() < nv12_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(UYVYToNV12(
|
|
src.as_ptr(),
|
|
width * 2,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
// ============================================================================
|
|
// I422 (YUV422P) -> I420 conversion
|
|
// ============================================================================
|
|
|
|
/// Convert I422 (YUV422P) to I420 (YUV420P) with separate planes and explicit strides
|
|
/// This performs vertical 2:1 chroma downsampling using SIMD
|
|
pub fn i422_to_i420_planar(
|
|
src_y: &[u8],
|
|
src_y_stride: i32,
|
|
src_u: &[u8],
|
|
src_u_stride: i32,
|
|
src_v: &[u8],
|
|
src_v_stride: i32,
|
|
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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(I422ToI420(
|
|
src_y.as_ptr(),
|
|
src_y_stride,
|
|
src_u.as_ptr(),
|
|
src_u_stride,
|
|
src_v.as_ptr(),
|
|
src_v_stride,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert I444 (YUV444P) to I420 (YUV420P) with separate planes and explicit strides
|
|
/// This performs horizontal and vertical chroma downsampling using SIMD
|
|
pub fn i444_to_i420_planar(
|
|
src_y: &[u8],
|
|
src_y_stride: i32,
|
|
src_u: &[u8],
|
|
src_u_stride: i32,
|
|
src_v: &[u8],
|
|
src_v_stride: i32,
|
|
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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(I444ToI420(
|
|
src_y.as_ptr(),
|
|
src_y_stride,
|
|
src_u.as_ptr(),
|
|
src_u_stride,
|
|
src_v.as_ptr(),
|
|
src_v_stride,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Split an interleaved UV plane into separate U and V planes using libyuv SIMD helpers.
|
|
///
|
|
/// `width` is the number of chroma samples per row, not the number of source bytes.
|
|
pub fn split_uv_plane(
|
|
src_uv: &[u8],
|
|
src_stride_uv: i32,
|
|
dst_u: &mut [u8],
|
|
dst_stride_u: i32,
|
|
dst_v: &mut [u8],
|
|
dst_stride_v: i32,
|
|
width: i32,
|
|
height: i32,
|
|
) -> Result<()> {
|
|
if width <= 0 || height <= 0 {
|
|
return Err(YuvError::InvalidDimensions);
|
|
}
|
|
|
|
let width = width as usize;
|
|
let height = height as usize;
|
|
let src_required = (src_stride_uv as usize).saturating_mul(height);
|
|
let dst_u_required = (dst_stride_u as usize).saturating_mul(height);
|
|
let dst_v_required = (dst_stride_v as usize).saturating_mul(height);
|
|
|
|
if src_uv.len() < src_required || dst_u.len() < dst_u_required || dst_v.len() < dst_v_required {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
unsafe {
|
|
SplitUVPlane(
|
|
src_uv.as_ptr(),
|
|
src_stride_uv,
|
|
dst_u.as_mut_ptr(),
|
|
dst_stride_u,
|
|
dst_v.as_mut_ptr(),
|
|
dst_stride_v,
|
|
width as i32,
|
|
height as i32,
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// ============================================================================
|
|
// I420 <-> NV12 conversions
|
|
// ============================================================================
|
|
|
|
/// Convert I420 (YUV420P) to NV12
|
|
pub fn i420_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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < i420_size(w, h) || dst.len() < nv12_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(I420ToNV12(
|
|
src.as_ptr(),
|
|
width,
|
|
src[y_size..].as_ptr(),
|
|
width / 2,
|
|
src[y_size + uv_size..].as_ptr(),
|
|
width / 2,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert I420 (YUV420P) to NV12 with separate planes and explicit strides
|
|
/// This is useful when working with decoder output that has stride padding
|
|
pub fn i420_to_nv12_planar(
|
|
y_plane: &[u8],
|
|
y_stride: i32,
|
|
u_plane: &[u8],
|
|
u_stride: i32,
|
|
v_plane: &[u8],
|
|
v_stride: i32,
|
|
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 dst.len() < nv12_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(I420ToNV12(
|
|
y_plane.as_ptr(),
|
|
y_stride,
|
|
u_plane.as_ptr(),
|
|
u_stride,
|
|
v_plane.as_ptr(),
|
|
v_stride,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert NV12 to I420 (YUV420P)
|
|
pub fn nv12_to_i420(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < nv12_size(w, h) || dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(NV12ToI420(
|
|
src.as_ptr(),
|
|
width,
|
|
src[y_size..].as_ptr(),
|
|
width,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert NV21 to I420 (YUV420P)
|
|
pub fn nv21_to_i420(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < nv12_size(w, h) || dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(NV21ToI420(
|
|
src.as_ptr(),
|
|
width,
|
|
src[y_size..].as_ptr(),
|
|
width,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
// ============================================================================
|
|
// ARGB/BGRA conversions (32-bit)
|
|
// Note: libyuv ARGB = BGRA in memory on little-endian systems
|
|
// ============================================================================
|
|
|
|
/// Convert BGRA to I420
|
|
///
|
|
/// Note: In libyuv, ARGB means BGRA byte order in memory
|
|
pub fn bgra_to_i420(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < argb_size(w, h) || dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(ARGBToI420(
|
|
src.as_ptr(),
|
|
width * 4,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert BGRA to NV12
|
|
pub fn bgra_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() < argb_size(w, h) || dst.len() < nv12_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(ARGBToNV12(
|
|
src.as_ptr(),
|
|
width * 4,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert RGBA to I420
|
|
///
|
|
/// Note: In libyuv, ABGR means RGBA byte order in memory
|
|
pub fn rgba_to_i420(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < argb_size(w, h) || dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(ABGRToI420(
|
|
src.as_ptr(),
|
|
width * 4,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert RGBA to NV12
|
|
pub fn rgba_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() < argb_size(w, h) || dst.len() < nv12_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(ABGRToNV12(
|
|
src.as_ptr(),
|
|
width * 4,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert BGRA to RGBA (swap R and B channels)
|
|
pub fn bgra_to_rgba(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
let size = argb_size(w, h);
|
|
|
|
if src.len() < size || dst.len() < size {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(ARGBToABGR(
|
|
src.as_ptr(),
|
|
width * 4,
|
|
dst.as_mut_ptr(),
|
|
width * 4,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert RGBA to BGRA (swap R and B channels)
|
|
pub fn rgba_to_bgra(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
let size = argb_size(w, h);
|
|
|
|
if src.len() < size || dst.len() < size {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(ABGRToARGB(
|
|
src.as_ptr(),
|
|
width * 4,
|
|
dst.as_mut_ptr(),
|
|
width * 4,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
// ============================================================================
|
|
// RGB24/BGR24 conversions (24-bit)
|
|
// Note: libyuv naming is confusing - RGB24 in libyuv is actually BGR in memory!
|
|
// ============================================================================
|
|
|
|
/// Convert RGB24 to I420
|
|
/// Note: Uses RAWToI420 because libyuv's "RAW" is actually RGB order
|
|
pub fn rgb24_to_i420(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < rgb24_size(w, h) || dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
// libyuv RAW = RGB byte order in memory
|
|
call_yuv!(RAWToI420(
|
|
src.as_ptr(),
|
|
width * 3,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert BGR24 to I420
|
|
/// Note: Uses RGB24ToI420 because libyuv's "RGB24" is actually BGR order
|
|
pub fn bgr24_to_i420(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < rgb24_size(w, h) || dst.len() < i420_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
// libyuv RGB24 = BGR byte order in memory
|
|
call_yuv!(RGB24ToI420(
|
|
src.as_ptr(),
|
|
width * 3,
|
|
dst.as_mut_ptr(),
|
|
width,
|
|
dst[y_size..].as_mut_ptr(),
|
|
width / 2,
|
|
dst[y_size + uv_size..].as_mut_ptr(),
|
|
width / 2,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert RGB24 to BGRA
|
|
pub fn rgb24_to_bgra(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
|
|
if src.len() < rgb24_size(w, h) || dst.len() < argb_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(RGB24ToARGB(
|
|
src.as_ptr(),
|
|
width * 3,
|
|
dst.as_mut_ptr(),
|
|
width * 4,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert BGR24 to BGRA
|
|
pub fn bgr24_to_bgra(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
|
|
if src.len() < rgb24_size(w, h) || dst.len() < argb_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(RAWToARGB(
|
|
src.as_ptr(),
|
|
width * 3,
|
|
dst.as_mut_ptr(),
|
|
width * 4,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
// ============================================================================
|
|
// YUV to RGB conversions (for display/JPEG encoding)
|
|
// ============================================================================
|
|
|
|
/// Convert I420 to RGB24
|
|
pub fn i420_to_rgb24(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < i420_size(w, h) || dst.len() < rgb24_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(I420ToRGB24(
|
|
src.as_ptr(),
|
|
width,
|
|
src[y_size..].as_ptr(),
|
|
width / 2,
|
|
src[y_size + uv_size..].as_ptr(),
|
|
width / 2,
|
|
dst.as_mut_ptr(),
|
|
width * 3,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert I420 to BGRA
|
|
pub fn i420_to_bgra(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;
|
|
let uv_size = (w / 2) * (h / 2);
|
|
|
|
if src.len() < i420_size(w, h) || dst.len() < argb_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(I420ToARGB(
|
|
src.as_ptr(),
|
|
width,
|
|
src[y_size..].as_ptr(),
|
|
width / 2,
|
|
src[y_size + uv_size..].as_ptr(),
|
|
width / 2,
|
|
dst.as_mut_ptr(),
|
|
width * 4,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert H444 (BT.709 limited-range YUV444P) to BGRA.
|
|
pub fn h444_to_bgra(
|
|
src_y: &[u8],
|
|
src_u: &[u8],
|
|
src_v: &[u8],
|
|
dst: &mut [u8],
|
|
width: i32,
|
|
height: i32,
|
|
) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
let plane_size = w * h;
|
|
|
|
if src_y.len() < plane_size || src_u.len() < plane_size || src_v.len() < plane_size {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
if dst.len() < argb_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(H444ToARGB(
|
|
src_y.as_ptr(),
|
|
width,
|
|
src_u.as_ptr(),
|
|
width,
|
|
src_v.as_ptr(),
|
|
width,
|
|
dst.as_mut_ptr(),
|
|
width * 4,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert NV12 to RGB24
|
|
pub fn nv12_to_rgb24(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() < rgb24_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(NV12ToRGB24(
|
|
src.as_ptr(),
|
|
width,
|
|
src[y_size..].as_ptr(),
|
|
width,
|
|
dst.as_mut_ptr(),
|
|
width * 3,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert NV12 to BGRA
|
|
pub fn nv12_to_bgra(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() < argb_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(NV12ToARGB(
|
|
src.as_ptr(),
|
|
width,
|
|
src[y_size..].as_ptr(),
|
|
width,
|
|
dst.as_mut_ptr(),
|
|
width * 4,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert YUYV to BGRA
|
|
pub fn yuy2_to_bgra(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
|
|
if src.len() < yuyv_size(w, h) || dst.len() < argb_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(YUY2ToARGB(
|
|
src.as_ptr(),
|
|
width * 2,
|
|
dst.as_mut_ptr(),
|
|
width * 4,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert YUYV to RGB24
|
|
pub fn yuy2_to_rgb24(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
|
|
if src.len() < yuyv_size(w, h) || dst.len() < rgb24_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
// libyuv doesn't have direct YUY2ToRGB24, use two-step conversion
|
|
// First convert to BGRA, then to RGB24
|
|
let mut bgra_buffer = vec![0u8; argb_size(w, h)];
|
|
yuy2_to_bgra(src, &mut bgra_buffer, width, height)?;
|
|
bgra_to_rgb24(&bgra_buffer, dst, width, height)
|
|
}
|
|
|
|
/// Convert UYVY to BGRA
|
|
pub fn uyvy_to_bgra(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
|
|
if src.len() < yuyv_size(w, h) || dst.len() < argb_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(UYVYToARGB(
|
|
src.as_ptr(),
|
|
width * 2,
|
|
dst.as_mut_ptr(),
|
|
width * 4,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert BGRA to RGB24
|
|
pub fn bgra_to_rgb24(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
|
|
if src.len() < argb_size(w, h) || dst.len() < rgb24_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(ARGBToRGB24(
|
|
src.as_ptr(),
|
|
width * 4,
|
|
dst.as_mut_ptr(),
|
|
width * 3,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert BGRA to BGR24
|
|
pub fn bgra_to_bgr24(src: &[u8], dst: &mut [u8], width: i32, height: i32) -> Result<()> {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
|
|
if src.len() < argb_size(w, h) || dst.len() < rgb24_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(ARGBToRAW(
|
|
src.as_ptr(),
|
|
width * 4,
|
|
dst.as_mut_ptr(),
|
|
width * 3,
|
|
width,
|
|
height,
|
|
))
|
|
}
|
|
|
|
/// Convert RGB24 to NV12 (via two-step conversion: RGB24 → I420 → NV12)
|
|
pub fn rgb24_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 src.len() < rgb24_size(w, h) || dst.len() < nv12_size(w, h) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
// Two-step conversion: RGB24 → I420 → NV12
|
|
let mut i420_buffer = vec![0u8; i420_size(w, h)];
|
|
rgb24_to_i420(src, &mut i420_buffer, width, height)?;
|
|
i420_to_nv12(&i420_buffer, dst, width, height)
|
|
}
|
|
|
|
/// Convert BGR24 to NV12 (via two-step conversion: BGR24 → I420 → 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);
|
|
}
|
|
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
|
|
if src.len() < rgb24_size(w, h) || dst.len() < nv12_size(w, h) {
|
|
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)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Scaling
|
|
// ============================================================================
|
|
|
|
/// Scale I420 frame
|
|
///
|
|
/// # Arguments
|
|
/// * `src` - Source I420 data
|
|
/// * `src_width` - Source width
|
|
/// * `src_height` - Source height
|
|
/// * `dst` - Destination buffer
|
|
/// * `dst_width` - Destination width
|
|
/// * `dst_height` - Destination height
|
|
/// * `filter` - Filtering mode
|
|
pub fn i420_scale(
|
|
src: &[u8],
|
|
src_width: i32,
|
|
src_height: i32,
|
|
dst: &mut [u8],
|
|
dst_width: i32,
|
|
dst_height: i32,
|
|
filter: FilterMode,
|
|
) -> Result<()> {
|
|
let sw = src_width as usize;
|
|
let sh = src_height as usize;
|
|
let dw = dst_width as usize;
|
|
let dh = dst_height as usize;
|
|
|
|
let src_y_size = sw * sh;
|
|
let src_uv_size = (sw / 2) * (sh / 2);
|
|
let dst_y_size = dw * dh;
|
|
let dst_uv_size = (dw / 2) * (dh / 2);
|
|
|
|
if src.len() < i420_size(sw, sh) || dst.len() < i420_size(dw, dh) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(I420Scale(
|
|
src.as_ptr(),
|
|
src_width,
|
|
src[src_y_size..].as_ptr(),
|
|
src_width / 2,
|
|
src[src_y_size + src_uv_size..].as_ptr(),
|
|
src_width / 2,
|
|
src_width,
|
|
src_height,
|
|
dst.as_mut_ptr(),
|
|
dst_width,
|
|
dst[dst_y_size..].as_mut_ptr(),
|
|
dst_width / 2,
|
|
dst[dst_y_size + dst_uv_size..].as_mut_ptr(),
|
|
dst_width / 2,
|
|
dst_width,
|
|
dst_height,
|
|
filter,
|
|
))
|
|
}
|
|
|
|
/// Scale NV12 frame
|
|
pub fn nv12_scale(
|
|
src: &[u8],
|
|
src_width: i32,
|
|
src_height: i32,
|
|
dst: &mut [u8],
|
|
dst_width: i32,
|
|
dst_height: i32,
|
|
filter: FilterMode,
|
|
) -> Result<()> {
|
|
let sw = src_width as usize;
|
|
let sh = src_height as usize;
|
|
let dw = dst_width as usize;
|
|
let dh = dst_height as usize;
|
|
|
|
let src_y_size = sw * sh;
|
|
let dst_y_size = dw * dh;
|
|
|
|
if src.len() < nv12_size(sw, sh) || dst.len() < nv12_size(dw, dh) {
|
|
return Err(YuvError::BufferTooSmall);
|
|
}
|
|
|
|
call_yuv!(NV12Scale(
|
|
src.as_ptr(),
|
|
src_width,
|
|
src[src_y_size..].as_ptr(),
|
|
src_width,
|
|
src_width,
|
|
src_height,
|
|
dst.as_mut_ptr(),
|
|
dst_width,
|
|
dst[dst_y_size..].as_mut_ptr(),
|
|
dst_width,
|
|
dst_width,
|
|
dst_height,
|
|
filter,
|
|
))
|
|
}
|
|
|
|
// ============================================================================
|
|
// High-level converter with buffer management
|
|
// ============================================================================
|
|
|
|
/// High-performance pixel format converter with pre-allocated buffers
|
|
///
|
|
/// This struct manages internal buffers to avoid repeated allocations
|
|
/// during continuous video processing.
|
|
pub struct Converter {
|
|
width: i32,
|
|
height: i32,
|
|
nv12_buffer: Vec<u8>,
|
|
i420_buffer: Vec<u8>,
|
|
}
|
|
|
|
impl Converter {
|
|
/// Create a new converter for the given resolution
|
|
pub fn new(width: i32, height: i32) -> Self {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
|
|
Self {
|
|
width,
|
|
height,
|
|
nv12_buffer: vec![0u8; nv12_size(w, h)],
|
|
i420_buffer: vec![0u8; i420_size(w, h)],
|
|
}
|
|
}
|
|
|
|
/// Get frame dimensions
|
|
pub fn dimensions(&self) -> (i32, i32) {
|
|
(self.width, self.height)
|
|
}
|
|
|
|
/// Update resolution (reallocates buffers if needed)
|
|
pub fn set_resolution(&mut self, width: i32, height: i32) {
|
|
if width != self.width || height != self.height {
|
|
let w = width as usize;
|
|
let h = height as usize;
|
|
self.width = width;
|
|
self.height = height;
|
|
self.nv12_buffer.resize(nv12_size(w, h), 0);
|
|
self.i420_buffer.resize(i420_size(w, h), 0);
|
|
}
|
|
}
|
|
|
|
/// Convert YUYV to NV12, returns reference to internal buffer
|
|
pub fn yuy2_to_nv12(&mut self, src: &[u8]) -> Result<&[u8]> {
|
|
yuy2_to_nv12(src, &mut self.nv12_buffer, self.width, self.height)?;
|
|
Ok(&self.nv12_buffer)
|
|
}
|
|
|
|
/// Convert YUYV to I420, returns reference to internal buffer
|
|
pub fn yuy2_to_i420(&mut self, src: &[u8]) -> Result<&[u8]> {
|
|
yuy2_to_i420(src, &mut self.i420_buffer, self.width, self.height)?;
|
|
Ok(&self.i420_buffer)
|
|
}
|
|
|
|
/// Convert UYVY to NV12, returns reference to internal buffer
|
|
pub fn uyvy_to_nv12(&mut self, src: &[u8]) -> Result<&[u8]> {
|
|
uyvy_to_nv12(src, &mut self.nv12_buffer, self.width, self.height)?;
|
|
Ok(&self.nv12_buffer)
|
|
}
|
|
|
|
/// Convert I420 to NV12, returns reference to internal buffer
|
|
pub fn i420_to_nv12(&mut self, src: &[u8]) -> Result<&[u8]> {
|
|
i420_to_nv12(src, &mut self.nv12_buffer, self.width, self.height)?;
|
|
Ok(&self.nv12_buffer)
|
|
}
|
|
|
|
/// Convert NV12 to I420, returns reference to internal buffer
|
|
pub fn nv12_to_i420(&mut self, src: &[u8]) -> Result<&[u8]> {
|
|
nv12_to_i420(src, &mut self.i420_buffer, self.width, self.height)?;
|
|
Ok(&self.i420_buffer)
|
|
}
|
|
|
|
/// Get NV12 buffer for direct writing
|
|
pub fn nv12_buffer_mut(&mut self) -> &mut [u8] {
|
|
&mut self.nv12_buffer
|
|
}
|
|
|
|
/// Get I420 buffer for direct writing
|
|
pub fn i420_buffer_mut(&mut self) -> &mut [u8] {
|
|
&mut self.i420_buffer
|
|
}
|
|
|
|
/// Get NV12 buffer reference
|
|
pub fn nv12_buffer(&self) -> &[u8] {
|
|
&self.nv12_buffer
|
|
}
|
|
|
|
/// Get I420 buffer reference
|
|
pub fn i420_buffer(&self) -> &[u8] {
|
|
&self.i420_buffer
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_buffer_sizes() {
|
|
assert_eq!(i420_size(1920, 1080), 1920 * 1080 * 3 / 2);
|
|
assert_eq!(nv12_size(1920, 1080), 1920 * 1080 * 3 / 2);
|
|
assert_eq!(yuyv_size(1920, 1080), 1920 * 1080 * 2);
|
|
assert_eq!(rgb24_size(1920, 1080), 1920 * 1080 * 3);
|
|
assert_eq!(argb_size(1920, 1080), 1920 * 1080 * 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_dimensions() {
|
|
let src = vec![0u8; 100];
|
|
let mut dst = vec![0u8; 100];
|
|
|
|
// Odd width should fail
|
|
assert!(matches!(
|
|
yuy2_to_nv12(&src, &mut dst, 3, 2),
|
|
Err(YuvError::InvalidDimensions)
|
|
));
|
|
|
|
// Odd height should fail
|
|
assert!(matches!(
|
|
yuy2_to_nv12(&src, &mut dst, 4, 3),
|
|
Err(YuvError::InvalidDimensions)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_buffer_too_small() {
|
|
let src = vec![0u8; 10]; // Too small
|
|
let mut dst = vec![0u8; nv12_size(4, 4)];
|
|
|
|
assert!(matches!(
|
|
yuy2_to_nv12(&src, &mut dst, 4, 4),
|
|
Err(YuvError::BufferTooSmall)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_converter() {
|
|
let mut converter = Converter::new(4, 4);
|
|
assert_eq!(converter.dimensions(), (4, 4));
|
|
|
|
converter.set_resolution(8, 8);
|
|
assert_eq!(converter.dimensions(), (8, 8));
|
|
assert_eq!(converter.nv12_buffer().len(), nv12_size(8, 8));
|
|
}
|
|
}
|