pub(crate) const NAL_IDR: u8 = 5;
pub(crate) const NAL_SPS: u8 = 7;
pub(crate) const NAL_PPS: u8 = 8;
const NAL_END_OF_SEQUENCE: u8 = 10;
const NAL_END_OF_STREAM: u8 = 11;
const NAL_FILLER: u8 = 12;
pub(crate) fn nal_types(data: &[u8]) -> Vec<u8> {
let mut types = Vec::new();
let mut i = 0;
while let Some(start) = next_start_code(data, i) {
if let Some(&header) = data.get(start) {
types.push(header & 0x1f);
}
i = start;
}
types
}
pub(crate) fn parameter_sets(data: &[u8]) -> Vec<u8> {
let mut starts = Vec::new();
let mut i = 0;
while let Some(start) = next_start_code(data, i) {
starts.push(start);
i = start;
}
let mut out = Vec::new();
for (k, &start) in starts.iter().enumerate() {
let Some(&header) = data.get(start) else {
continue;
};
if !matches!(header & 0x1f, NAL_SPS | NAL_PPS) {
continue;
}
let mut end = starts.get(k + 1).map_or(data.len(), |&next| next - 3);
while end > start && data[end - 1] == 0 && k + 1 < starts.len() {
end -= 1;
}
out.extend_from_slice(&[0, 0, 0, 1]);
out.extend_from_slice(&data[start..end]);
}
out
}
fn next_start_code(data: &[u8], from: usize) -> Option<usize> {
data.get(from..)?
.windows(3)
.position(|w| w == [0, 0, 1])
.map(|p| from + p + 3)
}
pub fn access_units(stream: &[u8]) -> AccessUnits<'_> {
AccessUnits {
data: stream,
next: next_start_code(stream, 0),
}
}
#[derive(Debug, Clone)]
pub struct AccessUnits<'a> {
data: &'a [u8],
next: Option<usize>,
}
impl<'a> Iterator for AccessUnits<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
let data = self.data;
let first = self.next?;
let start = start_code_begin(data, first);
let mut picture = false;
let mut nal = first;
loop {
let Some(&header) = data.get(nal) else {
self.next = None;
return Some(&data[start..]);
};
let kind = header & 0x1f;
let slice = matches!(kind, 1 | 5);
let new_picture = slice && data.get(nal + 1).is_some_and(|b| b & 0x80 != 0);
let leads = matches!(kind, 6..=9 | 14..=18);
if picture && (leads || new_picture) {
self.next = Some(nal);
return Some(&data[start..start_code_begin(data, nal)]);
}
picture |= slice;
match next_start_code(data, nal) {
Some(n) => nal = n,
None => {
self.next = None;
return Some(&data[start..]);
}
}
}
}
}
fn start_code_begin(data: &[u8], nal: usize) -> usize {
let begin = nal - 3;
if begin > 0 && data[begin - 1] == 0 {
begin - 1
} else {
begin
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) struct AccessUnit {
pub idr: bool,
pub parameter_sets: bool,
pub has_picture: bool,
pub trailing_only: bool,
}
impl AccessUnit {
pub(crate) fn parse(data: &[u8]) -> Self {
let types = nal_types(data);
Self {
idr: types.contains(&NAL_IDR),
parameter_sets: types.contains(&NAL_SPS) && types.contains(&NAL_PPS),
has_picture: types.iter().any(|t| (1..=5).contains(t)),
trailing_only: !types.is_empty()
&& types
.iter()
.all(|t| matches!(*t, NAL_END_OF_SEQUENCE | NAL_END_OF_STREAM | NAL_FILLER)),
}
}
pub(crate) fn ends_previous(&self) -> bool {
!self.has_picture && self.trailing_only
}
}
#[cfg(test)]
mod tests {
use super::*;
const SPS: &[u8] = &[0, 0, 0, 1, 0x67, 0x64, 0x00, 0x28];
const PPS: &[u8] = &[0, 0, 0, 1, 0x68, 0xee, 0x3c, 0x80];
const IDR: &[u8] = &[0, 0, 1, 0x65, 0x88, 0x84, 0x00];
const P_SLICE: &[u8] = &[0, 0, 0, 1, 0x41, 0x9a, 0x02];
const SEI: &[u8] = &[0, 0, 1, 0x06, 0x05, 0xff];
fn cat(parts: &[&[u8]]) -> Vec<u8> {
parts.concat()
}
#[test]
fn finds_nal_types_after_3_and_4_byte_start_codes() {
let au = cat(&[SPS, PPS, SEI, IDR]);
assert_eq!(nal_types(&au), vec![NAL_SPS, NAL_PPS, 6, NAL_IDR]);
}
#[test]
fn ignores_leading_garbage_and_empty_input() {
assert!(nal_types(&[]).is_empty());
assert!(nal_types(&[0, 0]).is_empty());
assert_eq!(nal_types(&cat(&[&[0xaa, 0xbb], P_SLICE])), vec![1]);
}
#[test]
fn start_code_at_the_very_end_has_no_nal() {
assert_eq!(nal_types(&cat(&[P_SLICE, &[0, 0, 1]])), vec![1]);
}
#[test]
fn keyframe_with_headers() {
let au = AccessUnit::parse(&cat(&[SPS, PPS, IDR]));
assert!(au.idr && au.parameter_sets && au.has_picture);
assert!(!au.ends_previous());
}
#[test]
fn idr_without_headers_is_still_idr() {
let au = AccessUnit::parse(IDR);
assert!(au.idr && !au.parameter_sets);
}
#[test]
fn predicted_frame() {
let au = AccessUnit::parse(P_SLICE);
assert!(!au.idr && !au.parameter_sets && au.has_picture);
}
#[test]
fn end_of_sequence_belongs_to_the_previous_access_unit() {
let eos: &[u8] = &[0, 0, 0, 1, 0x0a];
let au = AccessUnit::parse(eos);
assert!(au.ends_previous() && !au.parameter_sets);
assert!(AccessUnit::parse(&cat(&[eos, &[0, 0, 1, 0x0b]])).ends_previous());
assert!(!AccessUnit::parse(&cat(&[P_SLICE, eos])).ends_previous());
assert!(
!AccessUnit::parse(SEI).ends_previous(),
"SEI precedes a picture"
);
assert!(!AccessUnit::parse(&[]).ends_previous());
}
#[test]
fn extracts_parameter_sets_with_4_byte_start_codes() {
let au = cat(&[SPS, PPS, SEI, IDR]);
assert_eq!(parameter_sets(&au), cat(&[SPS, PPS]));
let short = cat(&[&[0, 0, 1, 0x67, 0x42], &[0, 0, 1, 0x68, 0xce], IDR]);
assert_eq!(
parameter_sets(&short),
vec![0, 0, 0, 1, 0x67, 0x42, 0, 0, 0, 1, 0x68, 0xce]
);
assert!(parameter_sets(&cat(&[P_SLICE, SEI])).is_empty());
assert_eq!(parameter_sets(&cat(&[IDR, PPS])), PPS.to_vec(), "last NAL");
}
#[test]
fn splits_access_units_at_headers_and_new_pictures() {
let p_slice2: &[u8] = &[0, 0, 1, 0x41, 0x40, 0x11];
let aud: &[u8] = &[0, 0, 0, 1, 0x09, 0xf0];
let stream = cat(&[SPS, PPS, IDR, P_SLICE, p_slice2, aud, SEI, P_SLICE]);
let aus: Vec<&[u8]> = access_units(&stream).collect();
assert_eq!(aus.len(), 3);
assert_eq!(aus[0], cat(&[SPS, PPS, IDR]).as_slice());
assert_eq!(aus[1], cat(&[P_SLICE, p_slice2]).as_slice());
assert_eq!(aus[2], cat(&[aud, SEI, P_SLICE]).as_slice());
assert_eq!(aus.concat(), stream, "nothing is lost or duplicated");
}
#[test]
fn end_of_sequence_stays_with_its_picture() {
let eos: &[u8] = &[0, 0, 0, 1, 0x0a];
let stream = cat(&[IDR, eos, SPS, PPS, IDR]);
let aus: Vec<&[u8]> = access_units(&stream).collect();
assert_eq!(aus, vec![cat(&[IDR, eos]), cat(&[SPS, PPS, IDR])]);
}
#[test]
fn access_units_of_degenerate_input() {
assert_eq!(access_units(&[]).count(), 0);
assert_eq!(access_units(&[1, 2, 3]).count(), 0);
assert_eq!(
access_units(&cat(&[&[0xaa], SPS])).collect::<Vec<_>>(),
vec![SPS]
);
let tail = cat(&[IDR, &[0, 0, 1]]);
assert_eq!(
access_units(&tail).collect::<Vec<_>>(),
vec![tail.as_slice()]
);
}
#[test]
fn header_only_buffer() {
let au = AccessUnit::parse(&cat(&[SPS, PPS]));
assert!(au.parameter_sets && !au.has_picture && !au.ends_previous());
assert!(!AccessUnit::parse(SPS).parameter_sets);
assert!(!AccessUnit::parse(SEI).has_picture);
}
}