use crate::bitreader::BitReader;
use crate::error::{Error, Result};
use alloc::string::String;
const H264_HIGH_PROFILES: &[u8] = &[100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135];
fn is_high_profile(profile_idc: u8) -> bool {
H264_HIGH_PROFILES.contains(&profile_idc)
}
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, 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> {
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")?;
let (chroma_format_idc, separate_colour_plane, bit_depth_luma, bit_depth_chroma) =
if is_high_profile(profile_idc) {
let chroma_format_idc = r.read_ue("chroma_format_idc")? as u8;
let separate_colour_plane = if chroma_format_idc == 3 {
r.read_flag("separate_colour_plane_flag")?
} else {
false
};
let bit_depth_luma_minus8 = r.read_ue("bit_depth_luma_minus8")? as u8;
let bit_depth_chroma_minus8 = r.read_ue("bit_depth_chroma_minus8")? as u8;
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_minus8 + 8,
bit_depth_chroma_minus8 + 8,
)
} else {
(1, false, 8, 8)
};
let _ = r.read_ue("log2_max_frame_num_minus4")?;
let pic_order_cnt_type = r.read_ue("pic_order_cnt_type")?;
if pic_order_cnt_type == 0 {
let _ = r.read_ue("log2_max_pic_order_cnt_lsb_minus4")?;
} else if pic_order_cnt_type == 1 {
let _ = 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")?;
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")?;
if !frame_mbs_only {
let _ = 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 crop_unit_x = sub_width_c(chroma_format_idc) as u64;
let crop_unit_y = sub_height_c(chroma_format_idc) as u64 * (2 - frame_mbs_only as u64);
let width = (pic_width_in_mbs * 16).saturating_sub(crop_unit_x * (crop_left + crop_right));
let height = (frame_height_in_mbs * 16).saturating_sub(crop_unit_y * (crop_top + crop_bottom));
let (num_units_in_tick, time_scale, fps) = parse_avc_vui_timing(&mut r);
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,
})
}
fn parse_avc_vui_timing(r: &mut BitReader) -> (Option<u32>, Option<u32>, Option<f32>) {
parse_avc_vui_timing_inner(r).unwrap_or((None, None, None))
}
fn parse_avc_vui_timing_inner(
r: &mut BitReader,
) -> crate::error::Result<(Option<u32>, Option<u32>, Option<f32>)> {
let vui_present = r.read_flag("vui_parameters_present_flag")?;
if !vui_present {
return Ok((None, None, None));
}
let ari_present = r.read_flag("aspect_ratio_info_present_flag")?;
if ari_present {
let aspect_ratio_idc = r.read_bits(8, "aspect_ratio_idc")? as u8;
if aspect_ratio_idc == 255 {
let _ = r.read_bits(16, "sar_width")?;
let _ = r.read_bits(16, "sar_height")?;
}
}
let overscan_present = r.read_flag("overscan_info_present_flag")?;
if overscan_present {
let _ = r.read_flag("overscan_appropriate_flag")?;
}
let vst_present = r.read_flag("video_signal_type_present_flag")?;
if vst_present {
let _ = r.read_bits(3, "video_format")?;
let _ = r.read_flag("video_full_range_flag")?;
let colour_desc = r.read_flag("colour_description_present_flag")?;
if colour_desc {
let _ = r.read_bits(8, "colour_primaries")?;
let _ = r.read_bits(8, "transfer_characteristics")?;
let _ = r.read_bits(8, "matrix_coefficients")?;
}
}
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 {
return Ok((None, None, None));
}
let num_units = r.read_bits(32, "num_units_in_tick")? as u32;
let ts = r.read_bits(32, "time_scale")? as u32;
let _ = 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
};
Ok((Some(num_units), Some(ts), fps))
}
#[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> {
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 = r.read_ue("chroma_format_idc")? as u8;
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_minus8 = r.read_ue("bit_depth_luma_minus8")? as u8;
let bit_depth_chroma_minus8 = r.read_ue("bit_depth_chroma_minus8")? as u8;
let sub_width_c = match chroma_format_idc {
1 | 2 => 2,
3 => 1,
_ => 1,
};
let sub_height_c = match chroma_format_idc {
1 => 2,
2 | 3 => 1,
_ => 1,
};
let width = pic_width_in_luma_samples
.saturating_sub((sub_width_c as u64) * (conf_win_left + conf_win_right));
let height = pic_height_in_luma_samples
.saturating_sub((sub_height_c as u64) * (conf_win_top + conf_win_bottom));
let (num_units_in_tick, time_scale, fps) =
parse_hevc_sps_to_vui_timing(&mut r, sps_max_sub_layers_minus1);
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_luma_minus8 + 8,
bit_depth_chroma: bit_depth_chroma_minus8 + 8,
width: width as u32,
height: height as u32,
num_units_in_tick,
time_scale,
fps,
})
}
fn parse_hevc_sps_to_vui_timing(
r: &mut BitReader,
sps_max_sub_layers_minus1: u8,
) -> (Option<u32>, Option<u32>, Option<f32>) {
parse_hevc_sps_to_vui_timing_inner(r, sps_max_sub_layers_minus1).unwrap_or((None, None, None))
}
fn parse_hevc_sps_to_vui_timing_inner(
r: &mut BitReader,
sps_max_sub_layers_minus1: u8,
) -> crate::error::Result<(Option<u32>, Option<u32>, Option<f32>)> {
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")?;
let mut prev_num_delta_pocs: u64 = 0;
for i in 0..num_short_term {
let inter_ref = if i != 0 {
r.read_flag("inter_ref_pic_set_prediction_flag")?
} else {
false
};
if inter_ref {
return Ok((None, None, None));
}
let num_negative = r.read_ue("num_negative_pics")?;
let num_positive = r.read_ue("num_positive_pics")?;
prev_num_delta_pocs = num_negative + num_positive;
for _j in 0..num_negative {
let _ = r.read_ue("delta_poc_s0_minus1")?;
let _ = r.read_flag("used_by_curr_pic_s0_flag")?;
}
for _j in 0..num_positive {
let _ = r.read_ue("delta_poc_s1_minus1")?;
let _ = r.read_flag("used_by_curr_pic_s1_flag")?;
}
}
let _ = prev_num_delta_pocs;
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));
}
let ari_present = r.read_flag("aspect_ratio_info_present_flag")?;
if ari_present {
let aspect_ratio_idc = r.read_bits(8, "aspect_ratio_idc")? as u8;
if aspect_ratio_idc == 255 {
let _ = r.read_bits(16, "sar_width")?;
let _ = r.read_bits(16, "sar_height")?;
}
}
let overscan_present = r.read_flag("overscan_info_present_flag")?;
if overscan_present {
let _ = r.read_flag("overscan_appropriate_flag")?;
}
let vst_present = r.read_flag("video_signal_type_present_flag")?;
if vst_present {
let _ = r.read_bits(3, "video_format")?;
let _ = r.read_flag("video_full_range_flag")?;
let colour_desc = r.read_flag("colour_description_present_flag")?;
if colour_desc {
let _ = r.read_bits(8, "colour_primaries")?;
let _ = r.read_bits(8, "transfer_characteristics")?;
let _ = r.read_bits(8, "matrix_coefficients")?;
}
}
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")?;
let _ = r.read_flag("field_seq_flag")?;
let _ = r.read_flag("frame_field_info_present_flag")?;
let ddw_present = r.read_flag("default_display_window_flag")?;
if ddw_present {
let _ = r.read_ue("def_disp_win_left_offset")?;
let _ = r.read_ue("def_disp_win_right_offset")?;
let _ = r.read_ue("def_disp_win_top_offset")?;
let _ = r.read_ue("def_disp_win_bottom_offset")?;
}
let timing_present = r.read_flag("vui_timing_info_present_flag")?;
if !timing_present {
return Ok((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 fps = if num_units > 0 {
Some(ts as f32 / num_units as f32)
} else {
None
};
Ok((Some(num_units), Some(ts), fps))
}
#[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")?;
let gci_present = r.read_flag("gci_present_flag")?;
if gci_present {
return Err(Error::InvalidValue {
field: "gci_present_flag",
value: 1,
reason: "general_constraint_info block not decoded (unsupported in this SPS reader)",
});
}
r.align_to_byte("gci_alignment_zero_bit")?;
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))
}
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,
}
}
pub fn rfc6381_hvc1(info: &HevcSpsInfo) -> String {
let mut s = String::with_capacity(48);
s.push_str("hvc1.");
let profile_byte = (info.general_profile_space << 5) | info.general_profile_idc;
write_hex_byte(&mut s, profile_byte);
s.push('.');
write_compat_flags(&mut s, info.general_profile_compatibility_flags);
s.push('.');
let tier_char = if info.general_tier_flag { 'H' } else { 'L' };
s.push(tier_char);
write_hex_byte(&mut s, 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
}
fn write_compat_flags(s: &mut String, flags: u32) {
let reversed = flags.reverse_bits();
let mut hex = [0u8; 8];
let mut v = reversed;
for i in (0..8).rev() {
let nib = (v & 0xF) as u8;
hex[i] = if nib < 10 {
b'0' + nib
} else {
b'A' + (nib - 10)
};
v >>= 4;
}
let end = (0..8)
.rev()
.find(|&i| hex[i] != b'0')
.map(|i| i + 1)
.unwrap_or(1);
for &b in &hex[..end] {
s.push(b as char);
}
}
pub fn rfc6381_vvc1(
general_profile_idc: u8,
general_tier_flag: bool,
general_level_idc: u8,
general_constraint_info: u64,
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 && general_constraint_info != 0 {
let n = num_bytes_constraint_info as usize;
let payload_bits = n * 8 - 2;
let aligned = general_constraint_info << (n * 8 - payload_bits);
let mut bytes = [0u8; 8];
for (i, b) in bytes.iter_mut().enumerate() {
let shift = (n - 1 - i) * 8;
*b = if i < n {
((aligned >> shift) & 0xFF) as u8
} else {
0
};
}
let last = bytes[..n].iter().rposition(|&b| b != 0);
if let Some(end) = last {
s.push_str(".CTA-");
for &b in &bytes[..=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);
}
}
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::*;
#[test]
fn rfc6381_avc1_baseline() {
assert_eq!(rfc6381_avc1(66, 0xC0, 13), "avc1.42C00D");
}
#[test]
fn rfc6381_avc1_main() {
assert_eq!(rfc6381_avc1(77, 0x40, 13), "avc1.4D400D");
}
#[test]
fn rfc6381_avc1_high() {
assert_eq!(rfc6381_avc1(100, 0x00, 13), "avc1.64000D");
}
#[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"
);
}
}
}