use anyhow::{Result, bail};
use bytes::Bytes;
use crate::frame::{PixelFormat, VideoFrame};
pub fn downsample_chroma_444_to_420(
y: &[u8],
cb: &[u8],
cr: &[u8],
width: usize,
height: usize,
) -> Vec<u8> {
debug_assert_eq!(y.len(), width * height, "Y plane size");
debug_assert_eq!(cb.len(), width * height, "Cb plane size (4:4:4)");
debug_assert_eq!(cr.len(), width * height, "Cr plane size (4:4:4)");
let cw = width.div_ceil(2);
let ch = height.div_ceil(2);
let mut out = Vec::with_capacity(width * height + 2 * cw * ch);
out.extend_from_slice(y);
for plane in [cb, cr] {
for cy in 0..ch {
let y0 = 2 * cy;
let y1 = (y0 + 1).min(height - 1);
for cx in 0..cw {
let x0 = 2 * cx;
let x1 = (x0 + 1).min(width - 1);
let s00 = plane[y0 * width + x0] as u16;
let s01 = plane[y0 * width + x1] as u16;
let s10 = plane[y1 * width + x0] as u16;
let s11 = plane[y1 * width + x1] as u16;
let avg = ((s00 + s01 + s10 + s11 + 2) >> 2) as u8;
out.push(avg);
}
}
}
out
}
pub fn downsample_chroma_444_to_420_10bit(
y: &[u16],
cb: &[u16],
cr: &[u16],
width: usize,
height: usize,
) -> Vec<u8> {
debug_assert_eq!(y.len(), width * height, "Y plane samples");
debug_assert_eq!(cb.len(), width * height, "Cb plane samples (4:4:4)");
debug_assert_eq!(cr.len(), width * height, "Cr plane samples (4:4:4)");
let cw = width.div_ceil(2);
let ch = height.div_ceil(2);
let total_samples = width * height + 2 * cw * ch;
let mut out = Vec::with_capacity(total_samples * 2);
for &s in y {
out.extend_from_slice(&s.to_le_bytes());
}
for plane in [cb, cr] {
for cy in 0..ch {
let y0 = 2 * cy;
let y1 = (y0 + 1).min(height - 1);
for cx in 0..cw {
let x0 = 2 * cx;
let x1 = (x0 + 1).min(width - 1);
let s00 = plane[y0 * width + x0] as u32;
let s01 = plane[y0 * width + x1] as u32;
let s10 = plane[y1 * width + x0] as u32;
let s11 = plane[y1 * width + x1] as u32;
let avg = ((s00 + s01 + s10 + s11 + 2) >> 2) as u16;
out.extend_from_slice(&avg.to_le_bytes());
}
}
}
out
}
pub fn downsample_444_to_420_frame(frame: &VideoFrame) -> Result<VideoFrame> {
let w = frame.width as usize;
let h = frame.height as usize;
if w == 0 || h == 0 {
bail!("zero-dimension frame");
}
match frame.format {
PixelFormat::Yuv444p => {
let plane = w * h;
if frame.data.len() < 3 * plane {
bail!(
"Yuv444p frame data too short for {}x{}: {} bytes",
w,
h,
frame.data.len()
);
}
let y = &frame.data[..plane];
let cb = &frame.data[plane..2 * plane];
let cr = &frame.data[2 * plane..3 * plane];
let out = downsample_chroma_444_to_420(y, cb, cr, w, h);
Ok(VideoFrame::new(
Bytes::from(out),
frame.width,
frame.height,
PixelFormat::Yuv420p,
frame.color_space,
frame.pts,
))
}
PixelFormat::Yuv444p10le | PixelFormat::Yuva444p10le => {
let plane = w * h;
let needed = if frame.format == PixelFormat::Yuva444p10le {
4 * plane * 2
} else {
3 * plane * 2
};
if frame.data.len() < needed {
bail!(
"{:?} frame data too short for {}x{}: {} bytes (need {})",
frame.format,
w,
h,
frame.data.len(),
needed
);
}
let y = super::read_u16le(&frame.data[..plane * 2]);
let cb = super::read_u16le(&frame.data[plane * 2..2 * plane * 2]);
let cr = super::read_u16le(&frame.data[2 * plane * 2..3 * plane * 2]);
if frame.format == PixelFormat::Yuva444p10le {
tracing::warn!(
pts = frame.pts,
"dropping alpha plane on 4:4:4→4:2:0 downsample \
(rav1e 0.7 has no alpha; pipeline target is Yuv420p10le)"
);
}
let out = downsample_chroma_444_to_420_10bit(&y, &cb, &cr, w, h);
Ok(VideoFrame::new(
Bytes::from(out),
frame.width,
frame.height,
PixelFormat::Yuv420p10le,
frame.color_space,
frame.pts,
))
}
other => bail!(
"downsample_444_to_420_frame: expected 4:4:4 input, got {:?}",
other
),
}
}