pub(crate) const NAL_IDR: u8 = 5;
pub(crate) const NAL_SPS: u8 = 7;
pub(crate) const NAL_PPS: u8 = 8;
pub(crate) const NAL_LENGTH_SIZE: usize = 4;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub(crate) struct AuScan {
pub(crate) nal_count: usize,
pub(crate) has_idr: bool,
pub(crate) has_parameter_set: bool,
}
impl AuScan {
pub(crate) fn note(&mut self, header: u8) {
let nal_type = header & 0x1F;
self.nal_count += 1;
if nal_type == NAL_IDR {
self.has_idr = true;
}
if nal_type == NAL_SPS || nal_type == NAL_PPS {
self.has_parameter_set = true;
}
}
}
pub(crate) fn walk_annexb<'a>(
data: &'a [u8],
mut on_nal: impl FnMut(&'a [u8]),
) -> Result<AuScan, String> {
if data.len() < 4 {
return Err(format!("Annex-B payload too short ({} bytes)", data.len()));
}
let mut pos = 0;
while pos < data.len() && data[pos] == 0 {
pos += 1;
}
if pos < 2 || pos >= data.len() || data[pos] != 1 {
return Err("payload does not begin with an Annex-B start code".to_string());
}
pos += 1;
let mut scan = AuScan::default();
loop {
let boundary = find_startcode(data, pos).unwrap_or(data.len());
let mut end = boundary;
while end > pos && data[end - 1] == 0 {
end -= 1;
}
if end == pos {
return Err("empty NAL unit".to_string());
}
scan.note(data[pos]);
on_nal(&data[pos..end]);
if !data[boundary..].iter().any(|&b| b != 0) {
break;
}
let mut next = boundary;
while next < data.len() && data[next] == 0 {
next += 1;
}
if next >= data.len() || data[next] != 1 {
return Err("malformed start code between NAL units".to_string());
}
pos = next + 1;
if pos >= data.len() {
return Err("trailing start code without a NAL unit".to_string());
}
}
Ok(scan)
}
#[inline(always)]
pub(crate) fn find_startcode(data: &[u8], from: usize) -> Option<usize> {
let n = data.len();
if n < 3 {
return None;
}
let end = n - 2;
let mut i = from;
while i < end {
if i + 4 <= n {
let w = u32::from_le_bytes(data[i..i + 4].try_into().expect("4-byte chunk"));
if (w & 0x00FF_0000) == 0 {
i += 1;
continue;
}
if (w & 0x00FF_FFFF) == 0x0001_0000 {
return Some(attribute(data, from, i));
}
i += 3;
} else {
if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
return Some(attribute(data, from, i));
}
i += 1;
}
}
None
}
fn attribute(data: &[u8], from: usize, i: usize) -> usize {
if i > from && data[i - 1] == 0 {
i - 1
} else {
i
}
}
pub(crate) fn walk_length_prefixed<'a>(
data: &'a [u8],
mut on_nal: impl FnMut(&'a [u8]),
) -> Result<AuScan, String> {
let mut scan = AuScan::default();
let mut pos = 0usize;
while pos < data.len() {
if data.len() - pos < NAL_LENGTH_SIZE {
return Err("truncated NAL length prefix".to_string());
}
let len = u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
as usize;
pos += NAL_LENGTH_SIZE;
if len == 0 {
return Err("zero-length NAL unit".to_string());
}
if data.len() - pos < len {
return Err(format!(
"NAL length {len} overruns the packet ({} bytes remain)",
data.len() - pos
));
}
scan.note(data[pos]);
on_nal(&data[pos..pos + len]);
pos += len;
}
if scan.nal_count == 0 {
return Err("packet contains no NAL units".to_string());
}
Ok(scan)
}
pub(crate) fn push_length_prefixed(nal: &[u8], out: &mut Vec<u8>) {
debug_assert!(nal.len() <= u32::MAX as usize);
out.extend_from_slice(&(nal.len() as u32).to_be_bytes());
out.extend_from_slice(nal);
}
#[cfg(test)]
pub(crate) fn collect_annexb(data: &[u8]) -> Result<Vec<&[u8]>, String> {
let mut nals = Vec::new();
walk_annexb(data, |n| nals.push(n))?;
Ok(nals)
}
#[cfg(test)]
pub(crate) fn collect_length_prefixed(data: &[u8]) -> Result<Vec<&[u8]>, String> {
let mut nals = Vec::new();
walk_length_prefixed(data, |n| nals.push(n))?;
Ok(nals)
}
#[cfg(test)]
mod tests {
use super::*;
const SPS: &[u8] = &[
0x67, 0x42, 0xC0, 0x1E, 0xD9, 0x01, 0x41, 0xFB, 0x01, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10,
0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x62, 0xE4, 0x80,
];
const PPS: &[u8] = &[0x68, 0xCB, 0x83, 0xCB, 0x20];
fn annexb_config() -> Vec<u8> {
let mut v = vec![0, 0, 0, 1];
v.extend_from_slice(SPS);
v.extend_from_slice(&[0, 0, 1]);
v.extend_from_slice(PPS);
v
}
#[test]
fn splits_three_and_four_byte_start_codes() {
let config = annexb_config();
let nals = collect_annexb(&config).unwrap();
assert_eq!(nals, vec![SPS, PPS]);
}
#[test]
fn rejects_garbage_prefix_and_empty_nals() {
assert!(collect_annexb(&[0x12, 0, 0, 1, 0x67]).is_err());
assert!(collect_annexb(&[0, 0, 1]).is_err());
assert!(collect_annexb(&[0, 0, 1, 0, 0, 1, 0x41, 0x9A]).is_err());
}
#[test]
fn converts_annexb_au_to_length_prefixed_in_one_walk() {
let mut au = vec![0, 0, 0, 1, 0x65, 0x88, 0x80];
au.extend_from_slice(&[0, 0, 1, 0x06, 0x05, 0xFF]);
let mut out = Vec::new();
let scan = walk_annexb(&au, |nal| push_length_prefixed(nal, &mut out)).unwrap();
assert!(scan.has_idr);
assert!(!scan.has_parameter_set);
assert_eq!(scan.nal_count, 2);
assert_eq!(
out,
vec![0, 0, 0, 3, 0x65, 0x88, 0x80, 0, 0, 0, 3, 0x06, 0x05, 0xFF]
);
let back = collect_length_prefixed(&out).unwrap();
assert_eq!(back, collect_annexb(&au).unwrap());
}
#[test]
fn trailing_zero_bytes_are_trimmed_like_ffmpeg_master() {
let au = vec![0, 0, 0, 1, 0x65, 0xAA, 0, 0, 0, 0, 1, 0x06, 0x05];
let nals = collect_annexb(&au).unwrap();
assert_eq!(nals, vec![&[0x65u8, 0xAA][..], &[0x06u8, 0x05][..]]);
let mut out = Vec::new();
walk_annexb(&au, |nal| push_length_prefixed(nal, &mut out)).unwrap();
assert_eq!(out, vec![0, 0, 0, 2, 0x65, 0xAA, 0, 0, 0, 2, 0x06, 0x05]);
let au = vec![0, 0, 0, 1, 0x65, 0xBB, 0, 0];
let nals = collect_annexb(&au).unwrap();
assert_eq!(nals, vec![&[0x65u8, 0xBB][..]]);
assert!(collect_annexb(&[0, 0, 0, 1, 0, 0]).is_err());
}
#[test]
fn length_prefixed_walk_rejects_overruns() {
assert!(collect_length_prefixed(&[0, 0, 0, 9, 0x65]).is_err());
assert!(collect_length_prefixed(&[0, 0, 0, 0]).is_err());
assert!(collect_length_prefixed(&[]).is_err());
assert!(collect_length_prefixed(&[0, 0, 0, 1, 0x65, 0xFF]).is_err());
}
#[test]
fn walkers_scan_without_materializing() {
let mut au = vec![0, 0, 0, 1, 0x65, 0x88, 0x80];
au.extend_from_slice(&[0, 0, 1, 0x06, 0x05, 0xFF]);
let mut count = 0usize;
let scan = walk_annexb(&au, |_| count += 1).unwrap();
assert_eq!(count, scan.nal_count);
let lp = vec![0, 0, 0, 1, 0x41];
let mut count = 0usize;
let scan = walk_length_prefixed(&lp, |_| count += 1).unwrap();
assert_eq!((count, scan.nal_count), (1, 1));
}
}