use super::coding::{CodingType, PictureInfo};
use super::startcode::{BitReader, find_start_code, skip_start_code};
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
const NAL_SLICE_NON_IDR: u8 = 1;
const NAL_SLICE_IDR: u8 = 5;
const NAL_SPS: u8 = 7;
const NAL_PPS: u8 = 8;
const NAL_AUD: u8 = 9;
fn h264_slice_coding_type(slice_type: u32) -> Option<CodingType> {
match slice_type {
0..=9 => Some(match slice_type % 5 {
0 | 3 => CodingType::P, 1 => CodingType::B,
_ => CodingType::I, }),
_ => None,
}
}
pub struct H264Parser {
sps: Option<Vec<u8>>,
pps: Option<Vec<u8>>,
cur_sps: Option<Vec<u8>>,
cur_pps: Option<Vec<u8>>,
}
impl Default for H264Parser {
fn default() -> Self {
Self::new()
}
}
impl H264Parser {
pub fn new() -> Self {
Self {
sps: None,
pps: None,
cur_sps: None,
cur_pps: None,
}
}
}
fn push_length_prefixed(out: &mut Vec<u8>, nal: &[u8]) {
let Ok(len) = u32::try_from(nal.len()) else {
return;
};
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(nal);
}
fn handle_param_set(
first: &mut Option<Vec<u8>>,
cur: &mut Option<Vec<u8>>,
nal: &[u8],
frame_data: &mut Vec<u8>,
) -> bool {
let is_first = first.is_none();
if is_first {
first.replace(nal.to_vec()); }
let changed = cur.as_deref() != Some(nal);
if changed {
*cur = Some(nal.to_vec());
}
if is_first || !changed {
return false;
}
push_length_prefixed(frame_data, nal);
true
}
fn reassert_active(prefix: &mut Vec<u8>, cur: &Option<Vec<u8>>, emitted: bool) {
if emitted {
return;
}
let Some(active) = cur.as_deref() else {
return;
};
push_length_prefixed(prefix, active);
}
impl CodecParser for H264Parser {
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
if pes.data.is_empty() {
return Vec::new();
}
let pts_ns = pes.pts.or(pes.dts).map(pts_to_ns).unwrap_or(0);
let mut keyframe = false;
let mut coding_type: Option<CodingType> = None;
let mut emitted_sps = false;
let mut emitted_pps = false;
let mut frame_data = Vec::with_capacity(pes.data.len() + 64);
for nal in NalIterator::new(&pes.data) {
let nal_type = nal[0] & 0x1F;
match nal_type {
NAL_SPS => {
emitted_sps |=
handle_param_set(&mut self.sps, &mut self.cur_sps, nal, &mut frame_data)
}
NAL_PPS => {
emitted_pps |=
handle_param_set(&mut self.pps, &mut self.cur_pps, nal, &mut frame_data)
}
NAL_AUD => {}
_ => {
if nal_type == NAL_SLICE_IDR {
keyframe = true;
}
if (nal_type == NAL_SLICE_NON_IDR || nal_type == NAL_SLICE_IDR)
&& coding_type.is_none()
{
let mut br = BitReader::new(&nal[1..]);
if let (Some(_first_mb), Some(slice_type)) = (br.read_ue(), br.read_ue()) {
coding_type = h264_slice_coding_type(slice_type);
}
}
let Ok(len) = u32::try_from(nal.len()) else {
continue;
};
frame_data.extend_from_slice(&len.to_be_bytes());
frame_data.extend_from_slice(nal);
}
}
}
if frame_data.is_empty() {
return Vec::new();
}
if keyframe {
let mut prefix = Vec::new();
reassert_active(&mut prefix, &self.cur_sps, emitted_sps);
reassert_active(&mut prefix, &self.cur_pps, emitted_pps);
if !prefix.is_empty() {
prefix.extend_from_slice(&frame_data);
frame_data = prefix;
}
}
vec![Frame {
coding: coding_type.map(PictureInfo::coding_type_only),
source: pes.source,
pts_ns,
keyframe,
data: frame_data,
duration_ns: None,
}]
}
fn codec_private(&self) -> Option<Vec<u8>> {
let sps = self.sps.as_ref()?;
let pps = self.pps.as_ref()?;
if sps.len() < 4 {
return None;
}
if sps.len() > 0xFFFF || pps.len() > 0xFFFF {
return None;
}
let mut record = vec![
1, sps[1], sps[2], sps[3], 0xFF, 0xE1, (sps.len() >> 8) as u8,
sps.len() as u8,
];
record.extend_from_slice(sps);
record.push(1); record.push((pps.len() >> 8) as u8);
record.push(pps.len() as u8);
record.extend_from_slice(pps);
let profile_idc = sps[1];
const HIGH_PROFILES: [u8; 14] = [
100, 110, 122, 144, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135,
];
if HIGH_PROFILES.contains(&profile_idc) {
if let Some((chroma_fmt, depth_luma, depth_chroma)) = parse_sps_high_profile_ext(sps) {
record.push(0xFC | (chroma_fmt & 0x03));
record.push(0xF8 | (depth_luma & 0x07));
record.push(0xF8 | (depth_chroma & 0x07));
record.push(0x00);
}
}
Some(record)
}
}
fn parse_sps_high_profile_ext(sps: &[u8]) -> Option<(u8, u8, u8)> {
let rbsp: Vec<u8> = {
let raw = &sps[1..]; let mut out = Vec::with_capacity(raw.len());
let mut i = 0;
while i < raw.len() {
if i + 2 < raw.len() && raw[i] == 0x00 && raw[i + 1] == 0x00 && raw[i + 2] == 0x03 {
out.push(0x00);
out.push(0x00);
i += 3; } else {
out.push(raw[i]);
i += 1;
}
}
out
};
if rbsp.len() < 4 {
return None;
}
let mut reader = SpsReader::new(&rbsp[3..]);
reader.read_ue()?;
let chroma_format_idc = reader.read_ue()?;
if chroma_format_idc == 3 {
reader.read_bits(1)?; }
let bit_depth_luma_minus8 = reader.read_ue()?;
let bit_depth_chroma_minus8 = reader.read_ue()?;
Some((
(chroma_format_idc & 0x03) as u8,
(bit_depth_luma_minus8 & 0x07) as u8,
(bit_depth_chroma_minus8 & 0x07) as u8,
))
}
struct SpsReader<'a> {
data: &'a [u8],
byte: usize,
bits_left: u8,
}
impl<'a> SpsReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self {
data,
byte: 0,
bits_left: if data.is_empty() { 0 } else { 8 },
}
}
fn read_bit(&mut self) -> Option<u8> {
if self.bits_left == 0 {
self.byte += 1;
if self.byte >= self.data.len() {
return None;
}
self.bits_left = 8;
}
self.bits_left -= 1;
Some((self.data[self.byte] >> self.bits_left) & 1)
}
fn read_bits(&mut self, n: u8) -> Option<u32> {
let mut val = 0u32;
for _ in 0..n {
val = (val << 1) | (self.read_bit()? as u32);
}
Some(val)
}
fn read_ue(&mut self) -> Option<u32> {
let mut leading_zeros = 0u8;
loop {
let bit = self.read_bit()?;
if bit == 1 {
break;
}
leading_zeros += 1;
if leading_zeros > 31 {
return None; }
}
if leading_zeros == 0 {
return Some(0);
}
let suffix = self.read_bits(leading_zeros)?;
Some((1u32 << leading_zeros) - 1 + suffix)
}
}
struct NalIterator<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> NalIterator<'a> {
fn new(data: &'a [u8]) -> Self {
let pos = find_start_code(data, 0).unwrap_or(data.len());
Self { data, pos }
}
}
impl<'a> Iterator for NalIterator<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
loop {
if self.pos >= self.data.len() {
return None;
}
let nal_start = skip_start_code(self.data, self.pos)?;
let nal_end = find_start_code(self.data, nal_start).unwrap_or(self.data.len());
let mut end = nal_end;
while end > nal_start && self.data[end - 1] == 0x00 {
end -= 1;
}
self.pos = nal_end;
if end > nal_start {
return Some(&self.data[nal_start..end]);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mux::ts::PesPacket;
fn make_pes(data: Vec<u8>, pts: Option<i64>) -> PesPacket {
PesPacket {
source: None,
pid: 0x1011,
pts,
dts: None,
data,
}
}
#[test]
fn parse_sps_pps() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x67); data.extend_from_slice(&[0x42, 0x00, 0x1E, 0xAB, 0xCD]); data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x68); data.extend_from_slice(&[0xCE, 0x01]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x65); data.extend_from_slice(&[0x88, 0x00, 0x10]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
let cp = parser.codec_private();
assert!(
cp.is_some(),
"codec_private should be Some after seeing SPS+PPS"
);
let cp = cp.unwrap();
assert_eq!(cp[0], 1, "configurationVersion");
assert_eq!(cp[1], 0x42, "profile from SPS[1]");
assert_eq!(cp[2], 0x00, "compatibility from SPS[2]");
assert_eq!(cp[3], 0x1E, "level from SPS[3]");
assert_eq!(cp[4], 0xFF, "reserved + lengthSizeMinusOne=3");
assert_eq!(cp[5], 0xE1, "reserved + numSPS=1");
assert_eq!(frames.len(), 1);
}
fn h264_nals_in(frame: &[u8]) -> Vec<Vec<u8>> {
let mut out = Vec::new();
let mut i = 0;
while i + 4 <= frame.len() {
let len =
u32::from_be_bytes([frame[i], frame[i + 1], frame[i + 2], frame[i + 3]]) as usize;
i += 4;
if i + len > frame.len() {
break;
}
out.push(frame[i..i + len].to_vec());
i += len;
}
out
}
fn h264_pps_bodies(nals: &[Vec<u8>]) -> Vec<Vec<u8>> {
nals.iter()
.filter(|n| !n.is_empty() && n[0] & 0x1F == 8)
.map(|n| n[1..].to_vec())
.collect()
}
fn h264_nal(t: u8, body: &[u8]) -> Vec<u8> {
let mut v = vec![0x00, 0x00, 0x01, t];
v.extend_from_slice(body);
v
}
#[test]
fn h264_populates_measured_coding_type_and_source() {
use super::super::coding::CodingType;
let src = crate::pes::SourcePos::at_byte(8192);
let parse = |nal_type: u8, body: u8| {
let mut p = H264Parser::new();
let mut pe = make_pes(h264_nal(nal_type, &[body]), Some(0));
pe.source = Some(src);
p.parse(&pe)
};
let fi = parse(NAL_SLICE_IDR, 0x88);
assert_eq!(fi.len(), 1);
assert!(fi[0].keyframe, "IDR is a keyframe");
let ci = fi[0].coding.expect("H.264 frame carries PictureInfo");
assert_eq!(ci.coding_type(), CodingType::I, "slice_type 7 → I");
assert!(
ci.field_order().is_none(),
"H.264 field order undecoded → None, never faked"
);
assert_eq!(
fi[0].source.unwrap().byte,
8192,
"source provenance carried"
);
let fp = parse(NAL_SLICE_NON_IDR, 0x98);
assert_eq!(
fp[0].coding.unwrap().coding_type(),
CodingType::P,
"slice_type 5 → P"
);
assert!(!fp[0].keyframe);
let fb = parse(NAL_SLICE_NON_IDR, 0x9C);
assert_eq!(
fb[0].coding.unwrap().coding_type(),
CodingType::B,
"slice_type 6 → B"
);
}
#[test]
fn h264_emits_switch_back_to_codecprivate_pps() {
let a = [0xA1u8, 0xA2];
let b = [0xB1u8, 0xB2, 0xB3];
let mut p = H264Parser::new();
p.parse(&make_pes(
[
h264_nal(0x67, &[0x42, 0x00, 0x1E, 0xAB]),
h264_nal(0x68, &a),
h264_nal(0x65, &[1]),
]
.concat(),
Some(0),
));
let f2 = p.parse(&make_pes(
[h264_nal(0x68, &b), h264_nal(0x65, &[2])].concat(),
Some(1),
));
assert!(
h264_pps_bodies(&h264_nals_in(&f2[0].data))
.iter()
.any(|x| x == &b),
"AU2 must carry redefined PPS-B in-band"
);
let f3 = p.parse(&make_pes(
[h264_nal(0x68, &a), h264_nal(0x65, &[3])].concat(),
Some(2),
));
assert!(
h264_pps_bodies(&h264_nals_in(&f3[0].data))
.iter()
.any(|x| x == &a),
"switch back to avcC PPS-A must be emitted in-band"
);
}
#[test]
fn h264_reasserts_active_pps_at_bare_keyframe() {
let a = [0xA1u8, 0xA2];
let b = [0xB1u8, 0xB2, 0xB3];
let mut p = H264Parser::new();
p.parse(&make_pes(
[
h264_nal(0x67, &[0x42, 0x00, 0x1E, 0xAB]),
h264_nal(0x68, &a),
h264_nal(0x65, &[1]),
]
.concat(),
Some(0),
));
p.parse(&make_pes(
[h264_nal(0x68, &b), h264_nal(0x65, &[2])].concat(),
Some(1),
));
let f3 = p.parse(&make_pes(h264_nal(0x65, &[3]), Some(2)));
let got = h264_pps_bodies(&h264_nals_in(&f3[0].data));
assert!(
got.iter().any(|x| x == &b),
"bare keyframe must re-assert active PPS-B"
);
assert!(
!got.iter().any(|x| x == &a),
"must not re-assert stale avcC PPS-A"
);
}
#[test]
fn codec_private_none_before_sps_pps() {
let parser = H264Parser::new();
assert!(parser.codec_private().is_none());
}
#[test]
fn parse_idr_keyframe() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x65); data.extend_from_slice(&[0x88, 0x00, 0x10, 0x20]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(
frames[0].keyframe,
"IDR slice should be detected as keyframe"
);
}
#[test]
fn parse_non_idr() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x41); data.extend_from_slice(&[0x9A, 0x00, 0x10]);
let pes = make_pes(data, Some(180000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(!frames[0].keyframe, "non-IDR slice should not be keyframe");
}
#[test]
fn length_prefix_conversion() {
let mut parser = H264Parser::new();
let nal_payload = [0x41, 0xAA, 0xBB, 0xCC, 0xDD]; let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&nal_payload);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
let frame_data = &frames[0].data;
assert!(
frame_data.len() >= 4,
"frame data should have length prefix"
);
let length =
u32::from_be_bytes([frame_data[0], frame_data[1], frame_data[2], frame_data[3]]);
assert_eq!(
length as usize,
nal_payload.len(),
"length prefix should match NAL size"
);
assert_eq!(&frame_data[4..], &nal_payload);
for i in 0..frame_data.len().saturating_sub(2) {
let is_sc =
frame_data[i] == 0x00 && frame_data[i + 1] == 0x00 && frame_data[i + 2] == 0x01;
assert!(!is_sc, "output should not contain Annex B start codes");
}
}
#[test]
fn aud_stripped_param_sets_reasserted_at_keyframe() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x09);
data.push(0xF0);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x67);
data.extend_from_slice(&[0x42, 0x00, 0x1E, 0xAB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x68);
data.extend_from_slice(&[0xCE, 0x01]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x65);
data.extend_from_slice(&[0x88, 0x00]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(parser.codec_private().is_some(), "SPS/PPS seed avcC");
let fd = &frames[0].data;
let mut types = Vec::new();
let mut o = 0;
while o + 4 <= fd.len() {
let len = u32::from_be_bytes([fd[o], fd[o + 1], fd[o + 2], fd[o + 3]]) as usize;
o += 4;
types.push(fd[o] & 0x1F);
o += len;
}
assert_eq!(
types,
vec![7, 8, 5],
"keyframe: SPS+PPS re-asserted ahead of IDR, AUD dropped"
);
}
#[test]
fn pts_conversion() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x41);
data.extend_from_slice(&[0x00, 0x10]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].pts_ns, 1_000_000_000);
}
#[test]
fn parse_empty_pes() {
let mut parser = H264Parser::new();
let pes = make_pes(Vec::new(), Some(0));
let frames = parser.parse(&pes);
assert!(frames.is_empty());
}
#[test]
fn pts_preferred_over_dts() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x41);
data.extend_from_slice(&[0x00, 0x10]);
let pes = PesPacket {
source: None,
pid: 0x1011,
pts: Some(180000), dts: Some(90000), data,
};
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].pts_ns, 2_000_000_000);
}
fn frame_nal_types(fd: &[u8]) -> Vec<u8> {
let mut types = Vec::new();
let mut off = 0;
while off + 4 <= fd.len() {
let len = u32::from_be_bytes([fd[off], fd[off + 1], fd[off + 2], fd[off + 3]]) as usize;
off += 4;
if off + len > fd.len() {
break;
}
types.push(fd[off] & 0x1F);
off += len;
}
types
}
#[test]
fn keyframes_self_contained_and_redefinition_emitted() {
let mut parser = H264Parser::new();
let mut au1 = Vec::new();
au1.extend_from_slice(&[0x00, 0x00, 0x01]);
au1.extend_from_slice(&[0x67, 0x42, 0x00, 0x1E, 0xAA]); au1.extend_from_slice(&[0x00, 0x00, 0x01]);
au1.extend_from_slice(&[0x68, 0x11]); au1.extend_from_slice(&[0x00, 0x00, 0x01]);
au1.extend_from_slice(&[0x65, 0x10, 0x20]); let f1 = parser.parse(&make_pes(au1, Some(0)));
assert_eq!(f1.len(), 1);
assert_eq!(
frame_nal_types(&f1[0].data),
vec![7, 8, 5],
"AU1 keyframe: SPS+PPS re-asserted ahead of IDR"
);
let mut au2 = Vec::new();
au2.extend_from_slice(&[0x00, 0x00, 0x01]);
au2.extend_from_slice(&[0x67, 0x42, 0x00, 0x1E, 0xAA]); au2.extend_from_slice(&[0x00, 0x00, 0x01]);
au2.extend_from_slice(&[0x68, 0x22]); au2.extend_from_slice(&[0x00, 0x00, 0x01]);
au2.extend_from_slice(&[0x65, 0x30, 0x40]); let f2 = parser.parse(&make_pes(au2, Some(90000)));
assert_eq!(f2.len(), 1);
let types = frame_nal_types(&f2[0].data);
assert_eq!(types, vec![7, 8, 5], "got {types:?}");
let mut o = 0;
let mut pps_body = None;
while o + 4 <= f2[0].data.len() {
let len = u32::from_be_bytes([
f2[0].data[o],
f2[0].data[o + 1],
f2[0].data[o + 2],
f2[0].data[o + 3],
]) as usize;
o += 4;
if f2[0].data[o] & 0x1F == 8 {
pps_body = Some(f2[0].data[o + 1]);
}
o += len;
}
assert_eq!(
pps_body,
Some(0x22),
"in-band PPS must be the redefined body B"
);
}
#[test]
fn repeated_identical_param_sets_reasserted_each_keyframe() {
let mut parser = H264Parser::new();
let mut au = Vec::new();
au.extend_from_slice(&[0x00, 0x00, 0x01]);
au.extend_from_slice(&[0x67, 0x42, 0x00, 0x1E, 0xAA]);
au.extend_from_slice(&[0x00, 0x00, 0x01]);
au.extend_from_slice(&[0x68, 0x11]);
au.extend_from_slice(&[0x00, 0x00, 0x01]);
au.extend_from_slice(&[0x65, 0x10]);
parser.parse(&make_pes(au.clone(), Some(0)));
let f = parser.parse(&make_pes(au, Some(90000)));
assert_eq!(
frame_nal_types(&f[0].data),
vec![7, 8, 5],
"each keyframe re-asserts the active SPS/PPS in-band"
);
}
#[test]
fn many_empty_nals_do_not_overflow_stack() {
let mut data = Vec::new();
for _ in 0..50_000 {
data.extend_from_slice(&[0x00, 0x00, 0x01]);
}
data.extend_from_slice(&[0x41, 0xAA, 0xBB]);
let mut parser = H264Parser::new();
let frames = parser.parse(&make_pes(data, Some(0)));
assert_eq!(frames.len(), 1);
let fd = &frames[0].data;
let len = u32::from_be_bytes([fd[0], fd[1], fd[2], fd[3]]) as usize;
assert_eq!(len, 3, "the single real NAL is length-prefixed");
assert_eq!(fd[4], 0x41);
}
#[test]
fn avcc_exact_length_fields_and_payload() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&[0x67, 0x4D, 0x00, 0x28, 0xAB, 0xCD]); data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&[0x68, 0xEE, 0x3C]); data.extend_from_slice(&[0x00, 0x00, 0x01, 0x65, 0x11]);
parser.parse(&make_pes(data, Some(0)));
let cp = parser.codec_private().expect("avcC");
assert_eq!(cp[0], 1, "configurationVersion");
assert_eq!(cp[1], 0x4D, "AVCProfileIndication = SPS[1]");
assert_eq!(cp[2], 0x00, "profile_compatibility = SPS[2]");
assert_eq!(cp[3], 0x28, "AVCLevelIndication = SPS[3]");
assert_eq!(cp[4], 0xFF, "lengthSizeMinusOne nibble (4-byte prefix)");
assert_eq!(cp[5], 0xE1, "numSPS = 1");
assert_eq!(u16::from_be_bytes([cp[6], cp[7]]), 6, "SPS length field");
assert_eq!(&cp[8..14], &[0x67, 0x4D, 0x00, 0x28, 0xAB, 0xCD]);
assert_eq!(cp[14], 1, "numPPS");
assert_eq!(u16::from_be_bytes([cp[15], cp[16]]), 3, "PPS length field");
assert_eq!(&cp[17..20], &[0x68, 0xEE, 0x3C]);
assert_eq!(cp.len(), 20);
}
#[test]
fn avcc_none_when_sps_shorter_than_four_bytes() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01, 0x67, 0x42]); data.extend_from_slice(&[0x00, 0x00, 0x01, 0x68, 0x11]); parser.parse(&make_pes(data, Some(0)));
assert!(
parser.codec_private().is_none(),
"SPS < 4 bytes must not yield an avcC"
);
}
#[test]
fn avcc_none_with_sps_but_no_pps() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1E, 0xAA]);
data.extend_from_slice(&[0x00, 0x00, 0x01, 0x65, 0x10]); parser.parse(&make_pes(data, Some(0)));
assert!(parser.codec_private().is_none());
}
#[test]
fn nal_type_masks_high_three_bits() {
for idr_hdr in [0x65u8, 0x25, 0x05, 0x85] {
let mut parser = H264Parser::new();
let data = vec![0x00, 0x00, 0x01, idr_hdr, 0x10, 0x20];
let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
assert!(
f[0].keyframe,
"header {idr_hdr:#x} is NAL type 5 (IDR) → keyframe"
);
}
}
#[test]
fn sps_recognized_regardless_of_ref_idc() {
for sps_hdr in [0x67u8, 0x27] {
let mut parser = H264Parser::new();
let mut data = vec![0x00, 0x00, 0x01, sps_hdr, 0x42, 0x00, 0x1E, 0xAA];
data.extend_from_slice(&[0x00, 0x00, 0x01, 0x68, 0x11]); parser.parse(&make_pes(data, Some(0)));
let cp = parser.codec_private().expect("avcC");
assert_eq!(cp[1], 0x42, "profile from SPS[1] regardless of ref_idc");
}
}
#[test]
fn four_byte_start_code_parsed() {
let mut parser = H264Parser::new();
let data = vec![0x00, 0x00, 0x00, 0x01, 0x41, 0xAA, 0xBB];
let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
let len = u32::from_be_bytes([f[0].data[0], f[0].data[1], f[0].data[2], f[0].data[3]]);
assert_eq!(len, 3);
assert_eq!(&f[0].data[4..], &[0x41, 0xAA, 0xBB]);
}
#[test]
fn trailing_zeros_of_next_start_code_stripped_from_nal() {
let mut parser = H264Parser::new();
let mut data = vec![0x00, 0x00, 0x01, 0x41, 0xAA]; data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01, 0x41, 0xBB]); let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
let len1 = u32::from_be_bytes([f[0].data[0], f[0].data[1], f[0].data[2], f[0].data[3]]);
assert_eq!(len1, 2, "NAL1 must not absorb the next start code's zeros");
assert_eq!(&f[0].data[4..6], &[0x41, 0xAA]);
}
#[test]
fn aud_dropped_but_following_slice_kept() {
let mut parser = H264Parser::new();
let mut data = vec![0x00, 0x00, 0x01, 0x09, 0xF0]; data.extend_from_slice(&[0x00, 0x00, 0x01, 0x41, 0xAA, 0xBB]); let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
assert_eq!(
frame_nal_types(&f[0].data),
vec![1],
"only the slice remains"
);
}
#[test]
fn param_set_only_pes_emits_no_frame() {
let mut parser = H264Parser::new();
let mut data = vec![0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1E, 0xAA];
data.extend_from_slice(&[0x00, 0x00, 0x01, 0x68, 0x11]);
let f = parser.parse(&make_pes(data, Some(0)));
assert!(f.is_empty(), "param-set-only PES emits no frame");
assert!(parser.codec_private().is_some());
}
#[test]
fn dts_fallback_when_pts_absent() {
let mut parser = H264Parser::new();
let pes = PesPacket {
source: None,
pid: 0x1011,
pts: None,
dts: Some(90000),
data: vec![0x00, 0x00, 0x01, 0x41, 0x10],
};
let f = parser.parse(&pes);
assert_eq!(f.len(), 1);
assert_eq!(f[0].pts_ns, 1_000_000_000, "falls back to DTS");
}
#[test]
fn no_pts_no_dts_defaults_zero() {
let mut parser = H264Parser::new();
let pes = PesPacket {
source: None,
pid: 0x1011,
pts: None,
dts: None,
data: vec![0x00, 0x00, 0x01, 0x41, 0x10],
};
let f = parser.parse(&pes);
assert_eq!(f.len(), 1);
assert_eq!(f[0].pts_ns, 0);
}
#[test]
fn no_start_code_emits_nothing() {
let mut parser = H264Parser::new();
let f = parser.parse(&make_pes(vec![0x41, 0xAA, 0xBB, 0xCC], Some(0)));
assert!(f.is_empty(), "no start code → no NAL → no frame");
}
#[test]
fn avcc_oversized_param_set_returns_none() {
let mut parser = H264Parser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.push(0x67);
data.extend_from_slice(&vec![0x11u8; 70_000]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&[0x68, 0x11]);
parser.parse(&make_pes(data, Some(0)));
assert!(
parser.codec_private().is_none(),
"oversized SPS must not produce a truncated avcC"
);
}
fn build_high_profile_sps(
profile_idc: u8,
chroma_format_idc: u32,
bit_depth_luma_minus8: u32,
bit_depth_chroma_minus8: u32,
) -> Vec<u8> {
struct BitWriter {
buf: Vec<u8>,
cur: u8,
bits: u8, }
impl BitWriter {
fn new() -> Self {
Self {
buf: Vec::new(),
cur: 0,
bits: 0,
}
}
fn push_bit(&mut self, bit: u8) {
self.cur = (self.cur << 1) | (bit & 1);
self.bits += 1;
if self.bits == 8 {
self.buf.push(self.cur);
self.cur = 0;
self.bits = 0;
}
}
fn write_ue(&mut self, val: u32) {
if val == 0 {
self.push_bit(1);
return;
}
let code = val + 1; let k = 31 - code.leading_zeros();
for _ in 0..k {
self.push_bit(0);
} self.push_bit(1); for i in (0..k).rev() {
self.push_bit(((code >> i) & 1) as u8);
}
}
fn finish(mut self) -> Vec<u8> {
if self.bits > 0 {
self.cur <<= 8 - self.bits;
self.buf.push(self.cur);
}
self.buf
}
}
let mut w = BitWriter::new();
w.write_ue(0); w.write_ue(chroma_format_idc);
if chroma_format_idc == 3 {
w.push_bit(0); }
w.write_ue(bit_depth_luma_minus8);
w.write_ue(bit_depth_chroma_minus8);
let payload = w.finish();
let mut sps = vec![
0x67, profile_idc,
0x00, 0x28, ];
sps.extend_from_slice(&payload);
sps
}
fn feed_sps_pps(parser: &mut H264Parser, sps_bytes: &[u8]) {
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(sps_bytes);
data.extend_from_slice(&[0x00, 0x00, 0x01, 0x68, 0xCE, 0x01]); data.extend_from_slice(&[0x00, 0x00, 0x01, 0x65, 0x88]); parser.parse(&make_pes(data, Some(0)));
}
#[test]
fn avcc_high_profile_appends_extension_bytes() {
let sps = build_high_profile_sps(100, 1, 0, 0);
let mut parser = H264Parser::new();
feed_sps_pps(&mut parser, &sps);
let cp = parser.codec_private().expect("avcC must be present");
let ext_off = sps.len() + 14;
assert!(
cp.len() == ext_off + 4,
"High-Profile avcC must have exactly 4 extension bytes (len={}, expected {})",
cp.len(),
ext_off + 4
);
assert_eq!(
cp[ext_off] & 0xFC,
0xFC,
"extension byte 0: reserved bits must be 111111xx"
);
assert_eq!(cp[ext_off] & 0x03, 1, "chroma_format_idc must be 1 (4:2:0)");
assert_eq!(
cp[ext_off + 1] & 0xF8,
0xF8,
"extension byte 1: reserved bits must be 11111xxx"
);
assert_eq!(cp[ext_off + 1] & 0x07, 0, "bit_depth_luma_minus8 must be 0");
assert_eq!(
cp[ext_off + 2] & 0xF8,
0xF8,
"extension byte 2: reserved bits must be 11111xxx"
);
assert_eq!(
cp[ext_off + 2] & 0x07,
0,
"bit_depth_chroma_minus8 must be 0"
);
assert_eq!(
cp[ext_off + 3],
0,
"num_of_sequence_parameter_set_ext must be 0"
);
}
#[test]
fn avcc_high_profile_extension_carries_correct_values() {
let sps = build_high_profile_sps(100, 3, 2, 2);
let mut parser = H264Parser::new();
feed_sps_pps(&mut parser, &sps);
let cp = parser.codec_private().expect("avcC");
let ext_off = sps.len() + 14;
assert_eq!(cp[ext_off] & 0x03, 3, "chroma_format_idc must be 3 (4:4:4)");
assert_eq!(cp[ext_off + 1] & 0x07, 2, "bit_depth_luma_minus8 must be 2");
assert_eq!(
cp[ext_off + 2] & 0x07,
2,
"bit_depth_chroma_minus8 must be 2"
);
assert_eq!(
cp[ext_off + 3],
0,
"num_of_sequence_parameter_set_ext must be 0"
);
}
#[test]
fn avcc_profile_244_appends_extension_bytes() {
let sps = build_high_profile_sps(244, 3, 4, 4);
let mut parser = H264Parser::new();
feed_sps_pps(&mut parser, &sps);
let cp = parser.codec_private().expect("avcC must be present");
let ext_off = sps.len() + 14;
assert_eq!(
cp.len(),
ext_off + 4,
"profile 244 avcC must have the 4 extension bytes (len={}, expected {})",
cp.len(),
ext_off + 4
);
assert_eq!(cp[ext_off] & 0x03, 3, "chroma_format_idc must be 3 (4:4:4)");
assert_eq!(cp[ext_off + 1] & 0x07, 4, "bit_depth_luma_minus8 must be 4");
assert_eq!(
cp[ext_off + 2] & 0x07,
4,
"bit_depth_chroma_minus8 must be 4"
);
}
#[test]
fn avcc_main_profile_no_extension_bytes() {
let sps = vec![
0x67, 77, 0x40, 0x28, 0x80,
];
let mut parser = H264Parser::new();
feed_sps_pps(&mut parser, &sps);
let cp = parser.codec_private().expect("avcC must be present");
let expected_len = sps.len() + 14;
assert_eq!(
cp.len(),
expected_len,
"Main-Profile avcC must NOT have extension bytes (len={}, expected {})",
cp.len(),
expected_len
);
}
}