use super::frame::{ChromaFormat, InterlaceMode, ProResProfile};
#[must_use]
pub fn write_frame(
slices: &[Vec<u8>],
profile: ProResProfile,
width: u16,
height: u16,
frame_rate_code: u8,
chroma: ChromaFormat,
interlace: InterlaceMode,
scan_order: u8,
log2_slice_mb_width: u8,
) -> Vec<u8> {
let fourcc = profile_fourcc(profile);
let chroma_code: u8 = match chroma {
ChromaFormat::Yuv422 => 2,
ChromaFormat::Yuv444 => 3,
};
let interlace_code: u8 = match interlace {
InterlaceMode::Progressive => 0,
InterlaceMode::TopFieldFirst => 1,
InterlaceMode::BottomFieldFirst => 2,
};
let mut frame_hdr = Vec::with_capacity(20);
frame_hdr.extend_from_slice(&20u16.to_be_bytes()); frame_hdr.push(0x00); frame_hdr.extend_from_slice(&fourcc); frame_hdr.extend_from_slice(&width.to_be_bytes()); frame_hdr.extend_from_slice(&height.to_be_bytes()); frame_hdr.push((chroma_code << 6) | (interlace_code << 2)); frame_hdr.push((scan_order << 4) | (frame_rate_code & 0x0F)); frame_hdr.push(1); frame_hdr.push(1); frame_hdr.push(1); frame_hdr.push(0x00); frame_hdr.push(0x00); frame_hdr.push(0x00); frame_hdr.push(0x00); debug_assert_eq!(frame_hdr.len(), 20);
let slice_count = slices.len() as u16;
let offset_table_size = 2 * slice_count as usize;
let mut offset_table = Vec::with_capacity(offset_table_size);
for slice in slices {
let sz = slice.len() as u16;
offset_table.extend_from_slice(&sz.to_be_bytes());
}
let slice_data_size: usize = slices.iter().map(|s| s.len()).sum();
let picture_size = 8u32 + offset_table_size as u32 + slice_data_size as u32;
let mut pic_hdr = Vec::with_capacity(8);
pic_hdr.push(8u8); pic_hdr.extend_from_slice(&picture_size.to_be_bytes()); pic_hdr.extend_from_slice(&slice_count.to_be_bytes()); pic_hdr.push(log2_slice_mb_width << 4); debug_assert_eq!(pic_hdr.len(), 8);
let payload_size = frame_hdr.len() + pic_hdr.len() + offset_table_size + slice_data_size;
let frame_size = 8u32 + payload_size as u32;
let mut out = Vec::with_capacity(frame_size as usize);
out.extend_from_slice(&frame_size.to_be_bytes());
out.extend_from_slice(b"icpf");
out.extend_from_slice(&frame_hdr);
out.extend_from_slice(&pic_hdr);
out.extend_from_slice(&offset_table);
for slice in slices {
out.extend_from_slice(slice);
}
out
}
fn profile_fourcc(profile: ProResProfile) -> [u8; 4] {
match profile {
ProResProfile::Proxy => *b"apco",
ProResProfile::Lt => *b"apcs",
ProResProfile::Standard => *b"apcn",
ProResProfile::Hq => *b"apch",
ProResProfile::P4444 => *b"ap4h",
ProResProfile::P4444Xq => *b"ap4x",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prores::frame::{parse_frame_header, FrameContainer};
use crate::prores::picture::parse_picture_header;
fn dummy_slices(n: usize) -> Vec<Vec<u8>> {
(0..n).map(|i| vec![0u8; 20 + i]).collect()
}
#[test]
fn write_frame_round_trips_through_parser() {
let slices = dummy_slices(4);
let frame = write_frame(
&slices,
ProResProfile::Standard,
32,
16,
0x05, ChromaFormat::Yuv422,
InterlaceMode::Progressive,
0,
3,
);
let (container, rest) = FrameContainer::parse(&frame).expect("container parse");
assert!(rest.is_empty(), "no trailing bytes expected");
let (fhdr, after_fhdr) = parse_frame_header(container.payload).expect("frame header");
assert_eq!(fhdr.width, 32);
assert_eq!(fhdr.height, 16);
assert_eq!(fhdr.profile, ProResProfile::Standard);
assert_eq!(fhdr.chroma_format, ChromaFormat::Yuv422);
assert_eq!(fhdr.interlace_mode, InterlaceMode::Progressive);
let (pic_hdr, after_pic) = parse_picture_header(after_fhdr).expect("picture header");
assert_eq!(pic_hdr.slice_count, 4);
assert_eq!(pic_hdr.log2_slice_mb_width, 3);
let offset_table = &after_pic[..8];
for (i, chunk) in offset_table.chunks(2).enumerate() {
let sz = u16::from_be_bytes([chunk[0], chunk[1]]) as usize;
assert_eq!(sz, slices[i].len(), "slice {i} size mismatch in table");
}
}
#[test]
fn write_frame_frame_size_field_is_correct() {
let slices = vec![vec![0xABu8; 100]];
let frame = write_frame(
&slices,
ProResProfile::Hq,
1920,
1080,
0x05,
ChromaFormat::Yuv422,
InterlaceMode::Progressive,
0,
3,
);
let declared_size = u32::from_be_bytes([frame[0], frame[1], frame[2], frame[3]]) as usize;
assert_eq!(
declared_size,
frame.len(),
"frame_size field should match actual length"
);
}
#[test]
fn write_frame_all_profiles() {
for profile in [
ProResProfile::Proxy,
ProResProfile::Lt,
ProResProfile::Standard,
ProResProfile::Hq,
] {
let frame = write_frame(
&dummy_slices(2),
profile,
16,
16,
0,
ChromaFormat::Yuv422,
InterlaceMode::Progressive,
0,
3,
);
let (container, _) = FrameContainer::parse(&frame).expect("container");
let (fhdr, _) = parse_frame_header(container.payload).expect("frame hdr");
assert_eq!(fhdr.profile, profile);
}
}
}