use std::borrow::Cow;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NalUnitType {
Slice, SliceDataA, SliceDataB, SliceDataC, SliceIdr, Sei, Sps, Pps, AccessUnitDelimiter, EndOfSequence, EndOfStream, FillerData, Other(u8),
}
impl From<u8> for NalUnitType {
fn from(val: u8) -> Self {
match val {
1 => NalUnitType::Slice,
2 => NalUnitType::SliceDataA,
3 => NalUnitType::SliceDataB,
4 => NalUnitType::SliceDataC,
5 => NalUnitType::SliceIdr,
6 => NalUnitType::Sei,
7 => NalUnitType::Sps,
8 => NalUnitType::Pps,
9 => NalUnitType::AccessUnitDelimiter,
10 => NalUnitType::EndOfSequence,
11 => NalUnitType::EndOfStream,
12 => NalUnitType::FillerData,
v => NalUnitType::Other(v),
}
}
}
#[derive(Debug)]
pub struct NalUnit<'a> {
pub nal_ref_idc: u8,
pub nal_unit_type: NalUnitType,
pub rbsp: Cow<'a, [u8]>,
}
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),
};
if let Some(nal) = parse_nal_bytes(&data[i..nal_end.0]) {
nals.push(nal);
}
match nal_end.1 {
Some(pos) => i = pos,
None => break,
}
}
nals
}
fn parse_nal_bytes(nal_data: &[u8]) -> Option<NalUnit<'_>> {
if nal_data.is_empty() {
return None;
}
let header = nal_data[0];
if header & 0x80 != 0 {
return None;
}
let nal_ref_idc = (header >> 5) & 0x03;
let nal_unit_type = NalUnitType::from(header & 0x1F);
let rbsp = remove_emulation_prevention(&nal_data[1..]);
Some(NalUnit {
nal_ref_idc,
nal_unit_type,
rbsp,
})
}
#[derive(Debug)]
pub struct AvccConfig<'a> {
pub length_size: usize,
pub sps_nals: Vec<NalUnit<'a>>,
pub pps_nals: Vec<NalUnit<'a>>,
}
pub fn parse_avcc_config(data: &[u8]) -> Result<AvccConfig<'_>, &'static str> {
if data.len() < 7 {
return Err("avcC: too short");
}
if data[0] != 1 {
return Err("avcC: unsupported configurationVersion");
}
let length_size = ((data[4] & 0x03) + 1) as usize;
if length_size != 1 && length_size != 2 && length_size != 4 {
return Err("avcC: invalid lengthSizeMinusOne");
}
let num_sps = (data[5] & 0x1F) as usize;
let mut off = 6;
let mut sps_nals = Vec::with_capacity(num_sps);
for _ in 0..num_sps {
if off + 2 > data.len() {
return Err("avcC: truncated SPS length");
}
let len = u16::from_be_bytes([data[off], data[off + 1]]) as usize;
off += 2;
if off + len > data.len() {
return Err("avcC: truncated SPS data");
}
if let Some(nal) = parse_nal_bytes(&data[off..off + len]) {
sps_nals.push(nal);
}
off += len;
}
if off >= data.len() {
return Err("avcC: missing PPS count");
}
let num_pps = data[off] as usize;
off += 1;
let mut pps_nals = Vec::with_capacity(num_pps);
for _ in 0..num_pps {
if off + 2 > data.len() {
return Err("avcC: truncated PPS length");
}
let len = u16::from_be_bytes([data[off], data[off + 1]]) as usize;
off += 2;
if off + len > data.len() {
return Err("avcC: truncated PPS data");
}
if let Some(nal) = parse_nal_bytes(&data[off..off + len]) {
pps_nals.push(nal);
}
off += len;
}
Ok(AvccConfig {
length_size,
sps_nals,
pps_nals,
})
}
pub fn parse_avcc(data: &[u8], length_size: usize) -> Vec<NalUnit<'_>> {
let mut nals = Vec::new();
if length_size != 1 && length_size != 2 && length_size != 4 {
return nals;
}
let mut i = 0;
while i + length_size <= data.len() {
let len = match length_size {
1 => data[i] as usize,
2 => u16::from_be_bytes([data[i], data[i + 1]]) as usize,
4 => u32::from_be_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]) as usize,
_ => unreachable!(),
};
i += length_size;
if i + len > data.len() {
break;
}
if let Some(nal) = parse_nal_bytes(&data[i..i + len]) {
nals.push(nal);
}
i += len;
}
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]> {
let has_epb = data.windows(3).any(|w| w[0] == 0 && w[1] == 0 && w[2] == 3);
if !has_epb {
return Cow::Borrowed(data);
}
let mut rbsp = Vec::with_capacity(data.len());
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);
i += 3; } else {
rbsp.push(data[i]);
i += 1;
}
}
Cow::Owned(rbsp)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_annex_b_single_frame() {
let data = std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/testdata/single_frame.h264"
))
.unwrap();
let nals = parse_annex_b(&data);
assert_eq!(nals.len(), 4);
assert_eq!(nals[0].nal_unit_type, NalUnitType::Sps);
assert_eq!(nals[1].nal_unit_type, NalUnitType::Pps);
assert_eq!(nals[2].nal_unit_type, NalUnitType::Sei);
assert_eq!(nals[3].nal_unit_type, NalUnitType::SliceIdr);
}
#[test]
fn test_emulation_prevention_removal() {
let input = [0x00, 0x00, 0x03, 0x01, 0xAB];
let rbsp = remove_emulation_prevention(&input);
assert_eq!(&*rbsp, &[0x00, 0x00, 0x01, 0xAB]);
assert!(
matches!(rbsp, Cow::Owned(_)),
"should allocate when EPB present"
);
}
#[test]
fn test_emulation_prevention_zero_copy() {
let input = [0x01, 0x02, 0x03, 0x04];
let rbsp = remove_emulation_prevention(&input);
assert_eq!(&*rbsp, &input);
assert!(
matches!(rbsp, Cow::Borrowed(_)),
"should borrow when no EPB"
);
}
#[test]
fn test_parse_avcc_two_nals_4byte_length() {
let data = [
0x00, 0x00, 0x00, 0x02, 0x65, 0xAA, 0x00, 0x00, 0x00, 0x03, 0x67, 0xBB, 0xCC, ];
let nals = parse_avcc(&data, 4);
assert_eq!(nals.len(), 2);
assert_eq!(nals[0].nal_unit_type, NalUnitType::SliceIdr);
assert_eq!(nals[0].nal_ref_idc, 3);
assert_eq!(&*nals[0].rbsp, &[0xAA]);
assert_eq!(nals[1].nal_unit_type, NalUnitType::Sps);
assert_eq!(&*nals[1].rbsp, &[0xBB, 0xCC]);
}
#[test]
fn test_parse_avcc_truncated() {
let data = [0x00, 0x00, 0x00, 0x0A, 0x65, 0xAA];
let nals = parse_avcc(&data, 4);
assert_eq!(nals.len(), 0);
}
#[test]
fn test_parse_avcc_2byte_length() {
let data = [
0x00, 0x02, 0x65, 0xAA, 0x00, 0x01, 0x67, ];
let nals = parse_avcc(&data, 2);
assert_eq!(nals.len(), 2);
assert_eq!(nals[0].nal_unit_type, NalUnitType::SliceIdr);
assert_eq!(nals[1].nal_unit_type, NalUnitType::Sps);
}
#[test]
fn test_parse_avcc_config_minimal() {
let data = [
0x01, 0x42, 0x00, 0x1E, 0xFF, 0xE1, 0x00, 0x04, 0x67, 0x42, 0x00, 0x1E, 0x01, 0x00, 0x02, 0x68, 0xCE, ];
let cfg = parse_avcc_config(&data).unwrap();
assert_eq!(cfg.length_size, 4);
assert_eq!(cfg.sps_nals.len(), 1);
assert_eq!(cfg.sps_nals[0].nal_unit_type, NalUnitType::Sps);
assert_eq!(&*cfg.sps_nals[0].rbsp, &[0x42, 0x00, 0x1E]);
assert_eq!(cfg.pps_nals.len(), 1);
assert_eq!(cfg.pps_nals[0].nal_unit_type, NalUnitType::Pps);
assert_eq!(&*cfg.pps_nals[0].rbsp, &[0xCE]);
}
#[test]
fn test_parse_avcc_config_invalid_version() {
let data = [0x02, 0x42, 0x00, 0x1E, 0xFF, 0xE0, 0x00];
assert!(parse_avcc_config(&data).is_err());
}
#[test]
fn test_parse_avcc_config_truncated() {
let data = [0x01, 0x42];
assert!(parse_avcc_config(&data).is_err());
}
}