#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AvccRepack {
pub config_record: Vec<u8>,
pub packetized: Vec<u8>,
}
const NAL_TYPE_SPS: u8 = 7;
const NAL_TYPE_PPS: u8 = 8;
pub fn annexb_to_avcc(stream: &[u8]) -> AvccRepack {
let mut sps_list: Vec<&[u8]> = Vec::new();
let mut pps_list: Vec<&[u8]> = Vec::new();
let mut packetized = Vec::with_capacity(stream.len());
for nal in split_annex_b(stream) {
if nal.is_empty() {
continue;
}
let nal_type = nal[0] & 0x1F;
match nal_type {
NAL_TYPE_SPS => {
if !sps_list.contains(&nal) {
sps_list.push(nal);
}
}
NAL_TYPE_PPS => {
if !pps_list.contains(&nal) {
pps_list.push(nal);
}
}
_ => {
packetized.extend_from_slice(&(nal.len() as u32).to_be_bytes());
packetized.extend_from_slice(nal);
}
}
}
let config_record = build_configuration_record(&sps_list, &pps_list);
AvccRepack {
config_record,
packetized,
}
}
fn build_configuration_record(sps_list: &[&[u8]], pps_list: &[&[u8]]) -> Vec<u8> {
if sps_list.is_empty() {
return Vec::new();
}
let first_sps = sps_list[0];
if first_sps.len() < 4 {
return Vec::new();
}
let profile_idc = first_sps[1];
let profile_compat = first_sps[2];
let level_idc = first_sps[3];
let n_sps = sps_list.len().min(31) as u8;
let n_pps = pps_list.len().min(255) as u8;
let mut out = Vec::with_capacity(
16 + sps_list.iter().map(|s| s.len() + 2).sum::<usize>()
+ pps_list.iter().map(|p| p.len() + 2).sum::<usize>(),
);
out.push(1); out.push(profile_idc);
out.push(profile_compat);
out.push(level_idc);
out.push(0xFF);
out.push(0xE0 | (n_sps & 0x1F));
for sps in sps_list.iter().take(n_sps as usize) {
out.extend_from_slice(&(sps.len() as u16).to_be_bytes());
out.extend_from_slice(sps);
}
out.push(n_pps);
for pps in pps_list.iter().take(n_pps as usize) {
out.extend_from_slice(&(pps.len() as u16).to_be_bytes());
out.extend_from_slice(pps);
}
out
}
fn split_annex_b(data: &[u8]) -> AnnexBIter<'_> {
AnnexBIter { data, pos: 0 }
}
struct AnnexBIter<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Iterator for AnnexBIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
let start_offset = find_start_code(&self.data[self.pos..])?;
let prefix_pos = self.pos + start_offset;
let after_prefix = prefix_pos + 3;
let next_prefix = find_start_code(&self.data[after_prefix..])
.map(|off| after_prefix + off)
.unwrap_or(self.data.len());
let mut nal_end = next_prefix;
while nal_end > after_prefix && self.data[nal_end - 1] == 0 {
nal_end -= 1;
}
self.pos = next_prefix;
Some(&self.data[after_prefix..nal_end])
}
}
fn find_start_code(bytes: &[u8]) -> Option<usize> {
if bytes.len() < 3 {
return None;
}
let mut i = 0;
while i + 2 < bytes.len() {
if bytes[i] == 0 && bytes[i + 1] == 0 && bytes[i + 2] == 1 {
return Some(i);
}
i += 1;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn fake_sps(profile_idc: u8, level_idc: u8) -> Vec<u8> {
vec![0x67, profile_idc, 0x00, level_idc, 0xDE, 0xAD]
}
fn fake_pps() -> Vec<u8> {
vec![0x68, 0xEE, 0x3C, 0x80]
}
fn fake_idr() -> Vec<u8> {
vec![0x65, 0x88, 0x84, 0x00, 0x10]
}
fn annex_b_stream(nals: &[&[u8]]) -> Vec<u8> {
let mut out = Vec::new();
for nal in nals {
out.extend_from_slice(&[0, 0, 0, 1]); out.extend_from_slice(nal);
}
out
}
#[test]
fn sps_pps_and_idr_split_into_record_and_packets() {
let sps = fake_sps(0x64, 0x28); let pps = fake_pps();
let idr = fake_idr();
let stream = annex_b_stream(&[&sps, &pps, &idr]);
let out = annexb_to_avcc(&stream);
let mut expected_pkt = Vec::new();
expected_pkt.extend_from_slice(&(idr.len() as u32).to_be_bytes());
expected_pkt.extend_from_slice(&idr);
assert_eq!(out.packetized, expected_pkt);
assert_eq!(out.config_record[0], 1, "configurationVersion");
assert_eq!(out.config_record[1], 0x64, "profile_idc");
assert_eq!(out.config_record[2], 0x00, "profile_compatibility");
assert_eq!(out.config_record[3], 0x28, "level_idc");
assert_eq!(
out.config_record[4], 0xFF,
"reserved + lengthSizeMinusOne = 0xFF (4-byte lengths)"
);
assert_eq!(
out.config_record[5] & 0x1F,
1,
"exactly one SPS in the record"
);
let sps_len_offset = 6;
let sps_len = u16::from_be_bytes([
out.config_record[sps_len_offset],
out.config_record[sps_len_offset + 1],
]) as usize;
assert_eq!(sps_len, sps.len());
let sps_body_start = sps_len_offset + 2;
assert_eq!(
&out.config_record[sps_body_start..sps_body_start + sps_len],
&sps[..]
);
let pps_count_offset = sps_body_start + sps_len;
assert_eq!(out.config_record[pps_count_offset], 1);
let pps_len = u16::from_be_bytes([
out.config_record[pps_count_offset + 1],
out.config_record[pps_count_offset + 2],
]) as usize;
assert_eq!(pps_len, pps.len());
let pps_body_start = pps_count_offset + 3;
assert_eq!(
&out.config_record[pps_body_start..pps_body_start + pps_len],
&pps[..]
);
}
#[test]
fn no_sps_yields_empty_config_record() {
let idr = fake_idr();
let stream = annex_b_stream(&[&idr]);
let out = annexb_to_avcc(&stream);
assert!(out.config_record.is_empty());
assert_eq!(out.packetized.len(), 4 + idr.len());
}
#[test]
fn three_byte_start_codes_are_recognised() {
let sps = fake_sps(0x42, 0x14);
let idr = fake_idr();
let mut stream = Vec::new();
stream.extend_from_slice(&[0, 0, 1]); stream.extend_from_slice(&sps);
stream.extend_from_slice(&[0, 0, 0, 1]); stream.extend_from_slice(&idr);
let out = annexb_to_avcc(&stream);
assert_eq!(out.config_record[1], 0x42, "profile_idc from SPS");
assert_eq!(out.config_record[3], 0x14, "level_idc from SPS");
assert_eq!(out.packetized.len(), 4 + idr.len());
}
#[test]
fn duplicate_parameter_sets_are_deduplicated() {
let sps = fake_sps(0x64, 0x1F);
let pps = fake_pps();
let idr = fake_idr();
let stream = annex_b_stream(&[&sps, &pps, &sps, &pps, &idr, &sps]);
let out = annexb_to_avcc(&stream);
assert_eq!(
out.config_record[5] & 0x1F,
1,
"duplicate SPS NALs collapse to one"
);
let pps_count = out.config_record[6 + 2 + sps.len()];
assert_eq!(pps_count, 1, "duplicate PPS NALs collapse to one");
}
#[test]
fn vcl_nal_order_is_preserved() {
let sps = fake_sps(0x64, 0x28);
let pps = fake_pps();
let idr = fake_idr();
let p_slice = vec![0x61, 0xE0, 0x12, 0x34];
let stream = annex_b_stream(&[&sps, &pps, &idr, &p_slice]);
let out = annexb_to_avcc(&stream);
let l1 = u32::from_be_bytes(out.packetized[0..4].try_into().unwrap()) as usize;
assert_eq!(&out.packetized[4..4 + l1], &idr[..]);
let off2 = 4 + l1;
let l2 = u32::from_be_bytes(out.packetized[off2..off2 + 4].try_into().unwrap()) as usize;
assert_eq!(&out.packetized[off2 + 4..off2 + 4 + l2], &p_slice[..]);
assert_eq!(out.packetized.len(), off2 + 4 + l2);
}
}