#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NalMuxCodec {
H264,
H265,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NalClass {
Vps,
Sps,
Pps,
Sample,
}
fn nal_type(nal: &[u8], codec: NalMuxCodec) -> u8 {
if nal.is_empty() {
return 0;
}
match codec {
NalMuxCodec::H264 => nal[0] & 0x1F, NalMuxCodec::H265 => (nal[0] >> 1) & 0x3F, }
}
fn classify(nal: &[u8], codec: NalMuxCodec) -> NalClass {
match (codec, nal_type(nal, codec)) {
(NalMuxCodec::H264, 7) => NalClass::Sps,
(NalMuxCodec::H264, 8) => NalClass::Pps,
(NalMuxCodec::H265, 32) => NalClass::Vps,
(NalMuxCodec::H265, 33) => NalClass::Sps,
(NalMuxCodec::H265, 34) => NalClass::Pps,
_ => NalClass::Sample,
}
}
fn is_aud(nal: &[u8], codec: NalMuxCodec) -> bool {
match codec {
NalMuxCodec::H264 => nal_type(nal, codec) == 9,
NalMuxCodec::H265 => nal_type(nal, codec) == 35,
}
}
fn is_idr(nal: &[u8], codec: NalMuxCodec) -> bool {
match codec {
NalMuxCodec::H264 => nal_type(nal, codec) == 5, NalMuxCodec::H265 => matches!(nal_type(nal, codec), 16..=23), }
}
fn is_vcl(nal: &[u8], codec: NalMuxCodec) -> bool {
let t = nal_type(nal, codec);
match codec {
NalMuxCodec::H264 => (1..=5).contains(&t),
NalMuxCodec::H265 => t <= 31,
}
}
fn first_slice_in_pic(nal: &[u8], codec: NalMuxCodec) -> bool {
match codec {
NalMuxCodec::H264 => nal.len() > 1 && (nal[1] & 0x80) != 0,
NalMuxCodec::H265 => nal.len() > 2 && (nal[2] & 0x80) != 0,
}
}
#[derive(Debug, Clone)]
pub struct AuSample {
pub data: Vec<u8>,
pub is_keyframe: bool,
}
pub fn split_annexb_nals(data: &[u8]) -> Vec<&[u8]> {
let mut nals = Vec::new();
let n = data.len();
let mut cursor = match find_start_code(data, 0) {
Some((pos, len)) => pos + len,
None => return nals, };
loop {
let (next_pos, next_len) = match find_start_code(data, cursor) {
Some(x) => x,
None => {
if n > cursor {
nals.push(&data[cursor..n]); }
break;
}
};
if next_pos > cursor {
nals.push(&data[cursor..next_pos]);
}
cursor = next_pos + next_len;
}
nals
}
fn find_start_code(data: &[u8], from: usize) -> Option<(usize, usize)> {
let n = data.len();
let mut i = from;
while i + 3 <= n {
if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
return Some((i, 3));
}
i += 1;
}
None
}
#[derive(Debug)]
pub struct NalSampleWriter {
codec: NalMuxCodec,
pub vps: Vec<Vec<u8>>,
pub sps: Vec<Vec<u8>>,
pub pps: Vec<Vec<u8>>,
inline_param_sets: bool,
}
impl NalSampleWriter {
pub fn new(codec: NalMuxCodec) -> Self {
Self { codec, vps: Vec::new(), sps: Vec::new(), pps: Vec::new(), inline_param_sets: false }
}
pub fn new_inline(codec: NalMuxCodec) -> Self {
Self { codec, vps: Vec::new(), sps: Vec::new(), pps: Vec::new(), inline_param_sets: true }
}
pub fn push_packet(&mut self, annexb: &[u8]) -> Vec<AuSample> {
let mut units: Vec<Vec<&[u8]>> = vec![Vec::new()];
let mut cur_has_vcl = false;
for nal in split_annexb_nals(annexb) {
let new_au = is_aud(nal, self.codec)
|| (is_vcl(nal, self.codec) && cur_has_vcl && first_slice_in_pic(nal, self.codec));
if new_au && !units.last().unwrap().is_empty() {
units.push(Vec::new());
cur_has_vcl = false;
}
if is_vcl(nal, self.codec) {
cur_has_vcl = true;
}
units.last_mut().unwrap().push(nal);
}
let codec = self.codec;
let inline = self.inline_param_sets;
let mut samples = Vec::new();
for unit in units {
let mut data = Vec::new();
let mut is_keyframe = false;
for nal in unit {
let push_inline = |data: &mut Vec<u8>| {
data.extend_from_slice(&(nal.len() as u32).to_be_bytes());
data.extend_from_slice(nal);
};
match classify(nal, codec) {
NalClass::Sample => {
if is_idr(nal, codec) {
is_keyframe = true;
}
push_inline(&mut data);
continue;
}
NalClass::Vps | NalClass::Sps | NalClass::Pps => {}
}
let store = match classify(nal, codec) {
NalClass::Vps => &mut self.vps,
NalClass::Sps => &mut self.sps,
NalClass::Pps => &mut self.pps,
NalClass::Sample => unreachable!(),
};
if inline {
if store.is_empty() {
store.push(nal.to_vec());
}
push_inline(&mut data);
} else {
dedup_push(store, nal);
}
}
if !data.is_empty() {
samples.push(AuSample { data, is_keyframe });
}
}
samples
}
pub fn has_param_sets(&self) -> bool {
let vps_ok = matches!(self.codec, NalMuxCodec::H264) || !self.vps.is_empty();
vps_ok && !self.sps.is_empty() && !self.pps.is_empty()
}
}
fn dedup_push(set: &mut Vec<Vec<u8>>, nal: &[u8]) {
if !set.iter().any(|n| n.as_slice() == nal) {
set.push(nal.to_vec());
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sc4(nal: &[u8]) -> Vec<u8> {
let mut v = vec![0, 0, 0, 1];
v.extend_from_slice(nal);
v
}
#[test]
fn splits_3_and_4_byte_start_codes() {
let mut buf = vec![0, 0, 0, 1, 0xAA, 0xBB];
buf.extend_from_slice(&[0, 0, 1, 0xCC]);
let nals = split_annexb_nals(&buf);
assert_eq!(nals.len(), 2);
assert_eq!(nals[0], &[0xAA, 0xBB]);
assert_eq!(nals[1], &[0xCC]);
}
#[test]
fn h264_strips_sps_pps_keeps_slice() {
let sps = [0x67u8, 0x42, 0x00, 0x1e, 0xAA];
let pps = [0x68u8, 0xCE, 0x3C];
let idr = [0x65u8, 0x88, 0x11, 0x22];
let mut frame = sc4(&sps);
frame.extend(sc4(&pps));
frame.extend(sc4(&idr));
let mut w = NalSampleWriter::new(NalMuxCodec::H264);
let samples = w.push_packet(&frame);
assert_eq!(samples.len(), 1, "no AUD → one access unit");
assert!(samples[0].is_keyframe, "contains an IDR slice");
assert_eq!(w.sps.len(), 1);
assert!(w.sps[0].starts_with(&sps));
assert!(w.pps[0].starts_with(&pps));
assert!(w.has_param_sets());
let mut expect = (idr.len() as u32).to_be_bytes().to_vec();
expect.extend_from_slice(&idr);
assert_eq!(samples[0].data, expect);
}
#[test]
fn splits_multi_au_packet_by_aud() {
let aud = [0x09u8, 0x10];
let idr = [0x65u8, 0x11];
let p = [0x41u8, 0x22];
let mut frame = sc4(&aud);
frame.extend(sc4(&idr)); frame.extend(sc4(&aud));
frame.extend(sc4(&p)); let mut w = NalSampleWriter::new(NalMuxCodec::H264);
let samples = w.push_packet(&frame);
assert_eq!(samples.len(), 2, "two AUDs → two samples");
assert!(samples[0].is_keyframe, "AU1 has the IDR");
assert!(!samples[1].is_keyframe, "AU2 is a P-frame");
}
#[test]
fn inline_mode_keeps_param_sets_in_sample() {
let sps = [0x67u8, 0x42, 0x00, 0x1e, 0xAA];
let pps = [0x68u8, 0xCE, 0x3C];
let idr = [0x65u8, 0x88, 0x11, 0x22];
let mut frame = sc4(&sps);
frame.extend(sc4(&pps));
frame.extend(sc4(&idr));
let mut w = NalSampleWriter::new_inline(NalMuxCodec::H264);
let inline = w.push_packet(&frame);
assert_eq!(inline.len(), 1);
assert!(inline[0].is_keyframe);
assert_eq!(w.sps.len(), 1);
assert!(w.sps[0].starts_with(&sps));
assert_eq!(w.pps.len(), 1);
let mut w2 = NalSampleWriter::new(NalMuxCodec::H264);
let oob = w2.push_packet(&frame);
assert!(
inline[0].data.len() > oob[0].data.len(),
"inline sample (SPS+PPS+IDR) must be larger than the stripped one ({} vs {})",
inline[0].data.len(),
oob[0].data.len()
);
assert_eq!(&inline[0].data[4..4 + sps.len()], &sps);
}
#[test]
fn h265_splits_multi_picture_packet_without_aud() {
let idr = [0x26u8, 0x01, 0xA0]; let trail = [0x02u8, 0x01, 0xA0]; let mut frame = sc4(&idr);
frame.extend(sc4(&trail));
let mut w = NalSampleWriter::new(NalMuxCodec::H265);
let samples = w.push_packet(&frame);
assert_eq!(samples.len(), 2, "two first-slice VCL NALs → two access units");
assert!(samples[0].is_keyframe);
assert!(!samples[1].is_keyframe);
}
#[test]
fn h265_captures_vps_sps_pps() {
let vps = [0x40u8, 0x01, 0x0c]; let sps = [0x42u8, 0x01, 0x01]; let pps = [0x44u8, 0x01, 0xc1]; let slice = [0x26u8, 0x01, 0xaf]; let mut frame = sc4(&vps);
frame.extend(sc4(&sps));
frame.extend(sc4(&pps));
frame.extend(sc4(&slice));
let mut w = NalSampleWriter::new(NalMuxCodec::H265);
let samples = w.push_packet(&frame);
assert_eq!(samples.len(), 1);
assert!(samples[0].is_keyframe, "type 19 is an IRAP/IDR");
assert!(w.vps[0].starts_with(&vps));
assert!(w.sps[0].starts_with(&sps));
assert!(w.pps[0].starts_with(&pps));
assert!(w.has_param_sets());
let mut expect = (slice.len() as u32).to_be_bytes().to_vec();
expect.extend_from_slice(&slice);
assert_eq!(samples[0].data, expect);
}
#[test]
fn preserves_slice_trailing_zero_bytes() {
let slice = [0x65u8, 0x88, 0x00, 0x00, 0x00];
let next = [0x41u8, 0x9a]; let mut frame = sc4(&slice);
frame.extend(sc4(&next));
let nals = split_annexb_nals(&frame);
assert_eq!(nals.len(), 2);
assert!(nals[0].starts_with(&slice), "slice trailing zeros must survive: {:?}", nals[0]);
assert!(nals[1].starts_with(&next));
let mut f2 = sc4(&slice);
f2.extend_from_slice(&[0, 0, 1]);
f2.extend_from_slice(&next);
let n2 = split_annexb_nals(&f2);
assert_eq!(n2[0], &slice, "trailing zeros kept exactly with a 3-byte next start code");
}
#[test]
fn dedups_repeated_param_sets() {
let sps = [0x67u8, 0x42, 0x00, 0x1e];
let pps = [0x68u8, 0xCE, 0x3C];
let idr = [0x65u8, 0x88];
let mut w = NalSampleWriter::new(NalMuxCodec::H264);
for _ in 0..2 {
let mut f = sc4(&sps);
f.extend(sc4(&pps));
f.extend(sc4(&idr));
w.push_packet(&f);
}
assert_eq!(w.sps.len(), 1);
assert_eq!(w.pps.len(), 1);
}
}