use crate::bitreader::BitReader;
use crate::error::{Error, Result};
use crate::ir::{
AspectRatioIdc, ColourDescription, EXTENDED_SAR_IDC, VideoFormat, VideoSignalType,
};
use alloc::string::String;
use alloc::vec::Vec;
const H264_HIGH_PROFILES: &[u8] = &[100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135];
pub(crate) fn is_high_profile(profile_idc: u8) -> bool {
H264_HIGH_PROFILES.contains(&profile_idc)
}
fn ue_to_u8(v: u64, spec_max: u64, field: &'static str) -> Result<u8> {
if v > spec_max {
return Err(Error::InvalidValue {
field,
value: v,
reason: "out of spec range",
});
}
u8::try_from(v).map_err(|_| Error::InvalidValue {
field,
value: v,
reason: "does not fit in u8",
})
}
fn ue_bit_depth(minus8: u64, spec_max: u64, field: &'static str) -> Result<u8> {
let minus8 = ue_to_u8(minus8, spec_max, field)?;
minus8.checked_add(8).ok_or(Error::InvalidValue {
field,
value: minus8 as u64,
reason: "bit depth overflow",
})
}
fn sub_width_c(chroma_format_idc: u8) -> u32 {
match chroma_format_idc {
0 => 1,
1 => 2,
2 => 2,
_ => 1,
}
}
fn sub_height_c(chroma_format_idc: u8) -> u32 {
match chroma_format_idc {
0 => 1,
1 => 2,
2 => 1,
_ => 1,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct AvcVuiTiming {
pub num_units_in_tick: u32,
pub time_scale: u32,
pub fixed_frame_rate: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AvcSpsInfo {
pub profile_idc: u8,
pub constraint_flags: u8,
pub level_idc: u8,
pub chroma_format_idc: u8,
pub separate_colour_plane: bool,
pub bit_depth_luma: u8,
pub bit_depth_chroma: u8,
pub frame_mbs_only: bool,
pub width: u32,
pub height: u32,
pub num_units_in_tick: Option<u32>,
pub time_scale: Option<u32>,
pub fps: Option<f32>,
}
pub fn decode_avc_sps(sps_bytes: &[u8]) -> Result<AvcSpsInfo> {
decode_avc_sps_inner(sps_bytes).map(|(info, _, _, _, _)| info)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AvcSliceSps {
pub sps_id: u8,
pub separate_colour_plane: bool,
pub log2_max_frame_num: u8,
pub pic_order_cnt_type: u8,
pub log2_max_poc_lsb: u8,
pub delta_pic_order_always_zero: bool,
pub frame_mbs_only: bool,
pub mb_adaptive_frame_field: bool,
pub pic_timing: Option<AvcPicTimingLayout>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AvcPicTimingLayout {
pub delay_bits: Option<(u8, u8)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct AvcReorderDepth {
pub profile_idc: u8,
pub constraint_flags: u8,
pub max_num_reorder_frames: Option<u32>,
pub max_dec_frame_buffering: Option<u32>,
}
const PROFILE_BASELINE: u8 = 66;
const INTRA_PROFILES: [u8; 6] = [44, 86, 100, 110, 122, 244];
const CONSTRAINT_SET3_MASK: u8 = 0x10;
impl AvcReorderDepth {
pub fn depth(&self) -> Option<u32> {
if let Some(n) = self.max_num_reorder_frames {
return Some(n);
}
let intra = INTRA_PROFILES.contains(&self.profile_idc)
&& self.constraint_flags & CONSTRAINT_SET3_MASK != 0;
(intra || self.profile_idc == PROFILE_BASELINE).then_some(0)
}
}
pub fn decode_avc_reorder_depth(sps_bytes: &[u8]) -> Result<AvcReorderDepth> {
decode_avc_sps_inner(sps_bytes).map(|(_, _, _, _, r)| r)
}
pub fn decode_avc_vui_timing(sps_bytes: &[u8]) -> Result<Option<AvcVuiTiming>> {
decode_avc_sps_inner(sps_bytes).map(|(_, _, _, t, _)| t)
}
pub fn decode_avc_slice_sps(sps_bytes: &[u8]) -> Result<AvcSliceSps> {
decode_avc_sps_inner(sps_bytes).map(|(_, s, _, _, _)| s)
}
use crate::ir::PictureRect;
pub(crate) struct DisplayFacts {
pub(crate) coded_width: u32,
pub(crate) coded_height: u32,
pub(crate) output_rect: PictureRect,
pub(crate) default_display_rect: Option<PictureRect>,
pub(crate) aspect_ratio: Option<AspectRatioIdc>,
pub(crate) overscan_appropriate: Option<bool>,
pub(crate) video_signal: Option<VideoSignalType>,
}
pub(crate) fn decode_avc_display_facts(sps_bytes: &[u8]) -> Result<DisplayFacts> {
decode_avc_sps_inner(sps_bytes).map(|(_, _, d, _, _)| d)
}
pub(crate) fn decode_hevc_display_facts(sps_bytes: &[u8]) -> Result<DisplayFacts> {
decode_hevc_sps_inner(sps_bytes).map(|(_, _, d, _)| d)
}
fn decode_avc_sps_inner(
sps_bytes: &[u8],
) -> Result<(
AvcSpsInfo,
AvcSliceSps,
DisplayFacts,
Option<AvcVuiTiming>,
AvcReorderDepth,
)> {
if sps_bytes.len() < 4 {
return Err(Error::BufferTooShort {
need: 4,
have: sps_bytes.len(),
what: "H.264 SPS header",
});
}
let profile_idc = sps_bytes[1];
let constraint_flags = sps_bytes[2];
let level_idc = sps_bytes[3];
let mut r = BitReader::with_unescape(&sps_bytes[4..], "H.264 SPS")?;
let sps_id = r.read_ue("seq_parameter_set_id")?.min(u8::MAX as u64) as u8;
let (chroma_format_idc, separate_colour_plane, bit_depth_luma, bit_depth_chroma) =
if is_high_profile(profile_idc) {
let chroma_format_idc =
ue_to_u8(r.read_ue("chroma_format_idc")?, 3, "chroma_format_idc")?;
let separate_colour_plane = if chroma_format_idc == 3 {
r.read_flag("separate_colour_plane_flag")?
} else {
false
};
let bit_depth_luma = ue_bit_depth(
r.read_ue("bit_depth_luma_minus8")?,
6,
"bit_depth_luma_minus8",
)?;
let bit_depth_chroma = ue_bit_depth(
r.read_ue("bit_depth_chroma_minus8")?,
6,
"bit_depth_chroma_minus8",
)?;
let _ = r.read_flag("qpprime_y_zero_transform_bypass_flag")?;
let scaling_matrix_present = r.read_flag("seq_scaling_matrix_present_flag")?;
if scaling_matrix_present {
let list_count = if chroma_format_idc != 3 { 8 } else { 12 };
for _i in 0..list_count {
let list_present = r.read_flag("seq_scaling_list_present_flag[i]")?;
if list_present {
let size: usize = if _i < 6 { 16 } else { 64 };
let mut last_scale: i64 = 8;
let mut next_scale: i64 = 8;
for _j in 0..size {
if next_scale != 0 {
let delta_scale = r.read_se("delta_scale")?;
next_scale = (last_scale + delta_scale + 256) % 256;
}
if next_scale == 0 {
} else {
last_scale = next_scale;
}
}
}
}
}
(
chroma_format_idc,
separate_colour_plane,
bit_depth_luma,
bit_depth_chroma,
)
} else {
(1, false, 8, 8)
};
let log2_max_frame_num = r.read_ue("log2_max_frame_num_minus4")?.min(12) as u8 + 4;
let pic_order_cnt_type = r.read_ue("pic_order_cnt_type")?;
let mut log2_max_poc_lsb = 0u8;
let mut delta_pic_order_always_zero = false;
if pic_order_cnt_type == 0 {
log2_max_poc_lsb = r.read_ue("log2_max_pic_order_cnt_lsb_minus4")?.min(12) as u8 + 4;
} else if pic_order_cnt_type == 1 {
delta_pic_order_always_zero = r.read_flag("delta_pic_order_always_zero_flag")?;
let _ = r.read_se("offset_for_non_ref_pic")?;
let _ = r.read_se("offset_for_top_to_bottom_field")?;
let num_ref_frames = r.read_ue("num_ref_frames_in_pic_order_cnt_cycle")?;
if num_ref_frames > 255 {
return Err(Error::InvalidValue {
field: "num_ref_frames_in_pic_order_cnt_cycle",
value: num_ref_frames,
reason: "out of range 0..=255 (H.264 §7.4.2.1)",
});
}
for _ in 0..num_ref_frames {
let _ = r.read_se("offset_for_ref_frame[i]")?;
}
}
let _ = r.read_ue("max_num_ref_frames")?;
let _ = r.read_flag("gaps_in_frame_num_value_allowed_flag")?;
let pic_width_in_mbs_minus1 = r.read_ue("pic_width_in_mbs_minus1")?;
let pic_height_in_map_units_minus1 = r.read_ue("pic_height_in_map_units_minus1")?;
let frame_mbs_only = r.read_flag("frame_mbs_only_flag")?;
let mut mb_adaptive_frame_field = false;
if !frame_mbs_only {
mb_adaptive_frame_field = r.read_flag("mb_adaptive_frame_field_flag")?;
}
let _ = r.read_flag("direct_8x8_inference_flag")?;
let frame_cropping = r.read_flag("frame_cropping_flag")?;
let (crop_left, crop_right, crop_top, crop_bottom) = if frame_cropping {
let cl = r.read_ue("frame_crop_left_offset")?;
let cr = r.read_ue("frame_crop_right_offset")?;
let ct = r.read_ue("frame_crop_top_offset")?;
let cb = r.read_ue("frame_crop_bottom_offset")?;
(cl, cr, ct, cb)
} else {
(0, 0, 0, 0)
};
let pic_width_in_mbs = pic_width_in_mbs_minus1 + 1;
let pic_height_in_map_units = pic_height_in_map_units_minus1 + 1;
let frame_height_in_mbs = (2 - frame_mbs_only as u64) * pic_height_in_map_units;
let chroma_array_type = if separate_colour_plane {
0u8
} else {
chroma_format_idc
};
let (crop_unit_x, crop_unit_y) = if chroma_array_type == 0 {
(1u64, 2u64 - frame_mbs_only as u64)
} else {
let sw = sub_width_c(chroma_format_idc) as u64;
let sh = sub_height_c(chroma_format_idc) as u64;
(sw, sh * (2u64 - frame_mbs_only as u64))
};
let coded_width = pic_width_in_mbs * AVC_MB_SIZE;
let coded_height = frame_height_in_mbs * AVC_MB_SIZE;
let total_h = crop_unit_x.saturating_mul(crop_left.saturating_add(crop_right));
let total_v = crop_unit_y.saturating_mul(crop_top.saturating_add(crop_bottom));
let width = coded_width.saturating_sub(total_h);
let height = coded_height.saturating_sub(total_v);
let output_rect = if total_h > coded_width || total_v > coded_height {
PictureRect::new(
0,
0,
u32::try_from(coded_width).unwrap_or(u32::MAX),
u32::try_from(coded_height).unwrap_or(u32::MAX),
)
} else {
PictureRect::new(
u32::try_from(crop_unit_x * crop_left).unwrap_or(0),
u32::try_from(crop_unit_y * crop_top).unwrap_or(0),
u32::try_from(coded_width - total_h).unwrap_or(0),
u32::try_from(coded_height - total_v).unwrap_or(0),
)
};
let vui = parse_avc_vui(&mut r);
let (num_units_in_tick, time_scale, fps) = vui.timing;
let avc_vui_timing = match (vui.timing.0, vui.timing.1) {
(Some(n), Some(ts)) => Some(AvcVuiTiming {
num_units_in_tick: n,
time_scale: ts,
fixed_frame_rate: vui.fixed_frame_rate,
}),
_ => None,
};
let clamp_u32 = |v: u64| u32::try_from(v).unwrap_or(u32::MAX);
let display = DisplayFacts {
coded_width: clamp_u32(coded_width),
coded_height: clamp_u32(coded_height),
output_rect,
default_display_rect: None,
aspect_ratio: vui.aspect_ratio,
overscan_appropriate: vui.overscan_appropriate,
video_signal: vui.video_signal,
};
Ok((
AvcSpsInfo {
profile_idc,
constraint_flags,
level_idc,
chroma_format_idc,
separate_colour_plane,
bit_depth_luma,
bit_depth_chroma,
frame_mbs_only,
width: width as u32,
height: height as u32,
num_units_in_tick,
time_scale,
fps,
},
AvcSliceSps {
sps_id,
separate_colour_plane,
log2_max_frame_num,
pic_order_cnt_type: pic_order_cnt_type.min(u8::MAX as u64) as u8,
log2_max_poc_lsb,
delta_pic_order_always_zero,
frame_mbs_only,
mb_adaptive_frame_field,
pic_timing: vui.pic_timing,
},
display,
avc_vui_timing,
AvcReorderDepth {
profile_idc,
constraint_flags,
max_num_reorder_frames: vui.restriction.map(|(reorder, _)| reorder),
max_dec_frame_buffering: vui.restriction.map(|(_, buffering)| buffering),
},
))
}
const AVC_MB_SIZE: u64 = 16;
#[derive(Default)]
struct AvcVuiFacts {
aspect_ratio: Option<AspectRatioIdc>,
overscan_appropriate: Option<bool>,
video_signal: Option<VideoSignalType>,
timing: (Option<u32>, Option<u32>, Option<f32>),
fixed_frame_rate: bool,
pic_timing: Option<AvcPicTimingLayout>,
restriction: Option<(u32, u32)>,
}
fn parse_avc_vui(r: &mut BitReader) -> AvcVuiFacts {
let mut facts = AvcVuiFacts::default();
let _ = parse_avc_vui_inner(r, &mut facts);
facts
}
const HRD_LENGTH_FIELD_BITS: usize = 5;
const HRD_MAX_CPB_CNT_MINUS1: u64 = 31;
fn parse_avc_hrd(r: &mut BitReader) -> crate::error::Result<(u8, u8)> {
let cpb_cnt_minus1 = r.read_ue("cpb_cnt_minus1")?;
if cpb_cnt_minus1 > HRD_MAX_CPB_CNT_MINUS1 {
return Err(Error::InvalidValue {
field: "cpb_cnt_minus1",
value: cpb_cnt_minus1,
reason: "out of range 0..=31 (H.264 §E.2.2)",
});
}
let _ = r.read_bits(4, "bit_rate_scale")?;
let _ = r.read_bits(4, "cpb_size_scale")?;
for _ in 0..=cpb_cnt_minus1 {
let _ = r.read_ue("bit_rate_value_minus1")?;
let _ = r.read_ue("cpb_size_value_minus1")?;
let _ = r.read_flag("cbr_flag")?;
}
let _ = r.read_bits(
HRD_LENGTH_FIELD_BITS,
"initial_cpb_removal_delay_length_minus1",
)?;
let cpb = r.read_bits(HRD_LENGTH_FIELD_BITS, "cpb_removal_delay_length_minus1")? as u8 + 1;
let dpb = r.read_bits(HRD_LENGTH_FIELD_BITS, "dpb_output_delay_length_minus1")? as u8 + 1;
let _ = r.read_bits(HRD_LENGTH_FIELD_BITS, "time_offset_length")?;
Ok((cpb, dpb))
}
fn read_aspect_ratio(r: &mut BitReader) -> Result<AspectRatioIdc> {
let idc = r.read_bits(ASPECT_RATIO_IDC_BITS, "aspect_ratio_idc")? as u8;
let extended = if idc == EXTENDED_SAR_IDC {
let w = r.read_bits(SAR_DIMENSION_BITS, "sar_width")? as u16;
let h = r.read_bits(SAR_DIMENSION_BITS, "sar_height")? as u16;
Some((w, h))
} else {
None
};
Ok(AspectRatioIdc::from_wire(idc, extended))
}
const ASPECT_RATIO_IDC_BITS: usize = 8;
const SAR_DIMENSION_BITS: usize = 16;
const VIDEO_FORMAT_BITS: usize = 3;
const COLOUR_CODE_BITS: usize = 8;
fn read_video_signal_type(r: &mut BitReader) -> Result<VideoSignalType> {
let code = r.read_bits(VIDEO_FORMAT_BITS, "video_format")? as u8;
let video_format = VideoFormat::from_wire(code).ok_or(Error::InvalidValue {
field: "video_format",
value: u64::from(code),
reason: "not a u(3) value (H.264 Table E-2 / H.265 Table E.2)",
})?;
let full_range = r.read_flag("video_full_range_flag")?;
let colour = if r.read_flag("colour_description_present_flag")? {
let primaries = r.read_bits(COLOUR_CODE_BITS, "colour_primaries")? as u8;
let transfer = r.read_bits(COLOUR_CODE_BITS, "transfer_characteristics")? as u8;
let matrix = r.read_bits(COLOUR_CODE_BITS, "matrix_coefficients")? as u8;
Some(ColourDescription::from_wire(primaries, transfer, matrix))
} else {
None
};
Ok(VideoSignalType::new(video_format, full_range, colour))
}
fn parse_avc_vui_inner(r: &mut BitReader, facts: &mut AvcVuiFacts) -> crate::error::Result<()> {
let vui_present = r.read_flag("vui_parameters_present_flag")?;
if !vui_present {
return Ok(());
}
let ari_present = r.read_flag("aspect_ratio_info_present_flag")?;
if ari_present {
facts.aspect_ratio = Some(read_aspect_ratio(r)?);
}
if r.read_flag("overscan_info_present_flag")? {
facts.overscan_appropriate = Some(r.read_flag("overscan_appropriate_flag")?);
}
if r.read_flag("video_signal_type_present_flag")? {
facts.video_signal = Some(read_video_signal_type(r)?);
}
let cli_present = r.read_flag("chroma_loc_info_present_flag")?;
if cli_present {
let _ = r.read_ue("chroma_sample_loc_type_top_field")?;
let _ = r.read_ue("chroma_sample_loc_type_bottom_field")?;
}
let timing_present = r.read_flag("timing_info_present_flag")?;
if timing_present {
let num_units = r.read_bits(32, "num_units_in_tick")? as u32;
let ts = r.read_bits(32, "time_scale")? as u32;
let fixed = r.read_flag("fixed_frame_rate_flag")?;
let fps = if num_units > 0 {
Some(ts as f32 / (2.0 * num_units as f32))
} else {
None
};
facts.timing = (Some(num_units), Some(ts), fps);
facts.fixed_frame_rate = fixed;
}
let nal_hrd = if r.read_flag("nal_hrd_parameters_present_flag")? {
Some(parse_avc_hrd(r)?)
} else {
None
};
let vcl_hrd = if r.read_flag("vcl_hrd_parameters_present_flag")? {
Some(parse_avc_hrd(r)?)
} else {
None
};
if nal_hrd.is_some() || vcl_hrd.is_some() {
let _ = r.read_flag("low_delay_hrd_flag")?;
}
let pic_struct_present = r.read_flag("pic_struct_present_flag")?;
if pic_struct_present {
facts.pic_timing = match (nal_hrd, vcl_hrd) {
(Some(a), Some(b)) if a != b => None,
(Some(a), _) | (None, Some(a)) => Some(AvcPicTimingLayout {
delay_bits: Some(a),
}),
(None, None) => Some(AvcPicTimingLayout { delay_bits: None }),
};
}
if r.read_flag("bitstream_restriction_flag")? {
let _ = r.read_flag("motion_vectors_over_pic_boundaries_flag")?;
let _ = r.read_ue("max_bytes_per_pic_denom")?;
let _ = r.read_ue("max_bits_per_mb_denom")?;
let _ = r.read_ue("log2_max_mv_length_horizontal")?;
let _ = r.read_ue("log2_max_mv_length_vertical")?;
let reorder = r.read_ue("max_num_reorder_frames")?;
let buffering = r.read_ue("max_dec_frame_buffering")?;
let clamp = |v: u64| u32::try_from(v).unwrap_or(u32::MAX);
facts.restriction = Some((clamp(reorder), clamp(buffering)));
}
Ok(())
}
#[derive(Debug, Clone, PartialEq)]
pub struct HevcSpsInfo {
pub general_profile_space: u8,
pub general_tier_flag: bool,
pub general_profile_idc: u8,
pub general_profile_compatibility_flags: u32,
pub general_constraint_indicator_flags: u64,
pub general_level_idc: u8,
pub chroma_format_idc: u8,
pub bit_depth_luma: u8,
pub bit_depth_chroma: u8,
pub width: u32,
pub height: u32,
pub num_units_in_tick: Option<u32>,
pub time_scale: Option<u32>,
pub fps: Option<f32>,
}
pub fn decode_hevc_sps(sps_bytes: &[u8]) -> Result<HevcSpsInfo> {
decode_hevc_sps_inner(sps_bytes).map(|(info, _, _, _)| info)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct HevcVuiTiming {
pub num_units_in_tick: u32,
pub time_scale: u32,
pub field_seq: bool,
pub frame_field_info_present: bool,
pub hrd_fixed_pic_rate: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct HevcInterlaceSps {
pub field_seq: bool,
pub frame_field_info_present: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct HevcVpsTiming {
pub num_units_in_tick: u32,
pub time_scale: u32,
}
pub fn decode_hevc_vui_timing(sps_bytes: &[u8]) -> Result<Option<HevcVuiTiming>> {
let (info, interlace, _, hrd_fixed) = decode_hevc_sps_inner(sps_bytes)?;
match (info.num_units_in_tick, info.time_scale) {
(Some(n), Some(ts)) => Ok(Some(HevcVuiTiming {
num_units_in_tick: n,
time_scale: ts,
field_seq: interlace.field_seq,
frame_field_info_present: interlace.frame_field_info_present,
hrd_fixed_pic_rate: hrd_fixed,
})),
_ => Ok(None),
}
}
pub fn decode_hevc_vps_timing(vps_nal: &[u8]) -> Result<Option<HevcVpsTiming>> {
if vps_nal.len() < 2 {
return Err(Error::BufferTooShort {
need: 2,
have: vps_nal.len(),
what: "HEVC VPS header",
});
}
let mut r = BitReader::with_unescape(&vps_nal[2..], "HEVC VPS")?;
let _ = r.read_bits(4, "vps_video_parameter_set_id")?;
let _ = r.read_flag("vps_base_layer_internal_flag")?;
let _ = r.read_flag("vps_base_layer_available_flag")?;
let _ = r.read_bits(6, "vps_max_layers_minus1")?;
let vps_max_sub_layers_minus1 = r.read_bits(3, "vps_max_sub_layers_minus1")? as u8;
let _ = r.read_flag("vps_temporal_id_nesting_flag")?;
let _ = r.read_bits(16, "vps_reserved_0xffff_16bits")?;
let _ = decode_ptl(&mut r, true, vps_max_sub_layers_minus1)?;
let ordering_present = r.read_flag("vps_sub_layer_ordering_info_present_flag")?;
let start = if ordering_present {
0u8
} else {
vps_max_sub_layers_minus1
};
for _ in start..=vps_max_sub_layers_minus1 {
let _ = r.read_ue("vps_max_dec_pic_buffering_minus1")?;
let _ = r.read_ue("vps_max_num_reorder_pics")?;
let _ = r.read_ue("vps_max_latency_increase_plus1")?;
}
let vps_max_layer_id = r.read_bits(6, "vps_max_layer_id")? as u8;
let vps_num_layer_sets_minus1 = r.read_ue("vps_num_layer_sets_minus1")?;
if vps_num_layer_sets_minus1 > 1023 {
return Err(Error::InvalidValue {
field: "vps_num_layer_sets_minus1",
value: vps_num_layer_sets_minus1,
reason: "out of range (HEVC VPS)",
});
}
for _i in 1..=vps_num_layer_sets_minus1 {
for _j in 0..=vps_max_layer_id {
let _ = r.read_flag("layer_id_included_flag")?;
}
}
let timing_present = r.read_flag("vps_timing_info_present_flag")?;
if !timing_present {
return Ok(None);
}
let num_units = r.read_bits(32, "vps_num_units_in_tick")? as u32;
let ts = r.read_bits(32, "vps_time_scale")? as u32;
if num_units == 0 || ts == 0 {
return Ok(None);
}
Ok(Some(HevcVpsTiming {
num_units_in_tick: num_units,
time_scale: ts,
}))
}
pub fn decode_hevc_interlace_sps(sps_bytes: &[u8]) -> Result<HevcInterlaceSps> {
decode_hevc_sps_inner(sps_bytes).map(|(_, i, _, _)| i)
}
const PTL_PROGRESSIVE_SOURCE_BIT: u64 = 1 << 47;
const PTL_INTERLACED_SOURCE_BIT: u64 = 1 << 46;
fn decode_hevc_sps_inner(
sps_bytes: &[u8],
) -> Result<(HevcSpsInfo, HevcInterlaceSps, DisplayFacts, Option<u32>)> {
if sps_bytes.len() < 2 {
return Err(Error::BufferTooShort {
need: 2,
have: sps_bytes.len(),
what: "HEVC SPS header",
});
}
let mut r = BitReader::with_unescape(&sps_bytes[2..], "HEVC SPS")?;
let _ = r.read_bits(4, "sps_video_parameter_set_id")?;
let sps_max_sub_layers_minus1 = r.read_bits(3, "sps_max_sub_layers_minus1")? as u8;
let _ = r.read_flag("sps_temporal_id_nesting_flag")?;
let (
general_profile_space,
general_tier_flag,
general_profile_idc,
general_profile_compatibility_flags,
general_constraint_indicator_flags,
general_level_idc,
) = decode_ptl(&mut r, true, sps_max_sub_layers_minus1)?;
let _ = r.read_ue("sps_seq_parameter_set_id")?;
let chroma_format_idc = ue_to_u8(r.read_ue("chroma_format_idc")?, 3, "chroma_format_idc")?;
if chroma_format_idc == 3 {
let _ = r.read_flag("separate_colour_plane_flag")?;
}
let pic_width_in_luma_samples = r.read_ue("pic_width_in_luma_samples")?;
let pic_height_in_luma_samples = r.read_ue("pic_height_in_luma_samples")?;
let conformance_window = r.read_flag("conformance_window_flag")?;
let (conf_win_left, conf_win_right, conf_win_top, conf_win_bottom) = if conformance_window {
let left = r.read_ue("conf_win_left_offset")?;
let right = r.read_ue("conf_win_right_offset")?;
let top = r.read_ue("conf_win_top_offset")?;
let bottom = r.read_ue("conf_win_bottom_offset")?;
(left, right, top, bottom)
} else {
(0, 0, 0, 0)
};
let bit_depth_luma = ue_bit_depth(
r.read_ue("bit_depth_luma_minus8")?,
8,
"bit_depth_luma_minus8",
)?;
let bit_depth_chroma = ue_bit_depth(
r.read_ue("bit_depth_chroma_minus8")?,
8,
"bit_depth_chroma_minus8",
)?;
let sub_width_c = match chroma_format_idc {
1 | 2 => 2u64,
3 => 1u64,
_ => 1u64,
};
let sub_height_c = match chroma_format_idc {
1 => 2u64,
2 | 3 => 1u64,
_ => 1u64,
};
let coded_width = pic_width_in_luma_samples;
let coded_height = pic_height_in_luma_samples;
let total_h = sub_width_c.saturating_mul(conf_win_left.saturating_add(conf_win_right));
let total_v = sub_height_c.saturating_mul(conf_win_top.saturating_add(conf_win_bottom));
let width = coded_width.saturating_sub(total_h);
let height = coded_height.saturating_sub(total_v);
let output_rect = if total_h > coded_width || total_v > coded_height {
PictureRect::new(
0,
0,
u32::try_from(coded_width).unwrap_or(u32::MAX),
u32::try_from(coded_height).unwrap_or(u32::MAX),
)
} else {
PictureRect::new(
u32::try_from(sub_width_c * conf_win_left).unwrap_or(0),
u32::try_from(sub_height_c * conf_win_top).unwrap_or(0),
u32::try_from(coded_width - total_h).unwrap_or(0),
u32::try_from(coded_height - total_v).unwrap_or(0),
)
};
let mut interlace = HevcInterlaceSps {
field_seq: false,
frame_field_info_present: general_constraint_indicator_flags & PTL_PROGRESSIVE_SOURCE_BIT
!= 0
&& general_constraint_indicator_flags & PTL_INTERLACED_SOURCE_BIT != 0,
};
let mut vui_colour = HevcVuiColour::default();
let (num_units_in_tick, time_scale, fps, hrd_fixed_pic_rate) = parse_hevc_sps_to_vui_timing(
&mut r,
sps_max_sub_layers_minus1,
&mut interlace,
&mut vui_colour,
);
let clamp_u32 = |v: u64| u32::try_from(v).unwrap_or(u32::MAX);
let default_display_rect = match vui_colour.default_display_offsets {
Some((dl, dr, dt, db)) => {
let left_off = conf_win_left.saturating_add(dl);
let right_off = conf_win_right.saturating_add(dr);
let top_off = conf_win_top.saturating_add(dt);
let bottom_off = conf_win_bottom.saturating_add(db);
let total_h = sub_width_c.saturating_mul(left_off.saturating_add(right_off));
let total_v = sub_height_c.saturating_mul(top_off.saturating_add(bottom_off));
if total_h > coded_width || total_v > coded_height {
None
} else {
Some(PictureRect::new(
u32::try_from(sub_width_c * left_off).unwrap_or(0),
u32::try_from(sub_height_c * top_off).unwrap_or(0),
u32::try_from(coded_width - total_h).unwrap_or(0),
u32::try_from(coded_height - total_v).unwrap_or(0),
))
}
}
None => None,
};
let display = DisplayFacts {
coded_width: clamp_u32(coded_width),
coded_height: clamp_u32(coded_height),
output_rect,
default_display_rect,
aspect_ratio: vui_colour.aspect_ratio,
overscan_appropriate: vui_colour.overscan_appropriate,
video_signal: vui_colour.video_signal,
};
Ok((
HevcSpsInfo {
general_profile_space,
general_tier_flag,
general_profile_idc,
general_profile_compatibility_flags,
general_constraint_indicator_flags,
general_level_idc,
chroma_format_idc,
bit_depth_luma,
bit_depth_chroma,
width: width as u32,
height: height as u32,
num_units_in_tick,
time_scale,
fps,
},
interlace,
display,
hrd_fixed_pic_rate,
))
}
fn parse_hevc_hrd(r: &mut BitReader, max_sub_layers_minus1: u8) -> Result<Option<u32>> {
let nal_hrd = r.read_flag("nal_hrd_parameters_present_flag")?;
let vcl_hrd = r.read_flag("vcl_hrd_parameters_present_flag")?;
let mut sub_pic = false;
if nal_hrd || vcl_hrd {
sub_pic = r.read_flag("sub_pic_hrd_params_present_flag")?;
if sub_pic {
let _ = r.read_bits(8, "tick_divisor_minus2")?;
let _ = r.read_bits(5, "du_cpb_removal_delay_increment_length_minus1")?;
let _ = r.read_flag("sub_pic_cpb_params_in_pic_timing_sei_flag")?;
let _ = r.read_bits(5, "dpb_output_delay_du_length_minus1")?;
}
let _ = r.read_bits(4, "bit_rate_scale")?;
let _ = r.read_bits(4, "cpb_size_scale")?;
if sub_pic {
let _ = r.read_bits(4, "cpb_size_du_scale")?;
}
let _ = r.read_bits(5, "initial_cpb_removal_delay_length_minus1")?;
let _ = r.read_bits(5, "au_cpb_removal_delay_length_minus1")?;
let _ = r.read_bits(5, "dpb_output_delay_length_minus1")?;
}
let max_idx = max_sub_layers_minus1 as usize;
let mut result: Option<u32> = None;
for i in 0..=max_idx {
let general = r.read_flag("fixed_pic_rate_general_flag")?;
let within = if general {
true
} else {
r.read_flag("fixed_pic_rate_within_cvs_flag")?
};
let low_delay = if within {
false
} else {
r.read_flag("low_delay_hrd_flag")?
};
let mut cpb_cnt: u64 = 1;
if within {
let ed = r.read_ue("elemental_duration_in_tc_minus1")?;
if i == max_idx {
result = Some((ed as u32).saturating_add(1));
}
} else {
}
if !low_delay {
let cpb_cnt_minus1 = r.read_ue("cpb_cnt_minus1")?;
if cpb_cnt_minus1 > 31 {
return Err(Error::InvalidValue {
field: "cpb_cnt_minus1",
value: cpb_cnt_minus1,
reason: "out of range 0..=31 (H.265 §7.4.8)",
});
}
cpb_cnt = cpb_cnt_minus1 + 1;
}
if nal_hrd {
parse_hevc_sub_layer_hrd(r, cpb_cnt, sub_pic)?;
}
if vcl_hrd {
parse_hevc_sub_layer_hrd(r, cpb_cnt, sub_pic)?;
}
}
Ok(result)
}
fn parse_hevc_sub_layer_hrd(r: &mut BitReader, cpb_cnt: u64, sub_pic: bool) -> Result<()> {
for _ in 0..cpb_cnt {
let _ = r.read_ue("bit_rate_value_minus1")?;
let _ = r.read_ue("cpb_size_value_minus1")?;
if sub_pic {
let _ = r.read_ue("cpb_size_du_value_minus1")?;
let _ = r.read_ue("bit_rate_du_value_minus1")?;
}
let _ = r.read_flag("cbr_flag")?;
}
Ok(())
}
fn parse_hevc_sps_to_vui_timing(
r: &mut BitReader,
sps_max_sub_layers_minus1: u8,
interlace: &mut HevcInterlaceSps,
colour: &mut HevcVuiColour,
) -> (Option<u32>, Option<u32>, Option<f32>, Option<u32>) {
parse_hevc_sps_to_vui_timing_inner(r, sps_max_sub_layers_minus1, interlace, colour)
.unwrap_or((None, None, None, None))
}
#[derive(Default)]
struct HevcVuiColour {
aspect_ratio: Option<AspectRatioIdc>,
overscan_appropriate: Option<bool>,
video_signal: Option<VideoSignalType>,
default_display_offsets: Option<(u64, u64, u64, u64)>,
}
const HEVC_MAX_RPS_PICS: u64 = 16;
#[allow(clippy::type_complexity)]
fn parse_hevc_sps_to_vui_timing_inner(
r: &mut BitReader,
sps_max_sub_layers_minus1: u8,
interlace: &mut HevcInterlaceSps,
colour: &mut HevcVuiColour,
) -> crate::error::Result<(Option<u32>, Option<u32>, Option<f32>, Option<u32>)> {
let log2_max_poc_lsb = r.read_ue("log2_max_pic_order_cnt_lsb_minus4")?;
let sub_layer_ordering = r.read_flag("sps_sub_layer_ordering_info_present_flag")?;
let start_layer = if sub_layer_ordering {
0
} else {
sps_max_sub_layers_minus1
};
for _i in start_layer..=sps_max_sub_layers_minus1 {
let _ = r.read_ue("sps_max_dec_pic_buffering_minus1")?;
let _ = r.read_ue("sps_max_num_reorder_pics")?;
let _ = r.read_ue("sps_max_latency_increase_plus1")?;
}
let _ = r.read_ue("log2_min_luma_coding_block_size_minus3")?;
let _ = r.read_ue("log2_diff_max_min_luma_coding_block_size")?;
let _ = r.read_ue("log2_min_luma_transform_block_size_minus2")?;
let _ = r.read_ue("log2_diff_max_min_luma_transform_block_size")?;
let _ = r.read_ue("max_transform_hierarchy_depth_inter")?;
let _ = r.read_ue("max_transform_hierarchy_depth_intra")?;
let scaling_list_enabled = r.read_flag("scaling_list_enabled_flag")?;
if scaling_list_enabled {
let sps_scaling_list_data_present = r.read_flag("sps_scaling_list_data_present_flag")?;
if sps_scaling_list_data_present {
for size_id in 0u32..4 {
let matrix_count = if size_id == 3 { 2 } else { 6 };
for _matrix_id in 0..matrix_count {
let pred_mode = r.read_flag("scaling_list_pred_mode_flag")?;
if !pred_mode {
let _ = r.read_ue("scaling_list_pred_matrix_id_delta")?;
} else {
let coef_num = core::cmp::min(64u32, 1 << (4 + (size_id << 1)));
if size_id > 1 {
let _ = r.read_ue("scaling_list_dc_coef_minus8")?;
}
for _i in 0..coef_num {
let _ = r.read_ue("scaling_list_delta_coef")?;
}
}
}
}
}
}
let _ = r.read_flag("amp_enabled_flag")?;
let _ = r.read_flag("sample_adaptive_offset_enabled_flag")?;
let pcm_enabled = r.read_flag("pcm_enabled_flag")?;
if pcm_enabled {
let _ = r.read_bits(4, "pcm_sample_bit_depth_luma_minus1")?;
let _ = r.read_bits(4, "pcm_sample_bit_depth_chroma_minus1")?;
let _ = r.read_ue("log2_min_pcm_luma_coding_block_size_minus3")?;
let _ = r.read_ue("log2_diff_max_min_pcm_luma_coding_block_size")?;
let _ = r.read_flag("pcm_loop_filter_disabled_flag")?;
}
let num_short_term = r.read_ue("num_short_term_ref_pic_sets")?;
if num_short_term > 64 {
return Err(Error::InvalidValue {
field: "num_short_term_ref_pic_sets",
value: num_short_term,
reason: "out of range 0..=64 (H.265 §7.4.3.2)",
});
}
let mut rps_sets: Vec<(Vec<i64>, Vec<i64>)> = Vec::new();
for i in 0..num_short_term {
let inter_ref = if i != 0 {
r.read_flag("inter_ref_pic_set_prediction_flag")?
} else {
false
};
let set = if inter_ref {
let (ref_s0, ref_s1) = &rps_sets[rps_sets.len() - 1];
let delta_rps_sign = r.read_flag("delta_rps_sign")?;
let abs_delta_rps_minus1 = r.read_ue("abs_delta_rps_minus1")?;
if abs_delta_rps_minus1 >= 1 << 15 {
return Err(Error::InvalidValue {
field: "abs_delta_rps_minus1",
value: abs_delta_rps_minus1,
reason: "out of range 0..=2^15-1 (H.265 §7.4.8)",
});
}
let magnitude = abs_delta_rps_minus1 as i64 + 1;
let delta_rps = if delta_rps_sign {
-magnitude
} else {
magnitude
};
let num_delta_pocs = ref_s0.len() + ref_s1.len();
let mut use_delta = Vec::with_capacity(num_delta_pocs + 1);
for _j in 0..=num_delta_pocs {
let used_by_curr = r.read_flag("used_by_curr_pic_flag")?;
use_delta.push(if used_by_curr {
true
} else {
r.read_flag("use_delta_flag")?
});
}
let (n0, n1) = (ref_s0.len(), ref_s1.len());
let mut s0: Vec<i64> = Vec::new();
let mut s1: Vec<i64> = Vec::new();
for j in (0..n1).rev() {
let d = ref_s1[j] + delta_rps;
if d < 0 && use_delta[n0 + j] {
s0.push(d);
}
}
if delta_rps < 0 && use_delta[num_delta_pocs] {
s0.push(delta_rps);
}
for j in 0..n0 {
let d = ref_s0[j] + delta_rps;
if d < 0 && use_delta[j] {
s0.push(d);
}
}
for j in (0..n0).rev() {
let d = ref_s0[j] + delta_rps;
if d > 0 && use_delta[j] {
s1.push(d);
}
}
if delta_rps > 0 && use_delta[num_delta_pocs] {
s1.push(delta_rps);
}
for j in 0..n1 {
let d = ref_s1[j] + delta_rps;
if d > 0 && use_delta[n0 + j] {
s1.push(d);
}
}
(s0, s1)
} else {
let num_negative = r.read_ue("num_negative_pics")?;
let num_positive = r.read_ue("num_positive_pics")?;
if num_negative > HEVC_MAX_RPS_PICS || num_positive > HEVC_MAX_RPS_PICS {
return Err(Error::InvalidValue {
field: "num_negative_pics/num_positive_pics",
value: num_negative.max(num_positive),
reason: "out of range 0..=16 (H.265 §7.4.8)",
});
}
let mut s0 = Vec::new();
let mut acc = 0i64;
for _j in 0..num_negative {
acc -= r.read_ue("delta_poc_s0_minus1")? as i64 + 1;
let _ = r.read_flag("used_by_curr_pic_s0_flag")?;
s0.push(acc);
}
let mut s1 = Vec::new();
let mut acc = 0i64;
for _j in 0..num_positive {
acc += r.read_ue("delta_poc_s1_minus1")? as i64 + 1;
let _ = r.read_flag("used_by_curr_pic_s1_flag")?;
s1.push(acc);
}
(s0, s1)
};
if set.0.len() > HEVC_MAX_RPS_PICS as usize || set.1.len() > HEVC_MAX_RPS_PICS as usize {
return Err(Error::InvalidValue {
field: "NumNegativePics/NumPositivePics",
value: set.0.len().max(set.1.len()) as u64,
reason: "derived RPS larger than 16 entries (H.265 §7.4.8)",
});
}
rps_sets.push(set);
}
let ltrp_present = r.read_flag("long_term_ref_pics_present_flag")?;
if ltrp_present {
let num_lt = r.read_ue("num_long_term_ref_pics_sps")?;
let poc_bits = log2_max_poc_lsb + 4;
for _i in 0..num_lt {
let _ = r.read_bits(poc_bits as usize, "lt_ref_pic_poc_lsb_sps")?;
let _ = r.read_flag("used_by_curr_pic_lt_sps_flag")?;
}
}
let _ = r.read_flag("sps_temporal_mvp_enabled_flag")?;
let _ = r.read_flag("strong_intra_smoothing_enabled_flag")?;
let vui_present = r.read_flag("vui_parameters_present_flag")?;
if !vui_present {
return Ok((None, None, None, None));
}
let ari_present = r.read_flag("aspect_ratio_info_present_flag")?;
if ari_present {
colour.aspect_ratio = Some(read_aspect_ratio(r)?);
}
if r.read_flag("overscan_info_present_flag")? {
colour.overscan_appropriate = Some(r.read_flag("overscan_appropriate_flag")?);
}
if r.read_flag("video_signal_type_present_flag")? {
colour.video_signal = Some(read_video_signal_type(r)?);
}
let cli_present = r.read_flag("chroma_loc_info_present_flag")?;
if cli_present {
let _ = r.read_ue("chroma_sample_loc_type_top_field")?;
let _ = r.read_ue("chroma_sample_loc_type_bottom_field")?;
}
let _ = r.read_flag("neutral_chroma_indication_flag")?;
interlace.field_seq = r.read_flag("field_seq_flag")?;
interlace.frame_field_info_present = r.read_flag("frame_field_info_present_flag")?;
let ddw_present = r.read_flag("default_display_window_flag")?;
if ddw_present {
let l = r.read_ue("def_disp_win_left_offset")?;
let rr = r.read_ue("def_disp_win_right_offset")?;
let t = r.read_ue("def_disp_win_top_offset")?;
let b = r.read_ue("def_disp_win_bottom_offset")?;
colour.default_display_offsets = Some((l, rr, t, b));
} else {
colour.default_display_offsets = None;
}
let timing_present = r.read_flag("vui_timing_info_present_flag")?;
if !timing_present {
return Ok((None, None, None, None));
}
let num_units = r.read_bits(32, "vui_num_units_in_tick")? as u32;
let ts = r.read_bits(32, "vui_time_scale")? as u32;
let poc_prop = r.read_flag("vui_poc_proportional_to_timing_flag")?;
if poc_prop {
let _ = r.read_ue("vui_num_ticks_poc_diff_one_minus1")?;
}
let hrd_present = r.read_flag("vui_hrd_parameters_present_flag")?;
let hrd_fixed = if hrd_present {
parse_hevc_hrd(r, sps_max_sub_layers_minus1)?
} else {
None
};
let fps = if num_units > 0 {
Some(ts as f32 / num_units as f32)
} else {
None
};
Ok((Some(num_units), Some(ts), fps, hrd_fixed))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VvcSpsInfo {
pub chroma_format_idc: u8,
pub general_profile_idc: u8,
pub general_tier_flag: bool,
pub general_level_idc: u8,
pub width: u32,
pub height: u32,
}
pub fn decode_vvc_sps(sps_bytes: &[u8]) -> Result<VvcSpsInfo> {
if sps_bytes.len() < 2 {
return Err(Error::BufferTooShort {
need: 2,
have: sps_bytes.len(),
what: "VVC SPS header",
});
}
let mut r = BitReader::with_unescape(&sps_bytes[2..], "VVC SPS")?;
let _ = r.read_bits(4, "sps_seq_parameter_set_id")?;
let _ = r.read_bits(4, "sps_video_parameter_set_id")?;
let sps_max_sublayers_minus1 = r.read_bits(3, "sps_max_sublayers_minus1")? as u8;
let chroma_format_idc = r.read_bits(2, "sps_chroma_format_idc")? as u8;
let _ = r.read_bits(2, "sps_log2_ctu_size_minus5")?;
let ptl_present = r.read_flag("sps_ptl_dpb_hrd_params_present_flag")?;
let (general_profile_idc, general_tier_flag, general_level_idc) = if ptl_present {
decode_vvc_ptl(&mut r, sps_max_sublayers_minus1)?
} else {
(0, false, 0)
};
let _ = r.read_flag("sps_gdr_enabled_flag")?;
let ref_pic_resampling = r.read_flag("sps_ref_pic_resampling_enabled_flag")?;
if ref_pic_resampling {
let _ = r.read_flag("sps_res_change_in_clvs_allowed_flag")?;
}
let width = r.read_ue("sps_pic_width_max_in_luma_samples")?;
let height = r.read_ue("sps_pic_height_max_in_luma_samples")?;
Ok(VvcSpsInfo {
chroma_format_idc,
general_profile_idc,
general_tier_flag,
general_level_idc,
width: width as u32,
height: height as u32,
})
}
fn decode_vvc_ptl(r: &mut BitReader, max_sublayers_minus1: u8) -> Result<(u8, bool, u8)> {
let general_profile_idc = r.read_bits(7, "general_profile_idc")? as u8;
let general_tier_flag = r.read_flag("general_tier_flag")?;
let general_level_idc = r.read_bits(8, "general_level_idc")? as u8;
let _ = r.read_flag("ptl_frame_only_constraint_flag")?;
let _ = r.read_flag("ptl_multilayer_enabled_flag")?;
skip_general_constraints_info(r)?;
let mut sublayer_present = [false; 8];
for i in (0..max_sublayers_minus1).rev() {
sublayer_present[i as usize] = r.read_flag("ptl_sublayer_level_present_flag[i]")?;
}
if max_sublayers_minus1 > 0 {
r.align_to_byte("ptl_reserved_zero_bit")?;
}
for i in (0..max_sublayers_minus1).rev() {
if sublayer_present[i as usize] {
let _ = r.read_bits(8, "sublayer_level_idc[i]")?;
}
}
let num_sub_profiles = r.read_bits(8, "ptl_num_sub_profiles")?;
for _ in 0..num_sub_profiles {
let _ = r.read_bits(32, "general_sub_profile_idc[j]")?;
}
Ok((general_profile_idc, general_tier_flag, general_level_idc))
}
const GCI_FLAG_BLOCK_BITS: [usize; 6] = [3, 4, 2, 16, 2, 43];
const GCI_ADDITIONAL_FLAG_BITS: usize = 6;
const GCI_ADDITIONAL_THRESHOLD: u64 = 5;
fn skip_general_constraints_info(r: &mut BitReader) -> Result<()> {
let present = r.read_flag("gci_present_flag")?;
if present {
for bits in GCI_FLAG_BLOCK_BITS {
let _ = r.read_bits(bits, "general_constraints_info flag")?;
}
let additional = r.read_bits(8, "gci_num_additional_bits")?;
let used = if additional > GCI_ADDITIONAL_THRESHOLD {
let _ = r.read_bits(GCI_ADDITIONAL_FLAG_BITS, "gci additional constraint flag")?;
GCI_ADDITIONAL_FLAG_BITS as u64
} else {
0
};
for _ in 0..(additional - used) {
let _ = r.read_bits(1, "gci_reserved_bit[i]")?;
}
}
r.align_to_byte("gci_alignment_zero_bit")?;
Ok(())
}
pub fn rfc6381_avc1(profile_idc: u8, constraint_flags: u8, level_idc: u8) -> String {
let mut s = String::with_capacity(12);
s.push_str("avc1.");
write_hex_byte(&mut s, profile_idc);
write_hex_byte(&mut s, constraint_flags);
write_hex_byte(&mut s, level_idc);
s
}
fn write_hex_byte(s: &mut String, byte: u8) {
let hi = (byte >> 4) & 0xF;
let lo = byte & 0xF;
s.push(hex_char(hi));
s.push(hex_char(lo));
}
fn hex_char(nibble: u8) -> char {
match nibble {
0..=9 => (b'0' + nibble) as char,
_ => (b'A' + (nibble - 10)) as char,
}
}
const HVC1_CODEC_STRING_CAPACITY: usize = 48;
const HEVC_PROFILE_SPACE_LETTERS: [char; 3] = ['A', 'B', 'C'];
pub fn rfc6381_hvc1(info: &HevcSpsInfo) -> String {
let mut s = String::with_capacity(HVC1_CODEC_STRING_CAPACITY);
s.push_str("hvc1.");
if let Some(letter) = usize::from(info.general_profile_space)
.checked_sub(1)
.and_then(|i| HEVC_PROFILE_SPACE_LETTERS.get(i))
{
s.push(*letter);
}
s.push_str(&alloc::format!("{}", info.general_profile_idc));
s.push('.');
s.push_str(&alloc::format!(
"{:X}",
info.general_profile_compatibility_flags.reverse_bits()
));
s.push('.');
s.push(if info.general_tier_flag { 'H' } else { 'L' });
s.push_str(&alloc::format!("{}", info.general_level_idc));
let constraints = info.general_constraint_indicator_flags;
let bytes: [u8; 6] = [
((constraints >> 40) & 0xFF) as u8,
((constraints >> 32) & 0xFF) as u8,
((constraints >> 24) & 0xFF) as u8,
((constraints >> 16) & 0xFF) as u8,
((constraints >> 8) & 0xFF) as u8,
(constraints & 0xFF) as u8,
];
let last_nonzero = bytes.iter().rposition(|&b| b != 0);
if let Some(end) = last_nonzero {
for (i, &b) in bytes.iter().enumerate() {
if i > end {
break;
}
s.push('.');
write_hex_byte(&mut s, b);
}
}
s
}
pub fn rfc6381_vvc1(
general_profile_idc: u8,
general_tier_flag: bool,
general_level_idc: u8,
general_constraint_info: &[u8],
num_bytes_constraint_info: u8,
) -> String {
let mut s = String::with_capacity(24);
s.push_str("vvc1.");
write_decimal(&mut s, general_profile_idc);
s.push('.');
s.push(if general_tier_flag { 'H' } else { 'L' });
write_decimal(&mut s, general_level_idc);
if num_bytes_constraint_info > 0 {
let n = num_bytes_constraint_info as usize;
let block = &general_constraint_info[..general_constraint_info.len().min(n)];
if let Some(end) = block.iter().rposition(|&b| b != 0) {
s.push_str(".CTA-");
for &b in &block[..=end] {
write_hex_byte(&mut s, b);
}
}
}
s
}
pub fn rfc6381_mp4a(aot: u8) -> String {
let mut s = String::with_capacity(16);
s.push_str("mp4a.40.");
write_decimal(&mut s, aot);
s
}
fn write_decimal(s: &mut String, mut n: u8) {
if n == 0 {
s.push('0');
return;
}
let mut buf = [0u8; 4];
let mut len = 0;
while n > 0 {
buf[len] = b'0' + (n % 10);
n /= 10;
len += 1;
}
for i in (0..len).rev() {
s.push(buf[i] as char);
}
}
pub(crate) fn decode_ptl(
r: &mut BitReader,
profile_present_flag: bool,
max_sub_layers_minus1: u8,
) -> Result<(u8, bool, u8, u32, u64, u8)> {
let (general_profile_space, general_tier_flag, general_profile_idc) = if profile_present_flag {
(
r.read_bits(2, "general_profile_space")? as u8,
r.read_flag("general_tier_flag")?,
r.read_bits(5, "general_profile_idc")? as u8,
)
} else {
(0, false, 0)
};
let general_profile_compatibility_flags =
r.read_bits(32, "general_profile_compatibility_flags")? as u32;
let general_constraint_indicator_flags =
r.read_bits(48, "general_constraint_indicator_flags")?;
let general_level_idc = r.read_bits(8, "general_level_idc")? as u8;
let mut sub_layer_profile_present_flag = [false; 7];
let mut sub_layer_level_present_flag = [false; 7];
for i in 0..max_sub_layers_minus1 {
sub_layer_profile_present_flag[i as usize] =
r.read_flag("sub_layer_profile_present_flag[i]")?;
sub_layer_level_present_flag[i as usize] =
r.read_flag("sub_layer_level_present_flag[i]")?;
}
if max_sub_layers_minus1 > 0 {
for _i in max_sub_layers_minus1..8 {
let _ = r.read_bits(2, "reserved_zero_2bits")?;
}
}
for i in 0..max_sub_layers_minus1 {
if sub_layer_profile_present_flag[i as usize] {
let _ = r.read_bits(2, "sub_layer_profile_space")?;
let _ = r.read_flag("sub_layer_tier_flag")?;
let _ = r.read_bits(5, "sub_layer_profile_idc")?;
let _ = r.read_bits(32, "sub_layer_profile_compatibility_flags")?;
let _ = r.read_bits(48, "sub_layer_constraint_indicator_flags")?;
}
if sub_layer_level_present_flag[i as usize] {
let _ = r.read_bits(8, "sub_layer_level_idc")?;
}
}
Ok((
general_profile_space,
general_tier_flag,
general_profile_idc,
general_profile_compatibility_flags,
general_constraint_indicator_flags,
general_level_idc,
))
}
#[cfg(test)]
mod tests {
use super::*;
const X264_PYRAMID_SPS: [u8; 25] = [
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x40, 0xa0, 0x2f, 0xf9, 0x61, 0x00, 0x00, 0x03, 0x00,
0x01, 0x00, 0x00, 0x03, 0x00, 0x32, 0x0f, 0x16, 0x2d, 0x96,
];
const X264_NO_PYRAMID_SPS: [u8; 25] = [
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd1, 0x00, 0xa0, 0x2f, 0xf9, 0x61, 0x00, 0x00, 0x03, 0x00,
0x01, 0x00, 0x00, 0x03, 0x00, 0x32, 0x0f, 0x16, 0x2d, 0x12,
];
const X264_NO_B_SPS: [u8; 25] = [
0x67, 0x64, 0x00, 0x1e, 0xac, 0xb2, 0x01, 0x40, 0x5f, 0xf2, 0xc2, 0x00, 0x00, 0x03, 0x00,
0x02, 0x00, 0x00, 0x03, 0x00, 0x64, 0x1e, 0x2c, 0x5c, 0x90,
];
#[test]
fn reorder_depth_is_read_from_real_x264_sps() {
let depth = |sps: &[u8]| decode_avc_reorder_depth(sps).unwrap();
assert_eq!(depth(&X264_PYRAMID_SPS).max_num_reorder_frames, Some(2));
assert_eq!(depth(&X264_NO_PYRAMID_SPS).max_num_reorder_frames, Some(1));
assert_eq!(depth(&X264_NO_B_SPS).max_num_reorder_frames, Some(0));
assert_eq!(depth(&X264_PYRAMID_SPS).depth(), Some(2));
assert!(depth(&X264_PYRAMID_SPS).max_dec_frame_buffering >= Some(2));
}
#[test]
fn reorder_depth_inference_follows_the_profile() {
let none = |profile_idc, constraint_flags| AvcReorderDepth {
profile_idc,
constraint_flags,
max_num_reorder_frames: None,
max_dec_frame_buffering: None,
};
assert_eq!(none(66, 0x00).depth(), Some(0));
assert_eq!(none(100, 0x10).depth(), Some(0));
assert_eq!(none(100, 0x00).depth(), None);
assert_eq!(none(77, 0x10).depth(), None);
let declared = AvcReorderDepth {
max_num_reorder_frames: Some(3),
..none(100, 0x10)
};
assert_eq!(declared.depth(), Some(3));
}
#[test]
fn rfc6381_avc1_profiles() {
assert_eq!(rfc6381_avc1(66, 0xC0, 13), "avc1.42C00D", "baseline");
assert_eq!(rfc6381_avc1(77, 0x40, 13), "avc1.4D400D", "main");
assert_eq!(rfc6381_avc1(100, 0x00, 13), "avc1.64000D", "high");
}
#[test]
fn rfc6381_mp4a_aac_lc() {
assert_eq!(rfc6381_mp4a(2), "mp4a.40.2");
}
#[test]
fn avc_sps_vui_timing_25fps() {
let sps: &[u8] = &[
0x67, 0x64, 0x00, 0x0D, 0xAC, 0xD9, 0x41, 0x41, 0xFB, 0x01, 0x10, 0x00, 0x00, 0x03,
0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x42, 0x99, 0x60,
];
let info = decode_avc_sps(sps).unwrap();
assert_eq!(info.profile_idc, 100);
assert_eq!(info.level_idc, 13);
assert_eq!(info.num_units_in_tick, Some(1));
assert_eq!(info.time_scale, Some(50));
let fps = info.fps.expect("fps must be Some for this SPS");
assert!(
(fps - 25.0_f32).abs() < 0.001,
"expected 25.0 fps, got {fps}"
);
}
#[test]
fn avc_sps_no_timing_info_yields_none() {
let sps: &[u8] = &[
0x67, 0x64, 0x00, 0x0D, 0xAD, 0xC8, 0xBF, 0xFE, 0x03, 0xC1, 0x41, 0xF9,
];
let info = decode_avc_sps(sps).unwrap();
assert_eq!(
info.num_units_in_tick, None,
"no timing_info → num_units_in_tick must be None"
);
assert_eq!(
info.time_scale, None,
"no timing_info → time_scale must be None"
);
assert_eq!(info.fps, None, "no timing_info → fps must be None");
}
#[test]
fn decode_high_sps_with_scaling_lists() {
let sps_nal: Vec<u8> = vec![
0x67, 0x64, 0x00, 0x0D, 0xAD, 0xC8, 0xBF, 0xFE, 0x03, 0xC1, 0x41, 0xF9,
];
let info = decode_avc_sps(&sps_nal).unwrap();
assert_eq!(info.profile_idc, 100);
assert_eq!(info.level_idc, 13);
assert_eq!(info.chroma_format_idc, 1);
assert_eq!(info.bit_depth_luma, 8);
assert_eq!(info.bit_depth_chroma, 8);
assert_eq!(info.width, 320);
assert_eq!(info.height, 240);
assert!(info.frame_mbs_only);
}
#[test]
fn hevc_sps_vui_timing_25fps() {
let sps: &[u8] = &[
0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00,
0x00, 0x03, 0x00, 0x3c, 0xa0, 0x0a, 0x08, 0x0f, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b,
0xc0, 0x5a, 0x02, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x32, 0x10,
];
let info = decode_hevc_sps(sps).unwrap();
assert_eq!(info.width, 320, "width regression");
assert_eq!(info.height, 240, "height regression");
assert_eq!(info.chroma_format_idc, 1, "chroma_format_idc regression");
assert_eq!(info.bit_depth_luma, 8, "bit_depth_luma regression");
assert_eq!(info.bit_depth_chroma, 8, "bit_depth_chroma regression");
assert_eq!(
info.general_profile_idc, 1,
"general_profile_idc regression"
);
assert_eq!(info.general_level_idc, 60, "general_level_idc regression");
assert_eq!(
info.num_units_in_tick,
Some(1),
"vui_num_units_in_tick must be 1"
);
assert_eq!(info.time_scale, Some(25), "vui_time_scale must be 25");
let fps = info.fps.expect("fps must be Some for this SPS");
assert!(
(fps - 25.0_f32).abs() < 0.001,
"expected 25.0 fps, got {fps}"
);
}
#[test]
fn hevc_sps_no_vui_yields_none() {
let truncated: &[u8] = &[
0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, ];
let info = decode_hevc_sps(truncated).ok();
if let Some(info) = info {
assert_eq!(
info.num_units_in_tick, None,
"truncated/no-VUI SPS must yield None for num_units_in_tick"
);
assert_eq!(
info.time_scale, None,
"truncated/no-VUI SPS must yield None for time_scale"
);
assert_eq!(
info.fps, None,
"truncated/no-VUI SPS must yield None for fps"
);
}
}
fn vvc_sps_with_gci(additional: u8, width: u64, height: u64) -> Vec<u8> {
const GCI_FLAG_BITS: u32 = 3 + 4 + 2 + 16 + 2 + 43;
let mut s = BitSink::new();
s.bits(0, 8); s.bits(0, 3); s.bits(1, 2); s.bits(0, 2); s.bit(true); s.bits(1, 7); s.bit(false); s.bits(32, 8); s.bit(false); s.bit(false); s.bit(true); for _ in 0..GCI_FLAG_BITS {
s.bit(false);
}
s.bits(u64::from(additional), 8); if additional > 5 {
for _ in 0..6 {
s.bit(false); }
for _ in 0..(additional - 6) {
s.bit(false); }
} else {
for _ in 0..additional {
s.bit(false); }
}
while !s.nbits.is_multiple_of(8) {
s.bit(false); }
s.bits(0, 8); s.bit(false); s.bit(false); s.ue(width);
s.ue(height);
let rbsp = s.finish();
let mut escaped = Vec::with_capacity(rbsp.len() + 4);
let mut zeros = 0usize;
for &byte in &rbsp {
if zeros >= 2 && byte <= 0x03 {
escaped.push(0x03);
zeros = 0;
}
escaped.push(byte);
zeros = if byte == 0 { zeros + 1 } else { 0 };
}
let mut nal = alloc::vec![0x00, 0x00];
nal.extend_from_slice(&escaped);
nal[1] = 15 << 3; nal
}
#[test]
fn vvc_sps_gci_additional_bits_counts_decode_dimensions() {
for additional in [0u8, 1, 3, 5, 6, 8, 255] {
let nal = vvc_sps_with_gci(additional, 1920, 1080);
let info = decode_vvc_sps(&nal)
.unwrap_or_else(|e| panic!("additional={additional} must decode, got {e:?}"));
assert_eq!(
(info.width, info.height),
(1920, 1080),
"additional={additional} must land on the right dimensions"
);
}
}
#[test]
fn vvc_sps_gci_truncated_is_error_not_panic_for_every_count() {
for additional in [0u8, 3, 5, 6, 8, 255] {
let nal = vvc_sps_with_gci(additional, 1920, 1080);
for cut in 4..nal.len() {
assert!(
decode_vvc_sps(&nal[..cut]).is_err(),
"additional={additional} cut={cut} of {} must error",
nal.len()
);
}
}
}
#[test]
fn vvc_sps_gci_present_flag_is_skipped_to_dimensions() {
const GCI_BITS: u32 = 1 + 3 + 4 + 2 + 13 + 6 + 3 + 6 + 15 + 7 + 5 + 6 + 8;
let mut s = BitSink::new();
s.bits(0, 8);
s.bits(0, 3);
s.bits(1, 2);
s.bits(0, 2);
s.bit(true);
s.bits(1, 7); s.bit(false); s.bits(32, 8); s.bit(false); s.bit(false); s.bit(true);
for _ in 1..GCI_BITS {
s.bit(false);
}
while !s.nbits.is_multiple_of(8) {
s.bit(false);
}
s.bits(0, 8);
s.bit(false);
s.bit(false);
s.ue(1920);
s.ue(1080);
let rbsp = s.finish();
let mut nal = alloc::vec![0x00, 0x00];
nal.extend_from_slice(&rbsp);
nal[1] = 15 << 3;
let info = decode_vvc_sps(&nal).expect("a GCI-present SPS must decode");
assert_eq!(info.chroma_format_idc, 1);
assert_eq!(info.general_profile_idc, 1);
assert_eq!(info.general_level_idc, 32);
assert_eq!(info.width, 1920);
assert_eq!(info.height, 1080);
}
#[test]
fn vvc_sps_gci_absent_still_decodes() {
let mut s = BitSink::new();
s.bits(0, 8); s.bits(0, 3); s.bits(1, 2); s.bits(0, 2); s.bit(true); s.bits(1, 7); s.bit(false); s.bits(32, 8); s.bit(false); s.bit(false); s.bit(false); while !s.nbits.is_multiple_of(8) {
s.bit(false); }
s.bits(0, 8); s.bit(false); s.bit(false); s.ue(640);
s.ue(360);
let rbsp = s.finish();
let mut nal = alloc::vec![0x00, 0x00];
nal.extend_from_slice(&rbsp);
nal[1] = 15 << 3;
let info = decode_vvc_sps(&nal).expect("GCI-absent SPS must decode");
assert_eq!((info.width, info.height), (640, 360));
}
#[test]
fn vvc_sps_width_above_u16_is_not_truncated() {
let mut s = BitSink::new();
s.bits(0, 8); s.bits(0, 3); s.bits(1, 2); s.bits(0, 2); s.bit(true); s.bits(1, 7); s.bit(false); s.bits(32, 8); s.bit(false); s.bit(false); s.bit(false); while !s.nbits.is_multiple_of(8) {
s.bit(false);
}
s.bits(0, 8); s.bit(false); s.bit(false); s.ue(70_000);
s.ue(70_000);
let rbsp = s.finish();
let mut nal = alloc::vec![0x00, 0x00];
nal.extend_from_slice(&rbsp);
nal[1] = 15 << 3;
let info = decode_vvc_sps(&nal).expect("oversized SPS must decode");
assert_eq!((info.width, info.height), (70_000, 70_000));
}
#[test]
fn vvc_sps_gci_present_truncated_is_error_not_panic() {
let nal: &[u8] = &[0x00, 0x78, 0x00, 0x40];
assert!(decode_vvc_sps(nal).is_err());
assert!(decode_vvc_sps(&[0x00, 0x78]).is_err());
}
use alloc::vec;
use alloc::vec::Vec;
struct BitSink {
bytes: Vec<u8>,
cur: u8,
nbits: u32,
}
impl BitSink {
fn new() -> Self {
Self {
bytes: Vec::new(),
cur: 0,
nbits: 0,
}
}
fn bit(&mut self, b: bool) {
if b {
self.cur |= 1 << (7 - self.nbits);
}
self.nbits += 1;
if self.nbits == 8 {
self.bytes.push(self.cur);
self.cur = 0;
self.nbits = 0;
}
}
fn bits(&mut self, v: u64, n: u32) {
for i in (0..n).rev() {
self.bit((v >> i) & 1 == 1);
}
}
fn ue(&mut self, v: u64) {
let code = v + 1;
let len = 64 - code.leading_zeros();
for _ in 0..len - 1 {
self.bit(false);
}
self.bits(code, len);
}
fn ue_huge(&mut self) {
for _ in 0..32 {
self.bit(false);
}
self.bit(true);
for _ in 0..32 {
self.bit(false);
}
}
fn finish(mut self) -> Vec<u8> {
if self.nbits > 0 {
self.bytes.push(self.cur);
}
self.bytes
}
}
#[test]
fn avc_sps_huge_poc_cycle_count_is_rejected() {
let mut nal = vec![0x67u8, 66, 0x00, 30]; let mut s = BitSink::new();
s.ue(0); s.ue(0); s.ue(1); s.bit(false); s.bit(true); s.bit(true); s.ue_huge(); nal.extend(s.finish());
let err =
decode_avc_sps(&nal).expect_err("H.264 §7.4.2.1 bounds the cycle count to 0..=255");
assert!(
matches!(
err,
Error::InvalidValue {
field: "num_ref_frames_in_pic_order_cnt_cycle",
..
}
),
"{err:?}"
);
}
#[test]
fn avc_sps_chroma_format_idc_above_3_is_rejected() {
let mut nal = vec![0x67u8, 100, 0x00, 30]; let mut s = BitSink::new();
s.ue(0); s.ue(4); nal.extend(s.finish());
let err = decode_avc_sps(&nal).expect_err("chroma_format_idc shall be 0..=3");
assert!(
matches!(
err,
Error::InvalidValue {
field: "chroma_format_idc",
..
}
),
"{err:?}"
);
}
#[test]
fn avc_sps_bit_depth_minus8_above_6_is_rejected() {
let mut nal = vec![0x67u8, 100, 0x00, 30];
let mut s = BitSink::new();
s.ue(0); s.ue(1); s.ue(7); nal.extend(s.finish());
let err = decode_avc_sps(&nal).expect_err("bit_depth_luma_minus8 shall be 0..=6");
assert!(
matches!(
err,
Error::InvalidValue {
field: "bit_depth_luma_minus8",
..
}
),
"{err:?}"
);
}
#[test]
fn hevc_sps_huge_short_term_ref_pic_set_count_terminates_promptly() {
let mut nal = vec![0x42u8, 0x01]; let mut s = BitSink::new();
s.bits(0, 4); s.bits(0, 3); s.bit(false); s.bits(0, 2); s.bit(false); s.bits(1, 5); s.bits(0, 32); s.bits(0xE0_00_00_00_00_00, 48); s.bits(93, 8); s.ue(0); s.ue(1); s.ue(16); s.ue(16); s.bit(false); s.ue(2); s.ue(2); s.ue(3); s.bit(false); s.ue(0); s.ue(0); s.ue(0); s.ue(0); s.ue(3); s.ue(0); s.ue(3); s.ue(1); s.ue(1); s.bit(false); s.bit(false); s.bit(false); s.bit(false); s.ue_huge(); nal.extend(s.finish());
let info = decode_hevc_sps(&nal)
.expect("mandatory fields parse; the bounded VUI walk error is swallowed");
assert_eq!(info.fps, None);
}
use crate::interlace::{PicStruct, SourceScanType, avc_facts, hevc_facts};
use crate::slice_header::AvcParamSets;
fn hevc_interlace_sps(field_seq: bool, ffi: bool, vui: bool, ptl_bits: u64) -> Vec<u8> {
hevc_sps_full(field_seq, ffi, vui, ptl_bits, None)
}
type AspectBits = Option<(u8, Option<(u16, u16)>)>;
fn put_aspect(s: &mut BitSink, aspect: AspectBits) {
s.bit(aspect.is_some()); if let Some((idc, ext)) = aspect {
s.bits(u64::from(idc), 8); if let Some((w, h)) = ext {
s.bits(u64::from(w), 16); s.bits(u64::from(h), 16); }
}
}
fn hevc_sps_full(
field_seq: bool,
ffi: bool,
vui: bool,
ptl_bits: u64,
aspect: AspectBits,
) -> Vec<u8> {
let mut nal = vec![0x42u8, 0x01];
let mut s = BitSink::new();
s.bits(0, 4); s.bits(0, 3); s.bit(true); s.bits(0, 2); s.bit(false); s.bits(1, 5); s.bits(0, 32); s.bits(ptl_bits, 48); s.bits(93, 8); s.ue(0); s.ue(1); s.ue(16); s.ue(16); s.bit(false); s.ue(0); s.ue(0); s.ue(3); s.bit(false); s.ue(0);
s.ue(0);
s.ue(0); s.ue(0);
s.ue(3);
s.ue(0);
s.ue(3); s.ue(1);
s.ue(1); s.bit(false); s.bit(false); s.bit(false); s.bit(false); s.ue(3); s.ue(1); s.ue(1); s.ue(0); s.bit(true); s.ue(1); s.bit(true); s.bit(true); s.bit(true); s.ue(2); s.bit(true); s.bit(false); s.bit(false); s.bit(true); s.bit(true); s.bit(false); s.ue(0); for _ in 0..3 {
s.bit(true); }
s.bit(false); s.bit(false); s.bit(false); s.bit(vui); if vui {
put_aspect(&mut s, aspect);
s.bit(false); s.bit(false); s.bit(false); s.bit(false); s.bit(field_seq); s.bit(ffi); s.bit(false); s.bit(false); s.bit(false); }
s.bit(true); nal.extend(s.finish());
nal
}
#[test]
fn hevc_field_seq_is_read_past_inter_rps_prediction() {
let sps = hevc_interlace_sps(true, true, true, 0);
let i = decode_hevc_interlace_sps(&sps).expect("parses");
assert!(i.field_seq, "field_seq_flag read through 3 st_ref_pic_sets");
assert!(i.frame_field_info_present);
let frames = decode_hevc_interlace_sps(&hevc_interlace_sps(false, false, true, 0)).unwrap();
assert_eq!(
(frames.field_seq, frames.frame_field_info_present),
(false, false)
);
assert!(decode_hevc_sps(&sps).is_ok());
}
#[test]
fn hevc_frame_field_info_is_inferred_from_ptl_when_vui_absent() {
let both = decode_hevc_interlace_sps(&hevc_interlace_sps(
false,
false,
false,
0xC0_00_00_00_00_00,
))
.unwrap();
assert!(both.frame_field_info_present && !both.field_seq);
let one = decode_hevc_interlace_sps(&hevc_interlace_sps(
false,
false,
false,
0x80_00_00_00_00_00,
))
.unwrap();
assert!(!one.frame_field_info_present);
}
fn hevc_nal(nal_type: u8, body: &[u8]) -> Vec<u8> {
let mut v = vec![nal_type << 1, 0x01];
v.extend_from_slice(body);
v
}
fn hevc_pic_timing_sei(pic_struct: u64, scan: u64) -> Vec<u8> {
let mut p = BitSink::new();
p.bits(pic_struct, 4);
p.bits(scan, 2);
p.bit(false); let payload = p.finish(); let mut body = vec![1u8, payload.len() as u8]; body.extend_from_slice(&payload);
body.push(0x80); hevc_nal(39, &body)
}
#[test]
fn hevc_pic_struct_and_scan_type_are_read_from_prefix_sei() {
let sps = hevc_interlace_sps(true, true, true, 0);
let vcl = hevc_nal(1, &[0x80]);
for (code, want, bottom_first) in [
(1u64, PicStruct::TopField, Some(false)),
(2, PicStruct::BottomField, Some(true)),
(10, PicStruct::BottomPairedWithPreviousTop, Some(true)),
(11, PicStruct::TopPairedWithNextBottom, Some(false)),
] {
let sei = hevc_pic_timing_sei(code, 0);
let f = hevc_facts(&sps, &[sei.as_slice(), vcl.as_slice()]).expect("facts");
assert!(f.field_pic);
assert_eq!(f.fields, 1);
assert_eq!(f.pic_struct, Some(want));
assert_eq!(f.bottom_field_first, bottom_first);
assert_eq!(f.source_scan_type, Some(SourceScanType::Interlaced));
}
let sei = hevc_pic_timing_sei(1, 3);
let f = hevc_facts(&sps, &[sei.as_slice(), vcl.as_slice()]).unwrap();
assert_eq!(f.source_scan_type, Some(SourceScanType::Unknown));
let sei = hevc_pic_timing_sei(13, 0);
let f = hevc_facts(&sps, &[sei.as_slice(), vcl.as_slice()]).unwrap();
assert_eq!(f.pic_struct, None);
let f = hevc_facts(&sps, &[vcl.as_slice()]).unwrap();
assert!(f.field_pic);
assert_eq!((f.pic_struct, f.bottom_field_first), (None, None));
assert!(hevc_facts(&sps, &[sei.as_slice()]).is_none());
let sps0 = hevc_interlace_sps(false, false, true, 0);
let sei = hevc_pic_timing_sei(4, 0);
let f = hevc_facts(&sps0, &[sei.as_slice(), vcl.as_slice()]).unwrap();
assert!(!f.field_pic);
assert_eq!(f.pic_struct, None);
}
fn avc_interlace_sps(
mbaff: bool,
pic_struct_present: bool,
nal_hrd: Option<(u64, u64)>,
vcl_hrd: Option<(u64, u64)>,
) -> Vec<u8> {
fn hrd(s: &mut BitSink, (cpb, dpb): (u64, u64)) {
s.ue(1); s.bits(0, 4); s.bits(0, 4); for _ in 0..2 {
s.ue(7); s.ue(9); s.bit(true); }
s.bits(22, 5); s.bits(cpb, 5); s.bits(dpb, 5); s.bits(24, 5); }
let mut nal = vec![0x67u8, 66, 0x00, 30];
let mut s = BitSink::new();
s.ue(0); s.ue(0); s.ue(2); s.ue(1); s.bit(false); s.ue(3); s.ue(1); s.bit(false); s.bit(mbaff); s.bit(true); s.bit(false); s.bit(true); s.bit(false); s.bit(false); s.bit(false); s.bit(false); s.bit(true); s.bits(1, 32); s.bits(50, 32); s.bit(true); s.bit(nal_hrd.is_some()); if let Some(h) = nal_hrd {
hrd(&mut s, h);
}
s.bit(vcl_hrd.is_some()); if let Some(h) = vcl_hrd {
hrd(&mut s, h);
}
if nal_hrd.is_some() || vcl_hrd.is_some() {
s.bit(false); }
s.bit(pic_struct_present); s.bit(false); s.bit(true); nal.extend(s.finish());
nal
}
fn avc_pps() -> Vec<u8> {
let mut s = BitSink::new();
s.ue(0); s.ue(0); s.bit(false); s.bit(false); s.ue(0); s.ue(0);
s.ue(0); s.bit(false); s.bits(0, 2); s.ue(0); s.ue(0); s.ue(0); s.bit(false); s.bit(false); s.bit(false); s.bit(true);
let mut nal = vec![0x68u8];
nal.extend(s.finish());
nal
}
fn avc_slice(field: Option<bool>) -> Vec<u8> {
let mut s = BitSink::new();
s.ue(0); s.ue(7); s.ue(0); s.bits(0, 4); match field {
None => s.bit(false), Some(bottom) => {
s.bit(true); s.bit(bottom); }
}
s.bits(0, 8); let mut nal = vec![0x61u8]; nal.extend(s.finish());
nal
}
fn avc_pic_timing_sei(delays: Option<(u32, u32)>, pic_struct: u64) -> Vec<u8> {
let mut p = BitSink::new();
if let Some((cpb, dpb)) = delays {
p.bits(0x15555 & ((1 << cpb) - 1), cpb); p.bits(0x2AAAA & ((1 << dpb) - 1), dpb); }
p.bits(pic_struct, 4);
p.bits(0, 3); let payload = p.finish();
let mut nal = vec![0x06u8, 1, payload.len() as u8];
nal.extend_from_slice(&payload);
nal.push(0x80);
nal
}
fn avc_ps(sps: &[u8]) -> AvcParamSets {
let mut ps = AvcParamSets::new();
ps.observe_nal(sps);
ps.observe_nal(&avc_pps());
ps
}
#[test]
fn avc_sps_scan_flags_and_pic_timing_layout() {
let paff = decode_avc_slice_sps(&avc_interlace_sps(false, false, None, None)).unwrap();
assert!(!paff.frame_mbs_only && !paff.mb_adaptive_frame_field);
assert_eq!(paff.pic_timing, None, "pic_struct_present_flag = 0");
let mbaff = decode_avc_slice_sps(&avc_interlace_sps(true, true, None, None)).unwrap();
assert!(mbaff.mb_adaptive_frame_field);
assert_eq!(
mbaff.pic_timing,
Some(AvcPicTimingLayout { delay_bits: None })
);
let hrd = decode_avc_slice_sps(&avc_interlace_sps(true, true, Some((4, 3)), None)).unwrap();
assert_eq!(
hrd.pic_timing,
Some(AvcPicTimingLayout {
delay_bits: Some((5, 4))
})
);
let both = decode_avc_slice_sps(&avc_interlace_sps(true, true, Some((4, 3)), Some((4, 3))))
.unwrap();
assert_eq!(both.pic_timing, hrd.pic_timing);
let clash =
decode_avc_slice_sps(&avc_interlace_sps(true, true, Some((4, 3)), Some((6, 3))))
.unwrap();
assert_eq!(clash.pic_timing, None);
}
#[test]
fn avc_pic_struct_is_read_after_the_hrd_delays() {
let sps = avc_interlace_sps(true, true, Some((4, 3)), None);
let ps = avc_ps(&sps);
let frame = avc_slice(None);
let sei = avc_pic_timing_sei(Some((5, 4)), 4);
let f = avc_facts(&ps, &[sei.as_slice(), frame.as_slice()]).expect("facts");
assert!(!f.field_pic);
assert_eq!(f.fields, 0);
assert_eq!(f.pic_struct, Some(PicStruct::BottomTop));
assert_eq!(f.bottom_field_first, Some(true));
let sei = avc_pic_timing_sei(Some((5, 4)), 9);
let f = avc_facts(&ps, &[sei.as_slice(), frame.as_slice()]).unwrap();
assert_eq!(f.pic_struct, None);
let sps = avc_interlace_sps(false, true, None, None);
let ps = avc_ps(&sps);
let sei = avc_pic_timing_sei(None, 3);
let f = avc_facts(&ps, &[sei.as_slice(), frame.as_slice()]).unwrap();
assert_eq!(f.pic_struct, Some(PicStruct::TopBottom));
assert_eq!(f.bottom_field_first, Some(false));
}
#[test]
fn avc_field_pair_parity_comes_from_the_first_slice() {
let ps = avc_ps(&avc_interlace_sps(false, false, None, None));
let (top, bottom) = (avc_slice(Some(false)), avc_slice(Some(true)));
let pair = avc_facts(&ps, &[top.as_slice(), bottom.as_slice()]).unwrap();
assert_eq!(
(pair.field_pic, pair.fields, pair.bottom_field_first),
(true, 2, Some(false))
);
let pair = avc_facts(&ps, &[bottom.as_slice(), top.as_slice()]).unwrap();
assert_eq!(
(pair.field_pic, pair.fields, pair.bottom_field_first),
(true, 2, Some(true))
);
let lone = avc_facts(&ps, &[bottom.as_slice()]).unwrap();
assert_eq!((lone.fields, lone.bottom_field_first), (1, Some(true)));
let same = avc_facts(&ps, &[top.as_slice(), top.as_slice()]).unwrap();
assert_eq!(same.fields, 1);
assert!(avc_facts(&AvcParamSets::new(), &[top.as_slice()]).is_none());
}
const SPEC_TABLE_E1: [(u8, u16, u16); 16] = [
(1, 1, 1),
(2, 12, 11),
(3, 10, 11),
(4, 16, 11),
(5, 40, 33),
(6, 24, 11),
(7, 20, 11),
(8, 32, 11),
(9, 80, 33),
(10, 18, 11),
(11, 15, 11),
(12, 64, 33),
(13, 160, 99),
(14, 4, 3),
(15, 3, 2),
(16, 2, 1),
];
fn avc_display_sps(vui: bool, aspect: AspectBits) -> Vec<u8> {
let mut nal = vec![0x67u8, 66, 0x00, 30];
let mut s = BitSink::new();
s.ue(0); s.ue(0); s.ue(2); s.ue(1); s.bit(false); s.ue(44); s.ue(35); s.bit(true); s.bit(true); s.bit(false); s.bit(vui); if vui {
put_aspect(&mut s, aspect);
s.bit(false); s.bit(false); s.bit(false); s.bit(true); s.bits(1, 32); s.bits(50, 32); s.bit(true); s.bit(false); s.bit(false); s.bit(false); s.bit(false); }
s.bit(true); nal.extend(s.finish());
nal
}
fn avc_geometry(aspect: AspectBits) -> crate::ir::DisplayGeometry {
let sps = avc_display_sps(true, aspect);
assert_eq!(decode_avc_sps(&sps).unwrap().fps, Some(25.0));
crate::ir::DisplayGeometry::from_avc_sps(&sps).unwrap()
}
fn hevc_geometry(aspect: AspectBits) -> crate::ir::DisplayGeometry {
let sps = hevc_sps_full(true, true, true, 0, aspect);
let i = decode_hevc_interlace_sps(&sps).unwrap();
assert!(i.field_seq && i.frame_field_info_present);
crate::ir::DisplayGeometry::from_hevc_sps(&sps).unwrap()
}
#[test]
fn avc_every_table_e1_idc_decodes_to_its_ratio() {
for (idc, w, h) in SPEC_TABLE_E1 {
let g = avc_geometry(Some((idc, None)));
assert_eq!(g.sample_aspect_ratio(), Some((w, h)), "H.264 idc {idc}");
assert_eq!(g.aspect_ratio.unwrap().idc(), idc);
assert_eq!((g.coded_width, g.coded_height), (720, 576));
}
}
use crate::ir::{
ColourDescription, ColourPrimaries, MatrixCoefficients, TransferCharacteristics,
VideoFormat, VideoSignalType,
};
type VideoSignalBits = (u8, bool, Option<(u8, u8, u8)>);
#[derive(Clone, Copy, Default)]
struct ColourVui {
overscan: Option<bool>,
vst: Option<VideoSignalBits>,
}
fn put_colour_vui(s: &mut BitSink, v: ColourVui) {
match v.overscan {
Some(appropriate) => {
s.bit(true); s.bit(appropriate); }
None => s.bit(false),
}
match v.vst {
Some((format, full_range, colour)) => {
s.bit(true); s.bits(u64::from(format), 3); s.bit(full_range); match colour {
Some((primaries, transfer, matrix)) => {
s.bit(true); s.bits(u64::from(primaries), 8);
s.bits(u64::from(transfer), 8);
s.bits(u64::from(matrix), 8);
}
None => s.bit(false),
}
}
None => s.bit(false),
}
}
fn colour(primaries: u8, transfer: u8, matrix: u8) -> ColourDescription {
ColourDescription::from_wire(primaries, transfer, matrix)
}
const SPEC_TABLE_E3: [(u8, &str); 12] = [
(1, "Rec. ITU-R BT.709-6"),
(2, "Unspecified"),
(4, "Rec. ITU-R BT.470-7 System M (historical)"),
(5, "Rec. ITU-R BT.470-7 System B, G (historical)"),
(6, "Rec. ITU-R BT.601-7 525"),
(7, "SMPTE 240M (1999, historical)"),
(8, "Generic film (colour filters using Illuminant C)"),
(9, "Rec. ITU-R BT.2020-2"),
(10, "SMPTE ST 428-1 (2006)"),
(11, "SMPTE RP 431-2 (2011)"),
(12, "SMPTE EG 432-1 (2010)"),
(22, "EBU Tech. 3213-E (1975)"),
];
const SPEC_TABLE_E4_ROWS: [u8; 17] =
[1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18];
const SPEC_TABLE_E5_ROWS: [u8; 17] =
[0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
#[test]
fn colour_primaries_match_table_e3_rows() {
for (code, label) in SPEC_TABLE_E3 {
let p = <ColourPrimaries>::from_wire(code);
assert_eq!(p.code(), code);
assert_eq!(p.name(), label, "colour_primaries {code}");
}
}
#[test]
fn transfer_matches_table_e4_defined_rows() {
for code in SPEC_TABLE_E4_ROWS {
let t = TransferCharacteristics::from_wire(code);
assert_eq!(t.code(), code);
assert_ne!(t.name(), "reserved", "transfer {code} must be defined");
}
assert_eq!(
TransferCharacteristics::from_wire(16),
TransferCharacteristics::SmpteSt2084
);
assert_eq!(
TransferCharacteristics::from_wire(18),
TransferCharacteristics::AribStdB67
);
}
#[test]
fn matrix_coefficients_match_table_e5_defined_rows() {
for code in SPEC_TABLE_E5_ROWS {
let m = MatrixCoefficients::from_wire(code);
assert_eq!(m.code(), code);
assert_ne!(m.name(), "reserved", "matrix {code} must be defined");
}
assert_eq!(
MatrixCoefficients::from_wire(5),
MatrixCoefficients::Bt470SystemB
);
assert_eq!(
MatrixCoefficients::from_wire(6),
MatrixCoefficients::Bt470SystemM
);
assert_ne!(
MatrixCoefficients::from_wire(1).name(),
MatrixCoefficients::from_wire(6).name()
);
}
#[test]
fn video_format_matches_table_e2() {
const MEANINGS: [&str; 8] = [
"Component",
"PAL",
"NTSC",
"SECAM",
"MAC",
"Unspecified video format",
"reserved",
"reserved",
];
for (code, label) in (0u8..).zip(MEANINGS) {
let f = VideoFormat::from_wire(code).expect("u(3) value");
assert_eq!(f.code(), code);
assert_eq!(f.name(), label);
}
assert_eq!(VideoFormat::from_wire(8), None);
assert_eq!(VideoFormat::from_wire(255), None);
}
#[test]
fn every_colour_code_round_trips_and_has_a_label() {
for code in 0..=u8::MAX {
let p = ColourPrimaries::from_wire(code);
let t = TransferCharacteristics::from_wire(code);
let m = MatrixCoefficients::from_wire(code);
assert_eq!((p.code(), t.code(), m.code()), (code, code, code));
for label in [p.name(), t.name(), m.name()] {
assert!(!label.is_empty());
}
let d = colour(code, code, code);
assert_eq!(d.primaries.code(), code);
assert_eq!(d.transfer.code(), code);
assert_eq!(d.matrix.code(), code);
}
assert_eq!(ColourPrimaries::from_wire(0), ColourPrimaries::Reserved(0));
assert_eq!(
TransferCharacteristics::from_wire(19),
TransferCharacteristics::Reserved(19)
);
assert_eq!(
MatrixCoefficients::from_wire(3),
MatrixCoefficients::Reserved(3)
);
assert_eq!(ColourPrimaries::Reserved(255).code(), 255u8);
}
fn avc_colour_sps(colour: ColourVui) -> Vec<u8> {
let mut nal = vec![0x67u8, 66, 0x00, 30];
let mut s = BitSink::new();
s.ue(0); s.ue(0); s.ue(2); s.ue(1); s.bit(false); s.ue(44); s.ue(35); s.bit(true); s.bit(true); s.bit(false); s.bit(true); put_aspect(&mut s, Some((1, None)));
put_colour_vui(&mut s, colour);
s.bit(false); s.bit(true); s.bits(1, 32); s.bits(50, 32); s.bit(true); s.bit(false); s.bit(false); s.bit(false); s.bit(false); s.bit(true); nal.extend(s.finish());
nal
}
fn hevc_colour_sps(colour: ColourVui) -> Vec<u8> {
let mut nal = vec![0x42u8, 0x01];
let mut s = BitSink::new();
s.bits(0, 4); s.bits(0, 3); s.bit(true); s.bits(0, 2); s.bit(false); s.bits(1, 5); s.bits(0, 32); s.bits(0, 48); s.bits(93, 8); s.ue(0); s.ue(1); s.ue(16); s.ue(16); s.bit(false); s.ue(0); s.ue(0); s.ue(3); s.bit(false); s.ue(0); s.ue(0); s.ue(0); s.ue(0);
s.ue(3);
s.ue(0);
s.ue(3); s.ue(1);
s.ue(1); s.bit(false); s.bit(false); s.bit(false); s.bit(false); s.ue(0); s.bit(false); s.bit(false); s.bit(false); s.bit(true); put_aspect(&mut s, Some((1, None)));
put_colour_vui(&mut s, colour);
s.bit(false); s.bit(false); s.bit(true); s.bit(true); s.bit(false); s.bit(false); s.bit(false); s.bit(true); nal.extend(s.finish());
nal
}
fn both_colour_geometries(v: ColourVui) -> [crate::ir::DisplayGeometry; 2] {
let avc = avc_colour_sps(v);
assert_eq!(decode_avc_sps(&avc).unwrap().fps, Some(25.0), "AVC walk");
let hevc = hevc_colour_sps(v);
let i = decode_hevc_interlace_sps(&hevc).expect("HEVC walk");
assert!(
i.field_seq && i.frame_field_info_present,
"HEVC walk past the colour block"
);
[
crate::ir::DisplayGeometry::from_avc_sps(&avc).unwrap(),
crate::ir::DisplayGeometry::from_hevc_sps(&hevc).unwrap(),
]
}
#[test]
fn absent_video_signal_type_is_none_with_the_spec_inferred_defaults() {
let g = both_colour_geometries(ColourVui::default());
for g in g {
assert_eq!(g.video_signal, None);
assert_eq!(g.overscan_appropriate, None);
assert!(!g.full_range());
assert_eq!(g.video_format(), VideoFormat::Unspecified);
let d = g.colour_description();
assert_eq!(d.primaries, ColourPrimaries::Unspecified);
assert_eq!(d.transfer, TransferCharacteristics::Unspecified);
assert_eq!(d.matrix, MatrixCoefficients::Unspecified);
}
}
#[test]
fn video_signal_without_colour_description_keeps_colour_none() {
let g = both_colour_geometries(ColourVui {
overscan: None,
vst: Some((0, true, None)),
});
for g in g {
assert_eq!(
g.video_signal,
Some(VideoSignalType::new(VideoFormat::Component, true, None))
);
assert!(g.full_range());
assert_eq!(g.video_format(), VideoFormat::Component);
assert_eq!(
g.colour_description().matrix,
MatrixCoefficients::Unspecified
);
}
}
#[test]
fn full_colour_description_is_read_in_spec_order() {
for (p, t, m) in [(5u8, 5u8, 5u8), (1, 1, 1), (9, 16, 9)] {
let g = both_colour_geometries(ColourVui {
overscan: None,
vst: Some((5, false, Some((p, t, m)))),
});
for g in g {
assert_eq!(g.colour_description(), colour(p, t, m));
assert_eq!(g.video_signal.unwrap().colour, Some(colour(p, t, m)));
assert!(!g.full_range());
}
}
}
#[test]
fn reserved_and_boundary_code_points_survive_the_walk() {
for (p, t, m) in [(0u8, 0u8, 0u8), (3, 3, 3), (255, 255, 255), (17, 19, 18)] {
let g = both_colour_geometries(ColourVui {
overscan: None,
vst: Some((5, false, Some((p, t, m)))),
});
for g in g {
let d = g.colour_description();
assert_eq!(
(d.primaries.code(), d.transfer.code(), d.matrix.code()),
(p, t, m)
);
}
}
}
#[test]
fn overscan_flag_precedes_the_video_signal_type() {
for appropriate in [true, false] {
let g = both_colour_geometries(ColourVui {
overscan: Some(appropriate),
vst: Some((5, true, Some((1, 1, 1)))),
});
for g in g {
assert_eq!(g.overscan_appropriate, Some(appropriate));
assert!(g.full_range());
assert_eq!(g.colour_description(), colour(1, 1, 1));
}
}
let g = both_colour_geometries(ColourVui {
overscan: Some(true),
vst: None,
});
for g in g {
assert_eq!(g.overscan_appropriate, Some(true));
assert_eq!(g.video_signal, None);
}
}
#[test]
fn hevc_every_table_e1_idc_decodes_to_its_ratio() {
for (idc, w, h) in SPEC_TABLE_E1 {
let g = hevc_geometry(Some((idc, None)));
assert_eq!(g.sample_aspect_ratio(), Some((w, h)), "H.265 idc {idc}");
assert_eq!(g.aspect_ratio.unwrap().idc(), idc);
assert_eq!((g.coded_width, g.coded_height), (16, 16));
}
}
#[test]
fn extended_sar_reads_width_then_height_for_both_codecs() {
for g in [
avc_geometry(Some((255, Some((64, 45))))),
hevc_geometry(Some((255, Some((64, 45))))),
] {
assert_eq!(
g.aspect_ratio,
Some(AspectRatioIdc::ExtendedSar {
sar_width: 64,
sar_height: 45
})
);
assert_eq!(g.sample_aspect_ratio(), Some((64, 45)));
}
let g = avc_geometry(Some((255, Some((0x1234, 0x0101)))));
assert_eq!(g.sample_aspect_ratio(), Some((0x1234, 0x0101)));
}
#[test]
fn unspecified_ratios_are_none_never_a_zero_sentinel() {
let cases: [AspectBits; 6] = [
Some((0, None)), Some((17, None)), Some((254, None)), Some((255, Some((0, 45)))), Some((255, Some((64, 0)))), Some((255, Some((0, 0)))), ];
for c in cases {
for g in [avc_geometry(c), hevc_geometry(c)] {
assert!(g.aspect_ratio.is_some(), "{c:?}");
assert_eq!(g.sample_aspect_ratio(), None, "{c:?}");
assert_eq!(g.typed_sample_aspect_ratio(), None, "{c:?}");
}
}
assert_eq!(
avc_geometry(Some((17, None))).aspect_ratio,
Some(AspectRatioIdc::Reserved(17))
);
assert_eq!(
avc_geometry(Some((254, None))).aspect_ratio,
Some(AspectRatioIdc::Reserved(254))
);
}
#[test]
fn absent_aspect_info_is_none_for_both_codecs() {
let g = crate::ir::DisplayGeometry::from_avc_sps(&avc_display_sps(false, None)).unwrap();
assert_eq!((g.aspect_ratio, g.sample_aspect_ratio()), (None, None));
let hevc = hevc_sps_full(false, false, false, 0, None);
let g = crate::ir::DisplayGeometry::from_hevc_sps(&hevc).unwrap();
assert_eq!((g.aspect_ratio, g.sample_aspect_ratio()), (None, None));
assert_eq!(avc_geometry(None).aspect_ratio, None);
assert_eq!(hevc_geometry(None).aspect_ratio, None);
}
#[test]
fn a_vui_truncated_inside_the_aspect_field_never_yields_a_half_read_value() {
let whole = avc_display_sps(true, Some((255, Some((64, 45)))));
let hevc_whole = hevc_sps_full(true, true, true, 0, Some((255, Some((64, 45)))));
let full = Some(AspectRatioIdc::ExtendedSar {
sar_width: 64,
sar_height: 45,
});
let mut absent_seen = false;
for cut in 0..whole.len() {
if let Ok(g) = crate::ir::DisplayGeometry::from_avc_sps(&whole[..cut]) {
assert!(
g.aspect_ratio.is_none() || g.aspect_ratio == full,
"avc cut {cut}"
);
absent_seen |= g.aspect_ratio.is_none();
}
}
for cut in 0..hevc_whole.len() {
if let Ok(g) = crate::ir::DisplayGeometry::from_hevc_sps(&hevc_whole[..cut]) {
assert!(
g.aspect_ratio.is_none() || g.aspect_ratio == full,
"hevc cut {cut}"
);
}
}
assert!(absent_seen);
let g = crate::ir::DisplayGeometry::from_avc_sps(&whole).unwrap();
assert_eq!(g.aspect_ratio, full);
}
fn hevc_sps_vui_hrd(
field_seq: bool,
ffi: bool,
num_units: u32,
time_scale: u32,
hrd: Option<(bool, bool, u32)>, ) -> Vec<u8> {
let mut nal = vec![0x42u8, 0x01];
let mut s = BitSink::new();
s.bits(0, 4); s.bits(0, 3); s.bit(true); s.bits(0, 2); s.bit(false); s.bits(1, 5); s.bits(0, 32); s.bits(0, 48); s.bits(93, 8); s.ue(0); s.ue(1); s.ue(16); s.ue(16); s.bit(false); s.ue(0); s.ue(0); s.ue(3); s.bit(false); s.ue(0);
s.ue(0);
s.ue(0);
s.ue(0);
s.ue(3);
s.ue(0);
s.ue(3);
s.ue(1);
s.ue(1);
s.bit(false); s.bit(false); s.bit(false); s.bit(false); s.ue(0); s.bit(false); s.bit(false); s.bit(false); s.bit(true); s.bit(false); s.bit(false); s.bit(false); s.bit(false); s.bit(false); s.bit(field_seq); s.bit(ffi); s.bit(false); s.bit(true); s.bits(u64::from(num_units), 32);
s.bits(u64::from(time_scale), 32);
s.bit(false); if let Some((general, within, ed)) = hrd {
s.bit(true); s.bit(true); s.bit(false); s.bit(false); s.bits(0, 4); s.bits(0, 4); s.bits(0, 5); s.bits(0, 5); s.bits(0, 5); s.bit(general); if !general {
s.bit(within); }
if general || within {
s.ue(u64::from(ed)); } else {
s.bit(false); s.ue(0); }
s.ue(0); s.ue(0); s.bit(false); } else {
s.bit(false); }
s.bit(false); s.bit(true); nal.extend(s.finish());
nal
}
fn hevc_cfg_for_sps(sps: Vec<u8>) -> crate::pipeline::CodecConfig {
crate::pipeline::CodecConfig::Hevc {
config: crate::hevc_config::HEVCConfigurationBox::new(
crate::hevc_config::HEVCDecoderConfigurationRecord {
configuration_version: 1,
general_profile_space: 0,
general_tier_flag: false,
general_profile_idc: 1,
general_profile_compatibility_flags: 0,
general_constraint_indicator_flags: 0,
general_level_idc: 93,
min_spatial_segmentation_idc: 0,
parallelism_type: 0,
chroma_format_idc: 1,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
avg_frame_rate: 0,
constant_frame_rate: 0,
num_temporal_layers: 1,
temporal_id_nested: true,
length_size_minus_one: 3,
arrays: vec![crate::nalu_types::HevcNalArray {
array_completeness: true,
nal_unit_type: 33,
nalus: vec![crate::nalu_types::HevcNalUnit(sps)],
}],
},
),
width: 320,
height: 240,
}
}
#[test]
fn hevc_vui_hrd_cases() {
use crate::pipeline::FrameRateBasis::Normative;
type Hrd = Option<(bool, bool, u32)>;
struct Case {
name: &'static str,
field_seq: bool,
ffip: bool,
num_units: u32,
hrd: Hrd,
fixed_pic_rate: Option<Option<u32>>,
field_seq_out: Option<bool>,
rate: Option<Option<(u32, crate::pipeline::FrameRateBasis)>>,
}
let cases = [
Case {
name: "ed_one_halves_rate",
field_seq: false,
ffip: false,
num_units: 1,
hrd: Some((true, true, 1)),
fixed_pic_rate: Some(Some(2)),
field_seq_out: None,
rate: Some(Some((12500, Normative))),
},
Case {
name: "field_ed_one_quarters",
field_seq: true,
ffip: true,
num_units: 1,
hrd: Some((true, true, 1)),
fixed_pic_rate: Some(Some(2)),
field_seq_out: Some(true),
rate: None,
},
Case {
name: "ffip_one_field_seq_zero_none",
field_seq: false,
ffip: true,
num_units: 1,
hrd: Some((true, true, 0)),
fixed_pic_rate: None,
field_seq_out: None,
rate: Some(None),
},
Case {
name: "general_zero_within_one_tier_b_or_a",
field_seq: false,
ffip: false,
num_units: 1,
hrd: Some((false, true, 0)),
fixed_pic_rate: Some(Some(1)),
field_seq_out: None,
rate: None,
},
Case {
name: "general_zero_within_zero_tier_b",
field_seq: false,
ffip: false,
num_units: 1,
hrd: Some((false, false, 0)),
fixed_pic_rate: Some(None),
field_seq_out: None,
rate: None,
},
Case {
name: "field_seq_one_no_hrd_none",
field_seq: true,
ffip: true,
num_units: 1,
hrd: None,
fixed_pic_rate: None,
field_seq_out: None,
rate: Some(None),
},
Case {
name: "zero_num_units_none",
field_seq: false,
ffip: false,
num_units: 0,
hrd: Some((true, true, 0)),
fixed_pic_rate: None,
field_seq_out: None,
rate: Some(None),
},
];
for c in cases {
let sps = hevc_sps_vui_hrd(c.field_seq, c.ffip, c.num_units, 25, c.hrd);
if let Some(expected) = c.fixed_pic_rate {
let t = crate::sps::decode_hevc_vui_timing(&sps).unwrap().unwrap();
assert_eq!(t.hrd_fixed_pic_rate, expected, "{}", c.name);
if let Some(fs) = c.field_seq_out {
assert_eq!(t.field_seq, fs, "{}", c.name);
}
}
if let Some(expected) = c.rate {
assert_eq!(
crate::pipeline::nominal_frame_rate(&hevc_cfg_for_sps(sps)),
expected,
"{}",
c.name
);
}
}
}
#[test]
fn hevc_field_seq_one_ffip_zero_no_hrd_is_none() {
let sps = hevc_sps_vui_hrd(true, false, 1, 25, None);
let cfg = crate::pipeline::CodecConfig::Hevc {
config: crate::hevc_config::HEVCConfigurationBox::new(
crate::hevc_config::HEVCDecoderConfigurationRecord {
configuration_version: 1,
general_profile_space: 0,
general_tier_flag: false,
general_profile_idc: 1,
general_profile_compatibility_flags: 0,
general_constraint_indicator_flags: 0,
general_level_idc: 93,
min_spatial_segmentation_idc: 0,
parallelism_type: 0,
chroma_format_idc: 1,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
avg_frame_rate: 0,
constant_frame_rate: 0,
num_temporal_layers: 1,
temporal_id_nested: true,
length_size_minus_one: 3,
arrays: vec![crate::nalu_types::HevcNalArray {
array_completeness: true,
nal_unit_type: 33,
nalus: vec![crate::nalu_types::HevcNalUnit(sps)],
}],
},
),
width: 320,
height: 240,
};
assert_eq!(crate::pipeline::nominal_frame_rate(&cfg), None);
}
#[test]
fn hevc_vps_timing_synthetic_valid() {
let mut nal = vec![0x40u8, 0x01]; let mut s = BitSink::new();
s.bits(0, 4);
s.bit(true);
s.bit(true);
s.bits(0, 6);
s.bits(0, 3);
s.bit(true);
s.bits(0xffff, 16);
s.bits(0, 2); s.bit(false); s.bits(1, 5); s.bits(0, 32); s.bits(0, 48); s.bits(0x21, 8); s.bit(true);
s.ue(0);
s.ue(0);
s.ue(0);
s.bits(0, 6);
s.ue(0);
s.bit(true);
s.bits(7, 32);
s.bits(9, 32);
nal.extend(s.finish());
let t = decode_hevc_vps_timing(&nal)
.expect("parse")
.expect("timing");
assert_eq!(t.num_units_in_tick, 7);
assert_eq!(t.time_scale, 9);
}
#[test]
fn hevc_vps_timing_synthetic_with_layer_set() {
let mut nal = vec![0x40u8, 0x01];
let mut s = BitSink::new();
s.bits(0, 4); s.bit(true); s.bit(true); s.bits(0, 6); s.bits(0, 3); s.bit(true); s.bits(0xffff, 16); s.bits(0, 2);
s.bit(false);
s.bits(1, 5);
s.bits(0, 32);
s.bits(0, 48);
s.bits(0x21, 8);
s.bit(true); s.ue(0);
s.ue(0);
s.ue(0);
s.bits(0, 6); s.ue(1); s.bit(true);
s.bit(true); s.bits(7, 32);
s.bits(9, 32);
nal.extend(s.finish());
let t = decode_hevc_vps_timing(&nal)
.expect("parse")
.expect("timing");
assert_eq!(t.num_units_in_tick, 7);
assert_eq!(t.time_scale, 9);
}
#[test]
fn hevc_vps_timing_synthetic_boundary_rejected() {
let mut nal = vec![0x40u8, 0x01];
let mut s = BitSink::new();
s.bits(0, 4); s.bit(true); s.bit(true); s.bits(0, 6); s.bits(0, 3); s.bit(true); s.bits(0xffff, 16); s.bits(0, 2);
s.bit(false);
s.bits(1, 5);
s.bits(0, 32);
s.bits(0, 48);
s.bits(0x21, 8);
s.bit(true); s.ue(0);
s.ue(0);
s.ue(0);
s.bits(0, 6); s.ue(1024);
s.bit(true);
s.bits(1, 32);
s.bits(1, 32);
nal.extend(s.finish());
let r = decode_hevc_vps_timing(&nal);
assert!(r.is_err(), "expected Err, got {:?}", r);
}
fn synth_vps(layer_sets_minus1: u32, num_units: u32, time_scale: u32) -> Vec<u8> {
let mut nal = vec![0x40u8, 0x01];
let mut s = BitSink::new();
s.bits(0, 4);
s.bit(true);
s.bit(true);
s.bits(0, 6);
s.bits(0, 3);
s.bit(true);
s.bits(0xffff, 16);
s.bits(0, 2);
s.bit(false);
s.bits(1, 5);
s.bits(0, 32);
s.bits(0, 48);
s.bits(0x21, 8);
s.bit(true);
s.ue(0);
s.ue(0);
s.ue(0);
s.bits(0, 6); s.ue(u64::from(layer_sets_minus1));
for _ in 0..layer_sets_minus1 {
s.bit(false); }
s.bit(true); s.bits(u64::from(num_units), 32);
s.bits(u64::from(time_scale), 32);
nal.extend(s.finish());
nal
}
#[test]
fn hevc_vps_layer_set_bound_is_exactly_1023() {
let ok = decode_hevc_vps_timing(&synth_vps(1023, 7, 9)).expect("1023 is in range");
let t = ok.expect("timing present");
assert_eq!((t.num_units_in_tick, t.time_scale), (7, 9));
assert!(
decode_hevc_vps_timing(&synth_vps(1024, 7, 9)).is_err(),
"1024 layer sets is out of range"
);
}
#[test]
fn hevc_vps_zero_timing_values_are_not_timing() {
assert_eq!(
decode_hevc_vps_timing(&synth_vps(0, 0, 9)).expect("parse"),
None
);
assert_eq!(
decode_hevc_vps_timing(&synth_vps(0, 7, 0)).expect("parse"),
None
);
assert!(
decode_hevc_vps_timing(&synth_vps(0, 7, 9))
.expect("parse")
.is_some()
);
}
}