use alloc::collections::VecDeque;
use alloc::vec::Vec;
use crate::nal::{NalCodec, nal_unit_type};
const AVC_VCL_MIN: u8 = 1;
const AVC_VCL_MAX: u8 = 5;
const AVC_AUD: u8 = 9;
const HEVC_VCL_MAX: u8 = 31;
const HEVC_AUD: u8 = 35;
const VVC_VCL_MAX: u8 = 11;
const VVC_AUD: u8 = 20;
fn header_len(codec: NalCodec) -> usize {
match codec {
NalCodec::Avc => 1,
NalCodec::Hevc | NalCodec::Vvc => 2,
}
}
fn is_vcl(codec: NalCodec, t: u8) -> bool {
match codec {
NalCodec::Avc => (AVC_VCL_MIN..=AVC_VCL_MAX).contains(&t),
NalCodec::Hevc => t <= HEVC_VCL_MAX,
NalCodec::Vvc => t <= VVC_VCL_MAX,
}
}
fn is_aud(codec: NalCodec, t: u8) -> bool {
match codec {
NalCodec::Avc => t == AVC_AUD,
NalCodec::Hevc => t == HEVC_AUD,
NalCodec::Vvc => t == VVC_AUD,
}
}
fn first_slice_of_picture(codec: NalCodec, nal_body: &[u8]) -> bool {
let hl = header_len(codec);
match codec {
NalCodec::Avc | NalCodec::Hevc => nal_body.get(hl).is_some_and(|b| b & 0x80 != 0),
NalCodec::Vvc => false,
}
}
fn start_code_positions(data: &[u8]) -> Vec<usize> {
let mut positions = Vec::new();
let n = data.len();
let mut p = 0usize;
while p + 3 <= n {
if data[p] == 0 && data[p + 1] == 0 && data[p + 2] == 1 {
positions.push(p);
p += 3;
} else {
p += 1;
}
}
positions
}
pub struct AccessUnitSplitter {
codec: NalCodec,
buf: Vec<u8>,
primed: bool,
au: Vec<u8>,
au_has_vcl: bool,
ready: VecDeque<Vec<u8>>,
}
impl AccessUnitSplitter {
pub fn new(codec: NalCodec) -> Self {
Self {
codec,
buf: Vec::new(),
primed: false,
au: Vec::new(),
au_has_vcl: false,
ready: VecDeque::new(),
}
}
pub fn push(&mut self, bytes: &[u8]) {
self.buf.extend_from_slice(bytes);
self.drain_complete_nals();
}
pub fn pop(&mut self) -> Option<Vec<u8>> {
self.ready.pop_front()
}
pub fn finish(&mut self) {
if self.primed && !self.buf.is_empty() {
let range = core::mem::take(&mut self.buf);
self.process_nal(&range);
}
if !self.au.is_empty() {
self.ready.push_back(core::mem::take(&mut self.au));
self.au_has_vcl = false;
}
}
fn drain_complete_nals(&mut self) {
let au_starts = self.nal_starts();
if !self.primed {
match au_starts.first() {
Some(&first) => {
if first > 0 {
self.buf.drain(..first);
}
self.primed = true;
}
None => return, }
}
let au_starts = self.nal_starts();
if au_starts.len() < 2 {
return;
}
let mut consumed = 0usize;
for w in au_starts.windows(2) {
let (start, end) = (w[0], w[1]);
let nal = self.buf[start..end].to_vec();
self.process_nal(&nal);
consumed = end;
}
self.buf.drain(..consumed);
}
fn nal_starts(&self) -> Vec<usize> {
start_code_positions(&self.buf)
.into_iter()
.map(|cp| {
let mut s = cp;
while s > 0 && self.buf[s - 1] == 0 {
s -= 1;
}
s
})
.collect()
}
fn process_nal(&mut self, nal_with_code: &[u8]) {
let body = &nal_with_code[start_code_len(nal_with_code)..];
let Some(t) = nal_unit_type(self.codec, body) else {
self.au.extend_from_slice(nal_with_code);
return;
};
let vcl = is_vcl(self.codec, t);
let starts_new_au = if is_aud(self.codec, t) {
true
} else if vcl {
self.au_has_vcl && first_slice_of_picture(self.codec, body)
} else {
self.au_has_vcl
};
if starts_new_au && !self.au.is_empty() {
self.ready.push_back(core::mem::take(&mut self.au));
self.au_has_vcl = false;
}
self.au.extend_from_slice(nal_with_code);
if vcl {
self.au_has_vcl = true;
}
}
}
fn start_code_len(nal_with_code: &[u8]) -> usize {
let n = nal_with_code.len();
let mut i = 0;
while i + 3 <= n {
if nal_with_code[i] == 0 && nal_with_code[i + 1] == 0 && nal_with_code[i + 2] == 1 {
return i + 3;
}
i += 1;
}
n
}
pub fn split_access_units(codec: NalCodec, annexb: &[u8]) -> Vec<Vec<u8>> {
let mut s = AccessUnitSplitter::new(codec);
s.push(annexb);
s.finish();
let mut out = Vec::new();
while let Some(au) = s.pop() {
out.push(au);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn avc_sps() -> Vec<u8> {
vec![0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e]
}
fn avc_pps() -> Vec<u8> {
vec![0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80]
}
fn avc_aud() -> Vec<u8> {
vec![0x00, 0x00, 0x00, 0x01, 0x09, 0xf0]
}
fn avc_idr_first() -> Vec<u8> {
vec![0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x00]
}
fn avc_p_first() -> Vec<u8> {
vec![0x00, 0x00, 0x01, 0x41, 0x9a, 0x00]
}
fn avc_p_cont() -> Vec<u8> {
vec![0x00, 0x00, 0x01, 0x41, 0x00, 0x11]
}
fn concat(parts: &[Vec<u8>]) -> Vec<u8> {
let mut v = Vec::new();
for p in parts {
v.extend_from_slice(p);
}
v
}
#[test]
fn splits_aud_delimited_stream() {
let stream = concat(&[
avc_aud(),
avc_sps(),
avc_pps(),
avc_idr_first(),
avc_aud(),
avc_p_first(),
]);
let aus = split_access_units(NalCodec::Avc, &stream);
assert_eq!(aus.len(), 2, "two AUDs → two access units");
assert_eq!(concat(&aus), stream, "AU concatenation is byte-exact");
}
#[test]
fn splits_audless_stream_on_first_slice_and_config() {
let stream = concat(&[
avc_sps(),
avc_pps(),
avc_idr_first(),
avc_p_first(),
avc_p_first(),
]);
let aus = split_access_units(NalCodec::Avc, &stream);
assert_eq!(aus.len(), 3);
assert_eq!(concat(&aus), stream);
assert!(aus[0].windows(1).any(|b| b[0] == 0x67));
}
#[test]
fn multi_slice_picture_stays_one_au() {
let stream = concat(&[avc_sps(), avc_idr_first(), avc_p_cont()]);
let aus = split_access_units(NalCodec::Avc, &stream);
assert_eq!(aus.len(), 1, "continuation slice stays in the same AU");
assert_eq!(concat(&aus), stream);
}
#[test]
fn streaming_matches_whole_buffer_at_every_split_point() {
let stream = concat(&[
avc_aud(),
avc_sps(),
avc_pps(),
avc_idr_first(),
avc_aud(),
avc_p_first(),
avc_p_cont(),
avc_aud(),
avc_p_first(),
]);
let whole = split_access_units(NalCodec::Avc, &stream);
let mut s = AccessUnitSplitter::new(NalCodec::Avc);
let mut chunked = Vec::new();
for &b in &stream {
s.push(&[b]);
while let Some(au) = s.pop() {
chunked.push(au);
}
}
s.finish();
while let Some(au) = s.pop() {
chunked.push(au);
}
assert_eq!(chunked, whole, "byte-by-byte streaming == whole-buffer");
assert_eq!(concat(&chunked), stream);
}
#[test]
fn hevc_aud_boundaries() {
let aud = vec![0x00u8, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50];
let vps = vec![0x00u8, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c];
let idr = vec![0x00u8, 0x00, 0x01, 0x26, 0x01, 0x80]; let stream = concat(&[aud.clone(), vps, idr, aud]);
let aus = split_access_units(NalCodec::Hevc, &stream);
assert_eq!(aus.len(), 2);
assert_eq!(concat(&aus), stream);
}
#[test]
fn leading_junk_before_first_start_code_is_dropped() {
let mut stream = vec![0xaa, 0xbb, 0xcc];
stream.extend_from_slice(&concat(&[avc_aud(), avc_idr_first()]));
let aus = split_access_units(NalCodec::Avc, &stream);
assert_eq!(aus.len(), 1);
assert_eq!(&aus[0][..4], &[0x00, 0x00, 0x00, 0x01]);
}
}