use std::borrow::Cow;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NalUnitType {
TrailN, TrailR, TsaN, TsaR, StsaN, StsaR, RadlN, RadlR, RaslN, RaslR, BlaWLp, BlaWRadl, BlaNLp, IdrWRadl, IdrNLp, Cra, Vps, Sps, Pps, Aud, Eos, Eob, Fd, PrefixSei, SuffixSei, Other(u8),
}
impl NalUnitType {
pub fn is_vcl(self) -> bool {
match self {
NalUnitType::TrailN
| NalUnitType::TrailR
| NalUnitType::TsaN
| NalUnitType::TsaR
| NalUnitType::StsaN
| NalUnitType::StsaR
| NalUnitType::RadlN
| NalUnitType::RadlR
| NalUnitType::RaslN
| NalUnitType::RaslR
| NalUnitType::BlaWLp
| NalUnitType::BlaWRadl
| NalUnitType::BlaNLp
| NalUnitType::IdrWRadl
| NalUnitType::IdrNLp
| NalUnitType::Cra => true,
NalUnitType::Other(v) => v < 32,
_ => false,
}
}
pub fn is_idr(self) -> bool {
matches!(self, NalUnitType::IdrWRadl | NalUnitType::IdrNLp)
}
pub fn is_irap(self) -> bool {
matches!(
self,
NalUnitType::BlaWLp
| NalUnitType::BlaWRadl
| NalUnitType::BlaNLp
| NalUnitType::IdrWRadl
| NalUnitType::IdrNLp
| NalUnitType::Cra
)
}
}
impl From<u8> for NalUnitType {
fn from(val: u8) -> Self {
match val {
0 => NalUnitType::TrailN,
1 => NalUnitType::TrailR,
2 => NalUnitType::TsaN,
3 => NalUnitType::TsaR,
4 => NalUnitType::StsaN,
5 => NalUnitType::StsaR,
6 => NalUnitType::RadlN,
7 => NalUnitType::RadlR,
8 => NalUnitType::RaslN,
9 => NalUnitType::RaslR,
16 => NalUnitType::BlaWLp,
17 => NalUnitType::BlaWRadl,
18 => NalUnitType::BlaNLp,
19 => NalUnitType::IdrWRadl,
20 => NalUnitType::IdrNLp,
21 => NalUnitType::Cra,
32 => NalUnitType::Vps,
33 => NalUnitType::Sps,
34 => NalUnitType::Pps,
35 => NalUnitType::Aud,
36 => NalUnitType::Eos,
37 => NalUnitType::Eob,
38 => NalUnitType::Fd,
39 => NalUnitType::PrefixSei,
40 => NalUnitType::SuffixSei,
v => NalUnitType::Other(v),
}
}
}
#[derive(Debug)]
pub struct NalUnit<'a> {
pub nal_unit_type: NalUnitType,
pub nuh_layer_id: u8,
pub temporal_id: u8,
pub rbsp: Cow<'a, [u8]>,
pub epb_positions: Vec<u32>,
}
pub fn parse_annex_b(data: &[u8]) -> Vec<NalUnit<'_>> {
let mut nals = Vec::new();
let mut i = match find_start_code(data, 0) {
Some((pos, _)) => pos,
None => return nals,
};
loop {
if i >= data.len() {
break;
}
let nal_end = match find_start_code(data, i) {
Some((pos, sc_start)) => {
let end = sc_start;
let mut e = end;
while e > i && data[e - 1] == 0 {
e -= 1;
}
(e, Some(pos))
}
None => (data.len(), None),
};
let nal_data = &data[i..nal_end.0];
if nal_data.len() >= 2 {
let b0 = nal_data[0];
let b1 = nal_data[1];
if b0 & 0x80 == 0 {
let nal_unit_type = NalUnitType::from((b0 >> 1) & 0x3F);
let nuh_layer_id = ((b0 & 0x01) << 5) | (b1 >> 3);
let temporal_id_plus1 = b1 & 0x07;
if temporal_id_plus1 > 0 {
let temporal_id = temporal_id_plus1 - 1;
let (rbsp, epb_positions) = remove_emulation_prevention(&nal_data[2..]);
nals.push(NalUnit {
nal_unit_type,
nuh_layer_id,
temporal_id,
epb_positions,
rbsp,
});
}
}
}
match nal_end.1 {
Some(pos) => i = pos,
None => break,
}
}
nals
}
fn find_start_code(data: &[u8], offset: usize) -> Option<(usize, usize)> {
let mut i = offset;
while i + 2 < data.len() {
if data[i] == 0 && data[i + 1] == 0 {
if data[i + 2] == 1 {
return Some((i + 3, i));
}
if i + 3 < data.len() && data[i + 2] == 0 && data[i + 3] == 1 {
return Some((i + 4, i));
}
}
i += 1;
}
None
}
fn remove_emulation_prevention(data: &[u8]) -> (Cow<'_, [u8]>, Vec<u32>) {
let has_epb = data.windows(3).any(|w| w[0] == 0 && w[1] == 0 && w[2] == 3);
if !has_epb {
return (Cow::Borrowed(data), Vec::new());
}
let mut rbsp = Vec::with_capacity(data.len());
let mut epb_positions = Vec::new();
let mut i = 0;
while i < data.len() {
if i + 2 < data.len() && data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 3 {
rbsp.push(0);
rbsp.push(0);
epb_positions.push((i + 2) as u32);
i += 3; } else {
rbsp.push(data[i]);
i += 1;
}
}
(Cow::Owned(rbsp), epb_positions)
}
#[cfg(test)]
mod tests {
use super::*;
fn hdr(nut: u8, layer_id: u8, temporal_id: u8) -> [u8; 2] {
let b0 = (nut & 0x3F) << 1 | ((layer_id >> 5) & 0x01);
let b1 = ((layer_id & 0x1F) << 3) | ((temporal_id + 1) & 0x07);
[b0, b1]
}
#[test]
fn test_header_packing_round_trip() {
let h = hdr(32, 0, 0);
assert_eq!(h, [0x40, 0x01]);
let h = hdr(33, 0, 0);
assert_eq!(h, [0x42, 0x01]);
let h = hdr(19, 0, 0);
assert_eq!(h, [0x26, 0x01]);
}
#[test]
fn test_parse_annex_b_mixed_start_codes() {
let mut data: Vec<u8> = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
data.extend_from_slice(&hdr(32, 0, 0));
data.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hdr(33, 0, 0));
data.extend_from_slice(&[0xDD, 0xEE]);
let nals = parse_annex_b(&data);
assert_eq!(nals.len(), 2);
assert_eq!(nals[0].nal_unit_type, NalUnitType::Vps);
assert_eq!(nals[0].nuh_layer_id, 0);
assert_eq!(nals[0].temporal_id, 0);
assert_eq!(&*nals[0].rbsp, &[0xAA, 0xBB, 0xCC]);
assert_eq!(nals[1].nal_unit_type, NalUnitType::Sps);
assert_eq!(nals[1].nuh_layer_id, 0);
assert_eq!(nals[1].temporal_id, 0);
assert_eq!(&*nals[1].rbsp, &[0xDD, 0xEE]);
}
#[test]
fn test_parse_annex_b_idr_with_temporal_id() {
let mut data: Vec<u8> = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
data.extend_from_slice(&hdr(19, 0, 2));
data.extend_from_slice(&[0x12, 0x34]);
let nals = parse_annex_b(&data);
assert_eq!(nals.len(), 1);
assert_eq!(nals[0].nal_unit_type, NalUnitType::IdrWRadl);
assert!(nals[0].nal_unit_type.is_idr());
assert!(nals[0].nal_unit_type.is_irap());
assert!(nals[0].nal_unit_type.is_vcl());
assert_eq!(nals[0].temporal_id, 2);
}
#[test]
fn test_parse_annex_b_strips_emulation_prevention() {
let mut data: Vec<u8> = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
data.extend_from_slice(&hdr(33, 0, 0)); data.extend_from_slice(&[0x00, 0x00, 0x03, 0x42, 0xAA]);
let nals = parse_annex_b(&data);
assert_eq!(nals.len(), 1);
assert_eq!(nals[0].nal_unit_type, NalUnitType::Sps);
assert_eq!(&*nals[0].rbsp, &[0x00, 0x00, 0x42, 0xAA]);
assert!(
matches!(nals[0].rbsp, Cow::Owned(_)),
"EPB removal should produce an owned buffer"
);
}
#[test]
fn test_parse_annex_b_zero_copy_when_no_epb() {
let mut data: Vec<u8> = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
data.extend_from_slice(&hdr(33, 0, 0));
data.extend_from_slice(&[0x11, 0x22, 0x33]);
let nals = parse_annex_b(&data);
assert_eq!(nals.len(), 1);
assert!(
matches!(nals[0].rbsp, Cow::Borrowed(_)),
"no EPB → should borrow"
);
}
#[test]
fn test_parse_annex_b_skips_invalid_forbidden_bit() {
let mut data: Vec<u8> = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
data.extend_from_slice(&[0x80, 0x01, 0xAA]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hdr(32, 0, 0));
data.extend_from_slice(&[0xBB]);
let nals = parse_annex_b(&data);
assert_eq!(nals.len(), 1);
assert_eq!(nals[0].nal_unit_type, NalUnitType::Vps);
}
#[test]
fn test_nuh_layer_id_packing() {
let h = hdr(33, 33, 0);
assert_eq!(h, [0x43, 0x09]);
let mut data: Vec<u8> = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
data.extend_from_slice(&h);
data.extend_from_slice(&[0xAA]);
let nals = parse_annex_b(&data);
assert_eq!(nals.len(), 1);
assert_eq!(nals[0].nuh_layer_id, 33);
}
}