use crate::bit_reader::BitReader;
use crate::BitstreamError;
pub const NAL_TYPE_TRAIL: u8 = 0;
pub const NAL_TYPE_STSA: u8 = 1;
pub const NAL_TYPE_RADL: u8 = 2;
pub const NAL_TYPE_RASL: u8 = 3;
pub const NAL_TYPE_RSV_VCL_4: u8 = 4;
pub const NAL_TYPE_RSV_VCL_5: u8 = 5;
pub const NAL_TYPE_RSV_VCL_6: u8 = 6;
pub const NAL_TYPE_IDR_W_RADL: u8 = 7;
pub const NAL_TYPE_IDR_N_LP: u8 = 8;
pub const NAL_TYPE_CRA: u8 = 9;
pub const NAL_TYPE_GDR: u8 = 10;
pub const NAL_TYPE_RSV_IRAP_11: u8 = 11;
pub const NAL_TYPE_OPI: u8 = 12;
pub const NAL_TYPE_DCI: u8 = 13;
pub const NAL_TYPE_VPS: u8 = 14;
pub const NAL_TYPE_SPS: u8 = 15;
pub const NAL_TYPE_PPS: u8 = 16;
pub const NAL_TYPE_PREFIX_APS: u8 = 17;
pub const NAL_TYPE_SUFFIX_APS: u8 = 18;
pub const NAL_TYPE_PH: u8 = 19;
pub const NAL_TYPE_AUD: u8 = 20;
pub const NAL_TYPE_EOS: u8 = 21;
pub const NAL_TYPE_EOB: u8 = 22;
pub const NAL_TYPE_PREFIX_SEI: u8 = 23;
pub const NAL_TYPE_SUFFIX_SEI: u8 = 24;
pub const NAL_TYPE_FD: u8 = 25;
pub const NAL_TYPE_RSV_NVCL_26: u8 = 26;
pub const NAL_TYPE_RSV_NVCL_27: u8 = 27;
pub fn split_annex_b(buf: &[u8]) -> Vec<&[u8]> {
let mut out = Vec::new();
let mut i = 0;
let n = buf.len();
let mut last_payload_start: Option<usize> = None;
while i < n {
let four =
i + 3 < n && buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 0 && buf[i + 3] == 1;
let three = !four && i + 2 < n && buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 1;
if four || three {
if let Some(start) = last_payload_start.take() {
out.push(&buf[start..i]);
}
i += if four { 4 } else { 3 };
last_payload_start = Some(i);
continue;
}
i += 1;
}
if let Some(start) = last_payload_start.take() {
out.push(&buf[start..n]);
}
out
}
pub fn ebsp_to_rbsp(ebsp: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(ebsp.len());
let mut i = 0;
let n = ebsp.len();
while i < n {
if i + 2 < n && ebsp[i] == 0 && ebsp[i + 1] == 0 && ebsp[i + 2] == 3 {
out.push(0);
out.push(0);
i += 3;
} else {
out.push(ebsp[i]);
i += 1;
}
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VvcNalHeader {
pub forbidden_zero_bit: u8,
pub nuh_reserved_zero_bit: u8,
pub nuh_layer_id: u8,
pub nal_unit_type: u8,
pub nuh_temporal_id_plus1: u8,
}
impl VvcNalHeader {
pub fn temporal_id(&self) -> u8 {
self.nuh_temporal_id_plus1.saturating_sub(1)
}
}
pub fn parse_nal_header(nal_body: &[u8]) -> Result<VvcNalHeader, BitstreamError> {
if nal_body.len() < 2 {
return Err(BitstreamError::unexpected_end(
"H.266 NAL header needs at least 2 bytes",
));
}
let b0 = nal_body[0];
let b1 = nal_body[1];
Ok(VvcNalHeader {
forbidden_zero_bit: (b0 >> 7) & 1,
nuh_reserved_zero_bit: (b0 >> 6) & 1,
nuh_layer_id: b0 & 0x3f,
nal_unit_type: (b1 >> 3) & 0x1f,
nuh_temporal_id_plus1: b1 & 0x7,
})
}
pub fn is_vcl(nal_unit_type: u8) -> bool {
nal_unit_type <= NAL_TYPE_RSV_IRAP_11
}
pub fn is_irap(nal_unit_type: u8) -> bool {
matches!(
nal_unit_type,
NAL_TYPE_IDR_W_RADL | NAL_TYPE_IDR_N_LP | NAL_TYPE_CRA
)
}
pub fn is_parameter_set(nal_unit_type: u8) -> bool {
matches!(
nal_unit_type,
NAL_TYPE_VPS
| NAL_TYPE_SPS
| NAL_TYPE_PPS
| NAL_TYPE_PREFIX_APS
| NAL_TYPE_SUFFIX_APS
| NAL_TYPE_DCI
| NAL_TYPE_OPI
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VvcProfileTierLevel {
pub general_profile_idc: Option<u8>,
pub general_tier_flag: Option<u8>,
pub general_level_idc: u8,
pub ptl_frame_only_constraint_flag: u8,
pub ptl_multilayer_enabled_flag: u8,
pub ptl_sublayer_level_present_flag: Vec<u8>,
pub sublayer_level_idc: Vec<u8>,
pub general_sub_profile_idc: Vec<u32>,
}
pub fn parse_profile_tier_level(
r: &mut BitReader<'_>,
profile_tier_present_flag: bool,
max_sublayers_minus1: u32,
) -> Result<VvcProfileTierLevel, BitstreamError> {
let (general_profile_idc, general_tier_flag) = if profile_tier_present_flag {
(Some(r.u(7) as u8), Some(r.u(1) as u8))
} else {
(None, None)
};
let general_level_idc = r.u(8) as u8;
let ptl_frame_only_constraint_flag = r.u(1) as u8;
let ptl_multilayer_enabled_flag = r.u(1) as u8;
if profile_tier_present_flag {
walk_general_constraints_info(r)?;
}
let n_sub = max_sublayers_minus1 as usize;
let mut ptl_sublayer_level_present_flag = vec![0u8; n_sub];
for i in (0..n_sub).rev() {
ptl_sublayer_level_present_flag[i] = r.u(1) as u8;
}
r.align_to_byte();
let mut sublayer_level_idc = Vec::with_capacity(n_sub);
for i in (0..n_sub).rev() {
if ptl_sublayer_level_present_flag[i] != 0 {
sublayer_level_idc.push(r.u(8) as u8);
}
}
sublayer_level_idc.reverse();
let mut general_sub_profile_idc = Vec::new();
if profile_tier_present_flag {
let ptl_num_sub_profiles = r.u(8) as usize;
for _ in 0..ptl_num_sub_profiles {
general_sub_profile_idc.push(r.u(32));
}
}
Ok(VvcProfileTierLevel {
general_profile_idc,
general_tier_flag,
general_level_idc,
ptl_frame_only_constraint_flag,
ptl_multilayer_enabled_flag,
ptl_sublayer_level_present_flag,
sublayer_level_idc,
general_sub_profile_idc,
})
}
fn walk_general_constraints_info(r: &mut BitReader<'_>) -> Result<(), BitstreamError> {
let gci_present_flag = r.u(1);
if gci_present_flag != 0 {
r.skip(69);
let gci_num_additional_bits = r.u(8);
let num_additional_bits_used = if gci_num_additional_bits > 5 {
r.skip(6);
6
} else {
0
};
if gci_num_additional_bits > num_additional_bits_used {
r.skip((gci_num_additional_bits - num_additional_bits_used) as usize);
}
}
r.align_to_byte();
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VvcVps {
pub vps_video_parameter_set_id: u8,
pub vps_max_layers_minus1: u8,
pub vps_max_sublayers_minus1: u8,
pub vps_layer_id: Vec<u8>,
}
pub fn parse_vps(nal_body: &[u8]) -> Result<VvcVps, BitstreamError> {
if nal_body.len() < 2 {
return Err(BitstreamError::unexpected_end(
"H.266 VPS NAL needs at least the 2-byte header",
));
}
let header = parse_nal_header(nal_body)?;
if header.nal_unit_type != NAL_TYPE_VPS {
return Err(BitstreamError::invalid(format!(
"expected VPS NAL (type {}), got {}",
NAL_TYPE_VPS, header.nal_unit_type
)));
}
let rbsp = ebsp_to_rbsp(&nal_body[2..]);
let mut r = BitReader::new(&rbsp);
let vps_video_parameter_set_id = r.u(4) as u8;
let vps_max_layers_minus1 = r.u(6) as u8;
let vps_max_sublayers_minus1 = r.u(3) as u8;
if vps_max_sublayers_minus1 > 6 {
return Err(BitstreamError::invalid(format!(
"vps_max_sublayers_minus1 = {vps_max_sublayers_minus1} > 6 (spec range 0..=6)"
)));
}
if vps_max_layers_minus1 > 0 {
return Err(BitstreamError::unsupported(
"VVC VPS with vps_max_layers_minus1 > 0 (multi-layer) not parsed in this round",
));
}
let vps_layer_id = vec![r.u(6) as u8];
Ok(VvcVps {
vps_video_parameter_set_id,
vps_max_layers_minus1,
vps_max_sublayers_minus1,
vps_layer_id,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VvcSps {
pub sps_seq_parameter_set_id: u8,
pub sps_video_parameter_set_id: u8,
pub sps_max_sublayers_minus1: u8,
pub sps_chroma_format_idc: u8,
pub sps_log2_ctu_size_minus5: u8,
pub sps_ptl_dpb_hrd_params_present_flag: u8,
pub profile_tier_level: Option<VvcProfileTierLevel>,
pub sps_pic_width_max_in_luma_samples: u32,
pub sps_pic_height_max_in_luma_samples: u32,
pub sps_subpic_info_present_flag: u8,
pub sps_bitdepth_minus8: u32,
}
impl VvcSps {
pub fn ctb_log2_size_y(&self) -> u32 {
self.sps_log2_ctu_size_minus5 as u32 + 5
}
pub fn ctb_size_y(&self) -> u32 {
1u32 << self.ctb_log2_size_y()
}
pub fn bit_depth(&self) -> u32 {
8 + self.sps_bitdepth_minus8
}
}
pub fn parse_sps(nal_body: &[u8]) -> Result<VvcSps, BitstreamError> {
if nal_body.len() < 2 {
return Err(BitstreamError::unexpected_end(
"H.266 SPS NAL needs at least the 2-byte header",
));
}
let header = parse_nal_header(nal_body)?;
if header.nal_unit_type != NAL_TYPE_SPS {
return Err(BitstreamError::invalid(format!(
"expected SPS NAL (type {}), got {}",
NAL_TYPE_SPS, header.nal_unit_type
)));
}
let rbsp = ebsp_to_rbsp(&nal_body[2..]);
let mut r = BitReader::new(&rbsp);
let sps_seq_parameter_set_id = r.u(4) as u8;
let sps_video_parameter_set_id = r.u(4) as u8;
let sps_max_sublayers_minus1 = r.u(3) as u8;
let sps_chroma_format_idc = r.u(2) as u8;
let sps_log2_ctu_size_minus5 = r.u(2) as u8;
if sps_log2_ctu_size_minus5 > 2 {
return Err(BitstreamError::invalid(format!(
"sps_log2_ctu_size_minus5 = {sps_log2_ctu_size_minus5} > 2 (spec range 0..=2)"
)));
}
let sps_ptl_dpb_hrd_params_present_flag = r.u(1) as u8;
let profile_tier_level = if sps_ptl_dpb_hrd_params_present_flag != 0 {
Some(parse_profile_tier_level(
&mut r,
true,
sps_max_sublayers_minus1 as u32,
)?)
} else {
None
};
let _sps_gdr_enabled_flag = r.u(1);
let sps_ref_pic_resampling_enabled_flag = r.u(1);
if sps_ref_pic_resampling_enabled_flag != 0 {
let _sps_res_change_in_clvs_allowed_flag = r.u(1);
}
let sps_pic_width_max_in_luma_samples = r.ue()?;
let sps_pic_height_max_in_luma_samples = r.ue()?;
let sps_conformance_window_flag = r.u(1);
if sps_conformance_window_flag != 0 {
let _l = r.ue()?;
let _rr = r.ue()?;
let _t = r.ue()?;
let _b = r.ue()?;
}
let sps_subpic_info_present_flag = r.u(1) as u8;
if sps_subpic_info_present_flag != 0 {
return Err(BitstreamError::unsupported(
"VVC SPS with sps_subpic_info_present_flag = 1 (subpicture signalling) \
not parsed in this round",
));
}
let sps_bitdepth_minus8 = r.ue()?;
if sps_bitdepth_minus8 > 8 {
return Err(BitstreamError::invalid(format!(
"sps_bitdepth_minus8 = {sps_bitdepth_minus8} > 8 (BitDepth ≤ 16)"
)));
}
Ok(VvcSps {
sps_seq_parameter_set_id,
sps_video_parameter_set_id,
sps_max_sublayers_minus1,
sps_chroma_format_idc,
sps_log2_ctu_size_minus5,
sps_ptl_dpb_hrd_params_present_flag,
profile_tier_level,
sps_pic_width_max_in_luma_samples,
sps_pic_height_max_in_luma_samples,
sps_subpic_info_present_flag,
sps_bitdepth_minus8,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VvcPps {
pub pps_pic_parameter_set_id: u8,
pub pps_seq_parameter_set_id: u8,
pub pps_mixed_nalu_types_in_pic_flag: u8,
pub pps_pic_width_in_luma_samples: u32,
pub pps_pic_height_in_luma_samples: u32,
pub pps_conformance_window_flag: u8,
pub pps_conf_win_offsets: Option<(u32, u32, u32, u32)>,
pub pps_scaling_window_explicit_signalling_flag: u8,
pub pps_scaling_win_offsets: Option<(i32, i32, i32, i32)>,
pub pps_output_flag_present_flag: u8,
pub pps_no_pic_partition_flag: u8,
pub pps_subpic_id_mapping_present_flag: u8,
}
pub fn parse_pps(nal_body: &[u8]) -> Result<VvcPps, BitstreamError> {
if nal_body.len() < 2 {
return Err(BitstreamError::unexpected_end(
"H.266 PPS NAL needs at least the 2-byte header",
));
}
let header = parse_nal_header(nal_body)?;
if header.nal_unit_type != NAL_TYPE_PPS {
return Err(BitstreamError::invalid(format!(
"expected PPS NAL (type {}), got {}",
NAL_TYPE_PPS, header.nal_unit_type
)));
}
let rbsp = ebsp_to_rbsp(&nal_body[2..]);
let mut r = BitReader::new(&rbsp);
let pps_pic_parameter_set_id = r.u(6) as u8;
let pps_seq_parameter_set_id = r.u(4) as u8;
let pps_mixed_nalu_types_in_pic_flag = r.u(1) as u8;
let pps_pic_width_in_luma_samples = r.ue()?;
let pps_pic_height_in_luma_samples = r.ue()?;
let pps_conformance_window_flag = r.u(1) as u8;
let pps_conf_win_offsets = if pps_conformance_window_flag != 0 {
let l = r.ue()?;
let rr = r.ue()?;
let t = r.ue()?;
let b = r.ue()?;
Some((l, rr, t, b))
} else {
None
};
let pps_scaling_window_explicit_signalling_flag = r.u(1) as u8;
let pps_scaling_win_offsets = if pps_scaling_window_explicit_signalling_flag != 0 {
let l = r.se()?;
let rr = r.se()?;
let t = r.se()?;
let b = r.se()?;
Some((l, rr, t, b))
} else {
None
};
let pps_output_flag_present_flag = r.u(1) as u8;
let pps_no_pic_partition_flag = r.u(1) as u8;
let pps_subpic_id_mapping_present_flag = r.u(1) as u8;
Ok(VvcPps {
pps_pic_parameter_set_id,
pps_seq_parameter_set_id,
pps_mixed_nalu_types_in_pic_flag,
pps_pic_width_in_luma_samples,
pps_pic_height_in_luma_samples,
pps_conformance_window_flag,
pps_conf_win_offsets,
pps_scaling_window_explicit_signalling_flag,
pps_scaling_win_offsets,
pps_output_flag_present_flag,
pps_no_pic_partition_flag,
pps_subpic_id_mapping_present_flag,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_annex_b_three_byte_start_codes() {
let stream = [
0x00, 0x00, 0x01, 0x40, 0x01, 0xaa, 0x00, 0x00, 0x01, 0x42, 0x01, 0xbb, 0xcc,
];
let nals = split_annex_b(&stream);
assert_eq!(nals.len(), 2);
assert_eq!(nals[0], [0x40, 0x01, 0xaa]);
assert_eq!(nals[1], [0x42, 0x01, 0xbb, 0xcc]);
}
#[test]
fn split_annex_b_four_byte_start_codes() {
let stream = [
0x00, 0x00, 0x00, 0x01, 0x78, 0x01, 0x11, 0x22, 0x00, 0x00, 0x00, 0x01, 0x7a, 0x01,
0x33,
];
let nals = split_annex_b(&stream);
assert_eq!(nals.len(), 2);
assert_eq!(nals[0], [0x78, 0x01, 0x11, 0x22]);
assert_eq!(nals[1], [0x7a, 0x01, 0x33]);
}
#[test]
fn split_annex_b_mixed_start_codes() {
let stream = [
0x00, 0x00, 0x00, 0x01, 0x70, 0x01, 0xaa, 0x00, 0x00, 0x01, 0x72, 0x01, 0xbb,
];
let nals = split_annex_b(&stream);
assert_eq!(nals.len(), 2);
assert_eq!(nals[0], [0x70, 0x01, 0xaa]);
assert_eq!(nals[1], [0x72, 0x01, 0xbb]);
}
#[test]
fn ebsp_to_rbsp_strips_emulation_byte() {
let ebsp = [0x12, 0x00, 0x00, 0x03, 0x00, 0x34];
let rbsp = ebsp_to_rbsp(&ebsp);
assert_eq!(rbsp, [0x12, 0x00, 0x00, 0x00, 0x34]);
}
#[test]
fn ebsp_to_rbsp_leaves_03_without_prefix_alone() {
let ebsp = [0x12, 0x03, 0x34, 0x00, 0x03, 0x56];
let rbsp = ebsp_to_rbsp(&ebsp);
assert_eq!(rbsp, ebsp);
}
#[test]
fn parse_nal_header_vps_layer0_tid0() {
let body = [0x00, 0x71, 0xff];
let h = parse_nal_header(&body).unwrap();
assert_eq!(h.forbidden_zero_bit, 0);
assert_eq!(h.nuh_reserved_zero_bit, 0);
assert_eq!(h.nuh_layer_id, 0);
assert_eq!(h.nal_unit_type, NAL_TYPE_VPS);
assert_eq!(h.nuh_temporal_id_plus1, 1);
assert_eq!(h.temporal_id(), 0);
}
#[test]
fn parse_nal_header_idr_layer0_tid0() {
let body = [0x00, 0x39, 0x00];
let h = parse_nal_header(&body).unwrap();
assert_eq!(h.nal_unit_type, NAL_TYPE_IDR_W_RADL);
assert_eq!(h.nuh_layer_id, 0);
assert_eq!(h.temporal_id(), 0);
assert!(is_irap(h.nal_unit_type));
assert!(is_vcl(h.nal_unit_type));
assert!(!is_parameter_set(h.nal_unit_type));
}
#[test]
fn parse_nal_header_layer_id_max() {
let body = [0x3f, 0x87];
let h = parse_nal_header(&body).unwrap();
assert_eq!(h.nuh_layer_id, 63);
assert_eq!(h.nal_unit_type, NAL_TYPE_PPS);
assert_eq!(h.nuh_temporal_id_plus1, 7);
assert_eq!(h.temporal_id(), 6);
assert!(is_parameter_set(h.nal_unit_type));
assert!(!is_vcl(h.nal_unit_type));
assert!(!is_irap(h.nal_unit_type));
}
#[test]
fn parse_nal_header_truncated() {
assert!(matches!(
parse_nal_header(&[]),
Err(BitstreamError::UnexpectedEnd(_))
));
assert!(matches!(
parse_nal_header(&[0x00]),
Err(BitstreamError::UnexpectedEnd(_))
));
}
#[test]
fn is_vcl_classifies_table5_correctly() {
for t in 0u8..=11 {
assert!(is_vcl(t), "{t} should be VCL");
}
for t in 12u8..=27 {
assert!(!is_vcl(t), "{t} should be non-VCL");
}
}
#[test]
fn is_irap_only_idr_and_cra() {
assert!(is_irap(NAL_TYPE_IDR_W_RADL));
assert!(is_irap(NAL_TYPE_IDR_N_LP));
assert!(is_irap(NAL_TYPE_CRA));
for t in [
NAL_TYPE_TRAIL,
NAL_TYPE_STSA,
NAL_TYPE_RADL,
NAL_TYPE_RASL,
NAL_TYPE_GDR,
NAL_TYPE_RSV_IRAP_11,
NAL_TYPE_VPS,
NAL_TYPE_SPS,
NAL_TYPE_PPS,
NAL_TYPE_PH,
NAL_TYPE_AUD,
] {
assert!(!is_irap(t), "{t} should not be IRAP");
}
}
fn build_sps_nal(rbsp: &[u8]) -> Vec<u8> {
let hdr_b0: u8 = 0; let hdr_b1: u8 = (NAL_TYPE_SPS << 3) | 1; let mut out = Vec::with_capacity(2 + rbsp.len());
out.push(hdr_b0);
out.push(hdr_b1);
out.extend_from_slice(rbsp);
out
}
#[test]
fn parse_sps_minimal_no_ptl_1080p_10bit() {
let rbsp = [0x00, 0x0c, 0x00, 0x0f, 0x02, 0x00, 0x43, 0x91, 0x80];
let nal = build_sps_nal(&rbsp);
let sps = parse_sps(&nal).expect("SPS should parse");
assert_eq!(sps.sps_seq_parameter_set_id, 0);
assert_eq!(sps.sps_video_parameter_set_id, 0);
assert_eq!(sps.sps_max_sublayers_minus1, 0);
assert_eq!(sps.sps_chroma_format_idc, 1);
assert_eq!(sps.sps_log2_ctu_size_minus5, 2);
assert_eq!(sps.ctb_log2_size_y(), 7);
assert_eq!(sps.ctb_size_y(), 128);
assert_eq!(sps.sps_ptl_dpb_hrd_params_present_flag, 0);
assert!(sps.profile_tier_level.is_none());
assert_eq!(sps.sps_pic_width_max_in_luma_samples, 1920);
assert_eq!(sps.sps_pic_height_max_in_luma_samples, 1080);
assert_eq!(sps.sps_subpic_info_present_flag, 0);
assert_eq!(sps.sps_bitdepth_minus8, 2);
assert_eq!(sps.bit_depth(), 10);
}
#[test]
fn parse_sps_with_profile_tier_level_4k_main10() {
let rbsp = [
0x23, 0x49, 0x42, 0x33, 0x80, 0x80, 0x23, 0x02, 0xca, 0xfe, 0xba, 0xbe, 0xde, 0xad,
0xbe, 0xef, 0xc0, 0x03, 0xc0, 0x40, 0x04, 0x38, 0xfc, 0xc0,
];
let nal = build_sps_nal(&rbsp);
let sps = parse_sps(&nal).expect("SPS+PTL should parse");
assert_eq!(sps.sps_seq_parameter_set_id, 2);
assert_eq!(sps.sps_video_parameter_set_id, 3);
assert_eq!(sps.sps_max_sublayers_minus1, 2);
assert_eq!(sps.sps_chroma_format_idc, 1);
assert_eq!(sps.sps_log2_ctu_size_minus5, 0);
assert_eq!(sps.ctb_size_y(), 32);
assert_eq!(sps.sps_ptl_dpb_hrd_params_present_flag, 1);
let ptl = sps.profile_tier_level.as_ref().expect("PTL present");
assert_eq!(ptl.general_profile_idc, Some(33));
assert_eq!(ptl.general_tier_flag, Some(0));
assert_eq!(ptl.general_level_idc, 51);
assert_eq!(ptl.ptl_frame_only_constraint_flag, 1);
assert_eq!(ptl.ptl_multilayer_enabled_flag, 0);
assert_eq!(ptl.ptl_sublayer_level_present_flag, vec![0, 1]);
assert_eq!(ptl.sublayer_level_idc, vec![35]);
assert_eq!(
ptl.general_sub_profile_idc,
vec![0xCAFE_BABEu32, 0xDEAD_BEEFu32]
);
assert_eq!(sps.sps_pic_width_max_in_luma_samples, 3840);
assert_eq!(sps.sps_pic_height_max_in_luma_samples, 2160);
assert_eq!(sps.sps_subpic_info_present_flag, 0);
assert_eq!(sps.sps_bitdepth_minus8, 2);
assert_eq!(sps.bit_depth(), 10);
}
#[test]
fn parse_sps_rejects_subpic_info_present_for_now() {
let rbsp = [0x00, 0x0c, 0x00, 0x0f, 0x02, 0x00, 0x43, 0x95, 0x80];
let nal = build_sps_nal(&rbsp);
let err = parse_sps(&nal).expect_err("subpic_info_present_flag=1 should be unsupported");
assert!(matches!(err, BitstreamError::Unsupported(_)));
}
#[test]
fn parse_sps_rejects_wrong_nal_type() {
let mut nal = vec![0u8; 4];
nal[0] = 0;
nal[1] = (NAL_TYPE_PPS << 3) | 1;
let err = parse_sps(&nal).expect_err("PPS NAL must be rejected");
assert!(matches!(err, BitstreamError::InvalidData(_)));
}
#[test]
fn parse_sps_rejects_truncated() {
let err = parse_sps(&[0x00]).expect_err("1-byte input must error");
assert!(matches!(err, BitstreamError::UnexpectedEnd(_)));
}
fn build_pps_nal(rbsp: &[u8]) -> Vec<u8> {
let hdr_b0: u8 = 0; let hdr_b1: u8 = (NAL_TYPE_PPS << 3) | 1; let mut out = Vec::with_capacity(2 + rbsp.len());
out.push(hdr_b0);
out.push(hdr_b1);
out.extend_from_slice(rbsp);
out
}
#[test]
fn parse_pps_minimal_64x32_no_windows() {
let rbsp = [0x00, 0x00, 0x41, 0x04, 0x26, 0x80];
let nal = build_pps_nal(&rbsp);
let pps = parse_pps(&nal).expect("minimal PPS should parse");
assert_eq!(pps.pps_pic_parameter_set_id, 0);
assert_eq!(pps.pps_seq_parameter_set_id, 0);
assert_eq!(pps.pps_mixed_nalu_types_in_pic_flag, 0);
assert_eq!(pps.pps_pic_width_in_luma_samples, 64);
assert_eq!(pps.pps_pic_height_in_luma_samples, 32);
assert_eq!(pps.pps_conformance_window_flag, 0);
assert!(pps.pps_conf_win_offsets.is_none());
assert_eq!(pps.pps_scaling_window_explicit_signalling_flag, 0);
assert!(pps.pps_scaling_win_offsets.is_none());
assert_eq!(pps.pps_output_flag_present_flag, 1);
assert_eq!(pps.pps_no_pic_partition_flag, 1);
assert_eq!(pps.pps_subpic_id_mapping_present_flag, 0);
}
#[test]
fn parse_pps_1920x1080_with_conf_and_scaling_windows_zero_offsets() {
let rbsp = [0x08, 0xc0, 0x07, 0x81, 0x00, 0x21, 0xcf, 0xff, 0xa0];
let nal = build_pps_nal(&rbsp);
let pps = parse_pps(&nal).expect("1920x1080 PPS should parse");
assert_eq!(pps.pps_pic_parameter_set_id, 2);
assert_eq!(pps.pps_seq_parameter_set_id, 3);
assert_eq!(pps.pps_mixed_nalu_types_in_pic_flag, 0);
assert_eq!(pps.pps_pic_width_in_luma_samples, 1920);
assert_eq!(pps.pps_pic_height_in_luma_samples, 1080);
assert_eq!(pps.pps_conformance_window_flag, 1);
assert_eq!(pps.pps_conf_win_offsets, Some((0, 0, 0, 0)));
assert_eq!(pps.pps_scaling_window_explicit_signalling_flag, 1);
assert_eq!(pps.pps_scaling_win_offsets, Some((0, 0, 0, 0)));
assert_eq!(pps.pps_output_flag_present_flag, 1);
assert_eq!(pps.pps_no_pic_partition_flag, 1);
assert_eq!(pps.pps_subpic_id_mapping_present_flag, 0);
}
#[test]
fn parse_pps_320x240_with_signed_scaling_offsets() {
let rbsp = [0x04, 0x80, 0x14, 0x10, 0x1e, 0x2a, 0x29, 0x84, 0x98];
let nal = build_pps_nal(&rbsp);
let pps = parse_pps(&nal).expect("scaling-window PPS should parse");
assert_eq!(pps.pps_pic_parameter_set_id, 1);
assert_eq!(pps.pps_seq_parameter_set_id, 2);
assert_eq!(pps.pps_pic_width_in_luma_samples, 320);
assert_eq!(pps.pps_pic_height_in_luma_samples, 240);
assert_eq!(pps.pps_conformance_window_flag, 0);
assert!(pps.pps_conf_win_offsets.is_none());
assert_eq!(pps.pps_scaling_window_explicit_signalling_flag, 1);
assert_eq!(pps.pps_scaling_win_offsets, Some((1, -2, 3, -4)));
assert_eq!(pps.pps_output_flag_present_flag, 0);
assert_eq!(pps.pps_no_pic_partition_flag, 0);
assert_eq!(pps.pps_subpic_id_mapping_present_flag, 1);
}
#[test]
fn parse_pps_rejects_wrong_nal_type() {
let mut nal = vec![0u8; 4];
nal[0] = 0;
nal[1] = (NAL_TYPE_SPS << 3) | 1;
let err = parse_pps(&nal).expect_err("SPS NAL must be rejected");
assert!(matches!(err, BitstreamError::InvalidData(_)));
}
#[test]
fn parse_pps_rejects_truncated() {
let err = parse_pps(&[0x00]).expect_err("1-byte input must error");
assert!(matches!(err, BitstreamError::UnexpectedEnd(_)));
}
#[test]
fn is_parameter_set_covers_vps_sps_pps_aps_dci_opi() {
for t in [
NAL_TYPE_VPS,
NAL_TYPE_SPS,
NAL_TYPE_PPS,
NAL_TYPE_PREFIX_APS,
NAL_TYPE_SUFFIX_APS,
NAL_TYPE_DCI,
NAL_TYPE_OPI,
] {
assert!(is_parameter_set(t), "{t} should be a parameter set");
}
for t in [
NAL_TYPE_TRAIL,
NAL_TYPE_IDR_W_RADL,
NAL_TYPE_CRA,
NAL_TYPE_GDR,
NAL_TYPE_PH,
NAL_TYPE_AUD,
NAL_TYPE_EOS,
NAL_TYPE_EOB,
NAL_TYPE_PREFIX_SEI,
NAL_TYPE_SUFFIX_SEI,
NAL_TYPE_FD,
] {
assert!(!is_parameter_set(t), "{t} should not be a parameter set");
}
}
fn build_vps_nal(rbsp: &[u8]) -> Vec<u8> {
let hdr_b0: u8 = 0; let hdr_b1: u8 = (NAL_TYPE_VPS << 3) | 1; let mut out = Vec::with_capacity(2 + rbsp.len());
out.push(hdr_b0);
out.push(hdr_b1);
out.extend_from_slice(rbsp);
out
}
#[test]
fn parse_vps_minimal_single_layer() {
let rbsp = [0x10, 0x00, 0x00];
let nal = build_vps_nal(&rbsp);
let vps = parse_vps(&nal).expect("minimal single-layer VPS should parse");
assert_eq!(vps.vps_video_parameter_set_id, 1);
assert_eq!(vps.vps_max_layers_minus1, 0);
assert_eq!(vps.vps_max_sublayers_minus1, 0);
assert_eq!(vps.vps_layer_id, vec![0]);
}
#[test]
fn parse_vps_single_layer_with_sublayers_and_nonzero_layer_id() {
let rbsp = [0x20, 0x28, 0xE0];
let nal = build_vps_nal(&rbsp);
let vps = parse_vps(&nal).expect("VPS should parse");
assert_eq!(vps.vps_video_parameter_set_id, 2);
assert_eq!(vps.vps_max_layers_minus1, 0);
assert_eq!(vps.vps_max_sublayers_minus1, 5);
assert_eq!(vps.vps_layer_id, vec![7]);
}
#[test]
fn parse_vps_rejects_multi_layer_for_now() {
let rbsp = [0x00, 0x40];
let nal = build_vps_nal(&rbsp);
let err = parse_vps(&nal).expect_err("vps_max_layers_minus1 > 0 should be unsupported");
assert!(matches!(err, BitstreamError::Unsupported(_)));
}
#[test]
fn parse_vps_rejects_out_of_range_sublayers() {
let rbsp = [0x00, 0x38];
let nal = build_vps_nal(&rbsp);
let err = parse_vps(&nal).expect_err("vps_max_sublayers_minus1 = 7 is illegal");
assert!(matches!(err, BitstreamError::InvalidData(_)));
}
#[test]
fn parse_vps_rejects_wrong_nal_type() {
let mut nal = vec![0u8; 4];
nal[0] = 0;
nal[1] = (NAL_TYPE_SPS << 3) | 1;
let err = parse_vps(&nal).expect_err("SPS NAL must be rejected by parse_vps");
assert!(matches!(err, BitstreamError::InvalidData(_)));
}
#[test]
fn parse_vps_rejects_truncated() {
let err = parse_vps(&[0x00]).expect_err("1-byte input must error");
assert!(matches!(err, BitstreamError::UnexpectedEnd(_)));
}
#[test]
fn parse_vps_via_bit_writer_round_trip() {
use crate::bit_writer::BitWriter;
let mut w = BitWriter::new();
w.write_bits(2, 4); w.write_bits(0, 6); w.write_bits(5, 3); w.write_bits(7, 6); let rbsp = w.finish();
assert_eq!(rbsp.len(), 3);
let nal = build_vps_nal(&rbsp);
let vps = parse_vps(&nal).expect("VPS round-trips through BitWriter");
assert_eq!(vps.vps_video_parameter_set_id, 2);
assert_eq!(vps.vps_max_sublayers_minus1, 5);
assert_eq!(vps.vps_layer_id, vec![7]);
}
}