use crate::obu::{parse_leb128, parse_obu, ObuType};
use crate::Error;
fn write_leb128(out: &mut Vec<u8>, mut value: u64) {
loop {
let mut byte = (value & 0x7f) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
out.push(byte);
if value == 0 {
break;
}
}
}
pub fn split_temporal_units(bytes: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
let mut out: Vec<Vec<u8>> = Vec::new();
let mut pos = 0usize;
while pos < bytes.len() {
let (tu_size, n) = parse_leb128(&bytes[pos..])?;
pos += n;
let tu_size = usize::try_from(tu_size).map_err(|_| Error::SizeOverflow)?;
let tu_end = pos.checked_add(tu_size).ok_or(Error::SizeOverflow)?;
if tu_end > bytes.len() {
return Err(Error::UnexpectedEnd);
}
let mut low_overhead: Vec<u8> = Vec::with_capacity(tu_size + 8);
let mut first_fu = true;
while pos < tu_end {
let (fu_size, n) = parse_leb128(&bytes[pos..tu_end])?;
pos += n;
let fu_size = usize::try_from(fu_size).map_err(|_| Error::SizeOverflow)?;
let fu_end = pos.checked_add(fu_size).ok_or(Error::SizeOverflow)?;
if fu_end > tu_end {
return Err(Error::AnnexBInvalid);
}
let mut first_obu = true;
while pos < fu_end {
let (obu_length, n) = parse_leb128(&bytes[pos..fu_end])?;
pos += n;
let obu_length = usize::try_from(obu_length).map_err(|_| Error::SizeOverflow)?;
let obu_end = pos.checked_add(obu_length).ok_or(Error::SizeOverflow)?;
if obu_end > fu_end {
return Err(Error::AnnexBInvalid);
}
append_low_overhead_obu(
&bytes[pos..obu_end],
first_fu && first_obu,
&mut low_overhead,
)?;
pos = obu_end;
first_obu = false;
}
if pos != fu_end {
return Err(Error::AnnexBInvalid);
}
first_fu = false;
}
if pos != tu_end {
return Err(Error::AnnexBInvalid);
}
out.push(low_overhead);
}
Ok(out)
}
fn append_low_overhead_obu(
obu_bytes: &[u8],
must_be_td: bool,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let first = *obu_bytes.first().ok_or(Error::AnnexBInvalid)?;
if (first >> 7) & 1 != 0 {
return Err(Error::ForbiddenBitSet);
}
let obu_type = ObuType::from_raw((first >> 3) & 0x0f);
let extension_flag = (first >> 2) & 1 != 0;
let has_size_field = (first >> 1) & 1 != 0;
let is_td = obu_type == ObuType::TemporalDelimiter;
if must_be_td != is_td {
return Err(Error::AnnexBInvalid);
}
let header_len = 1 + usize::from(extension_flag);
if obu_bytes.len() < header_len {
return Err(Error::UnexpectedEnd);
}
if has_size_field {
let (_, consumed) = parse_obu(obu_bytes)?;
if consumed != obu_bytes.len() {
return Err(Error::AnnexBInvalid);
}
out.extend_from_slice(obu_bytes);
} else {
let payload = &obu_bytes[header_len..];
out.push(first | 0b10);
if extension_flag {
out.push(obu_bytes[1]);
}
write_leb128(out, payload.len() as u64);
out.extend_from_slice(payload);
}
Ok(())
}
pub fn build_from_temporal_units(temporal_units: &[Vec<u8>]) -> Result<Vec<u8>, Error> {
let mut out: Vec<u8> = Vec::new();
for tu in temporal_units {
let mut frame_units: Vec<Vec<u8>> = Vec::new();
let mut current: Vec<u8> = Vec::new();
let mut current_has_frame = false;
let mut pos = 0usize;
let mut first = true;
while pos < tu.len() {
let (desc, consumed) = parse_obu(&tu[pos..])?;
let obu_bytes = &tu[pos..pos + consumed];
pos += consumed;
if first {
if desc.obu_type != ObuType::TemporalDelimiter {
return Err(Error::AnnexBInvalid);
}
first = false;
}
let starts_frame = matches!(desc.obu_type, ObuType::Frame | ObuType::FrameHeader);
if starts_frame && current_has_frame {
frame_units.push(std::mem::take(&mut current));
current_has_frame = false;
}
let mut obu_out: Vec<u8> = Vec::with_capacity(consumed + 4);
write_leb128(&mut obu_out, consumed as u64);
obu_out.extend_from_slice(obu_bytes);
current.extend_from_slice(&obu_out);
if starts_frame {
current_has_frame = true;
}
}
if !current.is_empty() {
frame_units.push(current);
}
let mut tu_body: Vec<u8> = Vec::new();
for fu in &frame_units {
write_leb128(&mut tu_body, fu.len() as u64);
tu_body.extend_from_slice(fu);
}
write_leb128(&mut out, tu_body.len() as u64);
out.extend_from_slice(&tu_body);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_then_split_round_trips_low_overhead_units() {
let tu: Vec<u8> = vec![0x12, 0x00, 0x32, 0x03, 1, 2, 3];
let annexb = build_from_temporal_units(std::slice::from_ref(&tu)).unwrap();
let back = split_temporal_units(&annexb).unwrap();
assert_eq!(back, vec![tu]);
}
#[test]
fn sizeless_obu_gains_a_size_field() {
let fu: Vec<u8> = vec![0x01, 0x10, 0x04, 0x30, 7, 8, 9];
let mut annexb: Vec<u8> = Vec::new();
write_leb128(&mut annexb, (fu.len() + 1) as u64);
write_leb128(&mut annexb, fu.len() as u64);
annexb.extend_from_slice(&fu);
let back = split_temporal_units(&annexb).unwrap();
assert_eq!(back, vec![vec![0x12, 0x00, 0x32, 0x03, 7, 8, 9]]);
}
#[test]
fn td_placement_is_enforced() {
let fu: Vec<u8> = vec![0x04, 0x30, 7, 8, 9];
let mut annexb: Vec<u8> = Vec::new();
write_leb128(&mut annexb, fu.len() as u64);
annexb.extend_from_slice(&fu);
assert_eq!(split_temporal_units(&annexb), Err(Error::AnnexBInvalid));
let fu: Vec<u8> = vec![0x01, 0x10, 0x01, 0x10];
let mut annexb: Vec<u8> = Vec::new();
write_leb128(&mut annexb, fu.len() as u64);
annexb.extend_from_slice(&fu);
assert_eq!(split_temporal_units(&annexb), Err(Error::AnnexBInvalid));
}
#[test]
fn size_length_inconsistency_is_invalid() {
let fu: Vec<u8> = vec![0x02, 0x12, 0x00, 0x06, 0x32, 0x03, 1, 2, 3, 0];
let mut annexb: Vec<u8> = Vec::new();
write_leb128(&mut annexb, fu.len() as u64);
annexb.extend_from_slice(&fu);
assert_eq!(split_temporal_units(&annexb), Err(Error::AnnexBInvalid));
}
#[test]
fn truncations_error_cleanly() {
let tu: Vec<u8> = vec![0x12, 0x00, 0x32, 0x03, 1, 2, 3];
let annexb = build_from_temporal_units(std::slice::from_ref(&tu)).unwrap();
for cut in 1..annexb.len() {
assert!(
split_temporal_units(&annexb[..cut]).is_err(),
"cut at {cut} must not parse"
);
}
}
}