use crate::encoder::bitwriter::BitWriter;
use crate::obu::ObuType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ObuExtensionHeader {
pub temporal_id: u8,
pub spatial_id: u8,
}
impl ObuExtensionHeader {
pub fn new(temporal_id: u8, spatial_id: u8) -> Self {
debug_assert!(temporal_id <= 7, "§5.3.3 temporal_id is 3 bits");
debug_assert!(spatial_id <= 3, "§5.3.3 spatial_id is 2 bits");
Self {
temporal_id,
spatial_id,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ObuHeader {
pub obu_type: ObuType,
pub extension: Option<ObuExtensionHeader>,
pub has_size: bool,
}
impl ObuHeader {
pub fn new(obu_type: ObuType) -> Self {
Self {
obu_type,
extension: None,
has_size: true,
}
}
pub fn with_extension(mut self, ext: ObuExtensionHeader) -> Self {
self.extension = Some(ext);
self
}
}
#[derive(Debug, Default)]
pub struct ObuWriter;
impl ObuWriter {
pub fn write(&self, out: &mut Vec<u8>, header: &ObuHeader, payload: &[u8]) -> usize {
let start_len = out.len();
write_obu(out, header, payload);
out.len() - start_len
}
}
pub fn obu_type_takes_trailing_bits(t: ObuType) -> bool {
!matches!(t, ObuType::TileGroup | ObuType::TileList | ObuType::Frame)
}
pub fn write_obu_with_size(out: &mut Vec<u8>, header: &ObuHeader, body_bytes: &[u8]) -> usize {
debug_assert!(
header.has_size,
"write_obu_with_size requires obu_has_size_field == 1 (§5.2 low-overhead)"
);
debug_assert!(
body_bytes.is_empty()
|| !obu_type_takes_trailing_bits(header.obu_type)
|| body_bytes.last() != Some(&0),
"trailer-taking OBU body must end with its §5.3.4 trailing_bits (last byte nonzero)"
);
let start_len = out.len();
write_obu(out, header, body_bytes);
out.len() - start_len
}
#[derive(Debug, Clone)]
pub struct ObuFrame {
pub header: ObuHeader,
pub body: Vec<u8>,
}
impl ObuFrame {
pub fn new(obu_type: ObuType, body: Vec<u8>) -> Self {
Self {
header: ObuHeader::new(obu_type),
body,
}
}
}
pub fn write_temporal_unit(frames: &[ObuFrame]) -> Vec<u8> {
let mut out = Vec::new();
for frame in frames {
write_obu_with_size(&mut out, &frame.header, &frame.body);
}
out
}
pub fn build_temporal_unit(seq_payload: Option<&[u8]>, frame_obus: &[ObuFrame]) -> Vec<u8> {
let mut frames: Vec<ObuFrame> = Vec::with_capacity(2 + frame_obus.len());
frames.push(ObuFrame::new(ObuType::TemporalDelimiter, Vec::new()));
if let Some(sh) = seq_payload {
frames.push(ObuFrame::new(ObuType::SequenceHeader, sh.to_vec()));
}
frames.extend_from_slice(frame_obus);
write_temporal_unit(&frames)
}
pub fn write_obu(out: &mut Vec<u8>, header: &ObuHeader, payload: &[u8]) {
let mut bw = BitWriter::new();
bw.write_bits(1, 0); bw.write_bits(4, u64::from(header.obu_type.as_raw() & 0x0f));
bw.write_bits(1, u64::from(header.extension.is_some()));
bw.write_bits(1, u64::from(header.has_size));
bw.write_bits(1, 0); debug_assert!(bw.is_byte_aligned());
if let Some(ext) = header.extension {
bw.write_bits(3, u64::from(ext.temporal_id & 0x7));
bw.write_bits(2, u64::from(ext.spatial_id & 0x3));
bw.write_bits(3, 0); debug_assert!(bw.is_byte_aligned());
}
let header_bytes = bw.finish();
out.extend_from_slice(&header_bytes);
if header.has_size {
let mut size_bw = BitWriter::new();
size_bw.write_leb128(payload.len() as u64);
out.extend_from_slice(&size_bw.finish());
}
out.extend_from_slice(payload);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::obu::{parse_obu, ObuIter};
#[test]
fn temporal_delimiter_byte_exact() {
let mut out = Vec::new();
ObuWriter.write(&mut out, &ObuHeader::new(ObuType::TemporalDelimiter), &[]);
assert_eq!(out, vec![0x12, 0x00]);
}
#[test]
fn round_trip_through_parser_temporal_delimiter() {
let mut out = Vec::new();
write_obu(&mut out, &ObuHeader::new(ObuType::TemporalDelimiter), &[]);
let (desc, consumed) = parse_obu(&out).unwrap();
assert_eq!(desc.obu_type, ObuType::TemporalDelimiter);
assert!(!desc.extension_flag);
assert!(desc.has_size_field);
assert_eq!(desc.temporal_id, 0);
assert_eq!(desc.spatial_id, 0);
assert_eq!(desc.payload_len, 0);
assert_eq!(consumed, out.len());
}
#[test]
fn round_trip_through_parser_frame_with_extension() {
let payload = [0xa1, 0xa2, 0xa3, 0xa4, 0xa5];
let header = ObuHeader::new(ObuType::Frame).with_extension(ObuExtensionHeader::new(3, 2));
let mut out = Vec::new();
write_obu(&mut out, &header, &payload);
let (desc, consumed) = parse_obu(&out).unwrap();
assert_eq!(desc.obu_type, ObuType::Frame);
assert!(desc.extension_flag);
assert!(desc.has_size_field);
assert_eq!(desc.temporal_id, 3);
assert_eq!(desc.spatial_id, 2);
assert_eq!(desc.payload_len, 5);
assert_eq!(desc.payload, &payload);
assert_eq!(consumed, out.len());
}
#[test]
fn round_trip_all_obu_types_no_extension() {
for obu_type in [
ObuType::SequenceHeader,
ObuType::TemporalDelimiter,
ObuType::FrameHeader,
ObuType::TileGroup,
ObuType::Metadata,
ObuType::Frame,
ObuType::RedundantFrameHeader,
ObuType::TileList,
ObuType::Padding,
] {
let payload: Vec<u8> = (0..7).collect();
let mut out = Vec::new();
write_obu(&mut out, &ObuHeader::new(obu_type), &payload);
let (desc, _) = parse_obu(&out).unwrap();
assert_eq!(desc.obu_type, obu_type);
assert_eq!(desc.payload, &payload[..]);
}
}
#[test]
fn multi_obu_concatenation_iter_walks() {
let mut out = Vec::new();
write_obu(&mut out, &ObuHeader::new(ObuType::TemporalDelimiter), &[]);
write_obu(
&mut out,
&ObuHeader::new(ObuType::SequenceHeader),
&[0x11, 0x22],
);
write_obu(
&mut out,
&ObuHeader::new(ObuType::Frame),
&[0x33, 0x44, 0x55],
);
let mut iter = ObuIter::new(&out);
let a = iter.next().unwrap().unwrap();
assert_eq!(a.obu_type, ObuType::TemporalDelimiter);
assert!(a.payload.is_empty());
let b = iter.next().unwrap().unwrap();
assert_eq!(b.obu_type, ObuType::SequenceHeader);
assert_eq!(b.payload, &[0x11, 0x22]);
let c = iter.next().unwrap().unwrap();
assert_eq!(c.obu_type, ObuType::Frame);
assert_eq!(c.payload, &[0x33, 0x44, 0x55]);
assert!(iter.next().is_none());
}
#[test]
fn leb128_size_field_handles_multi_byte() {
let payload = vec![0u8; 200];
let mut out = Vec::new();
write_obu(&mut out, &ObuHeader::new(ObuType::Padding), &payload);
assert_eq!(out.len(), 203);
let (desc, consumed) = parse_obu(&out).unwrap();
assert_eq!(desc.payload_len, 200);
assert_eq!(consumed, 203);
}
#[test]
fn extension_header_byte_layout_exact() {
let header =
ObuHeader::new(ObuType::FrameHeader).with_extension(ObuExtensionHeader::new(7, 3));
let mut out = Vec::new();
write_obu(&mut out, &header, &[]);
assert_eq!(out[0], 0x1E);
assert_eq!(out[1], 0xF8);
assert_eq!(out[2], 0x00); }
#[test]
fn reserved_obu_type_round_trips() {
let header = ObuHeader::new(ObuType::Reserved(9));
let mut out = Vec::new();
write_obu(&mut out, &header, &[0xde]);
let (desc, _) = parse_obu(&out).unwrap();
assert_eq!(desc.obu_type, ObuType::Reserved(9));
}
#[test]
fn write_obu_with_size_frames_pretrailered_body_verbatim() {
let body = vec![0x11, 0x22, 0x33, 0x80];
let mut out = Vec::new();
write_obu_with_size(&mut out, &ObuHeader::new(ObuType::SequenceHeader), &body);
let (desc, _consumed) = parse_obu(&out).unwrap();
assert_eq!(desc.obu_type, ObuType::SequenceHeader);
assert_eq!(desc.payload_len, 4); assert_eq!(desc.payload, &body[..]);
}
#[test]
fn write_obu_with_size_skips_trailer_for_frame_obu() {
let body = vec![0xAA, 0xBB];
let mut out = Vec::new();
write_obu_with_size(&mut out, &ObuHeader::new(ObuType::Frame), &body);
let (desc, _) = parse_obu(&out).unwrap();
assert_eq!(desc.payload_len, 2);
assert_eq!(desc.payload, &body[..]);
}
#[test]
fn write_obu_with_size_skips_trailer_for_tile_group_and_tile_list() {
for ty in [ObuType::TileGroup, ObuType::TileList] {
let body = vec![0x77, 0x88];
let mut out = Vec::new();
write_obu_with_size(&mut out, &ObuHeader::new(ty), &body);
let (desc, _) = parse_obu(&out).unwrap();
assert_eq!(desc.payload_len, 2, "type {ty:?}");
assert_eq!(desc.payload, &body[..], "type {ty:?}");
}
}
#[test]
fn write_obu_with_size_zero_body_emits_no_trailer() {
let mut out = Vec::new();
write_obu_with_size(&mut out, &ObuHeader::new(ObuType::TemporalDelimiter), &[]);
assert_eq!(out, vec![0x12, 0x00]);
}
#[test]
fn write_temporal_unit_walks_in_order() {
let frames = vec![
ObuFrame::new(ObuType::TemporalDelimiter, Vec::new()),
ObuFrame::new(ObuType::SequenceHeader, vec![0xAA, 0xBB, 0x80]),
ObuFrame::new(ObuType::FrameHeader, vec![0x11, 0x80]),
];
let bytes = write_temporal_unit(&frames);
let descs: Vec<_> = ObuIter::new(&bytes).collect::<Result<_, _>>().unwrap();
assert_eq!(descs.len(), 3);
assert_eq!(descs[0].obu_type, ObuType::TemporalDelimiter);
assert_eq!(descs[1].obu_type, ObuType::SequenceHeader);
assert_eq!(descs[1].payload_len, 3);
assert_eq!(descs[2].obu_type, ObuType::FrameHeader);
assert_eq!(descs[2].payload_len, 2);
}
#[test]
fn build_temporal_unit_emits_td_then_sh_then_frames() {
let sh_payload = [0xDE, 0xAD, 0x80];
let fh = ObuFrame::new(ObuType::FrameHeader, vec![0xBE, 0xEF, 0x80]);
let bytes = build_temporal_unit(Some(&sh_payload), &[fh]);
let descs: Vec<_> = ObuIter::new(&bytes).collect::<Result<_, _>>().unwrap();
assert_eq!(descs.len(), 3);
assert_eq!(descs[0].obu_type, ObuType::TemporalDelimiter);
assert_eq!(descs[0].payload_len, 0);
assert_eq!(descs[1].obu_type, ObuType::SequenceHeader);
assert_eq!(descs[1].payload_len, 3);
assert_eq!(descs[1].payload, &sh_payload);
assert_eq!(descs[2].obu_type, ObuType::FrameHeader);
assert_eq!(descs[2].payload_len, 3);
}
#[test]
fn build_temporal_unit_without_sh_just_emits_td_and_frames() {
let fh = ObuFrame::new(ObuType::FrameHeader, vec![0x42]);
let bytes = build_temporal_unit(None, &[fh]);
let descs: Vec<_> = ObuIter::new(&bytes).collect::<Result<_, _>>().unwrap();
assert_eq!(descs.len(), 2);
assert_eq!(descs[0].obu_type, ObuType::TemporalDelimiter);
assert_eq!(descs[1].obu_type, ObuType::FrameHeader);
}
#[test]
fn obu_type_takes_trailing_bits_matches_spec_exclusions() {
for ty in [
ObuType::SequenceHeader,
ObuType::TemporalDelimiter,
ObuType::FrameHeader,
ObuType::Metadata,
ObuType::RedundantFrameHeader,
ObuType::Padding,
ObuType::Reserved(9),
] {
assert!(obu_type_takes_trailing_bits(ty), "type {ty:?}");
}
for ty in [ObuType::TileGroup, ObuType::TileList, ObuType::Frame] {
assert!(!obu_type_takes_trailing_bits(ty), "type {ty:?}");
}
}
}