use anyhow::{Result, bail};
use bytes::BytesMut;
use crate::frame::{ColorSpace, PixelFormat, VideoFrame};
pub(super) fn nv12_to_yuv420p(frame: &VideoFrame) -> Result<VideoFrame> {
deinterleave_semiplanar_to_yuv420p(frame, false)
}
pub(super) fn nv21_to_yuv420p(frame: &VideoFrame) -> Result<VideoFrame> {
deinterleave_semiplanar_to_yuv420p(frame, true)
}
fn deinterleave_semiplanar_to_yuv420p(frame: &VideoFrame, v_first: bool) -> Result<VideoFrame> {
let w = frame.width as usize;
let h = frame.height as usize;
let y_size = w * h;
let uv_size = y_size / 4;
if frame.data.len() < y_size + 2 * uv_size {
bail!(
"{} frame too small for {}x{}: need {} bytes got {}",
if v_first { "NV21" } else { "NV12" },
w,
h,
y_size + 2 * uv_size,
frame.data.len()
);
}
let mut out = BytesMut::with_capacity(y_size + uv_size * 2);
out.extend_from_slice(&frame.data[..y_size]);
let uv = &frame.data[y_size..];
let mut u_plane = Vec::with_capacity(uv_size);
let mut v_plane = Vec::with_capacity(uv_size);
for i in 0..uv_size {
let (a, b) = (uv[i * 2], uv[i * 2 + 1]);
if v_first {
v_plane.push(a);
u_plane.push(b);
} else {
u_plane.push(a);
v_plane.push(b);
}
}
out.extend_from_slice(&u_plane);
out.extend_from_slice(&v_plane);
Ok(VideoFrame::new(
out.freeze(),
frame.width,
frame.height,
PixelFormat::Yuv420p,
frame.color_space,
frame.pts,
))
}
pub(super) fn yuv422p_to_yuv420p(frame: &VideoFrame) -> Result<VideoFrame> {
let w = frame.width as usize;
let h = frame.height as usize;
let cw = w.div_ceil(2);
let ch_in = h;
let ch_out = h.div_ceil(2);
let y_size = w * h;
let chroma_in_size = cw * ch_in;
let chroma_out_size = cw * ch_out;
if frame.data.len() < y_size + 2 * chroma_in_size {
bail!(
"Yuv422p frame too small for {}x{}: need {} bytes got {}",
w,
h,
y_size + 2 * chroma_in_size,
frame.data.len()
);
}
let (y_in, rest) = frame.data.split_at(y_size);
let (cb_in, cr_in) = rest.split_at(chroma_in_size);
let mut out = BytesMut::with_capacity(y_size + 2 * chroma_out_size);
out.extend_from_slice(y_in);
for plane in [cb_in, cr_in] {
for cy in 0..ch_out {
let y0 = 2 * cy;
let y1 = (y0 + 1).min(ch_in - 1);
for cx in 0..cw {
let s0 = plane[y0 * cw + cx] as u16;
let s1 = plane[y1 * cw + cx] as u16;
out.extend_from_slice(&[((s0 + s1 + 1) >> 1) as u8]);
}
}
}
Ok(VideoFrame::new(
out.freeze(),
frame.width,
frame.height,
PixelFormat::Yuv420p,
frame.color_space,
frame.pts,
))
}
pub(super) fn yuv422p10le_to_yuv420p10le(frame: &VideoFrame) -> Result<VideoFrame> {
let w = frame.width as usize;
let h = frame.height as usize;
let cw = w.div_ceil(2);
let ch_in = h;
let ch_out = h.div_ceil(2);
let y_samples = w * h;
let chroma_in_samples = cw * ch_in;
let chroma_out_samples = cw * ch_out;
let need_bytes = (y_samples + 2 * chroma_in_samples) * 2;
if frame.data.len() < need_bytes {
bail!(
"Yuv422p10le frame too small for {}x{}: need {} bytes got {}",
w,
h,
need_bytes,
frame.data.len()
);
}
let words = super::read_u16le(&frame.data[..need_bytes]);
let (y_in, rest) = words.split_at(y_samples);
let (cb_in, cr_in) = rest.split_at(chroma_in_samples);
let mut out = BytesMut::with_capacity((y_samples + 2 * chroma_out_samples) * 2);
super::write_u16le(&mut out, y_in);
for plane in [cb_in, cr_in] {
for cy in 0..ch_out {
let y0 = 2 * cy;
let y1 = (y0 + 1).min(ch_in - 1);
for cx in 0..cw {
let s0 = plane[y0 * cw + cx] as u32;
let s1 = plane[y1 * cw + cx] as u32;
let avg = ((s0 + s1 + 1) >> 1) as u16;
out.extend_from_slice(&avg.to_le_bytes());
}
}
}
Ok(VideoFrame::new(
out.freeze(),
frame.width,
frame.height,
PixelFormat::Yuv420p10le,
frame.color_space,
frame.pts,
))
}
pub(super) fn rgb_to_yuv420p_bt709(frame: &VideoFrame, has_alpha: bool) -> Result<VideoFrame> {
let w = frame.width as usize;
let h = frame.height as usize;
let stride = if has_alpha { 4 } else { 3 };
let need = w * h * stride;
if frame.data.len() < need {
bail!(
"{} frame too small for {}x{}: need {} bytes got {}",
if has_alpha { "Rgba32" } else { "Rgb24" },
w,
h,
need,
frame.data.len()
);
}
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let y_size = w * h;
let chroma_size = cw * ch;
let mut out = BytesMut::with_capacity(y_size + 2 * chroma_size);
out.resize(y_size + 2 * chroma_size, 0);
const Y_R: i32 = 5982;
const Y_G: i32 = 20128;
const Y_B: i32 = 2032;
const CB_R: i32 = -3299;
const CB_G: i32 = -11086;
const CB_B: i32 = 14385;
const CR_R: i32 = 14385;
const CR_G: i32 = -13066;
const CR_B: i32 = -1319;
for y in 0..h {
for x in 0..w {
let off = (y * w + x) * stride;
let r = frame.data[off] as i32;
let g = frame.data[off + 1] as i32;
let b = frame.data[off + 2] as i32;
let y_val = ((r * Y_R + g * Y_G + b * Y_B + (1 << 14)) >> 15) + 16;
out[y * w + x] = y_val.clamp(16, 235) as u8;
}
}
let cb_off = y_size;
let cr_off = y_size + chroma_size;
for cy in 0..ch {
let y0 = 2 * cy;
let y1 = (y0 + 1).min(h - 1);
for cx in 0..cw {
let x0 = 2 * cx;
let x1 = (x0 + 1).min(w - 1);
let mut r_sum = 0i32;
let mut g_sum = 0i32;
let mut b_sum = 0i32;
for &(py, px) in &[(y0, x0), (y0, x1), (y1, x0), (y1, x1)] {
let off = (py * w + px) * stride;
r_sum += frame.data[off] as i32;
g_sum += frame.data[off + 1] as i32;
b_sum += frame.data[off + 2] as i32;
}
let r = (r_sum + 2) >> 2;
let g = (g_sum + 2) >> 2;
let b = (b_sum + 2) >> 2;
let cb = ((r * CB_R + g * CB_G + b * CB_B + (1 << 14)) >> 15) + 128;
let cr = ((r * CR_R + g * CR_G + b * CR_B + (1 << 14)) >> 15) + 128;
out[cb_off + cy * cw + cx] = cb.clamp(16, 240) as u8;
out[cr_off + cy * cw + cx] = cr.clamp(16, 240) as u8;
}
}
Ok(VideoFrame::new(
out.freeze(),
frame.width,
frame.height,
PixelFormat::Yuv420p,
ColorSpace::Bt709,
frame.pts,
))
}