#![allow(
clippy::redundant_pub_crate,
reason = "workspace `unreachable_pub` policy (Cargo.toml) wants `pub(crate)` here; \
clippy::pedantic's redundant_pub_crate disagrees for private modules — the \
two lints are mutually exclusive for this shape, workspace policy wins"
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct NalHeader {
pub(crate) nal_unit_type: u8,
pub(crate) offset: usize,
}
pub(crate) fn scan_nal_headers(data: &[u8]) -> Vec<NalHeader> {
scan_nal_headers_generic(data, 1, |type_byte| type_byte & 0x1F)
}
pub(crate) fn scan_nal_headers_hevc(data: &[u8]) -> Vec<NalHeader> {
scan_nal_headers_generic(data, 2, |first_byte| (first_byte >> 1) & 0x3F)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ObuHeader {
pub(crate) obu_type: u8,
pub(crate) offset: usize,
}
pub(crate) fn scan_obu_headers(data: &[u8]) -> Vec<ObuHeader> {
let mut headers = Vec::new();
let mut i = 0usize;
while i < data.len() {
let header_byte = data[i];
let obu_type = (header_byte >> 3) & 0x0F;
let has_extension = (header_byte >> 2) & 1 == 1;
let has_size_field = (header_byte >> 1) & 1 == 1;
headers.push(ObuHeader {
obu_type,
offset: i,
});
let mut cursor = i + 1;
if has_extension {
cursor += 1;
}
if !has_size_field {
break;
}
let Some((obu_size, leb_len)) = read_leb128(data.get(cursor..).unwrap_or(&[])) else {
break;
};
cursor += leb_len;
let Some(obu_size) = usize::try_from(obu_size).ok() else {
break;
};
let Some(next) = cursor.checked_add(obu_size) else {
break;
};
if next > data.len() {
break;
}
i = next;
}
headers
}
fn read_leb128(data: &[u8]) -> Option<(u64, usize)> {
let mut value: u64 = 0;
for (i, &byte) in data.iter().enumerate().take(8) {
value |= u64::from(byte & 0x7F) << (i * 7);
if byte & 0x80 == 0 {
return Some((value, i + 1));
}
}
None
}
fn scan_nal_headers_generic(
data: &[u8],
header_len: usize,
extract_type: impl Fn(u8) -> u8,
) -> Vec<NalHeader> {
let mut headers = Vec::new();
let mut i = 0usize;
while i + 3 <= data.len() {
let is_start_code_3 = data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1;
let is_start_code_4 = i + 4 <= data.len()
&& data[i] == 0
&& data[i + 1] == 0
&& data[i + 2] == 0
&& data[i + 3] == 1;
let start_code_len = if is_start_code_4 {
4
} else if is_start_code_3 {
3
} else {
0
};
if start_code_len == 0 {
i += 1;
continue;
}
let type_offset = i + start_code_len;
if type_offset + header_len > data.len() {
break;
}
if let Some(&first_byte) = data.get(type_offset) {
headers.push(NalHeader {
nal_unit_type: extract_type(first_byte),
offset: type_offset,
});
}
i = type_offset + 1;
}
headers
}