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_VPS: u8 = 32;
const NAL_SPS: u8 = 33;
const NAL_PPS: u8 = 34;
const NAL_AUD: u8 = 35;
const NAL_SEI_PREFIX: u8 = 39;
const NAL_SEI_SUFFIX: u8 = 40;
const SEI_MASTERING_DISPLAY_COLOUR_VOLUME: u32 = 137;
const SEI_CONTENT_LIGHT_LEVEL_INFO: u32 = 144;
const _NAL_UNSPEC62_DV_RPU: u8 = 62;
const NAL_BLA_W_LP: u8 = 16;
const NAL_RSV_IRAP_VCL23: u8 = 23;
const NAL_CRA_NUT: u8 = 21;
const NAL_VCL_MAX: u8 = 31;
fn hevc_num_extra_slice_header_bits(pps_nal: &[u8]) -> Option<u32> {
let mut br = BitReader::new(pps_nal.get(2..)?);
br.read_ue()?; br.read_ue()?; br.skip_bits(2)?; let n = br.read_bits(3)?;
Some(n)
}
fn hevc_slice_coding_type(slice_type: u32) -> Option<CodingType> {
match slice_type {
0 => Some(CodingType::B),
1 => Some(CodingType::P),
2 => Some(CodingType::I),
_ => None,
}
}
fn hevc_first_slice_coding_type(nal: &[u8], nal_type: u8, num_extra: u32) -> Option<CodingType> {
let mut br = BitReader::new(nal.get(2..)?); if br.read_bit()? != 1 {
return None; }
if (NAL_BLA_W_LP..=NAL_RSV_IRAP_VCL23).contains(&nal_type) {
br.skip_bits(1)?; }
br.read_ue()?; br.skip_bits(num_extra)?; hevc_slice_coding_type(br.read_ue()?)
}
pub struct HevcParser {
vps: Option<Vec<u8>>,
sps: Option<Vec<u8>>,
pps: Option<Vec<u8>>,
cur_vps: Option<Vec<u8>>,
cur_sps: Option<Vec<u8>>,
cur_pps: Option<Vec<u8>>,
pending_clip_boundary: bool,
high_pts: Option<i64>,
pts_wrap_offset: i64,
sei_mastering: Option<MasteringDisplay>,
sei_content_light: Option<ContentLightLevel>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct MasteringDisplay {
display_primaries_x: [u16; 3],
display_primaries_y: [u16; 3],
white_point_x: u16,
white_point_y: u16,
max_display_mastering_luminance: u32,
min_display_mastering_luminance: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct ContentLightLevel {
max_content_light_level: u16,
max_pic_average_light_level: u16,
}
const BACKSTEP_TICKS: i64 = 270_000;
const PTS_WRAP_PERIOD: i64 = 1 << 33;
impl Default for HevcParser {
fn default() -> Self {
Self::new()
}
}
impl HevcParser {
pub fn new() -> Self {
Self {
vps: None,
sps: None,
pps: None,
cur_vps: None,
cur_sps: None,
cur_pps: None,
pending_clip_boundary: false,
high_pts: None,
pts_wrap_offset: 0,
sei_mastering: None,
sei_content_light: None,
}
}
fn hdr10(&self) -> Option<crate::mux::codec::Hdr10Metadata> {
let m = self.sei_mastering?;
let c = self.sei_content_light?;
Some(crate::mux::codec::Hdr10Metadata {
display_primaries_x: m.display_primaries_x,
display_primaries_y: m.display_primaries_y,
white_point_x: m.white_point_x,
white_point_y: m.white_point_y,
max_display_mastering_luminance: m.max_display_mastering_luminance,
min_display_mastering_luminance: m.min_display_mastering_luminance,
max_content_light_level: c.max_content_light_level,
max_pic_average_light_level: c.max_pic_average_light_level,
})
}
fn scan_sei(&mut self, nal: &[u8]) {
let Some(raw) = nal.get(2..) else {
return;
};
let rbsp = strip_emulation_prevention(raw);
let mut i = 0usize;
loop {
let Some(payload_type) = read_sei_ff_value(&rbsp, &mut i) else {
break;
};
let Some(payload_size) = read_sei_ff_value(&rbsp, &mut i) else {
break;
};
let payload_size = payload_size as usize;
let Some(payload) = rbsp.get(i..i.saturating_add(payload_size)) else {
break; };
match payload_type {
SEI_MASTERING_DISPLAY_COLOUR_VOLUME if self.sei_mastering.is_none() => {
if let Some(m) = parse_mastering_display(payload) {
self.sei_mastering = Some(m);
}
}
SEI_CONTENT_LIGHT_LEVEL_INFO if self.sei_content_light.is_none() => {
if let Some(c) = parse_content_light_level(payload) {
self.sei_content_light = Some(c);
}
}
_ => {}
}
i += payload_size;
if i >= rbsp.len() {
break;
}
}
}
pub fn mark_clip_boundary(&mut self) {
self.pending_clip_boundary = true;
}
}
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;
}
let Ok(len) = u32::try_from(nal.len()) else {
return false;
};
frame_data.extend_from_slice(&len.to_be_bytes());
frame_data.extend_from_slice(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);
}
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);
}
impl CodecParser for HevcParser {
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);
if let Some(raw_pts) = pes.pts {
let mut unwrapped = raw_pts + self.pts_wrap_offset;
if let Some(high) = self.high_pts {
if high - unwrapped > PTS_WRAP_PERIOD / 2 {
self.pts_wrap_offset += PTS_WRAP_PERIOD;
unwrapped += PTS_WRAP_PERIOD;
}
}
match self.high_pts {
Some(high) if unwrapped < high - BACKSTEP_TICKS => {
self.pending_clip_boundary = true;
self.high_pts = Some(unwrapped);
}
Some(high) => self.high_pts = Some(high.max(unwrapped)),
None => self.high_pts = Some(unwrapped),
}
}
let data = &pes.data;
let mut keyframe = false;
let mut coding_type: Option<CodingType> = None;
let mut emitted_vps = false;
let mut emitted_sps = false;
let mut emitted_pps = false;
let mut frame_data = Vec::with_capacity(data.len() + 64);
let mut pos = 0;
while let Some(sc_pos) = find_start_code(data, pos) {
if let Some(nal_start) = skip_start_code(data, sc_pos) {
let next = find_start_code(data, nal_start).unwrap_or(data.len());
let mut end = next;
while end > nal_start && data[end - 1] == 0x00 {
end -= 1;
}
if nal_start < data.len() && end > nal_start {
let nal_type = (data[nal_start] >> 1) & 0x3F;
if coding_type.is_none() && nal_type <= NAL_VCL_MAX {
if let Some(num_extra) = self
.cur_pps
.as_deref()
.and_then(hevc_num_extra_slice_header_bits)
{
coding_type = hevc_first_slice_coding_type(
&data[nal_start..end],
nal_type,
num_extra,
);
}
}
match nal_type {
NAL_VPS => {
emitted_vps |= handle_param_set(
&mut self.vps,
&mut self.cur_vps,
&data[nal_start..end],
&mut frame_data,
)
}
NAL_SPS => {
emitted_sps |= handle_param_set(
&mut self.sps,
&mut self.cur_sps,
&data[nal_start..end],
&mut frame_data,
)
}
NAL_PPS => {
emitted_pps |= handle_param_set(
&mut self.pps,
&mut self.cur_pps,
&data[nal_start..end],
&mut frame_data,
)
}
NAL_AUD => {}
t if (NAL_BLA_W_LP..=NAL_RSV_IRAP_VCL23).contains(&t) => {
keyframe = true;
if self.pending_clip_boundary && t == NAL_CRA_NUT {
self.pending_clip_boundary = false;
let mut rewritten = data[nal_start..end].to_vec();
rewritten[0] = (rewritten[0] & 0x81) | (NAL_BLA_W_LP << 1);
push_length_prefixed(&mut frame_data, &rewritten);
} else {
self.pending_clip_boundary = false;
push_length_prefixed(&mut frame_data, &data[nal_start..end]);
}
}
NAL_SEI_PREFIX | NAL_SEI_SUFFIX => {
self.scan_sei(&data[nal_start..end]);
push_length_prefixed(&mut frame_data, &data[nal_start..end]);
}
_ => {
push_length_prefixed(&mut frame_data, &data[nal_start..end]);
}
}
}
pos = next;
} else {
break;
}
}
if frame_data.is_empty() {
return Vec::new();
}
if keyframe {
let mut prefix = Vec::new();
reassert_active(&mut prefix, &self.cur_vps, emitted_vps);
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;
}
}
let hdr10 = self.hdr10();
vec![Frame {
coding: coding_type
.map(PictureInfo::coding_type_only)
.map(|p| p.with_hdr10(hdr10)),
source: pes.source,
pts_ns,
keyframe,
data: frame_data,
duration_ns: None,
}]
}
fn codec_private(&self) -> Option<Vec<u8>> {
let vps = self.vps.as_ref()?;
let sps = self.sps.as_ref()?;
let pps = self.pps.as_ref()?;
if vps.len() > 0xFFFF || sps.len() > 0xFFFF || pps.len() > 0xFFFF {
return None;
}
let mut record = Vec::new();
let ptl: Vec<u8> = if sps.len() > 2 {
strip_emulation_prevention(&sps[2..])
} else {
Vec::new()
};
let ptl_at = |i: usize| -> u8 { ptl.get(i).copied().unwrap_or(0) };
record.push(1); record.push(ptl_at(1));
for i in 2..6 {
record.push(ptl_at(i));
}
for i in 6..12 {
record.push(ptl_at(i));
}
record.push(ptl_at(12));
record.extend_from_slice(&[0xF0, 0x00]);
record.push(0xFC);
let chroma = parse_sps_chroma(sps).unwrap_or(SpsChroma {
chroma_format_idc: 1,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
max_sub_layers_minus1: 0,
temporal_id_nesting_flag: 0,
});
record.push(0xFC | (chroma.chroma_format_idc & 0x03));
record.push(0xF8 | (chroma.bit_depth_luma_minus8 & 0x07));
record.push(0xF8 | (chroma.bit_depth_chroma_minus8 & 0x07));
record.extend_from_slice(&[0, 0]);
let num_temporal_layers = chroma.max_sub_layers_minus1.saturating_add(1).min(7) & 0x07;
let temporal_id_nested = chroma.temporal_id_nesting_flag & 0x01;
record.push((num_temporal_layers << 3) | (temporal_id_nested << 2) | 0x03);
record.push(3);
record.push(0x20 | (NAL_VPS & 0x3F)); record.extend_from_slice(&[0, 1]); record.push((vps.len() >> 8) as u8);
record.push(vps.len() as u8);
record.extend_from_slice(vps);
record.push(0x20 | (NAL_SPS & 0x3F));
record.extend_from_slice(&[0, 1]);
record.push((sps.len() >> 8) as u8);
record.push(sps.len() as u8);
record.extend_from_slice(sps);
record.push(0x20 | (NAL_PPS & 0x3F));
record.extend_from_slice(&[0, 1]);
record.push((pps.len() >> 8) as u8);
record.push(pps.len() as u8);
record.extend_from_slice(pps);
Some(record)
}
}
struct SpsChroma {
chroma_format_idc: u8,
bit_depth_luma_minus8: u8,
bit_depth_chroma_minus8: u8,
max_sub_layers_minus1: u8,
temporal_id_nesting_flag: u8,
}
fn strip_emulation_prevention(rbsp: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(rbsp.len());
let mut zeros = 0usize;
for &b in rbsp {
if zeros >= 2 && b == 0x03 {
zeros = 0;
continue;
}
out.push(b);
if b == 0x00 {
zeros += 1;
} else {
zeros = 0;
}
}
out
}
fn read_sei_ff_value(rbsp: &[u8], i: &mut usize) -> Option<u32> {
let mut value: u32 = 0;
loop {
let b = *rbsp.get(*i)?;
*i += 1;
value = value.checked_add(b as u32)?;
if b != 0xFF {
return Some(value);
}
}
}
fn parse_mastering_display(p: &[u8]) -> Option<MasteringDisplay> {
if p.len() < 24 {
return None;
}
let u16_at = |off: usize| u16::from_be_bytes([p[off], p[off + 1]]);
let u32_at = |off: usize| u32::from_be_bytes([p[off], p[off + 1], p[off + 2], p[off + 3]]);
Some(MasteringDisplay {
display_primaries_x: [u16_at(0), u16_at(4), u16_at(8)],
display_primaries_y: [u16_at(2), u16_at(6), u16_at(10)],
white_point_x: u16_at(12),
white_point_y: u16_at(14),
max_display_mastering_luminance: u32_at(16),
min_display_mastering_luminance: u32_at(20),
})
}
fn parse_content_light_level(p: &[u8]) -> Option<ContentLightLevel> {
if p.len() < 4 {
return None;
}
Some(ContentLightLevel {
max_content_light_level: u16::from_be_bytes([p[0], p[1]]),
max_pic_average_light_level: u16::from_be_bytes([p[2], p[3]]),
})
}
fn parse_sps_chroma(sps: &[u8]) -> Option<SpsChroma> {
if sps.len() < 3 {
return None;
}
let rbsp = strip_emulation_prevention(&sps[2..]);
let mut r = BitReader::new(&rbsp);
r.skip_bits(4)?;
let max_sub_layers_minus1 = r.read_bits(3)?;
let temporal_id_nesting_flag = r.read_bit()?;
parse_profile_tier_level(&mut r, max_sub_layers_minus1)?;
r.read_ue()?;
let chroma_format_idc = r.read_ue()? as u8;
if chroma_format_idc == 3 {
r.skip_bits(1)?;
}
r.read_ue()?;
r.read_ue()?;
if r.read_bit()? == 1 {
r.read_ue()?;
r.read_ue()?;
r.read_ue()?;
r.read_ue()?;
}
let bit_depth_luma_minus8 = r.read_ue()? as u8;
let bit_depth_chroma_minus8 = r.read_ue()? as u8;
Some(SpsChroma {
chroma_format_idc,
bit_depth_luma_minus8,
bit_depth_chroma_minus8,
max_sub_layers_minus1: max_sub_layers_minus1 as u8,
temporal_id_nesting_flag: temporal_id_nesting_flag as u8,
})
}
fn parse_profile_tier_level(r: &mut BitReader, max_sub_layers_minus1: u32) -> Option<()> {
r.skip_bits(96)?;
if max_sub_layers_minus1 > 0 {
let mut profile_present = [false; 8];
let mut level_present = [false; 8];
for i in 0..max_sub_layers_minus1 as usize {
profile_present[i] = r.read_bit()? == 1;
level_present[i] = r.read_bit()? == 1;
}
if max_sub_layers_minus1 < 8 {
for _ in max_sub_layers_minus1..8 {
r.skip_bits(2)?;
}
}
for i in 0..max_sub_layers_minus1 as usize {
if profile_present[i] {
r.skip_bits(88)?;
}
if level_present[i] {
r.skip_bits(8)?;
}
}
}
Some(())
}
#[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,
}
}
fn hevc_nal_header(nal_type: u8) -> [u8; 2] {
[(nal_type & 0x3F) << 1, 0x01] }
fn sei_message(payload_type: u32, payload: &[u8]) -> Vec<u8> {
fn ff_encode(mut v: u32) -> Vec<u8> {
let mut out = Vec::new();
while v >= 255 {
out.push(0xFF);
v -= 255;
}
out.push(v as u8);
out
}
let mut m = ff_encode(payload_type);
m.extend(ff_encode(payload.len() as u32));
m.extend_from_slice(payload);
m
}
fn mastering_payload(
prim_x: [u16; 3],
prim_y: [u16; 3],
wp_x: u16,
wp_y: u16,
max_lum: u32,
min_lum: u32,
) -> Vec<u8> {
let mut p = Vec::new();
for c in 0..3 {
p.extend_from_slice(&prim_x[c].to_be_bytes());
p.extend_from_slice(&prim_y[c].to_be_bytes());
}
p.extend_from_slice(&wp_x.to_be_bytes());
p.extend_from_slice(&wp_y.to_be_bytes());
p.extend_from_slice(&max_lum.to_be_bytes());
p.extend_from_slice(&min_lum.to_be_bytes());
p
}
fn cll_payload(maxcll: u16, maxfall: u16) -> Vec<u8> {
let mut p = Vec::new();
p.extend_from_slice(&maxcll.to_be_bytes());
p.extend_from_slice(&maxfall.to_be_bytes());
p
}
fn emulation_prevent(rbsp: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
let mut zeros = 0;
for &b in rbsp {
if zeros >= 2 && b <= 0x03 {
out.push(0x03);
zeros = 0;
}
out.push(b);
if b == 0 {
zeros += 1;
} else {
zeros = 0;
}
}
out
}
fn sei_nal(messages: &[Vec<u8>]) -> Vec<u8> {
let mut rbsp = Vec::new();
for m in messages {
rbsp.extend_from_slice(m);
}
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(NAL_SEI_PREFIX));
v.extend_from_slice(&emulation_prevent(&rbsp));
v.push(0x80); v
}
#[test]
fn hevc_parses_hdr10_sei_with_exact_raw_values() {
let prim_x = [8500u16, 6550, 35400]; let prim_y = [39850u16, 2300, 14600];
let (wp_x, wp_y) = (15635u16, 16450);
let (max_lum, min_lum) = (10_000_000u32, 1u32);
let (maxcll, maxfall) = (1000u16, 400u16);
let pps = {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(NAL_PPS));
v.push(0xC0); v
};
let idr = {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(19)); v.push(0xEC); v
};
let mut data = pps;
data.extend_from_slice(&sei_nal(&[
sei_message(
SEI_MASTERING_DISPLAY_COLOUR_VOLUME,
&mastering_payload(prim_x, prim_y, wp_x, wp_y, max_lum, min_lum),
),
sei_message(SEI_CONTENT_LIGHT_LEVEL_INFO, &cll_payload(maxcll, maxfall)),
]));
data.extend_from_slice(&idr);
let mut parser = HevcParser::new();
let frames = parser.parse(&make_pes(data, Some(0)));
let h = frames[0]
.coding
.expect("HEVC frame carries PictureInfo")
.hdr10()
.expect("both HDR10 SEI present → metadata surfaced");
assert_eq!(h.display_primaries_x, prim_x, "primary X raw (G,B,R)");
assert_eq!(h.display_primaries_y, prim_y, "primary Y raw (G,B,R)");
assert_eq!(h.white_point_x, wp_x);
assert_eq!(h.white_point_y, wp_y);
assert_eq!(h.max_display_mastering_luminance, max_lum);
assert_eq!(h.min_display_mastering_luminance, min_lum);
assert_eq!(h.max_content_light_level, maxcll);
assert_eq!(h.max_pic_average_light_level, maxfall);
}
#[test]
fn hevc_requires_both_hdr10_sei_messages() {
let pps = {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(NAL_PPS));
v.push(0xC0);
v
};
let idr = {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(19));
v.push(0xEC); v
};
let mut data = pps;
data.extend_from_slice(&sei_nal(&[sei_message(
SEI_MASTERING_DISPLAY_COLOUR_VOLUME,
&mastering_payload([1, 2, 3], [4, 5, 6], 7, 8, 9, 10),
)]));
data.extend_from_slice(&idr);
let mut parser = HevcParser::new();
let frames = parser.parse(&make_pes(data, Some(0)));
assert!(
frames[0].coding.unwrap().hdr10().is_none(),
"mastering-only stream must NOT surface HDR10 (content-light absent)"
);
}
#[test]
fn hevc_sdr_stream_has_no_hdr10() {
let pps = {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(NAL_PPS));
v.push(0xC0);
v
};
let idr = {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(19));
v.push(0xEC); v
};
let mut data = pps;
data.extend_from_slice(&idr);
let mut parser = HevcParser::new();
let frames = parser.parse(&make_pes(data, Some(0)));
assert!(
frames[0].coding.unwrap().hdr10().is_none(),
"SDR / no-SEI stream must surface no HDR10 metadata"
);
}
#[test]
fn hevc_hdr10_sei_de_emulates() {
let prim_x = [0u16, 6550, 35400];
let prim_y = [2u16, 2300, 14600];
let payload = mastering_payload(prim_x, prim_y, 15635, 16450, 10_000_000, 1);
let mut emulated = Vec::new();
let mut zeros = 0;
for &b in &payload {
if zeros >= 2 && b <= 0x03 {
emulated.push(0x03);
zeros = 0;
}
emulated.push(b);
if b == 0 {
zeros += 1;
} else {
zeros = 0;
}
}
assert!(
emulated.len() > payload.len(),
"test must actually insert an emulation byte"
);
let mut nal = vec![0x00, 0x00, 0x01];
nal.extend_from_slice(&hevc_nal_header(NAL_SEI_PREFIX));
nal.push(137); nal.push(24); nal.extend_from_slice(&emulated);
nal.push(0x80);
let mut clnal = vec![0x00, 0x00, 0x01];
clnal.extend_from_slice(&hevc_nal_header(NAL_SEI_PREFIX));
clnal.push(144);
clnal.push(4);
clnal.extend_from_slice(&cll_payload(1000, 400));
clnal.push(0x80);
let pps = {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(NAL_PPS));
v.push(0xC0);
v
};
let idr = {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(19));
v.push(0xEC); v
};
let mut data = pps;
data.extend_from_slice(&nal);
data.extend_from_slice(&clnal);
data.extend_from_slice(&idr);
let mut parser = HevcParser::new();
let frames = parser.parse(&make_pes(data, Some(0)));
let h = frames[0].coding.unwrap().hdr10().unwrap();
assert_eq!(
h.display_primaries_x, prim_x,
"de-emulated payload must decode to original primary X (00 00 03 stripped)"
);
assert_eq!(h.display_primaries_y, prim_y);
assert_eq!(h.max_display_mastering_luminance, 10_000_000);
}
#[test]
fn hevc_populates_measured_coding_type_and_source() {
use super::super::coding::CodingType;
let nal = |t: u8, body: u8| {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(t));
v.push(body);
v
};
let src = crate::pes::SourcePos::at_byte(16384);
let run = |slice_body: u8| {
let mut p = HevcParser::new();
let mut data = nal(NAL_PPS, 0xC0); data.extend_from_slice(&nal(1, slice_body)); let mut pe = make_pes(data, Some(0));
pe.source = Some(src);
p.parse(&pe)
};
let fi = run(0xD8);
assert_eq!(fi.len(), 1);
let ci = fi[0].coding.expect("HEVC frame carries PictureInfo");
assert_eq!(ci.coding_type(), CodingType::I, "slice_type 2 → I");
assert!(
ci.field_order().is_none(),
"HEVC field order undecoded → None, never faked"
);
assert_eq!(
fi[0].source.unwrap().byte,
16384,
"source provenance carried"
);
assert_eq!(
run(0xD0)[0].coding.unwrap().coding_type(),
CodingType::P,
"slice_type 1 → P"
);
assert_eq!(
run(0xE0)[0].coding.unwrap().coding_type(),
CodingType::B,
"slice_type 0 → B"
);
let mut p = HevcParser::new();
let bare = p.parse(&make_pes(nal(1, 0xD8), Some(0)));
assert!(
bare[0].coding.is_none(),
"no active PPS → coding omitted, never a guessed type"
);
}
#[test]
fn parse_vps_sps_pps() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let vps_hdr = hevc_nal_header(32);
data.extend_from_slice(&vps_hdr);
data.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let sps_hdr = hevc_nal_header(33);
data.extend_from_slice(&sps_hdr);
data.extend_from_slice(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let pps_hdr = hevc_nal_header(34);
data.extend_from_slice(&pps_hdr);
data.extend_from_slice(&[0xDD, 0xEE]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let idr_hdr = hevc_nal_header(19);
data.extend_from_slice(&idr_hdr);
data.extend_from_slice(&[0x10, 0x20, 0x30]);
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 VPS+SPS+PPS"
);
let cp = cp.unwrap();
assert_eq!(cp[0], 1);
assert_eq!(cp[22], 3);
assert!(
cp.len() > 23,
"codec_private should contain VPS+SPS+PPS data"
);
}
#[test]
fn reasserts_active_pps_at_bare_keyframe() {
fn nal(t: u8, body: &[u8]) -> Vec<u8> {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(t));
v.extend_from_slice(body);
v
}
fn 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
}
let pps_of = |nals: &[Vec<u8>]| -> Vec<Vec<u8>> {
nals.iter()
.filter(|n| n.len() >= 2 && (n[0] >> 1) & 0x3F == 34)
.map(|n| n[2..].to_vec())
.collect()
};
let sps_body = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
];
let pps_a = [0xA1u8, 0xA2];
let pps_b = [0xB1u8, 0xB2, 0xB3];
let mut parser = HevcParser::new();
let au1 = [
nal(32, &[0xAA]),
nal(33, &sps_body),
nal(34, &pps_a),
nal(19, &[0x10]),
]
.concat();
parser.parse(&make_pes(au1, Some(0)));
let au2 = [nal(34, &pps_b), nal(19, &[0x11])].concat();
let f2 = parser.parse(&make_pes(au2, Some(3600)));
assert!(
pps_of(&nals_in(&f2[0].data)).iter().any(|b| b == &pps_b),
"AU2 must carry the redefined PPS-B in-band"
);
let au3 = nal(19, &[0x12]);
let f3 = parser.parse(&make_pes(au3, Some(7200)));
let got = pps_of(&nals_in(&f3[0].data));
assert!(
got.iter().any(|b| b == &pps_b),
"bare keyframe must re-assert the active PPS-B in-band, got {got:?}"
);
assert!(
!got.iter().any(|b| b == &pps_a),
"must not re-assert the stale codecPrivate PPS-A"
);
let au4 = [nal(34, &pps_a), nal(19, &[0x13])].concat();
parser.parse(&make_pes(au4, Some(10800)));
let au5 = nal(19, &[0x14]);
let f5 = parser.parse(&make_pes(au5, Some(14400)));
assert!(
pps_of(&nals_in(&f5[0].data)).iter().any(|b| b == &pps_a),
"bare keyframe must re-assert the active PPS even when == codecPrivate"
);
}
#[test]
fn emits_switch_back_to_codecprivate_pps() {
fn nal(t: u8, body: &[u8]) -> Vec<u8> {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(t));
v.extend_from_slice(body);
v
}
fn 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
}
let pps_body = |nals: &[Vec<u8>]| -> Vec<Vec<u8>> {
nals.iter()
.filter(|n| n.len() >= 2 && (n[0] >> 1) & 0x3F == 34)
.map(|n| n[2..].to_vec())
.collect()
};
let sps = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
];
let a = [0xA1u8, 0xA2];
let b = [0xB1u8, 0xB2, 0xB3];
let mut parser = HevcParser::new();
parser.parse(&make_pes(
[nal(32, &[0xAA]), nal(33, &sps), nal(34, &a), nal(19, &[1])].concat(),
Some(0),
));
parser.parse(&make_pes([nal(34, &b), nal(19, &[2])].concat(), Some(3600)));
let f3 = parser.parse(&make_pes([nal(34, &a), nal(19, &[3])].concat(), Some(7200)));
assert!(
pps_body(&nals_in(&f3[0].data)).iter().any(|p| p == &a),
"switch back to codecPrivate PPS-A must be emitted in-band"
);
let f4 = parser.parse(&make_pes(
[nal(34, &a), nal(19, &[4])].concat(),
Some(10800),
));
assert!(
pps_body(&nals_in(&f4[0].data)).iter().any(|p| p == &a),
"active PPS must be re-asserted at every keyframe (self-contained), even when == codecPrivate"
);
}
#[test]
fn hvcc_profile_tier_level_offsets() {
let mut parser = HevcParser::new();
let sps_rbsp: [u8; 13] = [
0xAB, 0x21, 0x60, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B, ];
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&sps_rbsp);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD, 0xEE]);
let pes = make_pes(data, Some(0));
parser.parse(&pes);
let cp = parser
.codec_private()
.expect("codec_private should be Some");
assert_eq!(cp[0], 1, "configurationVersion");
assert_eq!(
cp[1], 0x21,
"profile byte must come from SPS RBSP, not NAL hdr"
);
assert_eq!(&cp[2..6], &[0x60, 0x00, 0x00, 0x00], "compatibility flags");
assert_eq!(
&cp[6..12],
&[0x90, 0x00, 0x00, 0x00, 0x00, 0x00],
"constraint flags"
);
assert_eq!(cp[12], 0x7B, "level_idc must come from sps[14]");
}
#[test]
fn hvcc_short_sps_does_not_panic() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x11, 0x22, 0x33]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD]);
let pes = make_pes(data, Some(0));
parser.parse(&pes);
let cp = parser
.codec_private()
.expect("codec_private should be Some");
assert_eq!(cp[0], 1);
assert_eq!(cp[1], 0x22, "profile byte = sps[3]");
assert_eq!(&cp[2..6], &[0x33, 0x00, 0x00, 0x00]);
assert_eq!(&cp[6..12], &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
assert_eq!(cp[12], 0x00);
}
#[test]
fn codec_private_none_before_params() {
let parser = HevcParser::new();
assert!(parser.codec_private().is_none());
}
#[test]
fn codec_private_none_missing_pps() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA, 0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(1)); data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
parser.parse(&pes);
assert!(
parser.codec_private().is_none(),
"should be None without PPS"
);
}
#[test]
fn parse_irap_keyframe_idr_w_radl() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(19));
data.extend_from_slice(&[0x10, 0x20, 0x30]);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(
frames[0].keyframe,
"IDR_W_RADL (type 19) should be keyframe"
);
}
#[test]
fn parse_irap_keyframe_bla() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(16));
data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "BLA_W_LP (type 16) should be keyframe");
}
#[test]
fn parse_irap_keyframe_cra() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(21));
data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "CRA (type 21) should be keyframe");
}
#[test]
fn parse_irap_type_23() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(23));
data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "type 23 should be keyframe");
}
fn nals_of(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 nal_type_of(nal: &[u8]) -> u8 {
(nal[0] >> 1) & 0x3F
}
fn cra_au(payload: &[u8]) -> Vec<u8> {
let mut d = vec![0x00, 0x00, 0x01];
d.extend_from_slice(&hevc_nal_header(21));
d.extend_from_slice(payload);
d
}
#[test]
fn cra_at_marked_boundary_rewritten_to_bla() {
let mut parser = HevcParser::new();
parser.mark_clip_boundary();
let frames = parser.parse(&make_pes(cra_au(&[0x10, 0x20, 0x30]), Some(0)));
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "rewritten BLA is still a keyframe");
let nals = nals_of(&frames[0].data);
assert_eq!(nals.len(), 1);
assert_eq!(
nal_type_of(&nals[0]),
NAL_BLA_W_LP,
"marked-boundary CRA must be rewritten to BLA_W_LP (16)"
);
assert_eq!(nals[0][0] & 0x81, hevc_nal_header(21)[0] & 0x81);
assert_eq!(&nals[0][2..], &[0x10, 0x20, 0x30]);
let f2 = parser.parse(&make_pes(cra_au(&[0x40]), Some(90000)));
assert_eq!(
nal_type_of(&nals_of(&f2[0].data)[0]),
NAL_CRA_NUT,
"only the first CRA after a boundary is rewritten"
);
}
#[test]
fn cra_without_boundary_unchanged() {
let mut parser = HevcParser::new();
let frames = parser.parse(&make_pes(cra_au(&[0x10, 0x20]), Some(0)));
let nals = nals_of(&frames[0].data);
assert_eq!(
nal_type_of(&nals[0]),
NAL_CRA_NUT,
"an unmarked CRA must remain a CRA"
);
}
#[test]
fn cra_at_auto_detected_pts_backstep_rewritten_to_bla() {
let mut parser = HevcParser::new();
let one_hour = 90_000i64 * 3600;
parser.parse(&make_pes(cra_au(&[0x01]), Some(0)));
parser.parse(&make_pes(cra_au(&[0x02]), Some(one_hour)));
let dip = parser.parse(&make_pes(cra_au(&[0x03]), Some(one_hour - 3 * 3750)));
assert_eq!(
nal_type_of(&nals_of(&dip[0].data)[0]),
NAL_CRA_NUT,
"a sub-threshold B-frame PTS dip must not trigger the rewrite"
);
let splice = parser.parse(&make_pes(cra_au(&[0x04]), Some(0)));
assert_eq!(
nal_type_of(&nals_of(&splice[0].data)[0]),
NAL_BLA_W_LP,
"the splice CRA after a backward PTS reset must become BLA_W_LP"
);
let next = parser.parse(&make_pes(cra_au(&[0x05]), Some(90_000)));
assert_eq!(
nal_type_of(&nals_of(&next[0].data)[0]),
NAL_CRA_NUT,
"only the first CRA after the boundary is rewritten"
);
}
#[test]
fn cra_after_33bit_pts_wrap_not_rewritten() {
let mut parser = HevcParser::new();
let period = 1i64 << 33;
let near_wrap = period - 90_000; parser.parse(&make_pes(cra_au(&[0x01]), Some(near_wrap)));
parser.parse(&make_pes(cra_au(&[0x02]), Some(near_wrap + 3750)));
let wrapped = parser.parse(&make_pes(cra_au(&[0x03]), Some(7500)));
assert_eq!(
nal_type_of(&nals_of(&wrapped[0].data)[0]),
NAL_CRA_NUT,
"a CRA whose PTS merely wrapped 2^33->0 must stay CRA, not become BLA"
);
let after = parser.parse(&make_pes(cra_au(&[0x04]), Some(11250)));
assert_eq!(
nal_type_of(&nals_of(&after[0].data)[0]),
NAL_CRA_NUT,
"post-wrap in-clip CRA must stay CRA"
);
}
#[test]
fn non_cra_nals_never_rewritten_at_boundary() {
let mut parser = HevcParser::new();
parser.mark_clip_boundary();
let mut idr = vec![0x00, 0x00, 0x01];
idr.extend_from_slice(&hevc_nal_header(19)); idr.extend_from_slice(&[0x10]);
let f = parser.parse(&make_pes(idr, Some(0)));
assert_eq!(
nal_type_of(&nals_of(&f[0].data)[0]),
19,
"IDR at a marked boundary must stay IDR"
);
let f2 = parser.parse(&make_pes(cra_au(&[0x20]), Some(90000)));
assert_eq!(
nal_type_of(&nals_of(&f2[0].data)[0]),
NAL_CRA_NUT,
"the IDR consumed the boundary marker; later CRA stays CRA"
);
let mut parser = HevcParser::new();
parser.mark_clip_boundary();
let mut au = vec![0x00, 0x00, 0x01];
au.extend_from_slice(&hevc_nal_header(8)); au.extend_from_slice(&[0xAA]);
au.extend_from_slice(&[0x00, 0x00, 0x01]);
au.extend_from_slice(&hevc_nal_header(9)); au.extend_from_slice(&[0xBB]);
au.extend_from_slice(&cra_au(&[0xCC])); let f = parser.parse(&make_pes(au, Some(0)));
let nals = nals_of(&f[0].data);
let types: Vec<u8> = nals.iter().map(|n| nal_type_of(n)).collect();
assert_eq!(
types,
vec![8, 9, NAL_BLA_W_LP],
"RASLs pass through untouched; the CRA (after them) becomes BLA"
);
}
#[test]
fn no_boundary_marker_is_byte_identical() {
let build = || {
let mut d = Vec::new();
d.extend_from_slice(&[0x00, 0x00, 0x01]);
d.extend_from_slice(&hevc_nal_header(32));
d.extend_from_slice(&[0xAA]);
d.extend_from_slice(&[0x00, 0x00, 0x01]);
d.extend_from_slice(&hevc_nal_header(33));
d.extend_from_slice(&[0xBB, 0xCC, 0xDD]);
d.extend_from_slice(&[0x00, 0x00, 0x01]);
d.extend_from_slice(&hevc_nal_header(34));
d.extend_from_slice(&[0xEE]);
d.extend_from_slice(&cra_au(&[0x11, 0x22]));
d
};
let mut a = HevcParser::new();
let mut b = HevcParser::new();
let fa = a.parse(&make_pes(build(), Some(0)));
let fb = b.parse(&make_pes(build(), Some(0)));
assert_eq!(fa.len(), 1);
assert_eq!(fa[0].data, fb[0].data, "never-marked output must be stable");
let types: Vec<u8> = nals_of(&fa[0].data)
.iter()
.map(|n| nal_type_of(n))
.collect();
assert!(
types.contains(&NAL_CRA_NUT) && !types.contains(&NAL_BLA_W_LP),
"unmarked stream must keep its CRA (no BLA), got {types:?}"
);
let f2 = a.parse(&make_pes(cra_au(&[0x33]), Some(90000)));
let t2: Vec<u8> = nals_of(&f2[0].data)
.iter()
.map(|n| nal_type_of(n))
.collect();
assert!(
t2.contains(&NAL_CRA_NUT) && !t2.contains(&NAL_BLA_W_LP),
"unmarked mid-stream CRA must never become BLA, got {t2:?}"
);
}
#[test]
fn seamless_boundary_no_rewrite() {
let mut parser = HevcParser::new();
let f1 = parser.parse(&make_pes(cra_au(&[0x01]), Some(0)));
assert_eq!(nal_type_of(&nals_of(&f1[0].data)[0]), NAL_CRA_NUT);
let f2 = parser.parse(&make_pes(cra_au(&[0x02]), Some(90000)));
assert_eq!(
nal_type_of(&nals_of(&f2[0].data)[0]),
NAL_CRA_NUT,
"a seamless join (no marker) must never rewrite the CRA"
);
}
#[test]
fn parse_trailing_not_keyframe() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(1));
data.extend_from_slice(&[0x10, 0x20, 0x30]);
let pes = make_pes(data, Some(180000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(
!frames[0].keyframe,
"TRAIL_R (type 1) should not be keyframe"
);
}
#[test]
fn parse_tsa_not_keyframe() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(2));
data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(!frames[0].keyframe, "TSA_N (type 2) should not be keyframe");
}
#[test]
fn param_sets_seed_codecprivate_and_reassert_at_keyframe() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xCC]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let idr_hdr = hevc_nal_header(19);
data.extend_from_slice(&idr_hdr);
data.extend_from_slice(&[0x10, 0x20]);
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(
parser.codec_private().is_some(),
"VPS/SPS/PPS must seed codecPrivate"
);
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] >> 1) & 0x3F);
o += len;
}
assert_eq!(
types,
vec![32, 33, 34, 19],
"keyframe must re-assert VPS/SPS/PPS in-band ahead of the IDR slice"
);
}
#[test]
fn redefined_pps_emitted_inline() {
let mut parser = HevcParser::new();
let pps = |body: u8| {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(34)); v.extend_from_slice(&[body, body]);
v
};
let slice = || {
let mut v = vec![0x00, 0x00, 0x01];
v.extend_from_slice(&hevc_nal_header(1)); v.extend_from_slice(&[0x10, 0x20]);
v
};
let count_pps = |fd: &[u8]| {
let (mut n, mut o) = (0usize, 0usize);
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;
if o < fd.len() && (fd[o] >> 1) & 0x3F == 34 {
n += 1;
}
o += len;
}
n
};
let mut d = pps(0xAA);
d.extend(slice());
let f = parser.parse(&make_pes(d, Some(0)));
assert_eq!(count_pps(&f[0].data), 0, "first PPS goes to codecPrivate");
let mut d = pps(0xBB);
d.extend(slice());
let f = parser.parse(&make_pes(d, Some(1)));
assert_eq!(count_pps(&f[0].data), 1, "redefined PPS must be inline");
let mut d = pps(0xBB);
d.extend(slice());
let f = parser.parse(&make_pes(d, Some(2)));
assert_eq!(
count_pps(&f[0].data),
0,
"PPS equal to the active set carries no change → stripped"
);
let mut d = pps(0xAA);
d.extend(slice());
let f = parser.parse(&make_pes(d, Some(3)));
assert_eq!(
count_pps(&f[0].data),
1,
"switch back to the codecPrivate body is a change → emitted in-band"
);
}
#[test]
fn empty_nal_between_start_codes_emits_no_bare_prefix() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]); data.extend_from_slice(&[0x00, 0x00, 0x01]); data.extend_from_slice(&hevc_nal_header(1)); data.extend_from_slice(&[0x10, 0x20]);
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!(len > 0, "no bare zero-length prefix emitted");
assert_eq!(len + 4, fd.len(), "exactly one NAL in frame data");
}
#[test]
fn parse_empty_pes() {
let mut parser = HevcParser::new();
let pes = make_pes(Vec::new(), Some(0));
let frames = parser.parse(&pes);
assert!(frames.is_empty());
}
#[test]
fn pts_conversion() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(1));
data.extend_from_slice(&[0x10, 0x20]);
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 pts_preferred_over_dts() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(1)); data.extend_from_slice(&[0x10, 0x20]);
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,
"block timecode must be PTS"
);
}
#[test]
fn dv_rpu_nal_preserved() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA, 0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD, 0xEE]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let idr_hdr = hevc_nal_header(19);
data.extend_from_slice(&idr_hdr);
data.extend_from_slice(&[0x10, 0x20, 0x30]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
let rpu_hdr = hevc_nal_header(62);
data.extend_from_slice(&rpu_hdr);
let rpu_payload = [0xF0, 0xF1, 0xF2, 0xF3, 0xF4];
data.extend_from_slice(&rpu_payload);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1, "should produce one frame");
assert!(frames[0].keyframe, "IDR should mark keyframe");
let fd = &frames[0].data;
let mut nal_types = Vec::new();
let mut offset = 0;
while offset + 4 <= fd.len() {
let length =
u32::from_be_bytes([fd[offset], fd[offset + 1], fd[offset + 2], fd[offset + 3]])
as usize;
offset += 4;
assert!(offset + length <= fd.len(), "NAL length exceeds frame data");
let nal_type = (fd[offset] >> 1) & 0x3F;
nal_types.push(nal_type);
offset += length;
}
assert!(
nal_types.contains(&19),
"frame data must contain IDR NAL (type 19), got: {:?}",
nal_types
);
assert!(
nal_types.contains(&62),
"frame data must contain Dolby Vision RPU NAL (type 62), got: {:?}",
nal_types
);
assert_eq!(
nal_types,
vec![32, 33, 34, 19, 62],
"keyframe carries re-asserted param sets + IDR + preserved RPU, got: {:?}",
nal_types
);
let mut offset = 0;
while offset + 4 <= fd.len() {
let length =
u32::from_be_bytes([fd[offset], fd[offset + 1], fd[offset + 2], fd[offset + 3]])
as usize;
offset += 4;
let nal_type = (fd[offset] >> 1) & 0x3F;
if nal_type == 62 {
let nal_payload = &fd[offset + 2..offset + length];
assert_eq!(
nal_payload, &rpu_payload,
"RPU payload must be preserved verbatim"
);
}
offset += length;
}
}
struct BitWriter {
bytes: Vec<u8>,
nbits: usize,
}
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
nbits: 0,
}
}
fn put_bit(&mut self, b: u32) {
if self.nbits % 8 == 0 {
self.bytes.push(0);
}
if b & 1 != 0 {
let i = self.nbits / 8;
let shift = 7 - (self.nbits % 8);
self.bytes[i] |= 1 << shift;
}
self.nbits += 1;
}
fn put_bits(&mut self, v: u32, n: u32) {
for i in (0..n).rev() {
self.put_bit((v >> i) & 1);
}
}
fn put_ue(&mut self, v: u32) {
let val = v + 1;
let bits = 32 - val.leading_zeros();
for _ in 0..bits - 1 {
self.put_bit(0);
}
for i in (0..bits).rev() {
self.put_bit((val >> i) & 1);
}
}
}
fn make_sps_with_chroma(chroma_idc: u32, bd_luma_m8: u32, bd_chroma_m8: u32) -> Vec<u8> {
let mut w = BitWriter::new();
w.put_bits(0, 4); w.put_bits(0, 3); w.put_bit(1); for _ in 0..96 {
w.put_bit(0);
}
w.put_ue(0); w.put_ue(chroma_idc); if chroma_idc == 3 {
w.put_bit(0); }
w.put_ue(3840); w.put_ue(2160); w.put_bit(0); w.put_ue(bd_luma_m8); w.put_ue(bd_chroma_m8);
let mut sps = hevc_nal_header(33).to_vec();
sps.extend_from_slice(&w.bytes);
sps
}
fn codec_private_from_sps(sps_nal: &[u8]) -> Vec<u8> {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA, 0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(sps_nal);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD, 0xEE]);
parser.parse(&make_pes(data, Some(0)));
parser.codec_private().expect("codec_private")
}
#[test]
fn hvcc_emits_10bit_420_from_sps() {
let sps = make_sps_with_chroma(1, 2, 2);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 1, "chroma_format_idc = 1 (4:2:0)");
assert_eq!(cp[17], 0xF8 | 2, "bit_depth_luma_minus8 = 2 (10-bit)");
assert_eq!(cp[18], 0xF8 | 2, "bit_depth_chroma_minus8 = 2 (10-bit)");
}
#[test]
fn hvcc_emits_8bit_420_from_sps() {
let sps = make_sps_with_chroma(1, 0, 0);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 1);
assert_eq!(cp[17], 0xF8);
assert_eq!(cp[18], 0xF8);
}
#[test]
fn hvcc_emits_444_12bit_from_sps() {
let sps = make_sps_with_chroma(3, 4, 4);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 3, "chroma_format_idc = 3 (4:4:4)");
assert_eq!(cp[17], 0xF8 | 4, "bit_depth_luma_minus8 = 4 (12-bit)");
assert_eq!(cp[18], 0xF8 | 4);
}
#[test]
fn hvcc_byte21_from_sps_temporal_layers() {
let sps = make_sps_with_chroma(1, 2, 2);
let cp = codec_private_from_sps(&sps);
assert_eq!(
cp[21], 0x0F,
"byte 21: numTemporalLayers=1, temporalIdNested=1, lengthSizeMinusOne=3"
);
}
#[test]
fn hvcc_handles_emulation_prevention_in_sps() {
let mut sps = make_sps_with_chroma(1, 2, 2);
sps.extend_from_slice(&[0x00, 0x00, 0x03, 0x00]);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 1);
assert_eq!(cp[17], 0xF8 | 2);
assert_eq!(cp[18], 0xF8 | 2);
}
#[test]
fn bitreader_read_bits_msb_first() {
let mut r = BitReader::new(&[0b1011_0010]);
assert_eq!(r.read_bits(4), Some(11));
assert_eq!(r.read_bits(4), Some(2));
assert_eq!(r.read_bit(), None);
}
#[test]
fn bitreader_ue_golomb_values() {
let mut r = BitReader::new(&[0b1010_0110]);
assert_eq!(r.read_ue(), Some(0));
assert_eq!(r.read_ue(), Some(1));
assert_eq!(r.read_ue(), Some(2));
}
#[test]
fn bitreader_ue_large_value() {
let mut r = BitReader::new(&[0b0010_1000]);
assert_eq!(r.read_ue(), Some(4));
}
#[test]
fn bitreader_ue_runaway_zeros_bounded() {
let zeros = [0u8; 8]; let mut r = BitReader::new(&zeros);
assert_eq!(r.read_ue(), None, "runaway zero-run is bounded → None");
}
#[test]
fn bitreader_skip_bits_past_end_is_none() {
let mut r = BitReader::new(&[0xFF]);
assert_eq!(r.skip_bits(8), Some(()));
assert_eq!(r.skip_bits(1), None, "skipping past the buffer end → None");
}
#[test]
fn strip_ep_removes_third_byte_after_two_zeros() {
assert_eq!(
strip_emulation_prevention(&[0x00, 0x00, 0x03, 0x42]),
vec![0x00, 0x00, 0x42]
);
}
#[test]
fn strip_ep_leaves_03_after_single_zero() {
assert_eq!(
strip_emulation_prevention(&[0x00, 0x03, 0x42]),
vec![0x00, 0x03, 0x42]
);
}
#[test]
fn strip_ep_handles_consecutive_sequences() {
assert_eq!(
strip_emulation_prevention(&[0x00, 0x00, 0x03, 0x00, 0x00, 0x03]),
vec![0x00, 0x00, 0x00, 0x00]
);
}
#[test]
fn strip_ep_03_not_dropped_when_not_preceded_by_zeros() {
assert_eq!(
strip_emulation_prevention(&[0xAA, 0xBB, 0x03, 0xCC]),
vec![0xAA, 0xBB, 0x03, 0xCC]
);
}
#[test]
fn hvcc_chroma_monochrome_idc0() {
let sps = make_sps_with_chroma(0, 0, 0);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC, "chroma_format_idc = 0 (monochrome)");
}
#[test]
fn hvcc_chroma_422_idc2() {
let sps = make_sps_with_chroma(2, 2, 2);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 2, "chroma_format_idc = 2 (4:2:2)");
assert_eq!(cp[17], 0xF8 | 2);
}
#[test]
fn hvcc_asymmetric_bit_depths() {
let sps = make_sps_with_chroma(1, 2, 4);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[17], 0xF8 | 2, "bit_depth_luma_minus8 = 2");
assert_eq!(cp[18], 0xF8 | 4, "bit_depth_chroma_minus8 = 4");
}
fn make_sps_full(
chroma_idc: u32,
bd_luma_m8: u32,
bd_chroma_m8: u32,
max_sub_layers_minus1: u32,
conformance_window: bool,
) -> Vec<u8> {
let mut w = BitWriter::new();
w.put_bits(0, 4); w.put_bits(max_sub_layers_minus1, 3);
w.put_bit(1); for _ in 0..96 {
w.put_bit(0);
}
if max_sub_layers_minus1 > 0 {
let mut profile_present = Vec::new();
let mut level_present = Vec::new();
for _ in 0..max_sub_layers_minus1 {
w.put_bit(1); w.put_bit(1); profile_present.push(true);
level_present.push(true);
}
if max_sub_layers_minus1 < 8 {
for _ in max_sub_layers_minus1..8 {
w.put_bits(0, 2); }
}
for i in 0..max_sub_layers_minus1 as usize {
if profile_present[i] {
for _ in 0..88 {
w.put_bit(0); }
}
if level_present[i] {
w.put_bits(0, 8); }
}
}
w.put_ue(0); w.put_ue(chroma_idc);
if chroma_idc == 3 {
w.put_bit(0); }
w.put_ue(3840);
w.put_ue(2160);
if conformance_window {
w.put_bit(1); w.put_ue(0); w.put_ue(0); w.put_ue(0); w.put_ue(0); } else {
w.put_bit(0);
}
w.put_ue(bd_luma_m8);
w.put_ue(bd_chroma_m8);
let mut sps = hevc_nal_header(33).to_vec();
sps.extend_from_slice(&w.bytes);
sps
}
#[test]
fn hvcc_parses_chroma_through_sublayer_ptl() {
let sps = make_sps_full(1, 2, 2, 2, false);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 1, "4:2:0 after sub-layer PTL skip");
assert_eq!(cp[17], 0xF8 | 2, "10-bit luma after sub-layer PTL skip");
assert_eq!(cp[18], 0xF8 | 2);
assert_eq!(
cp[21],
(3 << 3) | (1 << 2) | 0x03,
"numTemporalLayers = 3, temporalIdNested = 1, lengthSizeMinusOne = 3"
);
}
#[test]
fn hvcc_parses_chroma_through_conformance_window() {
let sps = make_sps_full(1, 2, 2, 0, true);
let cp = codec_private_from_sps(&sps);
assert_eq!(
cp[17],
0xF8 | 2,
"10-bit luma after conformance-window skip"
);
assert_eq!(cp[18], 0xF8 | 2);
}
#[test]
fn hvcc_parses_444_with_separate_colour_plane() {
let sps = make_sps_full(3, 4, 4, 0, false);
let cp = codec_private_from_sps(&sps);
assert_eq!(cp[16], 0xFC | 3, "4:4:4");
assert_eq!(cp[17], 0xF8 | 4, "12-bit luma");
}
#[test]
fn hvcc_array_headers_and_lengths() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xA0, 0xA1, 0xA2]); data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09]); data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xC0, 0xC1]); parser.parse(&make_pes(data, Some(0)));
let cp = parser.codec_private().expect("hvcC");
assert_eq!(cp[22], 3);
let mut o = 23;
assert_eq!(cp[o], 0x20 | 32, "VPS array nal_type byte");
assert_eq!(
u16::from_be_bytes([cp[o + 1], cp[o + 2]]),
1,
"numNalus VPS"
);
let vps_len = u16::from_be_bytes([cp[o + 3], cp[o + 4]]) as usize;
assert_eq!(vps_len, 5, "VPS NAL length = 2 hdr + 3 payload");
o += 5 + vps_len;
assert_eq!(cp[o], 0x20 | 33, "SPS array nal_type byte");
let sps_len = u16::from_be_bytes([cp[o + 3], cp[o + 4]]) as usize;
assert_eq!(sps_len, 11, "SPS NAL length = 2 hdr + 9 payload");
o += 5 + sps_len;
assert_eq!(cp[o], 0x20 | 34, "PPS array nal_type byte");
let pps_len = u16::from_be_bytes([cp[o + 3], cp[o + 4]]) as usize;
assert_eq!(pps_len, 4, "PPS NAL length = 2 hdr + 2 payload");
}
#[test]
fn hvcc_none_missing_vps() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD, 0xEE]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(1)); data.extend_from_slice(&[0x10, 0x20]);
parser.parse(&make_pes(data, Some(0)));
assert!(parser.codec_private().is_none(), "no VPS → None");
}
#[test]
fn type_15_just_below_irap_not_keyframe() {
let mut parser = HevcParser::new();
let mut data = vec![0x00, 0x00, 0x01];
data.extend_from_slice(&hevc_nal_header(15));
data.extend_from_slice(&[0x10, 0x20]);
let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
assert!(!f[0].keyframe, "type 15 is below the IRAP range");
}
#[test]
fn type_24_just_above_irap_not_keyframe() {
let mut parser = HevcParser::new();
let mut data = vec![0x00, 0x00, 0x01];
data.extend_from_slice(&hevc_nal_header(24));
data.extend_from_slice(&[0x10, 0x20]);
let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
assert!(!f[0].keyframe, "type 24 is above the IRAP range");
}
#[test]
fn hevc_nal_type_extraction_masks_correctly() {
let mut parser = HevcParser::new();
let data = vec![0x00, 0x00, 0x01, 0xA6, 0x01, 0x10, 0x20]; let f = parser.parse(&make_pes(data, Some(0)));
assert_eq!(f.len(), 1);
assert!(
f[0].keyframe,
"0xA6 decodes to NAL type 19 (IDR) → keyframe"
);
}
#[test]
fn hevc_dts_fallback_when_pts_absent() {
let mut parser = HevcParser::new();
let pes = PesPacket {
source: None,
pid: 0x1011,
pts: None,
dts: Some(90000),
data: {
let mut d = vec![0x00, 0x00, 0x01];
d.extend_from_slice(&hevc_nal_header(1));
d.extend_from_slice(&[0x10, 0x20]);
d
},
};
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 parse_sps_chroma_too_short_returns_none() {
assert!(parse_sps_chroma(&[0x42]).is_none());
assert!(parse_sps_chroma(&[0x42, 0x01]).is_none());
}
#[test]
fn hvcc_falls_back_to_8bit_420_on_unparseable_sps() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA, 0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&[0x00]); data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD]);
parser.parse(&make_pes(data, Some(0)));
let cp = parser.codec_private().expect("hvcC");
assert_eq!(cp[16], 0xFC | 1, "fallback chroma_format_idc = 1 (4:2:0)");
assert_eq!(cp[17], 0xF8, "fallback 8-bit luma");
assert_eq!(cp[18], 0xF8, "fallback 8-bit chroma");
}
#[test]
fn hvcc_oversized_param_set_returns_none() {
let mut parser = HevcParser::new();
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(32));
data.extend_from_slice(&[0xAA, 0xBB]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(33));
data.extend_from_slice(&vec![0x11u8; 70_000]);
data.extend_from_slice(&[0x00, 0x00, 0x01]);
data.extend_from_slice(&hevc_nal_header(34));
data.extend_from_slice(&[0xDD, 0xEE]);
parser.parse(&make_pes(data, Some(0)));
assert!(
parser.codec_private().is_none(),
"oversized param set must not produce a (truncated) hvcC"
);
}
}