#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Codec {
H264,
H265,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Nal {
start: usize,
end: usize,
}
fn split_nals(stream: &[u8]) -> Vec<Nal> {
let mut nals = Vec::new();
let mut i = 0;
let mut current: Option<usize> = None;
while i + 2 < stream.len() {
if stream[i] == 0 && stream[i + 1] == 0 && stream[i + 2] == 1 {
let code_start = if i > 0 && stream[i - 1] == 0 {
i - 1
} else {
i
};
if let Some(start) = current.take() {
nals.push(Nal {
start,
end: code_start,
});
}
current = Some(i + 3);
i += 3;
} else if stream[i + 2] != 0 {
i += 3;
} else {
i += 1;
}
}
if let Some(start) = current
&& start < stream.len()
{
nals.push(Nal {
start,
end: stream.len(),
});
}
nals
}
fn is_vcl(codec: Codec, header: u8) -> bool {
match codec {
Codec::H264 => matches!(header & 0x1F, 1..=5),
Codec::H265 => (header >> 1) & 0x3F <= 31,
}
}
fn starts_picture(codec: Codec, payload: &[u8]) -> bool {
match codec {
Codec::H264 => payload.get(1).is_some_and(|&b| b & 0x80 != 0),
Codec::H265 => payload.get(2).is_some_and(|&b| b & 0x80 != 0),
}
}
pub struct AccessUnits<'a> {
stream: &'a [u8],
nals: Vec<Nal>,
next: usize,
codec: Codec,
}
impl<'a> AccessUnits<'a> {
pub fn new(stream: &'a [u8], codec: Codec) -> Self {
Self {
stream,
nals: split_nals(stream),
next: 0,
codec,
}
}
pub fn nal_count(&self) -> usize {
self.nals.len()
}
fn unit_start(&self, index: usize) -> usize {
let start = self.nals[index].start;
let code = start - 3;
if code > 0 && self.stream[code - 1] == 0 {
code - 1
} else {
code
}
}
}
impl<'a> Iterator for AccessUnits<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.nals.len() {
return None;
}
let first = self.next;
let mut seen_vcl = false;
let mut end = self.nals.len();
for i in first..self.nals.len() {
let nal = self.nals[i];
let header = self.stream[nal.start];
let vcl = is_vcl(self.codec, header);
if vcl {
let payload = &self.stream[nal.start..nal.end];
if seen_vcl && starts_picture(self.codec, payload) {
let mut boundary = i;
while boundary > first
&& !is_vcl(self.codec, self.stream[self.nals[boundary - 1].start])
{
boundary -= 1;
}
if (first..boundary)
.any(|j| is_vcl(self.codec, self.stream[self.nals[j].start]))
{
end = boundary;
break;
}
}
seen_vcl = true;
}
}
self.next = end;
let from = self.unit_start(first);
let to = self.nals[end - 1].end;
Some(&self.stream[from..to])
}
}
#[cfg(test)]
mod tests {
use super::*;
fn nal(header: u8, first: bool, len: usize) -> Vec<u8> {
let mut out = vec![0, 0, 0, 1, header];
match header & 0x1F {
1..=5 => out.push(if first { 0x80 } else { 0x40 }),
_ => out.push(0xFF),
}
out.resize(out.len() + len, 0xAA);
out
}
const SPS: u8 = 0x67; const PPS: u8 = 0x68; const IDR: u8 = 0x65; const SLICE: u8 = 0x41;
#[test]
fn nals_split_on_both_start_code_lengths() {
let mut stream = vec![0, 0, 1, SPS, 0xFF, 0x11]; stream.extend([0, 0, 0, 1, PPS, 0xFF, 0x22]); let units = AccessUnits::new(&stream, Codec::H264);
assert_eq!(units.nal_count(), 2);
}
#[test]
fn garbage_before_the_first_start_code_is_skipped() {
let mut stream = vec![0xDE, 0xAD, 0xBE];
stream.extend(nal(SPS, false, 4));
assert_eq!(AccessUnits::new(&stream, Codec::H264).nal_count(), 1);
}
#[test]
fn an_access_unit_is_the_prefixes_plus_the_picture() {
let mut stream = Vec::new();
stream.extend(nal(SPS, false, 8));
stream.extend(nal(PPS, false, 4));
stream.extend(nal(IDR, true, 32));
stream.extend(nal(SLICE, true, 16));
stream.extend(nal(SLICE, true, 16));
let units: Vec<&[u8]> = AccessUnits::new(&stream, Codec::H264).collect();
assert_eq!(units.len(), 3, "three pictures, three units");
assert!(units[0].len() > units[1].len(), "unit one carries SPS+PPS");
let total: usize = units.iter().map(|u| u.len()).sum();
assert_eq!(total, stream.len());
assert_eq!(units[0], &stream[..units[0].len()]);
}
#[test]
fn a_multi_slice_picture_stays_one_unit() {
let mut stream = Vec::new();
stream.extend(nal(IDR, true, 16));
stream.extend(nal(SLICE, false, 16)); stream.extend(nal(SLICE, true, 16));
let units: Vec<&[u8]> = AccessUnits::new(&stream, Codec::H264).collect();
assert_eq!(units.len(), 2, "a continuation slice must not split");
}
#[test]
fn prefixes_between_pictures_ride_with_the_next_one() {
let mut stream = Vec::new();
stream.extend(nal(IDR, true, 16));
stream.extend(nal(SPS, false, 8));
stream.extend(nal(PPS, false, 4));
stream.extend(nal(SLICE, true, 16));
let units: Vec<&[u8]> = AccessUnits::new(&stream, Codec::H264).collect();
assert_eq!(units.len(), 2);
let idr_only = nal(IDR, true, 16);
assert_eq!(units[0].len(), idr_only.len(), "unit one is the IDR alone");
assert!(
units[1].len() > nal(SLICE, true, 16).len(),
"unit two took the prefixes"
);
}
#[test]
fn units_start_with_their_start_codes() {
let mut stream = Vec::new();
stream.extend(nal(IDR, true, 8));
stream.extend(nal(SLICE, true, 8));
for unit in AccessUnits::new(&stream, Codec::H264) {
assert!(
unit.starts_with(&[0, 0, 0, 1]) || unit.starts_with(&[0, 0, 1]),
"a decoder is fed Annex-B, start codes included"
);
}
}
#[test]
fn an_empty_or_garbage_stream_yields_nothing_and_nobody_panics() {
assert_eq!(AccessUnits::new(&[], Codec::H264).count(), 0);
assert_eq!(AccessUnits::new(&[0, 0], Codec::H264).count(), 0);
assert_eq!(
AccessUnits::new(&[0xFF; 64], Codec::H264).count(),
0,
"no start code, no units"
);
assert_eq!(AccessUnits::new(&[0, 0, 1], Codec::H264).count(), 0);
let _ = AccessUnits::new(&[0, 0, 1, IDR], Codec::H264).count();
}
#[test]
fn hevc_pictures_split_on_the_first_slice_flag() {
let hevc_nal = |first: bool| -> Vec<u8> {
let mut out = vec![0, 0, 0, 1, 0x26, 0x01];
out.push(if first { 0x80 } else { 0x00 });
out.resize(out.len() + 8, 0xAA);
out
};
let mut stream = Vec::new();
stream.extend(hevc_nal(true));
stream.extend(hevc_nal(false)); stream.extend(hevc_nal(true));
let units: Vec<&[u8]> = AccessUnits::new(&stream, Codec::H265).collect();
assert_eq!(units.len(), 2);
}
#[test]
fn payload_bytes_that_look_like_headers_do_not_split_units() {
let mut stream = nal(IDR, true, 0);
stream.extend([0x65, 0x80, 0x41, 0x80, 0x67]); stream.extend(nal(SLICE, true, 4));
let units: Vec<&[u8]> = AccessUnits::new(&stream, Codec::H264).collect();
assert_eq!(units.len(), 2);
}
}
#[cfg(test)]
mod real_stream {
use super::*;
#[test]
#[ignore = "needs a stream on disk; see the doc comment"]
fn a_real_encoder_stream_parses_to_its_frame_count() {
let path = std::env::var("DENISE_TEST_H264").expect("DENISE_TEST_H264");
let stream = std::fs::read(path).expect("read the stream");
let units: Vec<&[u8]> = AccessUnits::new(&stream, Codec::H264).collect();
assert_eq!(units.len(), 180, "one access unit per encoded frame");
let total: usize = units.iter().map(|u| u.len()).sum();
assert_eq!(total, stream.len());
for unit in &units {
assert!(unit.starts_with(&[0, 0, 0, 1]) || unit.starts_with(&[0, 0, 1]));
}
}
}