use crate::frame::PixelFormat;
pub fn detect(codec: &str, samples: &[Vec<u8>]) -> PixelFormat {
if samples.is_empty() {
return PixelFormat::Yuv420p;
}
let result = match codec.to_lowercase().as_str() {
"h264" | "avc1" | "avc" => detect_h264(&samples[0]),
"h265" | "hevc" | "hvc1" | "hev1" => detect_hevc(&samples[0]),
"vp9" | "vp09" => detect_vp9(&samples[0]),
"av1" | "av01" => detect_av1(&samples[0]),
_ => None,
};
result.unwrap_or(PixelFormat::Yuv420p)
}
struct BitReader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn read_bits(&mut self, n: usize) -> Option<u32> {
let mut val = 0u32;
for _ in 0..n {
let byte_idx = self.pos / 8;
let bit_idx = 7 - (self.pos % 8);
if byte_idx >= self.data.len() {
return None;
}
val = (val << 1) | (((self.data[byte_idx] >> bit_idx) & 1) as u32);
self.pos += 1;
}
Some(val)
}
fn read_ue(&mut self) -> Option<u32> {
let mut zeros = 0;
while self.read_bits(1)? == 0 {
zeros += 1;
if zeros > 31 {
return None;
}
}
if zeros == 0 {
return Some(0);
}
let suffix = self.read_bits(zeros)?;
Some((1u32 << zeros) - 1 + suffix)
}
fn read_se(&mut self) -> Option<i32> {
let code = self.read_ue()? as i64;
let signed = if code & 1 == 1 {
((code + 1) / 2) as i32
} else {
-((code / 2) as i32)
};
Some(signed)
}
fn bit_pos(&self) -> usize {
self.pos
}
fn byte_align(&mut self) {
let rem = self.pos & 7;
if rem != 0 {
self.pos += 8 - rem;
}
}
fn read_su(&mut self, n: usize) -> Option<i32> {
let raw = self.read_bits(n)?;
let sign_bit = 1u32 << (n - 1);
let signed = if raw & sign_bit != 0 {
(raw as i32) - (1i32 << n)
} else {
raw as i32
};
Some(signed)
}
}
fn detect_h264(sample: &[u8]) -> Option<PixelFormat> {
let sps = find_h264_sps(sample)?;
let rbsp = remove_h264_rbsp_stuffing(sps);
let mut br = BitReader::new(&rbsp);
let profile_idc = br.read_bits(8)? as u8;
let _constraint_flags = br.read_bits(8)?;
let _level_idc = br.read_bits(8)?;
let _seq_parameter_set_id = br.read_ue()?;
let profile_gates_chroma = matches!(
profile_idc,
100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135
);
let (chroma_format_idc, bit_depth_luma) = if profile_gates_chroma {
let chroma_format_idc = br.read_ue()? as u8;
if chroma_format_idc == 3 {
let _separate_colour_plane_flag = br.read_bits(1)?;
}
let bit_depth_luma_minus8 = br.read_ue()? as u8;
(chroma_format_idc, bit_depth_luma_minus8 + 8)
} else {
(1, 8)
};
Some(PixelFormat::from_chroma_and_depth(
chroma_format_idc,
bit_depth_luma,
))
}
fn find_h264_sps(data: &[u8]) -> Option<&[u8]> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
if nal_byte >= data.len() {
return None;
}
let nal_unit_type = data[nal_byte] & 0x1F;
if nal_unit_type == 7 {
let start = nal_byte + 1;
let end = find_next_start_code(&data[start..])
.map(|off| start + off)
.unwrap_or(data.len());
return Some(&data[start..end]);
}
i += start_len;
}
None
}
fn find_next_start_code(data: &[u8]) -> Option<usize> {
(0..data.len().saturating_sub(3)).find(|&i| {
data[i] == 0
&& data[i + 1] == 0
&& (data[i + 2] == 1 || (data[i + 2] == 0 && data[i + 3] == 1))
})
}
fn remove_h264_rbsp_stuffing(sps: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(sps.len());
let mut i = 0;
while i < sps.len() {
if i + 2 < sps.len() && sps[i] == 0 && sps[i + 1] == 0 && sps[i + 2] == 3 {
out.push(0);
out.push(0);
i += 3;
} else {
out.push(sps[i]);
i += 1;
}
}
out
}
fn detect_hevc(sample: &[u8]) -> Option<PixelFormat> {
let sps = find_hevc_sps(sample)?;
let rbsp = remove_h264_rbsp_stuffing(sps);
let mut br = BitReader::new(&rbsp);
let _sps_video_parameter_set_id = br.read_bits(4)?;
let sps_max_sub_layers_minus1 = br.read_bits(3)? as usize;
let _sps_temporal_id_nesting_flag = br.read_bits(1)?;
skip_hevc_profile_tier_level(&mut br, sps_max_sub_layers_minus1)?;
let _sps_seq_parameter_set_id = br.read_ue()?;
let chroma_format_idc = br.read_ue()? as u8;
if chroma_format_idc == 3 {
let _separate_colour_plane_flag = br.read_bits(1)?;
}
let _pic_width = br.read_ue()?;
let _pic_height = br.read_ue()?;
let conformance_window_flag = br.read_bits(1)?;
if conformance_window_flag == 1 {
let _ = br.read_ue()?;
let _ = br.read_ue()?;
let _ = br.read_ue()?;
let _ = br.read_ue()?;
}
let bit_depth_luma = br.read_ue()? as u8 + 8;
let _bit_depth_chroma_minus8 = br.read_ue()?;
Some(PixelFormat::from_chroma_and_depth(
chroma_format_idc,
bit_depth_luma,
))
}
fn find_hevc_sps(data: &[u8]) -> Option<&[u8]> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
if nal_byte + 1 >= data.len() {
return None;
}
let nal_unit_type = (data[nal_byte] >> 1) & 0x3F;
if nal_unit_type == 33 {
let start = nal_byte + 2;
let end = find_next_start_code(&data[start..])
.map(|off| start + off)
.unwrap_or(data.len());
return Some(&data[start..end]);
}
i += start_len;
}
None
}
fn skip_hevc_profile_tier_level(br: &mut BitReader, max_sub_layers_minus1: usize) -> Option<()> {
let _ = br.read_bits(8)?;
let _ = br.read_bits(32)?;
let _ = br.read_bits(48)?;
let _ = br.read_bits(8)?;
let mut sub_layer_profile_present = Vec::with_capacity(max_sub_layers_minus1);
let mut sub_layer_level_present = Vec::with_capacity(max_sub_layers_minus1);
for _ in 0..max_sub_layers_minus1 {
sub_layer_profile_present.push(br.read_bits(1)?);
sub_layer_level_present.push(br.read_bits(1)?);
}
if max_sub_layers_minus1 > 0 {
for _ in max_sub_layers_minus1..8 {
let _ = br.read_bits(2)?;
}
}
for i in 0..max_sub_layers_minus1 {
if sub_layer_profile_present[i] == 1 {
let _ = br.read_bits(8)?;
let _ = br.read_bits(32)?;
let _ = br.read_bits(48)?;
}
if sub_layer_level_present[i] == 1 {
let _ = br.read_bits(8)?;
}
}
Some(())
}
fn detect_vp9(sample: &[u8]) -> Option<PixelFormat> {
if sample.len() < 2 {
return None;
}
let mut br = BitReader::new(sample);
let frame_marker = br.read_bits(2)?;
if frame_marker != 2 {
return None;
}
let profile_low = br.read_bits(1)?;
let profile_high = br.read_bits(1)?;
let profile = (profile_high << 1) | profile_low;
if profile == 3 {
let _reserved_zero = br.read_bits(1)?;
}
let show_existing_frame = br.read_bits(1)?;
if show_existing_frame == 1 {
return None;
}
let frame_type = br.read_bits(1)?;
let _show_frame = br.read_bits(1)?;
let _error_resilient = br.read_bits(1)?;
if frame_type != 0 {
return None;
}
let sync = br.read_bits(24)?;
if sync != 0x498342 {
return None;
}
let bit_depth = if profile >= 2 {
if br.read_bits(1)? == 0 { 10 } else { 12 }
} else {
8
};
let _color_space = br.read_bits(3)?;
let (sx, sy) = if profile == 1 || profile == 3 {
let _color_range = br.read_bits(1)?;
let sx = br.read_bits(1)?;
let sy = br.read_bits(1)?;
(sx, sy)
} else {
(1, 1) };
let chroma_idc = match (sx, sy) {
(1, 1) => 1, (1, 0) => 2, (0, 0) => 3, _ => 1,
};
Some(PixelFormat::from_chroma_and_depth(chroma_idc, bit_depth))
}
fn detect_av1(sample: &[u8]) -> Option<PixelFormat> {
let obu = find_av1_obu(sample, 1)?;
let mut br = BitReader::new(obu);
let _seq_profile = br.read_bits(3)?;
let _still_picture = br.read_bits(1)?;
let reduced_still_picture_header = br.read_bits(1)?;
if reduced_still_picture_header == 0 {
let timing_info_present = br.read_bits(1)?;
if timing_info_present == 1 {
let _num_units_in_display_tick = br.read_bits(32)?;
let _time_scale = br.read_bits(32)?;
let equal_picture_interval = br.read_bits(1)?;
if equal_picture_interval == 1 {
let _num_ticks_per_picture = br.read_ue()?; }
let decoder_model_info_present = br.read_bits(1)?;
if decoder_model_info_present == 1 {
let _buffer_delay_length_minus_1 = br.read_bits(5)?;
let _num_units_in_decoding_tick = br.read_bits(32)?;
let _buffer_removal_time_length_minus_1 = br.read_bits(5)?;
let _frame_presentation_time_length_minus_1 = br.read_bits(5)?;
}
}
return Some(PixelFormat::Yuv420p);
}
let _seq_level_idx_0 = br.read_bits(5)?;
Some(PixelFormat::Yuv420p)
}
fn find_av1_obu(data: &[u8], target_type: u8) -> Option<&[u8]> {
find_av1_obu_with_offset(data, target_type).map(|(bytes, _)| bytes)
}
pub fn find_av1_obu_with_offset_pub(data: &[u8], target_type: u8) -> Option<(&[u8], usize)> {
find_av1_obu_with_offset(data, target_type)
}
fn find_av1_obu_with_offset(data: &[u8], target_type: u8) -> Option<(&[u8], usize)> {
let mut i = 0;
while i < data.len() {
let header = data[i];
let obu_type = (header >> 3) & 0x0F;
let extension_flag = (header >> 2) & 0x01;
let has_size_field = (header >> 1) & 0x01;
i += 1;
if extension_flag == 1 {
i += 1;
}
if has_size_field == 0 {
return None;
}
let (size, leb_bytes) = read_leb128(&data[i..])?;
i += leb_bytes;
if obu_type == target_type {
let end = (i + size as usize).min(data.len());
return Some((&data[i..end], i));
}
i += size as usize;
}
None
}
fn read_leb128(data: &[u8]) -> Option<(u64, usize)> {
let mut value = 0u64;
for i in 0..8 {
if i >= data.len() {
return None;
}
let byte = data[i];
value |= ((byte & 0x7F) as u64) << (i * 7);
if byte & 0x80 == 0 {
return Some((value, i + 1));
}
}
None
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct H264SpsInfo {
pub profile_idc: u8,
pub constraint_set_flags: u8,
pub level_idc: u8,
pub chroma_format_idc: u8,
pub separate_colour_plane_flag: bool,
pub bit_depth_luma: u8,
pub bit_depth_chroma: u8,
pub frame_mbs_only: bool,
pub width: Option<u32>,
pub height: Option<u32>,
pub log2_max_frame_num_minus4: Option<u8>,
pub pic_order_cnt_type: Option<u8>,
pub log2_max_pic_order_cnt_lsb_minus4: Option<u8>,
pub delta_pic_order_always_zero_flag: Option<bool>,
pub qpprime_y_zero_transform_bypass_flag: Option<bool>,
pub seq_scaling_matrix_present_flag: Option<bool>,
pub max_num_ref_frames: Option<u8>,
pub gaps_in_frame_num_value_allowed_flag: Option<bool>,
pub mb_adaptive_frame_field_flag: Option<bool>,
pub direct_8x8_inference_flag: Option<bool>,
pub frame_cropping_flag: Option<bool>,
pub frame_crop_left_offset: Option<u32>,
pub frame_crop_right_offset: Option<u32>,
pub frame_crop_top_offset: Option<u32>,
pub frame_crop_bottom_offset: Option<u32>,
pub offset_for_non_ref_pic: Option<i32>,
pub offset_for_top_to_bottom_field: Option<i32>,
pub num_ref_frames_in_pic_order_cnt_cycle: Option<u8>,
pub offset_for_ref_frame: Vec<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HevcSpsInfo {
pub sps_video_parameter_set_id: u8,
pub sps_seq_parameter_set_id: u8,
pub sps_max_sub_layers_minus1: u8,
pub sps_temporal_id_nesting_flag: bool,
pub chroma_format_idc: u8,
pub separate_colour_plane_flag: bool,
pub bit_depth_luma: u8,
pub bit_depth_chroma: u8,
pub width: Option<u32>,
pub height: Option<u32>,
pub conf_win_left_offset: u32,
pub conf_win_right_offset: u32,
pub conf_win_top_offset: u32,
pub conf_win_bottom_offset: u32,
pub log2_max_pic_order_cnt_lsb_minus4: u8,
pub log2_min_luma_coding_block_size_minus3: u8,
pub log2_diff_max_min_luma_coding_block_size: u8,
pub log2_min_luma_transform_block_size_minus2: u8,
pub log2_diff_max_min_luma_transform_block_size: u8,
pub max_transform_hierarchy_depth_inter: u8,
pub max_transform_hierarchy_depth_intra: u8,
pub scaling_list_enabled_flag: bool,
pub sps_sub_layer_ordering_info_present_flag: bool,
pub amp_enabled_flag: bool,
pub sample_adaptive_offset_enabled_flag: bool,
pub pcm_enabled_flag: bool,
pub pcm_loop_filter_disabled_flag: bool,
pub num_short_term_ref_pic_sets: u8,
pub long_term_ref_pics_present_flag: bool,
pub sps_temporal_mvp_enabled_flag: bool,
pub strong_intra_smoothing_enabled_flag: bool,
pub profile_idc: u8,
pub level_idc: u8,
pub tier_flag: bool,
pub max_dec_pic_buffering_minus1: [u8; 7],
pub max_num_reorder_pics: [u8; 7],
pub max_latency_increase_plus1: [u32; 7],
pub profile_compatibility_flags: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct H265VpsInfo {
pub vps_video_parameter_set_id: u8,
pub vps_max_sub_layers_minus1: u8,
pub vps_temporal_id_nesting_flag: bool,
pub profile_idc: u8,
pub level_idc: u8,
pub tier_flag: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct H265PpsInfo {
pub pps_pic_parameter_set_id: u8,
pub pps_seq_parameter_set_id: u8,
pub dependent_slice_segments_enabled_flag: bool,
pub output_flag_present_flag: bool,
pub num_extra_slice_header_bits: u8,
pub sign_data_hiding_enabled_flag: bool,
pub cabac_init_present_flag: bool,
pub num_ref_idx_l0_default_active_minus1: u8,
pub num_ref_idx_l1_default_active_minus1: u8,
pub init_qp_minus26: i8,
pub constrained_intra_pred_flag: bool,
pub transform_skip_enabled_flag: bool,
pub cu_qp_delta_enabled_flag: bool,
pub diff_cu_qp_delta_depth: u8,
pub pps_cb_qp_offset: i8,
pub pps_cr_qp_offset: i8,
pub pps_slice_chroma_qp_offsets_present_flag: bool,
pub weighted_pred_flag: bool,
pub weighted_bipred_flag: bool,
pub transquant_bypass_enabled_flag: bool,
pub tiles_enabled_flag: bool,
pub entropy_coding_sync_enabled_flag: bool,
pub num_tile_columns_minus1: u8,
pub num_tile_rows_minus1: u8,
pub uniform_spacing_flag: bool,
pub loop_filter_across_tiles_enabled_flag: bool,
pub pps_loop_filter_across_slices_enabled_flag: bool,
pub deblocking_filter_control_present_flag: bool,
pub deblocking_filter_override_enabled_flag: bool,
pub pps_deblocking_filter_disabled_flag: bool,
pub pps_beta_offset_div2: i8,
pub pps_tc_offset_div2: i8,
pub pps_scaling_list_data_present_flag: bool,
pub lists_modification_present_flag: bool,
pub log2_parallel_merge_level_minus2: u8,
pub slice_segment_header_extension_present_flag: bool,
pub pps_extension_present_flag: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct H265SliceHeader {
pub first_slice_segment_in_pic_flag: bool,
pub nal_unit_type: u8,
pub slice_pic_parameter_set_id: u8,
pub slice_type: H265SliceType,
pub pic_order_cnt_lsb: u32,
pub short_term_ref_pic_set_sps_flag: bool,
pub short_term_ref_pic_set_idx: Option<u8>,
pub is_irap: bool,
pub is_idr: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum H265SliceType {
B,
P,
I,
}
impl H265SliceType {
fn from_ue(v: u32) -> Option<Self> {
match v {
0 => Some(Self::B),
1 => Some(Self::P),
2 => Some(Self::I),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Av1SequenceHeader {
pub seq_profile: u8,
pub still_picture: bool,
pub reduced_still_picture_header: bool,
pub max_frame_width_minus1: u32,
pub max_frame_height_minus1: u32,
pub seq_level_idx_0: u8,
pub seq_tier_0: u8,
pub bit_depth: u8,
pub monochrome: bool,
pub color_primaries: u8,
pub transfer_characteristics: u8,
pub matrix_coefficients: u8,
pub color_range: bool,
pub chroma_subsampling_x: bool,
pub chroma_subsampling_y: bool,
pub film_grain_params_present: bool,
pub enable_filter_intra: bool,
pub enable_intra_edge_filter: bool,
pub enable_interintra_compound: bool,
pub enable_masked_compound: bool,
pub enable_warped_motion: bool,
pub enable_dual_filter: bool,
pub enable_order_hint: bool,
pub enable_jnt_comp: bool,
pub enable_ref_frame_mvs: bool,
pub enable_superres: bool,
pub enable_cdef: bool,
pub enable_restoration: bool,
pub order_hint_bits: u8,
pub seq_force_screen_content_tools: u8,
pub seq_force_integer_mv: u8,
pub frame_width_bits_minus_1: u8,
pub frame_height_bits_minus_1: u8,
pub use_128x128_superblock: bool,
pub separate_uv_delta_q: bool,
}
#[derive(Debug, Clone)]
pub struct Av1FrameHeader {
pub show_frame: bool,
pub showable_frame: bool,
pub frame_type: Av1FrameType,
pub error_resilient_mode: bool,
pub disable_cdf_update: bool,
pub allow_screen_content_tools: bool,
pub force_integer_mv: bool,
pub order_hint: u32,
pub primary_ref_frame: u8,
pub refresh_frame_flags: u8,
pub frame_width: u32,
pub frame_height: u32,
pub render_width: u32,
pub render_height: u32,
pub use_ref_frame_mvs: bool,
pub allow_high_precision_mv: bool,
pub is_filter_switchable: bool,
pub disable_frame_end_update_cdf: bool,
pub allow_warped_motion: bool,
pub reduced_tx_set: bool,
pub allow_intrabc: bool,
pub frame_size_override_flag: bool,
pub use_superres: bool,
pub is_motion_mode_switchable: bool,
pub reference_select: bool,
pub skip_mode_present: bool,
pub tile_cols: u8,
pub tile_rows: u8,
pub uniform_tile_spacing_flag: bool,
pub tile_cols_log2: u8,
pub tile_rows_log2: u8,
pub mi_col_starts: Vec<u16>, pub mi_row_starts: Vec<u16>, pub width_in_sbs_minus_1: Vec<u16>, pub height_in_sbs_minus_1: Vec<u16>, pub context_update_tile_id: u16,
pub tile_size_bytes_minus_1: u8,
pub base_q_idx: u8,
pub delta_q_y_dc: i8,
pub delta_q_u_dc: i8,
pub delta_q_u_ac: i8,
pub delta_q_v_dc: i8,
pub delta_q_v_ac: i8,
pub using_qmatrix: bool,
pub qm_y: u8,
pub qm_u: u8,
pub qm_v: u8,
pub delta_q_present: bool,
pub delta_q_res: u8,
pub delta_lf_present: bool,
pub delta_lf_res: u8,
pub delta_lf_multi: bool,
pub segmentation_enabled: bool,
pub segmentation_update_map: bool,
pub segmentation_temporal_update: bool,
pub segmentation_update_data: bool,
pub feature_enabled: [[bool; 8]; 8],
pub feature_data: [[i16; 8]; 8],
pub loop_filter_level: [u8; 4],
pub loop_filter_sharpness: u8,
pub loop_filter_delta_enabled: bool,
pub loop_filter_delta_update: bool,
pub update_ref_delta_mask: u8, pub loop_filter_ref_deltas: [i8; 8],
pub update_mode_delta_mask: u8, pub loop_filter_mode_deltas: [i8; 2],
pub cdef_damping_minus_3: u8,
pub cdef_bits: u8,
pub cdef_y_pri_strength: [u8; 8],
pub cdef_y_sec_strength: [u8; 8],
pub cdef_uv_pri_strength: [u8; 8],
pub cdef_uv_sec_strength: [u8; 8],
pub lr_type: [u8; 3], pub lr_unit_shift: u8,
pub lr_uv_shift: u8,
pub tx_mode: u8,
pub interpolation_filter: u8,
pub tile_group_offset_in_obu: u32,
pub coded_lossless: bool,
}
impl Default for Av1FrameHeader {
fn default() -> Self {
Self {
show_frame: false,
showable_frame: false,
frame_type: Av1FrameType::Key,
error_resilient_mode: false,
disable_cdf_update: false,
allow_screen_content_tools: false,
force_integer_mv: false,
order_hint: 0,
primary_ref_frame: 7,
refresh_frame_flags: 0,
frame_width: 0,
frame_height: 0,
render_width: 0,
render_height: 0,
use_ref_frame_mvs: false,
allow_high_precision_mv: false,
is_filter_switchable: false,
disable_frame_end_update_cdf: false,
allow_warped_motion: false,
reduced_tx_set: false,
allow_intrabc: false,
frame_size_override_flag: false,
use_superres: false,
is_motion_mode_switchable: false,
reference_select: false,
skip_mode_present: false,
tile_cols: 1,
tile_rows: 1,
uniform_tile_spacing_flag: true,
tile_cols_log2: 0,
tile_rows_log2: 0,
mi_col_starts: Vec::new(),
mi_row_starts: Vec::new(),
width_in_sbs_minus_1: Vec::new(),
height_in_sbs_minus_1: Vec::new(),
context_update_tile_id: 0,
tile_size_bytes_minus_1: 3,
base_q_idx: 0,
delta_q_y_dc: 0,
delta_q_u_dc: 0,
delta_q_u_ac: 0,
delta_q_v_dc: 0,
delta_q_v_ac: 0,
using_qmatrix: false,
qm_y: 0,
qm_u: 0,
qm_v: 0,
delta_q_present: false,
delta_q_res: 0,
delta_lf_present: false,
delta_lf_res: 0,
delta_lf_multi: false,
segmentation_enabled: false,
segmentation_update_map: false,
segmentation_temporal_update: false,
segmentation_update_data: false,
feature_enabled: [[false; 8]; 8],
feature_data: [[0; 8]; 8],
loop_filter_level: [0; 4],
loop_filter_sharpness: 0,
loop_filter_delta_enabled: false,
loop_filter_delta_update: false,
update_ref_delta_mask: 0,
loop_filter_ref_deltas: [0; 8],
update_mode_delta_mask: 0,
loop_filter_mode_deltas: [0; 2],
cdef_damping_minus_3: 0,
cdef_bits: 0,
cdef_y_pri_strength: [0; 8],
cdef_y_sec_strength: [0; 8],
cdef_uv_pri_strength: [0; 8],
cdef_uv_sec_strength: [0; 8],
lr_type: [0; 3],
lr_unit_shift: 0,
lr_uv_shift: 0,
tx_mode: 0,
interpolation_filter: 0,
tile_group_offset_in_obu: 0,
coded_lossless: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Av1FrameType {
Key,
Inter,
IntraOnly,
Switch,
}
pub fn parse_av1_sequence_header(sample: &[u8]) -> Option<Av1SequenceHeader> {
let obu = find_av1_obu(sample, 1)?;
let mut br = BitReader::new(obu);
let seq_profile = br.read_bits(3)? as u8;
let still_picture = br.read_bits(1)? == 1;
let reduced_still_picture_header = br.read_bits(1)? == 1;
let mut seq_level_idx_0 = 0u8;
let mut seq_tier_0 = 0u8;
let (_operating_points_cnt_minus_1, _timing_info_present_flag);
let mut order_hint_bits = 0u8;
let mut enable_order_hint = false;
if reduced_still_picture_header {
seq_level_idx_0 = br.read_bits(5)? as u8;
_operating_points_cnt_minus_1 = 0;
_timing_info_present_flag = false;
} else {
let timing_info_present_flag = br.read_bits(1)? == 1;
_timing_info_present_flag = timing_info_present_flag;
let mut decoder_model_info_present_flag = false;
let mut buffer_delay_length_minus_1 = 0u32;
if timing_info_present_flag {
let _num_units_in_display_tick = br.read_bits(32)?;
let _time_scale = br.read_bits(32)?;
let equal_picture_interval = br.read_bits(1)? == 1;
if equal_picture_interval {
let _num_ticks_per_picture_minus_1 = read_av1_uvlc(&mut br)?;
}
decoder_model_info_present_flag = br.read_bits(1)? == 1;
if decoder_model_info_present_flag {
buffer_delay_length_minus_1 = br.read_bits(5)?;
let _num_units_in_decoding_tick = br.read_bits(32)?;
let _buffer_removal_time_length_minus_1 = br.read_bits(5)?;
let _frame_presentation_time_length_minus_1 = br.read_bits(5)?;
}
}
let initial_display_delay_present_flag = br.read_bits(1)? == 1;
let operating_points_cnt_minus_1 = br.read_bits(5)? as u8;
_operating_points_cnt_minus_1 = operating_points_cnt_minus_1;
for i in 0..=operating_points_cnt_minus_1 {
let _operating_point_idc = br.read_bits(12)?;
let seq_level_idx_i = br.read_bits(5)? as u8;
let seq_tier_i = if seq_level_idx_i > 7 {
br.read_bits(1)? as u8
} else {
0
};
if i == 0 {
seq_level_idx_0 = seq_level_idx_i;
seq_tier_0 = seq_tier_i;
}
if decoder_model_info_present_flag {
let decoder_model_present_for_this_op = br.read_bits(1)? == 1;
if decoder_model_present_for_this_op {
let n = (buffer_delay_length_minus_1 + 1) as usize;
let _buffer_delay = br.read_bits(n)?;
let _encoder_buffer_delay = br.read_bits(n)?;
let _low_delay_mode_flag = br.read_bits(1)?;
}
}
if initial_display_delay_present_flag {
let idd_present_for_this_op = br.read_bits(1)? == 1;
if idd_present_for_this_op {
let _initial_display_delay_minus_1 = br.read_bits(4)?;
}
}
}
}
let frame_width_bits_minus_1 = br.read_bits(4)? as usize;
let frame_height_bits_minus_1 = br.read_bits(4)? as usize;
let max_frame_width_minus1 = br.read_bits(frame_width_bits_minus_1 + 1)?;
let max_frame_height_minus1 = br.read_bits(frame_height_bits_minus_1 + 1)?;
let frame_id_numbers_present_flag = if reduced_still_picture_header {
false
} else {
br.read_bits(1)? == 1
};
if frame_id_numbers_present_flag {
let _delta_frame_id_length_minus_2 = br.read_bits(4)?;
let _additional_frame_id_length_minus_1 = br.read_bits(3)?;
}
let use_128x128_superblock = br.read_bits(1)? == 1;
let enable_filter_intra = br.read_bits(1)? == 1;
let enable_intra_edge_filter = br.read_bits(1)? == 1;
let mut enable_interintra_compound = false;
let mut enable_masked_compound = false;
let mut enable_warped_motion = false;
let mut enable_dual_filter = false;
let mut enable_jnt_comp = false;
let mut enable_ref_frame_mvs = false;
let mut seq_force_screen_content_tools: u8 = 2; let mut seq_force_integer_mv: u8 = 2;
if !reduced_still_picture_header {
enable_interintra_compound = br.read_bits(1)? == 1;
enable_masked_compound = br.read_bits(1)? == 1;
enable_warped_motion = br.read_bits(1)? == 1;
enable_dual_filter = br.read_bits(1)? == 1;
enable_order_hint = br.read_bits(1)? == 1;
if enable_order_hint {
enable_jnt_comp = br.read_bits(1)? == 1;
enable_ref_frame_mvs = br.read_bits(1)? == 1;
}
let seq_choose_screen_content_tools = br.read_bits(1)? == 1;
seq_force_screen_content_tools = if seq_choose_screen_content_tools {
2u8
} else {
br.read_bits(1)? as u8
};
if seq_force_screen_content_tools > 0 {
let seq_choose_integer_mv = br.read_bits(1)? == 1;
seq_force_integer_mv = if seq_choose_integer_mv {
2u8
} else {
br.read_bits(1)? as u8
};
}
if enable_order_hint {
order_hint_bits = br.read_bits(3)? as u8 + 1;
}
}
let enable_superres = br.read_bits(1)? == 1;
let enable_cdef = br.read_bits(1)? == 1;
let enable_restoration = br.read_bits(1)? == 1;
let high_bitdepth = br.read_bits(1)? == 1;
let bit_depth = if seq_profile == 2 && high_bitdepth {
if br.read_bits(1)? == 1 { 12 } else { 10 }
} else if high_bitdepth {
10
} else {
8
};
let monochrome = if seq_profile == 1 {
false
} else {
br.read_bits(1)? == 1
};
let color_description_present_flag = br.read_bits(1)? == 1;
let (color_primaries, transfer_characteristics, matrix_coefficients) =
if color_description_present_flag {
(
br.read_bits(8)? as u8,
br.read_bits(8)? as u8,
br.read_bits(8)? as u8,
)
} else {
(2u8, 2u8, 2u8) };
let color_range;
let (subx, suby);
let mut separate_uv_delta_q = false;
if monochrome {
color_range = br.read_bits(1)? == 1;
subx = true;
suby = true;
} else if color_primaries == 1 && transfer_characteristics == 13 && matrix_coefficients == 0 {
color_range = true;
subx = false;
suby = false;
} else {
color_range = br.read_bits(1)? == 1;
match seq_profile {
0 => {
subx = true;
suby = true;
}
1 => {
subx = false;
suby = false;
}
2 => {
if bit_depth == 12 {
subx = br.read_bits(1)? == 1;
suby = if subx { br.read_bits(1)? == 1 } else { false };
} else {
subx = true;
suby = false;
}
}
_ => {
subx = true;
suby = true;
}
}
if subx && suby {
let _chroma_sample_position = br.read_bits(2)?;
}
separate_uv_delta_q = br.read_bits(1)? == 1;
}
let film_grain_params_present = br.read_bits(1)? == 1;
Some(Av1SequenceHeader {
seq_profile,
still_picture,
reduced_still_picture_header,
max_frame_width_minus1,
max_frame_height_minus1,
seq_level_idx_0,
seq_tier_0,
bit_depth,
monochrome,
color_primaries,
transfer_characteristics,
matrix_coefficients,
color_range,
chroma_subsampling_x: subx,
chroma_subsampling_y: suby,
film_grain_params_present,
enable_filter_intra,
enable_intra_edge_filter,
enable_interintra_compound,
enable_masked_compound,
enable_warped_motion,
enable_dual_filter,
enable_order_hint,
enable_jnt_comp,
enable_ref_frame_mvs,
enable_superres,
enable_cdef,
enable_restoration,
order_hint_bits,
seq_force_screen_content_tools,
seq_force_integer_mv,
frame_width_bits_minus_1: frame_width_bits_minus_1 as u8,
frame_height_bits_minus_1: frame_height_bits_minus_1 as u8,
use_128x128_superblock,
separate_uv_delta_q,
})
}
pub fn parse_av1_frame_header(sample: &[u8], seq: &Av1SequenceHeader) -> Option<Av1FrameHeader> {
let obu_bytes = find_av1_obu(sample, 3).or_else(|| find_av1_obu(sample, 6))?;
let mut br = BitReader::new(obu_bytes);
let mut h = Av1FrameHeader::default();
if seq.reduced_still_picture_header {
h.frame_type = Av1FrameType::Key;
h.show_frame = true;
h.showable_frame = false;
h.error_resilient_mode = true;
} else {
let show_existing_frame = br.read_bits(1)? == 1;
if show_existing_frame {
let _frame_to_show_map_idx = br.read_bits(3)?;
h.show_frame = true;
h.showable_frame = true;
h.frame_type = Av1FrameType::Key;
h.frame_width = seq.max_frame_width_minus1 + 1;
h.frame_height = seq.max_frame_height_minus1 + 1;
h.render_width = h.frame_width;
h.render_height = h.frame_height;
return Some(h);
}
let ft_code = br.read_bits(2)?;
h.frame_type = match ft_code {
0 => Av1FrameType::Key,
1 => Av1FrameType::Inter,
2 => Av1FrameType::IntraOnly,
3 => Av1FrameType::Switch,
_ => return None,
};
h.show_frame = br.read_bits(1)? == 1;
h.showable_frame = if h.show_frame {
!matches!(h.frame_type, Av1FrameType::Key)
} else {
br.read_bits(1)? == 1
};
let is_key = matches!(h.frame_type, Av1FrameType::Key);
let is_switch = matches!(h.frame_type, Av1FrameType::Switch);
h.error_resilient_mode = if is_switch || (is_key && h.show_frame) {
true
} else {
br.read_bits(1)? == 1
};
}
let frame_is_intra = matches!(h.frame_type, Av1FrameType::Key | Av1FrameType::IntraOnly);
h.disable_cdf_update = br.read_bits(1)? == 1;
h.allow_screen_content_tools = if seq.seq_force_screen_content_tools == 2 {
br.read_bits(1)? == 1
} else {
seq.seq_force_screen_content_tools == 1
};
if h.allow_screen_content_tools {
h.force_integer_mv = if seq.seq_force_integer_mv == 2 {
br.read_bits(1)? == 1
} else {
seq.seq_force_integer_mv == 1
};
} else {
h.force_integer_mv = false;
}
if frame_is_intra {
h.force_integer_mv = true;
}
let is_switch = matches!(h.frame_type, Av1FrameType::Switch);
h.frame_size_override_flag = if is_switch {
true
} else if seq.reduced_still_picture_header {
false
} else {
br.read_bits(1)? == 1
};
if seq.enable_order_hint && seq.order_hint_bits > 0 {
h.order_hint = br.read_bits(seq.order_hint_bits as usize)?;
}
h.primary_ref_frame = if frame_is_intra || h.error_resilient_mode {
7 } else {
br.read_bits(3)? as u8
};
let all_frames = 0xFFu8;
h.refresh_frame_flags = if matches!(h.frame_type, Av1FrameType::Key) && h.show_frame {
all_frames
} else if is_switch {
all_frames
} else {
br.read_bits(8)? as u8
};
let (frame_width, frame_height) = if frame_is_intra {
let (w, h2) = parse_av1_frame_size(&mut br, seq, h.frame_size_override_flag)?;
h.use_superres = if seq.enable_superres {
br.read_bits(1)? == 1
} else {
false
};
if h.use_superres {
let _superres_denom_minus9 = br.read_bits(3)?;
}
parse_av1_render_size(&mut br, w, h2, &mut h.render_width, &mut h.render_height)?;
if h.allow_screen_content_tools
{
h.allow_intrabc = br.read_bits(1)? == 1;
}
(w, h2)
} else {
let frame_refs_short_signaling = if seq.enable_order_hint {
br.read_bits(1)? == 1
} else {
false
};
if frame_refs_short_signaling {
let _last_frame_idx = br.read_bits(3)?;
let _gold_frame_idx = br.read_bits(3)?;
}
for _ in 0..7u8
{
if !frame_refs_short_signaling {
let _ref_frame_idx = br.read_bits(3)?;
}
}
let (w, h2) = if h.frame_size_override_flag && !h.error_resilient_mode {
parse_av1_frame_size_with_refs(&mut br, seq)?
} else {
let (w, h2) = parse_av1_frame_size(&mut br, seq, h.frame_size_override_flag)?;
h.use_superres = if seq.enable_superres {
br.read_bits(1)? == 1
} else {
false
};
if h.use_superres {
let _superres_denom_minus9 = br.read_bits(3)?;
}
parse_av1_render_size(&mut br, w, h2, &mut h.render_width, &mut h.render_height)?;
(w, h2)
};
h.allow_high_precision_mv = if h.force_integer_mv {
false
} else {
br.read_bits(1)? == 1
};
h.is_filter_switchable = br.read_bits(1)? == 1;
h.interpolation_filter = if h.is_filter_switchable {
4 } else {
br.read_bits(2)? as u8
};
h.is_motion_mode_switchable = br.read_bits(1)? == 1;
h.use_ref_frame_mvs = if h.error_resilient_mode || !seq.enable_ref_frame_mvs {
false
} else {
br.read_bits(1)? == 1
};
(w, h2)
};
h.frame_width = frame_width;
h.frame_height = frame_height;
if h.render_width == 0 {
h.render_width = frame_width;
}
if h.render_height == 0 {
h.render_height = frame_height;
}
h.disable_frame_end_update_cdf = if seq.reduced_still_picture_header {
true
} else {
br.read_bits(1)? == 1
};
let sb_size_log2: u32 = 4; let mi_cols_raw = 2 * ((frame_width.saturating_sub(1) + 8) >> 3);
let mi_rows_raw = 2 * ((frame_height.saturating_sub(1) + 8) >> 3);
let sb_cols = (mi_cols_raw + (1 << sb_size_log2) - 1) >> sb_size_log2;
let sb_rows = (mi_rows_raw + (1 << sb_size_log2) - 1) >> sb_size_log2;
parse_av1_tile_info(
&mut br,
&mut h,
sb_cols,
sb_rows,
sb_size_log2,
mi_cols_raw,
mi_rows_raw,
)?;
parse_av1_quantization_params(&mut br, &mut h, seq)?;
parse_av1_segmentation_params(&mut br, &mut h)?;
h.delta_q_present = if h.base_q_idx > 0 {
br.read_bits(1)? == 1
} else {
false
};
h.delta_q_res = if h.delta_q_present {
br.read_bits(2)? as u8
} else {
0
};
h.delta_lf_present = if h.delta_q_present && !h.allow_intrabc {
br.read_bits(1)? == 1
} else {
false
};
if h.delta_lf_present {
h.delta_lf_res = br.read_bits(2)? as u8;
h.delta_lf_multi = br.read_bits(1)? == 1;
}
h.coded_lossless = h.base_q_idx == 0
&& h.delta_q_y_dc == 0
&& h.delta_q_u_dc == 0
&& h.delta_q_u_ac == 0
&& h.delta_q_v_dc == 0
&& h.delta_q_v_ac == 0;
parse_av1_loop_filter_params(&mut br, &mut h, frame_is_intra)?;
let num_planes_u32: u32 = if seq.monochrome { 1 } else { 3 };
if !h.coded_lossless && !h.allow_intrabc && seq.enable_cdef {
parse_av1_cdef_params(&mut br, &mut h, num_planes_u32)?;
} else {
h.cdef_bits = 0;
h.cdef_damping_minus_3 = 0;
h.cdef_y_pri_strength = [0; 8];
h.cdef_y_sec_strength = [0; 8];
h.cdef_uv_pri_strength = [0; 8];
h.cdef_uv_sec_strength = [0; 8];
}
if !h.coded_lossless && !h.allow_intrabc && seq.enable_restoration {
parse_av1_lr_params(&mut br, &mut h, num_planes_u32, seq)?;
}
h.tx_mode = if h.coded_lossless {
0 } else if br.read_bits(1)? == 1 {
2 } else {
1 };
h.reference_select = if !frame_is_intra {
br.read_bits(1)? == 1
} else {
false
};
let skip_mode_allowed = false; h.skip_mode_present = if skip_mode_allowed {
br.read_bits(1)? == 1
} else {
false
};
h.allow_warped_motion =
if !frame_is_intra && !h.error_resilient_mode && seq.enable_warped_motion {
br.read_bits(1)? == 1
} else {
false
};
h.reduced_tx_set = br.read_bits(1)? == 1;
if !frame_is_intra {
skip_av1_global_motion_params(&mut br)?;
}
if seq.film_grain_params_present && (h.show_frame || h.showable_frame) {
skip_av1_film_grain_params(&mut br, seq)?;
}
br.byte_align();
h.tile_group_offset_in_obu = (br.bit_pos() / 8) as u32;
Some(h)
}
fn parse_av1_frame_size(
br: &mut BitReader,
seq: &Av1SequenceHeader,
frame_size_override_flag: bool,
) -> Option<(u32, u32)> {
if frame_size_override_flag {
let w_bits = av1_bits_for_max(seq.max_frame_width_minus1 + 1);
let h_bits = av1_bits_for_max(seq.max_frame_height_minus1 + 1);
let w = br.read_bits(w_bits)? + 1;
let hgt = br.read_bits(h_bits)? + 1;
Some((w, hgt))
} else {
Some((
seq.max_frame_width_minus1 + 1,
seq.max_frame_height_minus1 + 1,
))
}
}
fn parse_av1_render_size(
br: &mut BitReader,
frame_w: u32,
frame_h: u32,
out_w: &mut u32,
out_h: &mut u32,
) -> Option<()> {
let render_and_frame_size_different = br.read_bits(1)? == 1;
if render_and_frame_size_different {
*out_w = br.read_bits(16)? + 1;
*out_h = br.read_bits(16)? + 1;
} else {
*out_w = frame_w;
*out_h = frame_h;
}
Some(())
}
fn parse_av1_frame_size_with_refs(
br: &mut BitReader,
seq: &Av1SequenceHeader,
) -> Option<(u32, u32)> {
let mut found_ref = false;
for _ in 0..7u8 {
if br.read_bits(1)? == 1 {
found_ref = true;
}
}
if !found_ref {
let (w, hgt) = parse_av1_frame_size(br, seq, true)?;
let mut rw = 0;
let mut rh = 0;
parse_av1_render_size(br, w, hgt, &mut rw, &mut rh)?;
if seq.enable_superres && br.read_bits(1)? == 1 {
let _denom = br.read_bits(3)?;
}
Some((w, hgt))
} else {
Some((
seq.max_frame_width_minus1 + 1,
seq.max_frame_height_minus1 + 1,
))
}
}
fn av1_bits_for_max(v: u32) -> usize {
let mut bits = 0usize;
let mut x = v.saturating_sub(1);
while x > 0 {
bits += 1;
x >>= 1;
}
bits.max(1)
}
fn parse_av1_tile_info(
br: &mut BitReader,
h: &mut Av1FrameHeader,
sb_cols: u32,
sb_rows: u32,
sb_size_log2: u32,
mi_cols: u32,
mi_rows: u32,
) -> Option<()> {
let max_tile_width_sb = 4096 >> (sb_size_log2 + 2); let max_tile_area_sb = (4096 * 2304) >> (2 * sb_size_log2 + 4); let min_log2_tile_cols = av1_tile_log2(max_tile_width_sb, sb_cols);
let max_log2_tile_cols = av1_tile_log2(1, sb_cols.min(64));
let max_log2_tile_rows = av1_tile_log2(1, sb_rows.min(64));
let min_log2_tiles = min_log2_tile_cols.max(av1_tile_log2(max_tile_area_sb, sb_rows * sb_cols));
h.uniform_tile_spacing_flag = br.read_bits(1)? == 1;
let tile_cols_log2: u32;
let tile_rows_log2: u32;
h.mi_col_starts.clear();
h.mi_row_starts.clear();
h.width_in_sbs_minus_1.clear();
h.height_in_sbs_minus_1.clear();
if h.uniform_tile_spacing_flag {
let mut tcl = min_log2_tile_cols;
while tcl < max_log2_tile_cols {
if br.read_bits(1)? == 0 {
break;
}
tcl += 1;
}
tile_cols_log2 = tcl;
let tile_width_sb = (sb_cols + (1 << tile_cols_log2) - 1) >> tile_cols_log2;
let mut start_sb = 0u32;
let mut mi_starts: Vec<u16> = vec![0];
let mut widths: Vec<u16> = Vec::new();
while start_sb < sb_cols {
let size_sb = tile_width_sb.min(sb_cols - start_sb);
widths.push((size_sb - 1) as u16);
start_sb += size_sb;
mi_starts.push(((start_sb << sb_size_log2).min(mi_cols)) as u16);
}
h.mi_col_starts = mi_starts;
h.width_in_sbs_minus_1 = widths;
h.tile_cols = h.width_in_sbs_minus_1.len() as u8;
let min_log2_tile_rows = min_log2_tiles.saturating_sub(tile_cols_log2);
let mut trl = min_log2_tile_rows;
while trl < max_log2_tile_rows {
if br.read_bits(1)? == 0 {
break;
}
trl += 1;
}
tile_rows_log2 = trl;
let tile_height_sb = (sb_rows + (1 << tile_rows_log2) - 1) >> tile_rows_log2;
let mut start_sb_r = 0u32;
let mut mi_starts_r: Vec<u16> = vec![0];
let mut heights: Vec<u16> = Vec::new();
while start_sb_r < sb_rows {
let size_sb = tile_height_sb.min(sb_rows - start_sb_r);
heights.push((size_sb - 1) as u16);
start_sb_r += size_sb;
mi_starts_r.push(((start_sb_r << sb_size_log2).min(mi_rows)) as u16);
}
h.mi_row_starts = mi_starts_r;
h.height_in_sbs_minus_1 = heights;
h.tile_rows = h.height_in_sbs_minus_1.len() as u8;
} else {
let mut start_sb = 0u32;
let mut mi_starts: Vec<u16> = vec![0];
let mut widths: Vec<u16> = Vec::new();
while start_sb < sb_cols {
let max_width = (sb_cols - start_sb).min(max_tile_width_sb);
let size_minus_1 = av1_read_ns(br, max_width)?;
let size = size_minus_1 + 1;
widths.push(size_minus_1 as u16);
start_sb += size;
mi_starts.push(((start_sb << sb_size_log2).min(mi_cols)) as u16);
}
h.mi_col_starts = mi_starts;
h.width_in_sbs_minus_1 = widths;
h.tile_cols = h.width_in_sbs_minus_1.len() as u8;
tile_cols_log2 = av1_tile_log2(1, h.tile_cols as u32);
let tile_cols = h.tile_cols as u32;
let max_tile_area_sb_r = if min_log2_tiles > 0 {
(sb_rows * sb_cols) >> (min_log2_tiles + 1)
} else {
sb_rows * sb_cols
};
let max_tile_height_sb = (max_tile_area_sb_r / tile_cols).max(1);
let mut start_sb_r = 0u32;
let mut mi_starts_r: Vec<u16> = vec![0];
let mut heights: Vec<u16> = Vec::new();
while start_sb_r < sb_rows {
let max_height = (sb_rows - start_sb_r).min(max_tile_height_sb);
let size_minus_1 = av1_read_ns(br, max_height)?;
let size = size_minus_1 + 1;
heights.push(size_minus_1 as u16);
start_sb_r += size;
mi_starts_r.push(((start_sb_r << sb_size_log2).min(mi_rows)) as u16);
}
h.mi_row_starts = mi_starts_r;
h.height_in_sbs_minus_1 = heights;
h.tile_rows = h.height_in_sbs_minus_1.len() as u8;
tile_rows_log2 = av1_tile_log2(1, h.tile_rows as u32);
}
h.tile_cols_log2 = tile_cols_log2 as u8;
h.tile_rows_log2 = tile_rows_log2 as u8;
if (tile_cols_log2 + tile_rows_log2) > 0 {
let n = (tile_cols_log2 + tile_rows_log2) as usize;
h.context_update_tile_id = br.read_bits(n)? as u16;
h.tile_size_bytes_minus_1 = br.read_bits(2)? as u8;
} else {
h.context_update_tile_id = 0;
h.tile_size_bytes_minus_1 = 0;
}
Some(())
}
fn av1_tile_log2(blksize: u32, target: u32) -> u32 {
let mut k = 0u32;
while (blksize << k) < target {
k += 1;
}
k
}
fn av1_read_ns(br: &mut BitReader, n: u32) -> Option<u32> {
if n == 0 {
return Some(0);
}
let w = av1_ceil_log2(n);
if w == 0 {
return Some(0);
}
let m = (1u32 << w) - n;
let v = br.read_bits((w - 1) as usize)?;
if v < m {
Some(v)
} else {
let extra = br.read_bits(1)?;
Some((v << 1) - m + extra)
}
}
fn av1_ceil_log2(n: u32) -> u32 {
if n <= 1 {
return 1;
}
let mut k = 0;
let mut x = n - 1;
while x > 0 {
k += 1;
x >>= 1;
}
k
}
fn parse_av1_quantization_params(
br: &mut BitReader,
h: &mut Av1FrameHeader,
seq: &Av1SequenceHeader,
) -> Option<()> {
h.base_q_idx = br.read_bits(8)? as u8;
h.delta_q_y_dc = read_delta_q(br)?;
let (diff_uv_delta, num_planes) = if seq.monochrome {
(false, 1u32)
} else {
let diff = if seq.seq_profile == 2 {
br.read_bits(1)? == 1
} else {
false
};
(diff, 3u32)
};
if num_planes > 1 {
h.delta_q_u_dc = read_delta_q(br)?;
h.delta_q_u_ac = read_delta_q(br)?;
if diff_uv_delta {
h.delta_q_v_dc = read_delta_q(br)?;
h.delta_q_v_ac = read_delta_q(br)?;
} else {
h.delta_q_v_dc = h.delta_q_u_dc;
h.delta_q_v_ac = h.delta_q_u_ac;
}
}
h.using_qmatrix = br.read_bits(1)? == 1;
if h.using_qmatrix {
h.qm_y = br.read_bits(4)? as u8;
h.qm_u = br.read_bits(4)? as u8;
h.qm_v = if seq.monochrome {
h.qm_u
} else if br.read_bits(1)? == 0 {
h.qm_u
} else {
br.read_bits(4)? as u8
};
}
Some(())
}
fn read_delta_q(br: &mut BitReader) -> Option<i8> {
let present = br.read_bits(1)? == 1;
if present {
Some(br.read_su(7)? as i8)
} else {
Some(0)
}
}
fn parse_av1_segmentation_params(br: &mut BitReader, h: &mut Av1FrameHeader) -> Option<()> {
h.segmentation_enabled = br.read_bits(1)? == 1;
if h.segmentation_enabled {
if h.primary_ref_frame == 7 {
h.segmentation_update_map = true;
h.segmentation_temporal_update = false;
h.segmentation_update_data = true;
} else {
h.segmentation_update_map = br.read_bits(1)? == 1;
if h.segmentation_update_map {
h.segmentation_temporal_update = br.read_bits(1)? == 1;
}
h.segmentation_update_data = br.read_bits(1)? == 1;
}
if h.segmentation_update_data {
const FEAT_INFO: [(u32, bool); 8] = [
(8, true), (6, true), (6, true), (6, true), (6, true), (3, false), (0, false), (0, false), ];
for seg in 0..8 {
for (feat, &(bits, signed)) in FEAT_INFO.iter().enumerate() {
let enabled = br.read_bits(1)? == 1;
h.feature_enabled[seg][feat] = enabled;
if enabled {
if bits == 0 {
h.feature_data[seg][feat] = 1;
} else if signed {
h.feature_data[seg][feat] = br.read_su(bits as usize + 1)? as i16;
} else {
h.feature_data[seg][feat] = br.read_bits(bits as usize)? as i16;
}
}
}
}
}
}
Some(())
}
fn parse_av1_loop_filter_params(
br: &mut BitReader,
h: &mut Av1FrameHeader,
frame_is_intra: bool,
) -> Option<()> {
if h.coded_lossless || h.allow_intrabc {
h.loop_filter_level = [0; 4];
h.loop_filter_sharpness = 0;
h.loop_filter_delta_enabled = false;
h.loop_filter_ref_deltas = [1, 0, 0, 0, -1, 0, -1, -1];
h.loop_filter_mode_deltas = [0, 0];
return Some(());
}
h.loop_filter_level[0] = br.read_bits(6)? as u8;
h.loop_filter_level[1] = br.read_bits(6)? as u8;
if h.loop_filter_level[0] > 0 || h.loop_filter_level[1] > 0 {
h.loop_filter_level[2] = br.read_bits(6)? as u8;
h.loop_filter_level[3] = br.read_bits(6)? as u8;
}
h.loop_filter_sharpness = br.read_bits(3)? as u8;
h.loop_filter_delta_enabled = br.read_bits(1)? == 1;
h.loop_filter_ref_deltas = [1, 0, 0, 0, -1, 0, -1, -1];
h.loop_filter_mode_deltas = [0, 0];
if h.loop_filter_delta_enabled {
h.loop_filter_delta_update = br.read_bits(1)? == 1;
if h.loop_filter_delta_update {
let mut update_mask = 0u8;
for i in 0..8 {
let update = br.read_bits(1)? == 1;
if update {
update_mask |= 1 << i;
h.loop_filter_ref_deltas[i] = br.read_su(7)? as i8;
}
}
h.update_ref_delta_mask = update_mask;
let mut mode_mask = 0u8;
for i in 0..2 {
let update = br.read_bits(1)? == 1;
if update {
mode_mask |= 1 << i;
h.loop_filter_mode_deltas[i] = br.read_su(7)? as i8;
}
}
h.update_mode_delta_mask = mode_mask;
}
}
let _ = frame_is_intra; Some(())
}
fn parse_av1_cdef_params(
br: &mut BitReader,
h: &mut Av1FrameHeader,
num_planes: u32,
) -> Option<()> {
h.cdef_damping_minus_3 = br.read_bits(2)? as u8;
h.cdef_bits = br.read_bits(2)? as u8;
let count = 1usize << h.cdef_bits;
for i in 0..count {
h.cdef_y_pri_strength[i] = br.read_bits(4)? as u8;
let y_sec = br.read_bits(2)? as u8;
h.cdef_y_sec_strength[i] = if y_sec == 3 { 4 } else { y_sec };
if num_planes > 1 {
h.cdef_uv_pri_strength[i] = br.read_bits(4)? as u8;
let uv_sec = br.read_bits(2)? as u8;
h.cdef_uv_sec_strength[i] = if uv_sec == 3 { 4 } else { uv_sec };
}
}
Some(())
}
fn parse_av1_lr_params(
br: &mut BitReader,
h: &mut Av1FrameHeader,
num_planes: u32,
seq: &Av1SequenceHeader,
) -> Option<()> {
let mut uses_lr = false;
let mut uses_chroma_lr = false;
for i in 0..(num_planes as usize) {
let lr_type = br.read_bits(2)? as u8;
h.lr_type[i] = lr_type;
if lr_type != 0 {
uses_lr = true;
if i > 0 {
uses_chroma_lr = true;
}
}
}
if uses_lr {
let base = br.read_bits(1)? as u8;
h.lr_unit_shift = if base != 0 {
let extra = br.read_bits(1)? as u8;
base + extra
} else {
0
};
if num_planes > 1 && uses_chroma_lr && seq.chroma_subsampling_x && seq.chroma_subsampling_y
{
h.lr_uv_shift = br.read_bits(1)? as u8;
}
}
Some(())
}
fn skip_av1_global_motion_params(br: &mut BitReader) -> Option<()> {
for _ in 0..7 {
let is_global = br.read_bits(1)? == 1;
let is_rot_zoom = if is_global {
br.read_bits(1)? == 1
} else {
false
};
let _is_translation = if is_global && !is_rot_zoom {
br.read_bits(1)? == 1
} else {
false
};
let gm_type = if is_global && !is_rot_zoom {
2u8
} else if is_rot_zoom {
3u8
} else if is_global {
4u8
} else {
0u8
};
if gm_type >= 3 {
for _ in 0..2 {
let _a = av1_read_subexp(br, 12, 0)?;
let _b = av1_read_subexp(br, 12, 0)?;
}
}
if gm_type >= 2 {
for _ in 0..2 {
let _a = av1_read_subexp(br, 12, 0)?;
}
}
}
Some(())
}
fn av1_read_subexp(br: &mut BitReader, num_syms: u32, _ref: i32) -> Option<i32> {
let bits = av1_ceil_log2(num_syms) as usize + 1; let _ = br.read_bits(bits.min(16))?;
Some(0)
}
fn skip_av1_film_grain_params(br: &mut BitReader, seq: &Av1SequenceHeader) -> Option<()> {
let apply_grain = br.read_bits(1)? == 1;
if !apply_grain {
return Some(());
}
let _grain_seed = br.read_bits(16)?;
let update_grain = br.read_bits(1)? == 1;
if !update_grain {
let _film_grain_params_ref_idx = br.read_bits(3)?;
return Some(());
}
let num_y_points = br.read_bits(4)?;
for _ in 0..num_y_points {
let _point_y_value = br.read_bits(8)?;
let _point_y_scaling = br.read_bits(8)?;
}
let chroma_scaling_from_luma = if seq.monochrome {
false
} else {
br.read_bits(1)? == 1
};
let num_cb_points: u32;
let num_cr_points: u32;
if seq.monochrome
|| chroma_scaling_from_luma
|| (seq.chroma_subsampling_x && seq.chroma_subsampling_y && num_y_points == 0)
{
num_cb_points = 0;
num_cr_points = 0;
} else {
num_cb_points = br.read_bits(4)?;
for _ in 0..num_cb_points {
let _point_cb_value = br.read_bits(8)?;
let _point_cb_scaling = br.read_bits(8)?;
}
num_cr_points = br.read_bits(4)?;
for _ in 0..num_cr_points {
let _point_cr_value = br.read_bits(8)?;
let _point_cr_scaling = br.read_bits(8)?;
}
}
let _grain_scaling_minus_8 = br.read_bits(2)?;
let ar_coeff_lag = br.read_bits(2)?;
let num_pos_y = 2 * ar_coeff_lag * (ar_coeff_lag + 1);
let num_pos_chroma = if num_y_points > 0 {
num_pos_y + 1
} else {
num_pos_y
};
for _ in 0..num_pos_y {
let _ar_coeff_y_plus_128 = br.read_bits(8)?;
}
if chroma_scaling_from_luma || num_cb_points > 0 {
for _ in 0..num_pos_chroma {
let _ar_coeff_cb_plus_128 = br.read_bits(8)?;
}
}
if chroma_scaling_from_luma || num_cr_points > 0 {
for _ in 0..num_pos_chroma {
let _ar_coeff_cr_plus_128 = br.read_bits(8)?;
}
}
let _ar_coeff_shift_minus_6 = br.read_bits(2)?;
let _grain_scale_shift = br.read_bits(2)?;
if num_cb_points > 0 {
let _cb_mult = br.read_bits(8)?;
let _cb_luma_mult = br.read_bits(8)?;
let _cb_offset = br.read_bits(9)?;
}
if num_cr_points > 0 {
let _cr_mult = br.read_bits(8)?;
let _cr_luma_mult = br.read_bits(8)?;
let _cr_offset = br.read_bits(9)?;
}
let _overlap_flag = br.read_bits(1)?;
let _clip_to_restricted_range = br.read_bits(1)?;
Some(())
}
pub fn av1_frame_header_offset(sample: &[u8]) -> Option<u32> {
let mut i = 0usize;
while i < sample.len() {
let header = sample[i];
let obu_type = (header >> 3) & 0x0F;
let extension_flag = (header >> 2) & 0x01;
let has_size_field = (header >> 1) & 0x01;
let mut p = i + 1;
if extension_flag == 1 {
p += 1;
}
let (size, leb) = if has_size_field == 1 {
let (s, n) = read_leb128(&sample[p..])?;
p += n;
(s as usize, n)
} else {
return None;
};
let _ = leb;
if obu_type == 3 || obu_type == 6 {
return Some(p as u32);
}
p += size;
i = p;
}
None
}
pub fn av1_tile_group_offset(sample: &[u8], seq: &Av1SequenceHeader) -> Option<u32> {
let mut i = 0usize;
while i < sample.len() {
let header = sample[i];
let obu_type = (header >> 3) & 0x0F;
let extension_flag = (header >> 2) & 0x01;
let has_size_field = (header >> 1) & 0x01;
let mut p = i + 1;
if extension_flag == 1 {
p += 1;
}
let size = if has_size_field == 1 {
let (s, n) = read_leb128(&sample[p..])?;
p += n;
s as usize
} else {
return None;
};
if obu_type == 4 {
return Some(p as u32);
}
p += size;
i = p;
}
let (_obu_bytes, payload_offset) = find_av1_obu_with_offset(sample, 6)?;
let hdr = parse_av1_frame_header(sample, seq)?;
Some(payload_offset as u32 + hdr.tile_group_offset_in_obu)
}
pub fn av1_tile_group_offset_fallback(sample: &[u8]) -> Option<u32> {
let mut i = 0usize;
while i < sample.len() {
let header = sample[i];
let obu_type = (header >> 3) & 0x0F;
let extension_flag = (header >> 2) & 0x01;
let has_size_field = (header >> 1) & 0x01;
let mut p = i + 1;
if extension_flag == 1 {
p += 1;
}
let size = if has_size_field == 1 {
let (s, n) = read_leb128(&sample[p..])?;
p += n;
s as usize
} else {
return None;
};
if obu_type == 4 {
return Some(p as u32);
}
p += size;
i = p;
}
av1_frame_header_offset(sample)
}
fn read_av1_uvlc(br: &mut BitReader) -> Option<u32> {
let mut leading_zeros = 0;
while leading_zeros < 32 {
if br.read_bits(1)? == 1 {
break;
}
leading_zeros += 1;
}
if leading_zeros >= 32 {
return None;
}
if leading_zeros == 0 {
return Some(0);
}
let suffix = br.read_bits(leading_zeros)?;
Some((1u32 << leading_zeros) - 1 + suffix)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mpeg2SeqInfo {
pub width: u32,
pub height: u32,
}
pub fn detect_dims(codec: &str, samples: &[Vec<u8>]) -> Option<(u32, u32)> {
if samples.is_empty() {
return None;
}
let sample = &samples[0];
match codec.to_lowercase().as_str() {
"h264" | "avc1" | "avc" | "avc3" => {
let info = parse_h264_sps(sample)?;
Some((info.width?, info.height?))
}
"h265" | "hevc" | "hvc1" | "hev1" | "hvc2" | "hev2" => {
let info = parse_hevc_sps(sample)?;
Some((info.width?, info.height?))
}
"mpeg2" | "mpeg2video" | "mp2v" => {
let info = parse_mpeg2_sequence_header(sample)?;
Some((info.width, info.height))
}
_ => None,
}
}
pub fn parse_h264_sps(sample: &[u8]) -> Option<H264SpsInfo> {
let sps = find_h264_sps(sample)?;
let rbsp = remove_h264_rbsp_stuffing(sps);
let mut br = BitReader::new(&rbsp);
let profile_idc = br.read_bits(8)? as u8;
let constraint_set_flags = br.read_bits(8)? as u8;
let level_idc = br.read_bits(8)? as u8;
let _seq_parameter_set_id = br.read_ue()?;
let profile_gates_chroma = matches!(
profile_idc,
100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135
);
let (
chroma_format_idc,
separate_colour_plane_flag,
bit_depth_luma,
bit_depth_chroma,
qpprime_y_zero,
scaling_matrix,
) = if profile_gates_chroma {
let chroma = br.read_ue()? as u8;
let separate = if chroma == 3 {
br.read_bits(1)? == 1
} else {
false
};
let bit_depth_luma_m8 = br.read_ue()?;
let bit_depth_chroma_m8 = br.read_ue()?;
let qpprime = br.read_bits(1)? == 1;
let scaling_matrix_present = br.read_bits(1)? == 1;
if scaling_matrix_present {
let num_lists = if chroma == 3 { 12 } else { 8 };
for i in 0..num_lists {
if br.read_bits(1)? == 1 {
let size = if i < 6 { 16 } else { 64 };
let mut last_scale: i32 = 8;
let mut next_scale: i32 = 8;
for _j in 0..size {
if next_scale != 0 {
let delta = br.read_se()?;
next_scale = (last_scale + delta + 256).rem_euclid(256);
}
if next_scale != 0 {
last_scale = next_scale;
}
}
}
}
}
(
chroma,
separate,
bit_depth_luma_m8 as u8 + 8,
bit_depth_chroma_m8 as u8 + 8,
qpprime,
scaling_matrix_present,
)
} else {
(1u8, false, 8u8, 8u8, false, false)
};
let info_prefix = H264SpsInfo {
profile_idc,
constraint_set_flags,
level_idc,
chroma_format_idc,
separate_colour_plane_flag,
bit_depth_luma,
bit_depth_chroma,
frame_mbs_only: true,
width: None,
height: None,
log2_max_frame_num_minus4: None,
pic_order_cnt_type: None,
log2_max_pic_order_cnt_lsb_minus4: None,
delta_pic_order_always_zero_flag: None,
qpprime_y_zero_transform_bypass_flag: Some(qpprime_y_zero),
seq_scaling_matrix_present_flag: Some(scaling_matrix),
max_num_ref_frames: None,
gaps_in_frame_num_value_allowed_flag: None,
mb_adaptive_frame_field_flag: None,
direct_8x8_inference_flag: None,
frame_cropping_flag: None,
frame_crop_left_offset: None,
frame_crop_right_offset: None,
frame_crop_top_offset: None,
frame_crop_bottom_offset: None,
offset_for_non_ref_pic: None,
offset_for_top_to_bottom_field: None,
num_ref_frames_in_pic_order_cnt_cycle: None,
offset_for_ref_frame: Vec::new(),
};
let Some(dims) = parse_h264_sps_dims(&mut br, chroma_format_idc, separate_colour_plane_flag)
else {
return Some(info_prefix);
};
Some(H264SpsInfo {
frame_mbs_only: dims.frame_mbs_only,
width: Some(dims.width),
height: Some(dims.height),
log2_max_frame_num_minus4: Some(dims.log2_max_frame_num_minus4),
pic_order_cnt_type: Some(dims.pic_order_cnt_type),
log2_max_pic_order_cnt_lsb_minus4: dims.log2_max_pic_order_cnt_lsb_minus4,
delta_pic_order_always_zero_flag: dims.delta_pic_order_always_zero_flag,
max_num_ref_frames: Some(dims.max_num_ref_frames),
gaps_in_frame_num_value_allowed_flag: Some(dims.gaps_in_frame_num_value_allowed_flag),
mb_adaptive_frame_field_flag: dims.mb_adaptive_frame_field_flag,
direct_8x8_inference_flag: Some(dims.direct_8x8_inference_flag),
frame_cropping_flag: Some(dims.frame_cropping_flag),
frame_crop_left_offset: Some(dims.crop_left),
frame_crop_right_offset: Some(dims.crop_right),
frame_crop_top_offset: Some(dims.crop_top),
frame_crop_bottom_offset: Some(dims.crop_bottom),
offset_for_non_ref_pic: dims.offset_for_non_ref_pic,
offset_for_top_to_bottom_field: dims.offset_for_top_to_bottom_field,
num_ref_frames_in_pic_order_cnt_cycle: dims.num_ref_frames_in_pic_order_cnt_cycle,
offset_for_ref_frame: dims.offset_for_ref_frame,
..info_prefix
})
}
struct H264Dims {
width: u32,
height: u32,
frame_mbs_only: bool,
log2_max_frame_num_minus4: u8,
pic_order_cnt_type: u8,
log2_max_pic_order_cnt_lsb_minus4: Option<u8>,
delta_pic_order_always_zero_flag: Option<bool>,
offset_for_non_ref_pic: Option<i32>,
offset_for_top_to_bottom_field: Option<i32>,
num_ref_frames_in_pic_order_cnt_cycle: Option<u8>,
offset_for_ref_frame: Vec<i32>,
max_num_ref_frames: u8,
gaps_in_frame_num_value_allowed_flag: bool,
mb_adaptive_frame_field_flag: Option<bool>,
direct_8x8_inference_flag: bool,
frame_cropping_flag: bool,
crop_left: u32,
crop_right: u32,
crop_top: u32,
crop_bottom: u32,
}
fn parse_h264_sps_dims(
br: &mut BitReader,
chroma_format_idc: u8,
separate_colour_plane_flag: bool,
) -> Option<H264Dims> {
let log2_max_frame_num_minus4 = br.read_ue()? as u8;
let pic_order_cnt_type = br.read_ue()? as u8;
let mut log2_max_pic_order_cnt_lsb_minus4 = None;
let mut delta_pic_order_always_zero_flag = None;
let mut offset_for_non_ref_pic = None;
let mut offset_for_top_to_bottom_field = None;
let mut num_ref_frames_in_pic_order_cnt_cycle: Option<u8> = None;
let mut offset_for_ref_frame: Vec<i32> = Vec::new();
match pic_order_cnt_type {
0 => {
log2_max_pic_order_cnt_lsb_minus4 = Some(br.read_ue()? as u8);
}
1 => {
delta_pic_order_always_zero_flag = Some(br.read_bits(1)? == 1);
offset_for_non_ref_pic = Some(br.read_se()?);
offset_for_top_to_bottom_field = Some(br.read_se()?);
let cycle_len = br.read_ue()?;
let capped = cycle_len.min(255) as u8;
num_ref_frames_in_pic_order_cnt_cycle = Some(capped);
offset_for_ref_frame.reserve(capped as usize);
for _ in 0..capped {
offset_for_ref_frame.push(br.read_se()?);
}
}
2 => { }
_ => return None, }
let max_num_ref_frames = br.read_ue()?.min(u8::MAX as u32) as u8;
let gaps_in_frame_num_value_allowed_flag = br.read_bits(1)? == 1;
let pic_width_in_mbs_minus1 = br.read_ue()?;
let pic_height_in_map_units_minus1 = br.read_ue()?;
let frame_mbs_only_flag = br.read_bits(1)?;
let mut mb_adaptive_frame_field_flag = None;
if frame_mbs_only_flag == 0 {
mb_adaptive_frame_field_flag = Some(br.read_bits(1)? == 1);
}
let direct_8x8_inference_flag = br.read_bits(1)? == 1;
let frame_cropping_flag = br.read_bits(1)? == 1;
let (cl, cr, ct, cb) = if frame_cropping_flag {
(br.read_ue()?, br.read_ue()?, br.read_ue()?, br.read_ue()?)
} else {
(0, 0, 0, 0)
};
let pic_width_in_mbs = pic_width_in_mbs_minus1.saturating_add(1);
let pic_height_in_map_units = pic_height_in_map_units_minus1.saturating_add(1);
let frame_mbs_only = frame_mbs_only_flag == 1;
let frame_height_in_mbs = if frame_mbs_only {
pic_height_in_map_units
} else {
pic_height_in_map_units.saturating_mul(2)
};
let chroma_array_type = if separate_colour_plane_flag {
0
} else {
chroma_format_idc
};
let (sub_w, sub_h) = match chroma_array_type {
0 => (1u32, 1u32), 1 => (2, 2), 2 => (2, 1), 3 => (1, 1), _ => (1, 1),
};
let (crop_x, crop_y) = if chroma_array_type == 0 {
(1u32, 2u32 - frame_mbs_only_flag)
} else {
(sub_w, sub_h * (2 - frame_mbs_only_flag))
};
let width = pic_width_in_mbs
.saturating_mul(16)
.saturating_sub(crop_x.saturating_mul(cl.saturating_add(cr)));
let height = frame_height_in_mbs
.saturating_mul(16)
.saturating_sub(crop_y.saturating_mul(ct.saturating_add(cb)));
Some(H264Dims {
width,
height,
frame_mbs_only,
log2_max_frame_num_minus4,
pic_order_cnt_type,
log2_max_pic_order_cnt_lsb_minus4,
delta_pic_order_always_zero_flag,
offset_for_non_ref_pic,
offset_for_top_to_bottom_field,
num_ref_frames_in_pic_order_cnt_cycle,
offset_for_ref_frame,
max_num_ref_frames,
gaps_in_frame_num_value_allowed_flag,
mb_adaptive_frame_field_flag,
direct_8x8_inference_flag,
frame_cropping_flag,
crop_left: cl,
crop_right: cr,
crop_top: ct,
crop_bottom: cb,
})
}
pub fn parse_hevc_sps(sample: &[u8]) -> Option<HevcSpsInfo> {
let sps = find_hevc_sps(sample)?;
let rbsp = remove_h264_rbsp_stuffing(sps);
let mut br = BitReader::new(&rbsp);
let sps_video_parameter_set_id = br.read_bits(4)? as u8;
let sps_max_sub_layers_minus1 = br.read_bits(3)? as u8;
let sps_temporal_id_nesting_flag = br.read_bits(1)? == 1;
let general_profile_space = br.read_bits(2)?;
let tier_flag = br.read_bits(1)? == 1;
let profile_idc = br.read_bits(5)? as u8;
let _ = general_profile_space;
let profile_compatibility_flags = br.read_bits(32)?;
let _ = br.read_bits(48)?;
let level_idc = br.read_bits(8)? as u8;
let mut spl = Vec::with_capacity(sps_max_sub_layers_minus1 as usize);
let mut sll = Vec::with_capacity(sps_max_sub_layers_minus1 as usize);
for _ in 0..sps_max_sub_layers_minus1 {
spl.push(br.read_bits(1)?);
sll.push(br.read_bits(1)?);
}
if sps_max_sub_layers_minus1 > 0 {
for _ in sps_max_sub_layers_minus1 as usize..8 {
let _ = br.read_bits(2)?;
}
}
for i in 0..sps_max_sub_layers_minus1 as usize {
if spl[i] == 1 {
let _ = br.read_bits(8)?;
let _ = br.read_bits(32)?;
let _ = br.read_bits(48)?;
}
if sll[i] == 1 {
let _ = br.read_bits(8)?;
}
}
let sps_seq_parameter_set_id = br.read_ue()? as u8;
let chroma_format_idc = br.read_ue()? as u8;
let separate_colour_plane_flag = if chroma_format_idc == 3 {
br.read_bits(1)? == 1
} else {
false
};
let pic_width = br.read_ue()?;
let pic_height = br.read_ue()?;
let conformance_window_flag = br.read_bits(1)?;
let (cl, cr, ct, cb) = if conformance_window_flag == 1 {
(br.read_ue()?, br.read_ue()?, br.read_ue()?, br.read_ue()?)
} else {
(0u32, 0u32, 0u32, 0u32)
};
let bit_depth_luma_m8 = br.read_ue()?;
let bit_depth_chroma_m8 = br.read_ue()?;
let log2_max_pic_order_cnt_lsb_minus4 = br.read_ue()? as u8;
let sps_sub_layer_ordering_info_present_flag = br.read_bits(1)? == 1;
let mut max_dec_pic_buffering_minus1 = [0u8; 7];
let mut max_num_reorder_pics = [0u8; 7];
let mut max_latency_increase_plus1 = [0u32; 7];
let start = if sps_sub_layer_ordering_info_present_flag {
0
} else {
sps_max_sub_layers_minus1
};
for i in start..=sps_max_sub_layers_minus1 {
let dec = br.read_ue()?;
let nro = br.read_ue()?;
let latency = br.read_ue()?;
let idx = (i as usize).min(6);
max_dec_pic_buffering_minus1[idx] = dec.min(u8::MAX as u32) as u8;
max_num_reorder_pics[idx] = nro.min(u8::MAX as u32) as u8;
max_latency_increase_plus1[idx] = latency;
}
if !sps_sub_layer_ordering_info_present_flag {
let top = sps_max_sub_layers_minus1 as usize;
for i in 0..top {
max_dec_pic_buffering_minus1[i] = max_dec_pic_buffering_minus1[top];
max_num_reorder_pics[i] = max_num_reorder_pics[top];
max_latency_increase_plus1[i] = max_latency_increase_plus1[top];
}
}
let log2_min_luma_coding_block_size_minus3 = br.read_ue()? as u8;
let log2_diff_max_min_luma_coding_block_size = br.read_ue()? as u8;
let log2_min_luma_transform_block_size_minus2 = br.read_ue()? as u8;
let log2_diff_max_min_luma_transform_block_size = br.read_ue()? as u8;
let max_transform_hierarchy_depth_inter = br.read_ue()? as u8;
let max_transform_hierarchy_depth_intra = br.read_ue()? as u8;
let scaling_list_enabled_flag = br.read_bits(1)? == 1;
if scaling_list_enabled_flag {
let sps_scaling_list_data_present_flag = br.read_bits(1)? == 1;
if sps_scaling_list_data_present_flag {
skip_hevc_scaling_list_data(&mut br)?;
}
}
let amp_enabled_flag = br.read_bits(1)? == 1;
let sample_adaptive_offset_enabled_flag = br.read_bits(1)? == 1;
let pcm_enabled_flag = br.read_bits(1)? == 1;
let mut pcm_loop_filter_disabled_flag = false;
if pcm_enabled_flag {
let _pcm_sample_bit_depth_luma_minus1 = br.read_bits(4)?;
let _pcm_sample_bit_depth_chroma_minus1 = br.read_bits(4)?;
let _log2_min_pcm_luma_cb_size_minus3 = br.read_ue()?;
let _log2_diff_max_min_pcm_luma_cb_size = br.read_ue()?;
pcm_loop_filter_disabled_flag = br.read_bits(1)? == 1;
}
let num_short_term_ref_pic_sets = br.read_ue()? as u8;
let mut st_rps_offsets: Vec<()> = Vec::with_capacity(num_short_term_ref_pic_sets as usize);
for rps_idx in 0..num_short_term_ref_pic_sets {
skip_hevc_short_term_rps(&mut br, rps_idx, num_short_term_ref_pic_sets)?;
st_rps_offsets.push(());
}
let long_term_ref_pics_present_flag = br.read_bits(1)? == 1;
if long_term_ref_pics_present_flag {
let num_long_term_ref_pics_sps = br.read_ue()?;
let lsb_bits = (log2_max_pic_order_cnt_lsb_minus4 as usize) + 4;
for _ in 0..num_long_term_ref_pics_sps {
let _lt_ref_pic_poc_lsb_sps = br.read_bits(lsb_bits)?;
let _used_by_curr_pic_lt_sps_flag = br.read_bits(1)?;
}
}
let sps_temporal_mvp_enabled_flag = br.read_bits(1)? == 1;
let strong_intra_smoothing_enabled_flag = br.read_bits(1)? == 1;
let chroma_array_type = if separate_colour_plane_flag {
0
} else {
chroma_format_idc
};
let (sub_w, sub_h) = match chroma_array_type {
0 => (1u32, 1u32),
1 => (2, 2),
2 => (2, 1),
3 => (1, 1),
_ => (1, 1),
};
let width = pic_width.saturating_sub(sub_w.saturating_mul(cl.saturating_add(cr)));
let height = pic_height.saturating_sub(sub_h.saturating_mul(ct.saturating_add(cb)));
Some(HevcSpsInfo {
sps_video_parameter_set_id,
sps_seq_parameter_set_id,
sps_max_sub_layers_minus1,
sps_temporal_id_nesting_flag,
chroma_format_idc,
separate_colour_plane_flag,
bit_depth_luma: bit_depth_luma_m8 as u8 + 8,
bit_depth_chroma: bit_depth_chroma_m8 as u8 + 8,
width: Some(width),
height: Some(height),
conf_win_left_offset: cl,
conf_win_right_offset: cr,
conf_win_top_offset: ct,
conf_win_bottom_offset: cb,
log2_max_pic_order_cnt_lsb_minus4,
log2_min_luma_coding_block_size_minus3,
log2_diff_max_min_luma_coding_block_size,
log2_min_luma_transform_block_size_minus2,
log2_diff_max_min_luma_transform_block_size,
max_transform_hierarchy_depth_inter,
max_transform_hierarchy_depth_intra,
scaling_list_enabled_flag,
sps_sub_layer_ordering_info_present_flag,
amp_enabled_flag,
sample_adaptive_offset_enabled_flag,
pcm_enabled_flag,
pcm_loop_filter_disabled_flag,
num_short_term_ref_pic_sets,
long_term_ref_pics_present_flag,
sps_temporal_mvp_enabled_flag,
strong_intra_smoothing_enabled_flag,
profile_idc,
level_idc,
tier_flag,
max_dec_pic_buffering_minus1,
max_num_reorder_pics,
max_latency_increase_plus1,
profile_compatibility_flags,
})
}
fn skip_hevc_scaling_list_data(br: &mut BitReader) -> Option<()> {
for size_id in 0..4 {
let matrix_count = if size_id == 3 { 2 } else { 6 };
for _matrix_id in 0..matrix_count {
let scaling_list_pred_mode_flag = br.read_bits(1)? == 1;
if !scaling_list_pred_mode_flag {
let _scaling_list_pred_matrix_id_delta = br.read_ue()?;
} else {
let coef_num: usize = (1 << (4 + (size_id << 1))).min(64);
if size_id > 1 {
let _scaling_list_dc_coef_minus8 = br.read_se()?;
}
for _ in 0..coef_num {
let _scaling_list_delta_coef = br.read_se()?;
}
}
}
}
Some(())
}
fn skip_hevc_short_term_rps(br: &mut BitReader, st_rps_idx: u8, num_st_rps: u8) -> Option<()> {
let inter_ref_pic_set_prediction_flag = if st_rps_idx != 0 {
br.read_bits(1)? == 1
} else {
false
};
if inter_ref_pic_set_prediction_flag {
if st_rps_idx == num_st_rps {
let _delta_idx_minus1 = br.read_ue()?;
}
let _delta_rps_sign = br.read_bits(1)?;
let _abs_delta_rps_minus1 = br.read_ue()?;
for _ in 0..16 {
let used = br.read_bits(1)?;
if used == 0 {
let _use_delta_flag = br.read_bits(1)?;
}
}
} else {
let num_negative_pics = br.read_ue()?;
let num_positive_pics = br.read_ue()?;
for _ in 0..num_negative_pics {
let _delta_poc_s0_minus1 = br.read_ue()?;
let _used_by_curr_pic_s0_flag = br.read_bits(1)?;
}
for _ in 0..num_positive_pics {
let _delta_poc_s1_minus1 = br.read_ue()?;
let _used_by_curr_pic_s1_flag = br.read_bits(1)?;
}
}
Some(())
}
pub fn parse_h265_vps(sample: &[u8]) -> Option<H265VpsInfo> {
let nal = find_hevc_nal_by_type(sample, 32)?;
let rbsp = remove_h264_rbsp_stuffing(nal);
let mut br = BitReader::new(&rbsp);
let vps_video_parameter_set_id = br.read_bits(4)? as u8;
let _vps_base_layer_internal_flag = br.read_bits(1)?;
let _vps_base_layer_available_flag = br.read_bits(1)?;
let _vps_max_layers_minus1 = br.read_bits(6)?;
let vps_max_sub_layers_minus1 = br.read_bits(3)? as u8;
let vps_temporal_id_nesting_flag = br.read_bits(1)? == 1;
let _vps_reserved_0xffff_16bits = br.read_bits(16)?;
let _gp_space = br.read_bits(2)?;
let tier_flag = br.read_bits(1)? == 1;
let profile_idc = br.read_bits(5)? as u8;
let _ = br.read_bits(32)?; let _ = br.read_bits(48)?; let level_idc = br.read_bits(8)? as u8;
Some(H265VpsInfo {
vps_video_parameter_set_id,
vps_max_sub_layers_minus1,
vps_temporal_id_nesting_flag,
profile_idc,
level_idc,
tier_flag,
})
}
pub fn parse_h265_pps(sample: &[u8]) -> Option<H265PpsInfo> {
let nal = find_hevc_nal_by_type(sample, 34)?;
let rbsp = remove_h264_rbsp_stuffing(nal);
let mut br = BitReader::new(&rbsp);
let pps_pic_parameter_set_id = br.read_ue()? as u8;
let pps_seq_parameter_set_id = br.read_ue()? as u8;
let dependent_slice_segments_enabled_flag = br.read_bits(1)? == 1;
let output_flag_present_flag = br.read_bits(1)? == 1;
let num_extra_slice_header_bits = br.read_bits(3)? as u8;
let sign_data_hiding_enabled_flag = br.read_bits(1)? == 1;
let cabac_init_present_flag = br.read_bits(1)? == 1;
let num_ref_idx_l0_default_active_minus1 = br.read_ue()? as u8;
let num_ref_idx_l1_default_active_minus1 = br.read_ue()? as u8;
let init_qp_minus26 = clamp_to_i8(br.read_se()?);
let constrained_intra_pred_flag = br.read_bits(1)? == 1;
let transform_skip_enabled_flag = br.read_bits(1)? == 1;
let cu_qp_delta_enabled_flag = br.read_bits(1)? == 1;
let diff_cu_qp_delta_depth = if cu_qp_delta_enabled_flag {
br.read_ue()? as u8
} else {
0
};
let pps_cb_qp_offset = clamp_to_i8(br.read_se()?);
let pps_cr_qp_offset = clamp_to_i8(br.read_se()?);
let pps_slice_chroma_qp_offsets_present_flag = br.read_bits(1)? == 1;
let weighted_pred_flag = br.read_bits(1)? == 1;
let weighted_bipred_flag = br.read_bits(1)? == 1;
let transquant_bypass_enabled_flag = br.read_bits(1)? == 1;
let tiles_enabled_flag = br.read_bits(1)? == 1;
let entropy_coding_sync_enabled_flag = br.read_bits(1)? == 1;
let mut num_tile_columns_minus1 = 0u8;
let mut num_tile_rows_minus1 = 0u8;
let mut uniform_spacing_flag = true;
let mut loop_filter_across_tiles_enabled_flag = true;
if tiles_enabled_flag {
num_tile_columns_minus1 = br.read_ue().unwrap_or(0) as u8;
num_tile_rows_minus1 = br.read_ue().unwrap_or(0) as u8;
uniform_spacing_flag = br.read_bits(1).unwrap_or(1) == 1;
if !uniform_spacing_flag {
for _ in 0..num_tile_columns_minus1 {
let _ = br.read_ue();
}
for _ in 0..num_tile_rows_minus1 {
let _ = br.read_ue();
}
}
loop_filter_across_tiles_enabled_flag = br.read_bits(1).unwrap_or(1) == 1;
}
let pps_loop_filter_across_slices_enabled_flag = br.read_bits(1)? == 1;
let deblocking_filter_control_present_flag = br.read_bits(1)? == 1;
let mut deblocking_filter_override_enabled_flag = false;
let mut pps_deblocking_filter_disabled_flag = false;
let mut pps_beta_offset_div2 = 0i8;
let mut pps_tc_offset_div2 = 0i8;
if deblocking_filter_control_present_flag {
deblocking_filter_override_enabled_flag = br.read_bits(1)? == 1;
pps_deblocking_filter_disabled_flag = br.read_bits(1)? == 1;
if !pps_deblocking_filter_disabled_flag {
pps_beta_offset_div2 = clamp_to_i8(br.read_se()?);
pps_tc_offset_div2 = clamp_to_i8(br.read_se()?);
}
}
let pps_scaling_list_data_present_flag = br.read_bits(1)? == 1;
let lists_modification_present_flag = br.read_bits(1)? == 1;
let log2_parallel_merge_level_minus2 = br.read_ue().unwrap_or(0) as u8;
let slice_segment_header_extension_present_flag = br.read_bits(1)? == 1;
let pps_extension_present_flag = br.read_bits(1).unwrap_or(0) == 1;
Some(H265PpsInfo {
pps_pic_parameter_set_id,
pps_seq_parameter_set_id,
dependent_slice_segments_enabled_flag,
output_flag_present_flag,
num_extra_slice_header_bits,
sign_data_hiding_enabled_flag,
cabac_init_present_flag,
num_ref_idx_l0_default_active_minus1,
num_ref_idx_l1_default_active_minus1,
init_qp_minus26,
constrained_intra_pred_flag,
transform_skip_enabled_flag,
cu_qp_delta_enabled_flag,
diff_cu_qp_delta_depth,
pps_cb_qp_offset,
pps_cr_qp_offset,
pps_slice_chroma_qp_offsets_present_flag,
weighted_pred_flag,
weighted_bipred_flag,
transquant_bypass_enabled_flag,
tiles_enabled_flag,
entropy_coding_sync_enabled_flag,
num_tile_columns_minus1,
num_tile_rows_minus1,
uniform_spacing_flag,
loop_filter_across_tiles_enabled_flag,
pps_loop_filter_across_slices_enabled_flag,
deblocking_filter_control_present_flag,
deblocking_filter_override_enabled_flag,
pps_deblocking_filter_disabled_flag,
pps_beta_offset_div2,
pps_tc_offset_div2,
pps_scaling_list_data_present_flag,
lists_modification_present_flag,
log2_parallel_merge_level_minus2,
slice_segment_header_extension_present_flag,
pps_extension_present_flag,
})
}
pub fn parse_h265_slice_header(
sample: &[u8],
sps: &HevcSpsInfo,
pps: &H265PpsInfo,
) -> Option<H265SliceHeader> {
let (nal_unit_type, rbsp) = find_hevc_slice_nal(sample)?;
let mut br = BitReader::new(&rbsp);
let first_slice_segment_in_pic_flag = br.read_bits(1)? == 1;
let is_irap = (16..=23).contains(&nal_unit_type);
let is_idr = matches!(nal_unit_type, 19 | 20);
if is_irap {
let _no_output_of_prior_pics_flag = br.read_bits(1)?;
}
let slice_pic_parameter_set_id = br.read_ue()? as u8;
let dependent_slice_segment_flag =
if !first_slice_segment_in_pic_flag && pps.dependent_slice_segments_enabled_flag {
br.read_bits(1)? == 1
} else {
false
};
if !first_slice_segment_in_pic_flag {
return None;
}
let _ = dependent_slice_segment_flag;
for _ in 0..pps.num_extra_slice_header_bits {
let _ = br.read_bits(1)?;
}
let slice_type_code = br.read_ue()?;
let slice_type = H265SliceType::from_ue(slice_type_code)?;
if pps.output_flag_present_flag {
let _pic_output_flag = br.read_bits(1)?;
}
if sps.separate_colour_plane_flag {
let _colour_plane_id = br.read_bits(2)?;
}
let (pic_order_cnt_lsb, short_term_ref_pic_set_sps_flag, short_term_ref_pic_set_idx) =
if !is_idr {
let lsb_bits = (sps.log2_max_pic_order_cnt_lsb_minus4 as usize) + 4;
let lsb = br.read_bits(lsb_bits)?;
let sps_flag = br.read_bits(1)? == 1;
let idx = if sps_flag {
if sps.num_short_term_ref_pic_sets > 1 {
let bits =
((sps.num_short_term_ref_pic_sets as f64).log2().ceil() as usize).max(1);
Some(br.read_bits(bits)? as u8)
} else {
Some(0)
}
} else {
None
};
(lsb, sps_flag, idx)
} else {
(0, false, None)
};
Some(H265SliceHeader {
first_slice_segment_in_pic_flag,
nal_unit_type,
slice_pic_parameter_set_id,
slice_type,
pic_order_cnt_lsb,
short_term_ref_pic_set_sps_flag,
short_term_ref_pic_set_idx,
is_irap,
is_idr,
})
}
fn find_hevc_nal_by_type(data: &[u8], target: u8) -> Option<&[u8]> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
if nal_byte + 1 >= data.len() {
return None;
}
let nal_unit_type = (data[nal_byte] >> 1) & 0x3F;
if nal_unit_type == target {
let start = nal_byte + 2; let end = find_next_start_code(&data[start..])
.map(|off| start + off)
.unwrap_or(data.len());
return Some(&data[start..end]);
}
i += start_len;
}
None
}
pub fn hevc_first_slice_nal_offset(data: &[u8]) -> Option<u32> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3usize, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4usize, i + 4)
} else {
i += 1;
continue;
};
if nal_byte + 1 >= data.len() {
return None;
}
let t = (data[nal_byte] >> 1) & 0x3F;
if (0..=9).contains(&t) || (16..=23).contains(&t) {
return Some(nal_byte as u32);
}
i += start_len;
}
None
}
pub fn h264_first_slice_nal_offset(data: &[u8]) -> Option<u32> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3usize, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4usize, i + 4)
} else {
i += 1;
continue;
};
if nal_byte >= data.len() {
return None;
}
let t = data[nal_byte] & 0x1F;
if matches!(t, 1 | 5 | 19) {
return Some(nal_byte as u32);
}
i += start_len;
}
None
}
fn find_hevc_slice_nal(data: &[u8]) -> Option<(u8, Vec<u8>)> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
if nal_byte + 1 >= data.len() {
return None;
}
let t = (data[nal_byte] >> 1) & 0x3F;
if (0..=9).contains(&t) || (16..=23).contains(&t) {
let start = nal_byte + 2;
let end = find_next_start_code(&data[start..])
.map(|off| start + off)
.unwrap_or(data.len());
return Some((t, remove_h264_rbsp_stuffing(&data[start..end])));
}
i += start_len;
}
None
}
pub fn parse_mpeg2_sequence_header(sample: &[u8]) -> Option<Mpeg2SeqInfo> {
let seq_hdr_start = find_mpeg2_start_code(sample, 0xB3)?;
let hdr_body_off = seq_hdr_start + 4;
if hdr_body_off + 3 > sample.len() {
return None;
}
let b = &sample[hdr_body_off..hdr_body_off + 3];
let mut width = (((b[0] as u32) << 4) | ((b[1] as u32) >> 4)) & 0x0FFF;
let mut height = (((b[1] as u32 & 0x0F) << 8) | (b[2] as u32)) & 0x0FFF;
let search_from = hdr_body_off + 3;
if search_from < sample.len()
&& let Some(ext_start) = find_mpeg2_start_code(&sample[search_from..], 0xB5)
{
let ext_body_off = search_from + ext_start + 4;
if ext_body_off + 3 <= sample.len() {
let mut br = BitReader::new(&sample[ext_body_off..]);
if let Some(id) = br.read_bits(4)
&& id == 1
{
let _profile_level = br.read_bits(8)?;
let _progressive = br.read_bits(1)?;
let _chroma = br.read_bits(2)?;
let h_ext = br.read_bits(2)?;
let v_ext = br.read_bits(2)?;
width |= h_ext << 12;
height |= v_ext << 12;
}
}
}
if width == 0 || height == 0 {
return None;
}
Some(Mpeg2SeqInfo { width, height })
}
fn find_mpeg2_start_code(data: &[u8], target: u8) -> Option<usize> {
let mut i = 0;
while i + 4 <= data.len() {
if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 && data[i + 3] == target {
return Some(i);
}
i += 1;
}
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct H264PpsInfo {
pub pic_parameter_set_id: u8,
pub seq_parameter_set_id: u8,
pub entropy_coding_mode_flag: bool,
pub bottom_field_pic_order_in_frame_present_flag: bool,
pub num_slice_groups_minus1: u8,
pub num_ref_idx_l0_default_active_minus1: u8,
pub num_ref_idx_l1_default_active_minus1: u8,
pub weighted_pred_flag: bool,
pub weighted_bipred_idc: u8,
pub pic_init_qp_minus26: i8,
pub pic_init_qs_minus26: i8,
pub chroma_qp_index_offset: i8,
pub deblocking_filter_control_present_flag: bool,
pub constrained_intra_pred_flag: bool,
pub redundant_pic_cnt_present_flag: bool,
pub transform_8x8_mode_flag: Option<bool>,
pub pic_scaling_matrix_present_flag: Option<bool>,
pub second_chroma_qp_index_offset: Option<i8>,
}
pub fn parse_h264_pps(sample: &[u8]) -> Option<H264PpsInfo> {
let pps = find_h264_nal_by_type(sample, 8)?;
let rbsp = remove_h264_rbsp_stuffing(pps);
let mut br = BitReader::new(&rbsp);
let pic_parameter_set_id = br.read_ue()? as u8;
let seq_parameter_set_id = br.read_ue()? as u8;
let entropy_coding_mode_flag = br.read_bits(1)? == 1;
let bottom_field_pic_order_in_frame_present_flag = br.read_bits(1)? == 1;
let num_slice_groups_minus1 = br.read_ue()?;
if num_slice_groups_minus1 > 0 {
let slice_group_map_type = br.read_ue()?;
match slice_group_map_type {
0 => {
for _ in 0..=num_slice_groups_minus1 {
let _run_length_minus1 = br.read_ue()?;
}
}
2 => {
for _ in 0..num_slice_groups_minus1 {
let _top_left = br.read_ue()?;
let _bottom_right = br.read_ue()?;
}
}
3..=5 => {
let _slice_group_change_direction_flag = br.read_bits(1)?;
let _slice_group_change_rate_minus1 = br.read_ue()?;
}
6 => {
let pic_size_in_map_units_minus1 = br.read_ue()?;
let bits = ((num_slice_groups_minus1 + 1) as f64).log2().ceil() as usize;
let bits = bits.max(1);
for _ in 0..=pic_size_in_map_units_minus1 {
let _slice_group_id = br.read_bits(bits)?;
}
}
_ => {}
}
}
let num_ref_idx_l0_default_active_minus1 = br.read_ue()? as u8;
let num_ref_idx_l1_default_active_minus1 = br.read_ue()? as u8;
let weighted_pred_flag = br.read_bits(1)? == 1;
let weighted_bipred_idc = br.read_bits(2)? as u8;
let pic_init_qp_minus26 = clamp_to_i8(br.read_se()?);
let pic_init_qs_minus26 = clamp_to_i8(br.read_se()?);
let chroma_qp_index_offset = clamp_to_i8(br.read_se()?);
let deblocking_filter_control_present_flag = br.read_bits(1)? == 1;
let constrained_intra_pred_flag = br.read_bits(1)? == 1;
let redundant_pic_cnt_present_flag = br.read_bits(1)? == 1;
let (transform_8x8_mode_flag, pic_scaling_matrix_present_flag, second_chroma_qp_index_offset) =
if more_rbsp_data(&br, &rbsp) {
let t8 = br.read_bits(1).map(|v| v == 1);
let psm = br.read_bits(1).map(|v| v == 1);
if let Some(true) = psm {
let count = 6 + if let Some(true) = t8 { 2 } else { 0 };
for i in 0..count {
if br.read_bits(1) == Some(1) {
let size = if i < 6 { 16 } else { 64 };
let mut last_scale: i32 = 8;
let mut next_scale: i32 = 8;
for _ in 0..size {
if next_scale != 0 {
let delta = br.read_se().unwrap_or(0);
next_scale = (last_scale + delta + 256).rem_euclid(256);
}
if next_scale != 0 {
last_scale = next_scale;
}
}
}
}
}
let s2 = br.read_se().map(clamp_to_i8);
(t8, psm, s2)
} else {
(None, None, None)
};
Some(H264PpsInfo {
pic_parameter_set_id,
seq_parameter_set_id,
entropy_coding_mode_flag,
bottom_field_pic_order_in_frame_present_flag,
num_slice_groups_minus1: num_slice_groups_minus1.min(u8::MAX as u32) as u8,
num_ref_idx_l0_default_active_minus1,
num_ref_idx_l1_default_active_minus1,
weighted_pred_flag,
weighted_bipred_idc,
pic_init_qp_minus26,
pic_init_qs_minus26,
chroma_qp_index_offset,
deblocking_filter_control_present_flag,
constrained_intra_pred_flag,
redundant_pic_cnt_present_flag,
transform_8x8_mode_flag,
pic_scaling_matrix_present_flag,
second_chroma_qp_index_offset,
})
}
fn more_rbsp_data(br: &BitReader, rbsp: &[u8]) -> bool {
let pos = br.pos;
let total_bits = rbsp.len() * 8;
total_bits.saturating_sub(pos) > 8
}
fn clamp_to_i8(v: i32) -> i8 {
v.clamp(i8::MIN as i32, i8::MAX as i32) as i8
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum H264SliceType {
P,
B,
I,
SP,
SI,
}
impl H264SliceType {
fn from_ue(v: u32) -> Option<Self> {
match v % 5 {
0 => Some(Self::P),
1 => Some(Self::B),
2 => Some(Self::I),
3 => Some(Self::SP),
4 => Some(Self::SI),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct H264SliceHeader {
pub first_mb_in_slice: u32,
pub slice_type: H264SliceType,
pub pic_parameter_set_id: u8,
pub is_idr: bool,
pub frame_num: u32,
pub field_pic_flag: bool,
pub bottom_field_flag: bool,
pub colour_plane_id: Option<u8>,
pub idr_pic_id: Option<u32>,
pub pic_order_cnt_lsb: Option<u32>,
pub delta_pic_order_cnt_bottom: Option<i32>,
pub delta_pic_order_cnt: [Option<i32>; 2],
}
pub fn parse_h264_slice_header(
sample: &[u8],
sps: &H264SpsInfo,
pps: &H264PpsInfo,
) -> Option<H264SliceHeader> {
let (nal_type, rbsp) = find_h264_slice_nal(sample)?;
let is_idr = nal_type == 5;
let mut br = BitReader::new(&rbsp);
let first_mb_in_slice = br.read_ue()?;
let slice_type_code = br.read_ue()?;
let slice_type = H264SliceType::from_ue(slice_type_code)?;
let pic_parameter_set_id = br.read_ue()? as u8;
let colour_plane_id = if sps.separate_colour_plane_flag {
Some(br.read_bits(2)? as u8)
} else {
None
};
let frame_num_bits = (sps.log2_max_frame_num_minus4? as usize) + 4;
let frame_num = br.read_bits(frame_num_bits)?;
let (field_pic_flag, bottom_field_flag) = if !sps.frame_mbs_only {
let f = br.read_bits(1)? == 1;
let b = if f { br.read_bits(1)? == 1 } else { false };
(f, b)
} else {
(false, false)
};
let idr_pic_id = if is_idr { Some(br.read_ue()?) } else { None };
let poc_type = sps.pic_order_cnt_type?;
let mut pic_order_cnt_lsb = None;
let mut delta_pic_order_cnt_bottom = None;
let mut delta_pic_order_cnt: [Option<i32>; 2] = [None, None];
match poc_type {
0 => {
let bits = (sps.log2_max_pic_order_cnt_lsb_minus4? as usize) + 4;
pic_order_cnt_lsb = Some(br.read_bits(bits)?);
if pps.bottom_field_pic_order_in_frame_present_flag && !field_pic_flag {
delta_pic_order_cnt_bottom = Some(br.read_se()?);
}
}
1 => {
let always_zero = sps.delta_pic_order_always_zero_flag.unwrap_or(false);
if !always_zero {
delta_pic_order_cnt[0] = Some(br.read_se()?);
if pps.bottom_field_pic_order_in_frame_present_flag && !field_pic_flag {
delta_pic_order_cnt[1] = Some(br.read_se()?);
}
}
}
2 => { }
_ => return None,
}
Some(H264SliceHeader {
first_mb_in_slice,
slice_type,
pic_parameter_set_id,
is_idr,
frame_num,
field_pic_flag,
bottom_field_flag,
colour_plane_id,
idr_pic_id,
pic_order_cnt_lsb,
delta_pic_order_cnt_bottom,
delta_pic_order_cnt,
})
}
fn find_h264_slice_nal(data: &[u8]) -> Option<(u8, Vec<u8>)> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
if nal_byte >= data.len() {
return None;
}
let nal_unit_type = data[nal_byte] & 0x1F;
if matches!(nal_unit_type, 1 | 5 | 19) {
let start = nal_byte + 1;
let end = find_next_start_code(&data[start..])
.map(|off| start + off)
.unwrap_or(data.len());
let rbsp = remove_h264_rbsp_stuffing(&data[start..end]);
return Some((nal_unit_type, rbsp));
}
i += start_len;
}
None
}
fn find_h264_nal_by_type(data: &[u8], target_type: u8) -> Option<&[u8]> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
if nal_byte >= data.len() {
return None;
}
let nal_unit_type = data[nal_byte] & 0x1F;
if nal_unit_type == target_type {
let start = nal_byte + 1;
let end = find_next_start_code(&data[start..])
.map(|off| start + off)
.unwrap_or(data.len());
return Some(&data[start..end]);
}
i += start_len;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_h264_baseline_yuv420p() {
let sps_rbsp = vec![
66, 0, 30, 0b1000_0000,
];
let mut sample = vec![0, 0, 0, 1, 0x27]; sample.extend_from_slice(&sps_rbsp);
let pf = detect_h264(&sample).unwrap();
assert_eq!(pf, PixelFormat::Yuv420p);
}
#[test]
fn empty_samples_returns_default() {
let pf = detect("h264", &[]);
assert_eq!(pf, PixelFormat::Yuv420p);
}
#[test]
fn unknown_codec_returns_default() {
let pf = detect("prores", &[vec![1, 2, 3]]);
assert_eq!(pf, PixelFormat::Yuv420p);
}
#[test]
fn from_chroma_and_depth_420_8bit() {
assert_eq!(
PixelFormat::from_chroma_and_depth(1, 8),
PixelFormat::Yuv420p
);
assert_eq!(
PixelFormat::from_chroma_and_depth(1, 10),
PixelFormat::Yuv420p10le
);
assert_eq!(
PixelFormat::from_chroma_and_depth(2, 8),
PixelFormat::Yuv422p
);
assert_eq!(
PixelFormat::from_chroma_and_depth(3, 8),
PixelFormat::Yuv444p
);
}
#[test]
fn as_ffmpeg_str_matches_python_names() {
assert_eq!(PixelFormat::Yuv420p.as_ffmpeg_str(), "yuv420p");
assert_eq!(PixelFormat::Yuv420p10le.as_ffmpeg_str(), "yuv420p10le");
assert_eq!(PixelFormat::Yuv444p.as_ffmpeg_str(), "yuv444p");
}
struct BitWriter {
bytes: Vec<u8>,
bit_pos: usize, }
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
bit_pos: 8,
}
}
fn write_bit(&mut self, b: u8) {
if self.bit_pos == 8 {
self.bytes.push(0);
self.bit_pos = 0;
}
if b != 0 {
let idx = self.bytes.len() - 1;
self.bytes[idx] |= 1 << (7 - self.bit_pos);
}
self.bit_pos += 1;
}
fn write_bits(&mut self, val: u64, n: usize) {
for i in 0..n {
let bit = ((val >> (n - 1 - i)) & 1) as u8;
self.write_bit(bit);
}
}
fn write_ue(&mut self, v: u32) {
let z = if v == 0 { 0 } else { (v + 1).ilog2() as usize };
for _ in 0..z {
self.write_bit(0);
}
self.write_bit(1);
if z > 0 {
let suffix = (v + 1) - (1u32 << z);
self.write_bits(suffix as u64, z);
}
}
fn bytes(self) -> Vec<u8> {
self.bytes
}
}
fn build_h264_baseline_sps(width_in_mbs: u32, height_in_mbs: u32) -> Vec<u8> {
let mut w = BitWriter::new();
w.write_bits(66, 8); w.write_bits(0, 8); w.write_bits(30, 8); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_ue(1); w.write_bit(0); w.write_ue(width_in_mbs - 1); w.write_ue(height_in_mbs - 1); w.write_bit(1); w.write_bit(1); w.write_bit(0); w.write_bit(0); w.write_bit(1); let mut sample = vec![0x00, 0x00, 0x00, 0x01, 0x67]; sample.extend_from_slice(&w.bytes());
sample
}
#[test]
fn parse_h264_sps_baseline_1280x720() {
let sample = build_h264_baseline_sps(1280 / 16, 720 / 16);
let info = parse_h264_sps(&sample).expect("parse");
assert_eq!(info.profile_idc, 66);
assert_eq!(info.chroma_format_idc, 1); assert_eq!(info.width, Some(1280));
assert_eq!(info.height, Some(720));
assert!(info.frame_mbs_only);
}
#[test]
fn parse_h264_sps_baseline_640x480() {
let sample = build_h264_baseline_sps(640 / 16, 480 / 16);
let info = parse_h264_sps(&sample).expect("parse");
assert_eq!(info.width, Some(640));
assert_eq!(info.height, Some(480));
}
#[test]
fn parse_h264_sps_with_cropping_1920x1080() {
let mut w = BitWriter::new();
w.write_bits(66, 8);
w.write_bits(0, 8);
w.write_bits(40, 8);
w.write_ue(0);
w.write_ue(0);
w.write_ue(0);
w.write_ue(0);
w.write_ue(1);
w.write_bit(0);
w.write_ue(1920 / 16 - 1); w.write_ue(1088 / 16 - 1); w.write_bit(1); w.write_bit(1); w.write_bit(1); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_ue(4); w.write_bit(0); w.write_bit(1); let mut sample = vec![0, 0, 0, 1, 0x67];
sample.extend_from_slice(&w.bytes());
let info = parse_h264_sps(&sample).expect("parse");
assert_eq!(info.width, Some(1920));
assert_eq!(info.height, Some(1080));
}
#[test]
fn parse_h264_sps_high_profile_422_returns_chroma_even_without_dims() {
let mut w = BitWriter::new();
w.write_bits(122, 8); w.write_bits(0, 8);
w.write_bits(40, 8);
w.write_ue(0); w.write_ue(2); w.write_ue(0); w.write_ue(0); w.write_bit(0); w.write_bit(0); let mut sample = vec![0, 0, 0, 1, 0x67];
sample.extend_from_slice(&w.bytes());
let info = parse_h264_sps(&sample).expect("parse");
assert_eq!(info.profile_idc, 122);
assert_eq!(info.chroma_format_idc, 2);
}
fn build_hevc_sps(pic_width: u32, pic_height: u32) -> Vec<u8> {
build_hevc_sps_full(pic_width, pic_height, false, 0, 0, 0, 0)
}
fn build_hevc_sps_full(
pic_width: u32,
pic_height: u32,
conformance_window: bool,
cwl: u32,
cwr: u32,
cwt: u32,
cwb: u32,
) -> Vec<u8> {
let mut w = BitWriter::new();
w.write_bits(0, 4); w.write_bits(0, 3); w.write_bits(1, 1); w.write_bits(0b0_0_00001, 8); w.write_bits(0x40000000, 32); w.write_bits(0, 48); w.write_bits(93, 8);
w.write_ue(0); w.write_ue(1); w.write_ue(pic_width);
w.write_ue(pic_height);
if conformance_window {
w.write_bit(1);
w.write_ue(cwl);
w.write_ue(cwr);
w.write_ue(cwt);
w.write_ue(cwb);
} else {
w.write_bit(0); }
w.write_ue(0); w.write_ue(0); w.write_ue(4); w.write_bit(1); w.write_ue(4); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_ue(3); w.write_ue(0); w.write_ue(3); w.write_ue(2); w.write_ue(2); w.write_bit(0); w.write_bit(1); w.write_bit(1); w.write_bit(0); w.write_ue(0); w.write_bit(0); w.write_bit(1); w.write_bit(0); w.write_bit(0); w.write_bit(0); w.write_bit(1); let mut sample = vec![0, 0, 0, 1, 0x42, 0x01];
sample.extend_from_slice(&w.bytes());
sample
}
#[test]
fn parse_hevc_sps_1920x1080_no_crop() {
let sample = build_hevc_sps(1920, 1080);
let info = parse_hevc_sps(&sample).expect("parse");
assert_eq!(info.chroma_format_idc, 1);
assert_eq!(info.bit_depth_luma, 8);
assert_eq!(info.width, Some(1920));
assert_eq!(info.height, Some(1080));
}
#[test]
fn parse_hevc_sps_with_conformance_window() {
let sample = build_hevc_sps_full(1920, 1088, true, 0, 0, 0, 4);
let info = parse_hevc_sps(&sample).expect("parse");
assert_eq!(info.width, Some(1920));
assert_eq!(info.height, Some(1080));
}
#[test]
fn parse_mpeg2_sequence_header_no_extension_640x480() {
let sample = vec![0x00, 0x00, 0x01, 0xB3, 0x28, 0x01, 0xE0, 0x13, 0xFF, 0xFF];
let info = parse_mpeg2_sequence_header(&sample).expect("parse");
assert_eq!(info.width, 640);
assert_eq!(info.height, 480);
}
#[test]
fn parse_mpeg2_sequence_header_with_extension_upgrades_to_14bit() {
let mut bytes = vec![0x00, 0x00, 0x01, 0xB3, 0x78, 0x04, 0x38, 0x13, 0xFF, 0xFF];
let mut w = BitWriter::new();
w.write_bits(1, 4); w.write_bits(0, 8); w.write_bit(1); w.write_bits(1, 2); w.write_bits(1, 2); w.write_bits(2, 2); w.write_bits(0, 1); bytes.extend_from_slice(&[0x00, 0x00, 0x01, 0xB5]);
bytes.extend_from_slice(&w.bytes());
let info = parse_mpeg2_sequence_header(&bytes).expect("parse");
assert_eq!(info.width, 1920 | (1 << 12)); assert_eq!(info.height, 1080 | (2 << 12)); }
#[test]
fn parse_mpeg2_sequence_header_none_when_no_start_code() {
let sample = vec![0xFFu8; 128];
assert!(parse_mpeg2_sequence_header(&sample).is_none());
}
#[test]
fn detect_dims_dispatches_by_codec() {
let h264 = build_h264_baseline_sps(1280 / 16, 720 / 16);
let hevc = build_hevc_sps(1920, 1080);
let mpeg2 = vec![0x00, 0x00, 0x01, 0xB3, 0x28, 0x01, 0xE0, 0x13, 0xFF, 0xFF];
assert_eq!(detect_dims("h264", &[h264.clone()]), Some((1280, 720)));
assert_eq!(detect_dims("avc1", &[h264]), Some((1280, 720)));
assert_eq!(detect_dims("h265", &[hevc.clone()]), Some((1920, 1080)));
assert_eq!(detect_dims("hevc", &[hevc]), Some((1920, 1080)));
assert_eq!(detect_dims("mpeg2", &[mpeg2]), Some((640, 480)));
assert_eq!(detect_dims("unknown", &[vec![0u8; 8]]), None);
assert_eq!(detect_dims("h264", &[]), None);
}
fn build_h264_baseline_pps(pps_id: u32, sps_id: u32) -> Vec<u8> {
let mut w = BitWriter::new();
w.write_ue(pps_id); w.write_ue(sps_id); w.write_bit(0); w.write_bit(0); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_bit(0); w.write_bits(0, 2); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_bit(1); w.write_bit(0); w.write_bit(0); w.write_bit(1); let mut sample = vec![0x00, 0x00, 0x00, 0x01, 0x68]; sample.extend_from_slice(&w.bytes());
sample
}
#[test]
fn parse_h264_pps_baseline_roundtrip() {
let sample = build_h264_baseline_pps(0, 0);
let info = parse_h264_pps(&sample).expect("PPS parses");
assert_eq!(info.pic_parameter_set_id, 0);
assert_eq!(info.seq_parameter_set_id, 0);
assert!(!info.entropy_coding_mode_flag);
assert!(!info.bottom_field_pic_order_in_frame_present_flag);
assert_eq!(info.num_slice_groups_minus1, 0);
assert_eq!(info.num_ref_idx_l0_default_active_minus1, 0);
assert_eq!(info.num_ref_idx_l1_default_active_minus1, 0);
assert!(!info.weighted_pred_flag);
assert_eq!(info.weighted_bipred_idc, 0);
assert_eq!(info.pic_init_qp_minus26, 0);
assert_eq!(info.pic_init_qs_minus26, 0);
assert_eq!(info.chroma_qp_index_offset, 0);
assert!(info.deblocking_filter_control_present_flag);
assert!(!info.constrained_intra_pred_flag);
assert!(!info.redundant_pic_cnt_present_flag);
}
#[test]
fn parse_h264_pps_nonzero_ids_and_flags() {
let mut w = BitWriter::new();
w.write_ue(3); w.write_ue(7); w.write_bit(1); w.write_bit(1); w.write_ue(0); w.write_ue(2); w.write_ue(1); w.write_bit(1); w.write_bits(2, 2); w.write_ue(10);
w.write_ue(5);
w.write_ue(0);
w.write_bit(0); w.write_bit(1); w.write_bit(1); w.write_bit(1); let mut sample = vec![0x00, 0x00, 0x00, 0x01, 0x68];
sample.extend_from_slice(&w.bytes());
let info = parse_h264_pps(&sample).expect("parse");
assert_eq!(info.pic_parameter_set_id, 3);
assert_eq!(info.seq_parameter_set_id, 7);
assert!(info.entropy_coding_mode_flag);
assert!(info.bottom_field_pic_order_in_frame_present_flag);
assert_eq!(info.num_ref_idx_l0_default_active_minus1, 2);
assert_eq!(info.num_ref_idx_l1_default_active_minus1, 1);
assert!(info.weighted_pred_flag);
assert_eq!(info.weighted_bipred_idc, 2);
assert_eq!(info.pic_init_qp_minus26, -5);
assert_eq!(info.pic_init_qs_minus26, 3);
assert!(!info.deblocking_filter_control_present_flag);
assert!(info.constrained_intra_pred_flag);
assert!(info.redundant_pic_cnt_present_flag);
}
#[test]
fn parse_h264_pps_returns_none_when_no_pps_in_sample() {
let sample = build_h264_baseline_sps(80, 45); assert!(parse_h264_pps(&sample).is_none());
}
fn build_h264_idr_slice_header_rbsp() -> Vec<u8> {
let mut w = BitWriter::new();
w.write_ue(0); w.write_ue(7); w.write_ue(0); w.write_bits(0, 4); w.write_ue(0); w.write_bits(0, 4); w.bytes()
}
#[test]
fn parse_h264_slice_header_idr_i_slice() {
let sps = parse_h264_sps(&build_h264_baseline_sps(1280 / 16, 720 / 16)).expect("sps");
let pps = parse_h264_pps(&build_h264_baseline_pps(0, 0)).expect("pps");
let rbsp = build_h264_idr_slice_header_rbsp();
let mut sample = vec![0x00, 0x00, 0x00, 0x01, 0x65];
sample.extend_from_slice(&rbsp);
let sh = parse_h264_slice_header(&sample, &sps, &pps).expect("slice");
assert_eq!(sh.first_mb_in_slice, 0);
assert_eq!(sh.slice_type, H264SliceType::I);
assert_eq!(sh.pic_parameter_set_id, 0);
assert!(sh.is_idr);
assert_eq!(sh.frame_num, 0);
assert!(!sh.field_pic_flag);
assert_eq!(sh.idr_pic_id, Some(0));
assert_eq!(sh.pic_order_cnt_lsb, Some(0));
}
#[test]
fn parse_h264_slice_header_returns_none_without_sps_context() {
let mut w = BitWriter::new();
w.write_bits(122, 8);
w.write_bits(0, 8);
w.write_bits(40, 8);
w.write_ue(0); w.write_ue(2); w.write_ue(0);
w.write_ue(0);
w.write_bit(0); w.write_bit(0); let mut sample = vec![0, 0, 0, 1, 0x67];
sample.extend_from_slice(&w.bytes());
let sps = parse_h264_sps(&sample).expect("sps parses");
assert!(sps.pic_order_cnt_type.is_none());
let pps = parse_h264_pps(&build_h264_baseline_pps(0, 0)).expect("pps");
let rbsp = build_h264_idr_slice_header_rbsp();
let mut slice_sample = vec![0x00, 0x00, 0x00, 0x01, 0x65];
slice_sample.extend_from_slice(&rbsp);
assert!(parse_h264_slice_header(&slice_sample, &sps, &pps).is_none());
}
#[test]
fn parse_h264_slice_type_ue_mapping_covers_both_halves() {
for (code, expected) in [
(0, H264SliceType::P),
(5, H264SliceType::P),
(1, H264SliceType::B),
(6, H264SliceType::B),
(2, H264SliceType::I),
(7, H264SliceType::I),
(3, H264SliceType::SP),
(8, H264SliceType::SP),
(4, H264SliceType::SI),
(9, H264SliceType::SI),
] {
assert_eq!(
H264SliceType::from_ue(code),
Some(expected),
"code {}",
code
);
}
}
#[test]
fn bit_reader_read_se_exp_golomb_mapping() {
for (code, expected) in [(0u32, 0i32), (1, 1), (2, -1), (3, 2), (4, -2), (5, 3)] {
let mut w = BitWriter::new();
w.write_ue(code);
let bytes = w.bytes();
let mut br = BitReader::new(&bytes);
assert_eq!(
br.read_se(),
Some(expected),
"codeNum={} expected={}",
code,
expected
);
}
}
}