use std::io::{Cursor, ErrorKind, Read};
use nc_common::{RawFrame, chroma_len, yuv};
use nc_encoder::{CodecBackend, EncodedFrame, Encoder};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputFormat {
I420,
Yv12,
Nv12,
}
impl InputFormat {
pub fn parse(text: &str) -> Result<Self, String> {
match text {
"i420" => Ok(Self::I420),
"yv12" => Ok(Self::Yv12),
"nv12" => Ok(Self::Nv12),
other => Err(format!(
"unsupported --input-format '{other}' (i420, yv12, nv12)"
)),
}
}
}
pub struct FrameReader<R: Read> {
input: Replayed<R>,
format: InputFormat,
packed: Vec<u8>,
}
impl<R: Read> FrameReader<R> {
pub fn new(input: R, format: InputFormat, width: usize, height: usize) -> Self {
Self {
input: Replayed {
replay: Cursor::new(Vec::new()),
input,
},
format,
packed: packed_buffer(format, width, height),
}
}
pub fn set_size(&mut self, width: usize, height: usize) {
self.packed = packed_buffer(self.format, width, height);
}
pub fn encode_next<B: CodecBackend>(
&mut self,
encoder: &mut Encoder<B>,
timestamp_us: Option<i64>,
) -> Result<Option<EncodedFrame>, String> {
if self.format == InputFormat::I420 {
let read = yuv::read_frame(&mut self.input, encoder.source_mut())
.map_err(|e| e.to_string())?;
return Ok(read.then(|| match timestamp_us {
Some(t) => encoder.encode_written_at(t),
None => encoder.encode_written(),
}));
}
match self.input.read_exact(&mut self.packed) {
Ok(()) => {}
Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None),
Err(e) => return Err(e.to_string()),
}
let (width, height) = (encoder.config().width, encoder.config().height);
let frame = match self.format {
InputFormat::Nv12 => RawFrame::packed_nv12(&self.packed, width, height),
InputFormat::Yv12 => packed_yv12(&self.packed, width, height),
InputFormat::I420 => unreachable!("read in place above"),
}
.map_err(|e| e.to_string())?;
encoder
.encode_raw(&frame)
.map(Some)
.map_err(|e| e.to_string())
}
pub fn look_ahead_second<B: CodecBackend>(
&mut self,
encoder: &mut Encoder<B>,
) -> Result<(), String> {
if !encoder.wants_look_ahead() {
return Ok(());
}
let (width, height) = (encoder.config().width, encoder.config().height);
let frame_bytes = width * height + 2 * chroma_len(width) * chroma_len(height);
let mut ahead = Vec::with_capacity(2 * frame_bytes);
(&mut self.input)
.take(2 * frame_bytes as u64)
.read_to_end(&mut ahead)
.map_err(|e| e.to_string())?;
let handed = if ahead.len() == 2 * frame_bytes {
let packed = &ahead[frame_bytes..];
let frame = match self.format {
InputFormat::I420 => RawFrame::packed_i420(packed, width, height),
InputFormat::Nv12 => RawFrame::packed_nv12(packed, width, height),
InputFormat::Yv12 => packed_yv12(packed, width, height),
}
.map_err(|e| e.to_string())?;
encoder.look_ahead(&frame).map_err(|e| e.to_string())
} else {
Ok(())
};
self.input.replay = Cursor::new(ahead);
handed
}
}
struct Replayed<R: Read> {
replay: Cursor<Vec<u8>>,
input: R,
}
impl<R: Read> Read for Replayed<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self.replay.read(buf)? {
0 => self.input.read(buf),
read => Ok(read),
}
}
}
fn packed_buffer(format: InputFormat, width: usize, height: usize) -> Vec<u8> {
let chroma = chroma_len(width) * chroma_len(height);
match format {
InputFormat::I420 => Vec::new(),
InputFormat::Yv12 | InputFormat::Nv12 => vec![0; width * height + 2 * chroma],
}
}
fn packed_yv12(data: &[u8], width: usize, height: usize) -> nc_common::Result<RawFrame<'_>> {
match RawFrame::packed_i420(data, width, height)? {
RawFrame::I420 { y, u: cr, v: cb } => Ok(RawFrame::yv12(y, cr, cb)),
RawFrame::Nv12 { .. } => unreachable!("packed_i420 builds a planar frame"),
}
}
#[cfg(test)]
mod tests {
use super::{InputFormat, packed_yv12};
use nc_common::RawFrame;
#[test]
fn parses_formats() {
assert_eq!(InputFormat::parse("nv12"), Ok(InputFormat::Nv12));
assert!(InputFormat::parse("rgb").is_err());
}
#[test]
fn yv12_reads_cr_before_cb() {
let data = [0, 0, 0, 0, 9, 4]; let RawFrame::I420 { u, v, .. } = packed_yv12(&data, 2, 2).unwrap() else {
panic!("planar")
};
assert_eq!((u.row(0)[0], v.row(0)[0]), (4, 9));
}
}