use crate::DecodeError;
use mediaway_sw::h264::BitReader;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(
clippy::struct_excessive_bools,
reason = "each field names one ITU-T H.264 PPS syntax element (`entropy_coding_mode_flag`, …); a state machine would obscure the 1:1 spec mapping this crate relies on for review"
)]
pub(super) struct Pps {
pub(super) pic_parameter_set_id: u32,
pub(super) entropy_coding_mode_flag: bool,
pub(super) pic_order_present_flag: bool,
pub(super) pic_init_qp_minus26: i32,
pub(super) pic_init_qs_minus26: i32,
pub(super) chroma_qp_index_offset: i32,
pub(super) second_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,
}
impl Pps {
#[allow(
clippy::similar_names,
reason = "pic_init_qp_minus26 / pic_init_qs_minus26 are the ITU-T H.264 spec's own names"
)]
pub(super) fn parse(rbsp: &[u8]) -> Result<Self, DecodeError> {
let mut r = BitReader::new(rbsp);
let map_err = |_| DecodeError::InvalidInput;
let pic_parameter_set_id = r.read_ue().map_err(map_err)?;
let _seq_parameter_set_id = r.read_ue().map_err(map_err)?;
let entropy_coding_mode_flag = r.read_bit().map_err(map_err)? != 0;
let pic_order_present_flag = r.read_bit().map_err(map_err)? != 0;
let num_slice_groups_minus1 = r.read_ue().map_err(map_err)?;
if num_slice_groups_minus1 != 0 {
return Err(DecodeError::Unsupported);
}
let _num_ref_idx_l0_default_active_minus1 = r.read_ue().map_err(map_err)?;
let _num_ref_idx_l1_default_active_minus1 = r.read_ue().map_err(map_err)?;
let _weighted_pred_flag = r.read_bit().map_err(map_err)?;
let _weighted_bipred_idc = r.read_bits(2).map_err(map_err)?;
let pic_init_qp_minus26 = r.read_se().map_err(map_err)?;
let pic_init_qs_minus26 = r.read_se().map_err(map_err)?;
let chroma_qp_index_offset = r.read_se().map_err(map_err)?;
let deblocking_filter_control_present_flag = r.read_bit().map_err(map_err)? != 0;
let constrained_intra_pred_flag = r.read_bit().map_err(map_err)? != 0;
let redundant_pic_cnt_present_flag = r.read_bit().map_err(map_err)? != 0;
let second_chroma_qp_index_offset = if more_rbsp_data(rbsp, r.bits_read()) {
let transform_8x8_mode_flag = r.read_bit().map_err(map_err)? != 0;
let pic_scaling_matrix_present_flag = r.read_bit().map_err(map_err)? != 0;
if transform_8x8_mode_flag || pic_scaling_matrix_present_flag {
return Err(DecodeError::Unsupported);
}
r.read_se().map_err(map_err)?
} else {
chroma_qp_index_offset
};
Ok(Self {
pic_parameter_set_id,
entropy_coding_mode_flag,
pic_order_present_flag,
pic_init_qp_minus26,
pic_init_qs_minus26,
chroma_qp_index_offset,
second_chroma_qp_index_offset,
deblocking_filter_control_present_flag,
constrained_intra_pred_flag,
redundant_pic_cnt_present_flag,
})
}
}
fn more_rbsp_data(rbsp: &[u8], bits_read: usize) -> bool {
let total_bits = rbsp.len() * 8;
if bits_read >= total_bits {
return false;
}
for bit_idx in (0..total_bits).rev() {
let byte = rbsp[bit_idx / 8];
let shift = 7 - (bit_idx % 8);
if (byte >> shift) & 1 == 1 {
return bits_read < bit_idx;
}
}
false
}
#[cfg(test)]
#[path = "pps_tests.rs"]
mod tests;