use crate::DecodeError;
use mediaway_sw::h264::BitReader;
use smallvec::SmallVec;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(super) struct Sps {
pub(super) profile_idc: u8,
pub(super) level_idc: u8,
pub(super) seq_parameter_set_id: u32,
pub(super) bit_depth_luma_minus8: u32,
pub(super) bit_depth_chroma_minus8: u32,
pub(super) log2_max_frame_num: u32,
pub(super) pic_order_cnt_type: u32,
pub(super) log2_max_pic_order_cnt_lsb: u32,
pub(super) delta_pic_order_always_zero_flag: bool,
pub(super) offset_for_non_ref_pic: i32,
pub(super) offset_for_top_to_bottom_field: i32,
pub(super) offset_for_ref_frame: SmallVec<[i32; 8]>,
pub(super) max_num_ref_frames: u32,
pub(super) mb_width: u32,
pub(super) mb_height: u32,
pub(super) direct_8x8_inference_flag: bool,
pub(super) cropped_width: u32,
pub(super) cropped_height: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[allow(
clippy::struct_excessive_bools,
reason = "mirrors ITU-T H.264's own PPS syntax element list 1:1 — each bool is an \
independent bitstream flag, not a state machine"
)]
pub(super) struct Pps {
pub(super) pic_parameter_set_id: u32,
pub(super) seq_parameter_set_id: u32,
pub(super) entropy_coding_mode_flag: bool,
pub(super) bottom_field_pic_order_in_frame_present_flag: bool,
pub(super) num_slice_groups_minus1: u32,
pub(super) num_ref_idx_l0_default_active_minus1: u32,
pub(super) num_ref_idx_l1_default_active_minus1: u32,
pub(super) weighted_pred_flag: bool,
pub(super) weighted_bipred_idc: u32,
pub(super) pic_init_qp_minus26: i32,
pub(super) pic_init_qs_minus26: i32,
pub(super) chroma_qp_index_offset: i32,
pub(super) deblocking_filter_control_present_flag: bool,
pub(super) constrained_intra_pred_flag: bool,
pub(super) redundant_pic_cnt_present_flag: bool,
pub(super) transform_8x8_mode_flag: bool,
pub(super) second_chroma_qp_index_offset: i32,
}
fn map_bit_err<T>(r: Result<T, mediaway_sw::h264::H264Error>) -> Result<T, DecodeError> {
r.map_err(|_err| DecodeError::InvalidInput)
}
fn read_bit(r: &mut BitReader<'_>) -> Result<bool, DecodeError> {
Ok(map_bit_err(r.read_bit())? != 0)
}
fn read_bits(r: &mut BitReader<'_>, count: u32) -> Result<u32, DecodeError> {
map_bit_err(r.read_bits(count))
}
fn read_ue(r: &mut BitReader<'_>) -> Result<u32, DecodeError> {
map_bit_err(r.read_ue())
}
fn read_se(r: &mut BitReader<'_>) -> Result<i32, DecodeError> {
map_bit_err(r.read_se())
}
fn skip_scaling_list(r: &mut BitReader<'_>, size: usize) -> Result<(), DecodeError> {
let mut last_scale = 8i32;
let mut next_scale = 8i32;
for _ in 0..size {
if next_scale != 0 {
let delta_scale = read_se(r)?;
next_scale = (last_scale + delta_scale + 256) % 256;
}
last_scale = if next_scale == 0 {
last_scale
} else {
next_scale
};
}
Ok(())
}
const HIGH_CHROMA_PROFILES: [u8; 12] = [100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134];
fn rbsp_stop_bit_pos(rbsp: &[u8]) -> usize {
for (byte_idx, &byte) in rbsp.iter().enumerate().rev() {
if byte != 0 {
let msb_index = 7 - byte.trailing_zeros() as usize;
return byte_idx * 8 + msb_index;
}
}
0
}
fn more_rbsp_data(bits_read: usize, rbsp: &[u8]) -> bool {
bits_read < rbsp_stop_bit_pos(rbsp)
}
pub(super) fn parse_sps(rbsp: &[u8]) -> Result<Sps, DecodeError> {
let mut r = BitReader::new(rbsp);
let profile_idc = u8::try_from(read_bits(&mut r, 8)?).unwrap_or(0);
let _constraint_and_reserved = read_bits(&mut r, 8)?;
let level_idc = u8::try_from(read_bits(&mut r, 8)?).unwrap_or(0);
let seq_parameter_set_id = read_ue(&mut r)?;
let mut chroma_format_idc = 1u32;
let mut separate_colour_plane_flag = false;
let mut bit_depth_luma_minus8 = 0u32;
let mut bit_depth_chroma_minus8 = 0u32;
if HIGH_CHROMA_PROFILES.contains(&profile_idc) {
chroma_format_idc = read_ue(&mut r)?;
if chroma_format_idc == 3 {
separate_colour_plane_flag = read_bit(&mut r)?;
}
bit_depth_luma_minus8 = read_ue(&mut r)?;
bit_depth_chroma_minus8 = read_ue(&mut r)?;
let _qpprime_y_zero_transform_bypass_flag = read_bit(&mut r)?;
let seq_scaling_matrix_present_flag = read_bit(&mut r)?;
if seq_scaling_matrix_present_flag {
let count = if chroma_format_idc == 3 { 12 } else { 8 };
for i in 0..count {
if read_bit(&mut r)? {
let size = if i < 6 { 16 } else { 64 };
skip_scaling_list(&mut r, size)?;
}
}
}
}
if chroma_format_idc != 1 || separate_colour_plane_flag {
return Err(DecodeError::Unsupported);
}
let log2_max_frame_num_minus4 = read_ue(&mut r)?;
let pic_order_cnt_type = read_ue(&mut r)?;
let mut log2_max_pic_order_cnt_lsb_minus4 = 0u32;
let mut delta_pic_order_always_zero_flag = false;
let mut offset_for_non_ref_pic = 0i32;
let mut offset_for_top_to_bottom_field = 0i32;
let mut offset_for_ref_frame: SmallVec<[i32; 8]> = SmallVec::new();
match pic_order_cnt_type {
0 => log2_max_pic_order_cnt_lsb_minus4 = read_ue(&mut r)?,
1 => {
delta_pic_order_always_zero_flag = read_bit(&mut r)?;
offset_for_non_ref_pic = read_se(&mut r)?;
offset_for_top_to_bottom_field = read_se(&mut r)?;
let num_ref_frames_in_pic_order_cnt_cycle = read_ue(&mut r)?;
for _ in 0..num_ref_frames_in_pic_order_cnt_cycle {
offset_for_ref_frame.push(read_se(&mut r)?);
}
}
2 => {}
_ => return Err(DecodeError::InvalidInput),
}
let max_num_ref_frames = read_ue(&mut r)?;
let _gaps_in_frame_num_value_allowed_flag = read_bit(&mut r)?;
let pic_width_in_mbs_minus1 = read_ue(&mut r)?;
let pic_height_in_map_units_minus1 = read_ue(&mut r)?;
let frame_mbs_only_flag = read_bit(&mut r)?;
if !frame_mbs_only_flag {
return Err(DecodeError::Unsupported);
}
let direct_8x8_inference_flag = read_bit(&mut r)?;
let frame_cropping_flag = read_bit(&mut r)?;
let (mut crop_left, mut crop_right, mut crop_top, mut crop_bottom) = (0u32, 0u32, 0u32, 0u32);
if frame_cropping_flag {
crop_left = read_ue(&mut r)?;
crop_right = read_ue(&mut r)?;
crop_top = read_ue(&mut r)?;
crop_bottom = read_ue(&mut r)?;
}
let mb_width = pic_width_in_mbs_minus1 + 1;
let mb_height = pic_height_in_map_units_minus1 + 1; let width = mb_width * 16;
let height = mb_height * 16;
let cropped_width = width.saturating_sub(2 * (crop_left + crop_right));
let cropped_height = height.saturating_sub(2 * (crop_top + crop_bottom));
Ok(Sps {
profile_idc,
level_idc,
seq_parameter_set_id,
bit_depth_luma_minus8,
bit_depth_chroma_minus8,
log2_max_frame_num: log2_max_frame_num_minus4 + 4,
pic_order_cnt_type,
log2_max_pic_order_cnt_lsb: log2_max_pic_order_cnt_lsb_minus4 + 4,
delta_pic_order_always_zero_flag,
offset_for_non_ref_pic,
offset_for_top_to_bottom_field,
offset_for_ref_frame,
max_num_ref_frames: max_num_ref_frames.max(1),
mb_width,
mb_height,
direct_8x8_inference_flag,
cropped_width,
cropped_height,
})
}
#[allow(
clippy::similar_names,
reason = "pic_init_qp_minus26/pic_init_qs_minus26 intentionally pair ITU-T H.264's own PPS field names"
)]
pub(super) fn parse_pps(rbsp: &[u8]) -> Result<Pps, DecodeError> {
let mut r = BitReader::new(rbsp);
let pic_parameter_set_id = read_ue(&mut r)?;
let seq_parameter_set_id = read_ue(&mut r)?;
let entropy_coding_mode_flag = read_bit(&mut r)?;
let bottom_field_pic_order_in_frame_present_flag = read_bit(&mut r)?;
let num_slice_groups_minus1 = read_ue(&mut r)?;
if num_slice_groups_minus1 != 0 {
return Err(DecodeError::Unsupported);
}
let num_ref_idx_l0_default_active_minus1 = read_ue(&mut r)?;
let num_ref_idx_l1_default_active_minus1 = read_ue(&mut r)?;
let weighted_pred_flag = read_bit(&mut r)?;
let weighted_bipred_idc = read_bits(&mut r, 2)?;
let pic_init_qp_minus26 = read_se(&mut r)?;
let pic_init_qs_minus26 = read_se(&mut r)?;
let chroma_qp_index_offset = read_se(&mut r)?;
let deblocking_filter_control_present_flag = read_bit(&mut r)?;
let constrained_intra_pred_flag = read_bit(&mut r)?;
let redundant_pic_cnt_present_flag = read_bit(&mut r)?;
let (transform_8x8_mode_flag, second_chroma_qp_index_offset) =
if more_rbsp_data(r.bits_read(), rbsp) {
let transform_8x8_mode_flag = read_bit(&mut r)?;
let pic_scaling_matrix_present_flag = read_bit(&mut r)?;
if pic_scaling_matrix_present_flag {
let count = 6 + if transform_8x8_mode_flag { 2 } else { 0 };
for i in 0..count {
if read_bit(&mut r)? {
let size = if i < 6 { 16 } else { 64 };
skip_scaling_list(&mut r, size)?;
}
}
}
(transform_8x8_mode_flag, read_se(&mut r)?)
} else {
(false, chroma_qp_index_offset)
};
Ok(Pps {
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_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,
second_chroma_qp_index_offset,
})
}
#[cfg(test)]
#[path = "h264_sps_pps_tests.rs"]
mod tests;