use crate::DecodeError;
use mediaway_sw::h264::{BitReader, H264Error};
fn map_bit_err<T>(r: Result<T, 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())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum HevcNalUnitType {
Trail(u8),
Idr,
Cra,
Vps,
Sps,
Pps,
Other(u8),
}
impl HevcNalUnitType {
const fn from_u8(value: u8) -> Self {
match value {
0..=9 => Self::Trail(value),
19 | 20 => Self::Idr,
21 => Self::Cra,
32 => Self::Vps,
33 => Self::Sps,
34 => Self::Pps,
other => Self::Other(other),
}
}
pub(super) const fn is_idr(self) -> bool {
matches!(self, Self::Idr)
}
}
pub(super) const fn is_reference_nal(raw_nal_unit_type: u8) -> bool {
raw_nal_unit_type > 15 || raw_nal_unit_type % 2 == 1
}
#[derive(Debug, Clone)]
pub(super) struct HevcNalUnit {
pub(super) unit_type: HevcNalUnitType,
pub(super) raw_nal_unit_type: u8,
pub(super) rbsp: Vec<u8>,
}
impl HevcNalUnit {
pub(super) fn parse(data: &[u8]) -> Result<Self, DecodeError> {
let &first = data.first().ok_or(DecodeError::InvalidInput)?;
let &second = data.get(1).ok_or(DecodeError::InvalidInput)?;
let raw_nal_unit_type = (first >> 1) & 0x3F;
let nuh_layer_id = ((first & 0x1) << 5) | (second >> 3);
if nuh_layer_id != 0 {
return Err(DecodeError::Unsupported);
}
let rbsp = remove_emulation_prevention(data.get(2..).ok_or(DecodeError::InvalidInput)?);
Ok(Self {
unit_type: HevcNalUnitType::from_u8(raw_nal_unit_type),
raw_nal_unit_type,
rbsp,
})
}
}
fn remove_emulation_prevention(data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(data.len());
let mut zero_run = 0u32;
for &byte in data {
if zero_run >= 2 && byte == 0x03 {
zero_run = 0;
continue;
}
out.push(byte);
zero_run = if byte == 0 { zero_run + 1 } else { 0 };
}
out
}
fn skip_profile_tier_level(r: &mut BitReader<'_>) -> Result<(), DecodeError> {
let _general_profile_space = read_bits(r, 2)?;
let _general_tier_flag = read_bit(r)?;
let _general_profile_idc = read_bits(r, 5)?;
let _general_profile_compatibility_flags = read_bits(r, 32)?;
let _ = read_bits(r, 32)?;
let _ = read_bits(r, 16)?;
let _general_level_idc = read_bits(r, 8)?;
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(
clippy::struct_excessive_bools,
reason = "each bool is a real, independent ITU-T H.265 SPS flag that must be echoed \
into DXVA_PicParams_HEVC exactly as signaled — same reasoning h264_sps_pps.rs's Pps \
gives for its own identical allow"
)]
#[allow(
clippy::struct_field_names,
reason = "sps_temporal_mvp_enabled_flag is the real ITU-T H.265 SPS syntax element \
name (it literally starts with `sps_` in the spec itself) — renaming would obscure \
the 1:1 spec mapping"
)]
pub(super) struct Sps {
pub(super) pic_width_in_luma_samples: u32,
pub(super) pic_height_in_luma_samples: u32,
pub(super) log2_max_pic_order_cnt_lsb: u32,
pub(super) max_dec_pic_buffering: u32,
pub(super) log2_min_cb_size: u32,
pub(super) log2_diff_max_min_cb_size: u32,
pub(super) log2_min_tb_size: u32,
pub(super) log2_diff_max_min_tb_size: u32,
pub(super) max_transform_hierarchy_depth_inter: u32,
pub(super) max_transform_hierarchy_depth_intra: u32,
pub(super) amp_enabled_flag: bool,
pub(super) sample_adaptive_offset_enabled_flag: bool,
pub(super) sps_temporal_mvp_enabled_flag: bool,
pub(super) strong_intra_smoothing_enabled_flag: bool,
}
#[allow(
clippy::too_many_lines,
reason = "one linear ITU-T H.265 § 7.3.2.2.1 syntax-element sequence through the \
fields this module needs; splitting would just move consecutive reads of the same \
RBSP into a same-file helper, mirrors h264_sps_pps.rs::parse_sps's identical shape"
)]
#[allow(
clippy::similar_names,
reason = "log2_min_cb_size/log2_min_tb_size and log2_diff_max_min_cb_size/\
log2_diff_max_min_tb_size are the real ITU-T H.265 § 7.3.2.2.1 syntax element names \
(coding-block vs transform-block size) — renaming to look less similar would obscure \
the spec mapping, mirrors crate::vulkan::hevc_params::HevcSps::parse's identical allow"
)]
pub(super) fn parse_sps(rbsp: &[u8]) -> Result<Sps, DecodeError> {
let mut r = BitReader::new(rbsp);
let _sps_video_parameter_set_id = read_bits(&mut r, 4)?;
let sps_max_sub_layers_minus1 = read_bits(&mut r, 3)?;
if sps_max_sub_layers_minus1 != 0 {
return Err(DecodeError::Unsupported);
}
let _sps_temporal_id_nesting_flag = read_bit(&mut r)?;
skip_profile_tier_level(&mut r)?;
let _sps_seq_parameter_set_id = read_ue(&mut r)?;
let chroma_format_idc = read_ue(&mut r)?;
if chroma_format_idc == 3 {
let _separate_colour_plane_flag = read_bit(&mut r)?;
}
if chroma_format_idc != 1 {
return Err(DecodeError::Unsupported);
}
let pic_width_in_luma_samples = read_ue(&mut r)?;
let pic_height_in_luma_samples = read_ue(&mut r)?;
if read_bit(&mut r)? {
let _conf_win_left_offset = read_ue(&mut r)?;
let _conf_win_right_offset = read_ue(&mut r)?;
let _conf_win_top_offset = read_ue(&mut r)?;
let _conf_win_bottom_offset = read_ue(&mut r)?;
}
let bit_depth_luma_minus8 = read_ue(&mut r)?;
let bit_depth_chroma_minus8 = read_ue(&mut r)?;
if bit_depth_luma_minus8 != 0 || bit_depth_chroma_minus8 != 0 {
return Err(DecodeError::Unsupported);
}
let log2_max_pic_order_cnt_lsb = read_ue(&mut r)?
.checked_add(4)
.ok_or(DecodeError::InvalidInput)?;
let _sps_sub_layer_ordering_info_present_flag = read_bit(&mut r)?;
let max_dec_pic_buffering = read_ue(&mut r)?
.checked_add(1)
.ok_or(DecodeError::InvalidInput)?;
let _sps_max_num_reorder_pics = read_ue(&mut r)?;
let _sps_max_latency_increase_plus1 = read_ue(&mut r)?;
let log2_min_cb_size = read_ue(&mut r)?
.checked_add(3)
.ok_or(DecodeError::InvalidInput)?;
let log2_diff_max_min_cb_size = read_ue(&mut r)?;
let log2_min_tb_size = read_ue(&mut r)?
.checked_add(2)
.ok_or(DecodeError::InvalidInput)?;
let log2_diff_max_min_tb_size = read_ue(&mut r)?;
let max_transform_hierarchy_depth_inter = read_ue(&mut r)?;
let max_transform_hierarchy_depth_intra = read_ue(&mut r)?;
if read_bit(&mut r)? {
return Err(DecodeError::Unsupported); }
let amp_enabled_flag = read_bit(&mut r)?;
let sample_adaptive_offset_enabled_flag = read_bit(&mut r)?;
if read_bit(&mut r)? {
return Err(DecodeError::Unsupported); }
if read_ue(&mut r)? > 0 {
return Err(DecodeError::Unsupported); }
if read_bit(&mut r)? {
return Err(DecodeError::Unsupported); }
let sps_temporal_mvp_enabled_flag = read_bit(&mut r)?;
let strong_intra_smoothing_enabled_flag = read_bit(&mut r)?;
Ok(Sps {
pic_width_in_luma_samples,
pic_height_in_luma_samples,
log2_max_pic_order_cnt_lsb,
max_dec_pic_buffering,
log2_min_cb_size,
log2_diff_max_min_cb_size,
log2_min_tb_size,
log2_diff_max_min_tb_size,
max_transform_hierarchy_depth_inter,
max_transform_hierarchy_depth_intra,
amp_enabled_flag,
sample_adaptive_offset_enabled_flag,
sps_temporal_mvp_enabled_flag,
strong_intra_smoothing_enabled_flag,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(
clippy::struct_excessive_bools,
reason = "each bool is a real, independent ITU-T H.265 PPS flag that must be echoed \
into DXVA_PicParams_HEVC exactly as signaled — the driver re-parses the full slice \
header itself (DXVA_Slice_HEVC_Short carries none of it), so a mismatched flag here \
desyncs the driver's own parse, not just this module's; same reasoning h264_sps_pps.rs \
gives for its own identical allow"
)]
#[allow(
clippy::struct_field_names,
reason = "pps_cb_qp_offset/pps_cr_qp_offset/pps_slice_chroma_qp_offsets_present_flag/\
pps_loop_filter_across_slices_enabled_flag are the real ITU-T H.265 PPS syntax \
element names (they literally start with `pps_` in the spec itself) — renaming \
would obscure the 1:1 spec mapping"
)]
pub(super) struct Pps {
pub(super) dependent_slice_segments_enabled_flag: bool,
pub(super) output_flag_present_flag: bool,
pub(super) num_extra_slice_header_bits: u32,
pub(super) sign_data_hiding_enabled_flag: bool,
pub(super) cabac_init_present_flag: bool,
pub(super) num_ref_idx_l0_default_active_minus1: u32,
pub(super) num_ref_idx_l1_default_active_minus1: u32,
pub(super) init_qp_minus26: i32,
pub(super) constrained_intra_pred_flag: bool,
pub(super) transform_skip_enabled_flag: bool,
pub(super) cu_qp_delta_enabled_flag: bool,
pub(super) diff_cu_qp_delta_depth: u32,
pub(super) pps_cb_qp_offset: i32,
pub(super) pps_cr_qp_offset: i32,
pub(super) pps_slice_chroma_qp_offsets_present_flag: bool,
pub(super) weighted_pred_flag: bool,
pub(super) weighted_bipred_flag: bool,
pub(super) transquant_bypass_enabled_flag: bool,
pub(super) pps_loop_filter_across_slices_enabled_flag: bool,
pub(super) lists_modification_present_flag: bool,
pub(super) log2_parallel_merge_level_minus2: u32,
pub(super) slice_segment_header_extension_present_flag: bool,
}
#[allow(
clippy::too_many_lines,
reason = "one linear ITU-T H.265 § 7.3.2.3.1 syntax-element sequence through the \
fields DXVA_PicParams_HEVC needs; mirrors h264_sps_pps.rs::parse_pps's identical shape"
)]
pub(super) fn parse_pps(rbsp: &[u8]) -> Result<Pps, DecodeError> {
let mut r = BitReader::new(rbsp);
let _pps_pic_parameter_set_id = read_ue(&mut r)?;
let _pps_seq_parameter_set_id = read_ue(&mut r)?;
let dependent_slice_segments_enabled_flag = read_bit(&mut r)?;
let output_flag_present_flag = read_bit(&mut r)?;
let num_extra_slice_header_bits = read_bits(&mut r, 3)?;
let sign_data_hiding_enabled_flag = read_bit(&mut r)?;
let cabac_init_present_flag = read_bit(&mut r)?;
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 init_qp_minus26 = read_se(&mut r)?;
let constrained_intra_pred_flag = read_bit(&mut r)?;
let transform_skip_enabled_flag = read_bit(&mut r)?;
let cu_qp_delta_enabled_flag = read_bit(&mut r)?;
let diff_cu_qp_delta_depth = if cu_qp_delta_enabled_flag {
read_ue(&mut r)?
} else {
0
};
let pps_cb_qp_offset = read_se(&mut r)?;
let pps_cr_qp_offset = read_se(&mut r)?;
let pps_slice_chroma_qp_offsets_present_flag = read_bit(&mut r)?;
let weighted_pred_flag = read_bit(&mut r)?;
let weighted_bipred_flag = read_bit(&mut r)?;
let transquant_bypass_enabled_flag = read_bit(&mut r)?;
let tiles_enabled_flag = read_bit(&mut r)?;
let entropy_coding_sync_enabled_flag = read_bit(&mut r)?;
if tiles_enabled_flag || entropy_coding_sync_enabled_flag {
return Err(DecodeError::Unsupported);
}
let pps_loop_filter_across_slices_enabled_flag = read_bit(&mut r)?;
let deblocking_filter_control_present_flag = read_bit(&mut r)?;
if deblocking_filter_control_present_flag {
return Err(DecodeError::Unsupported);
}
if read_bit(&mut r)? {
return Err(DecodeError::Unsupported); }
let lists_modification_present_flag = read_bit(&mut r)?;
let log2_parallel_merge_level_minus2 = read_ue(&mut r)?;
let slice_segment_header_extension_present_flag = read_bit(&mut r)?;
Ok(Pps {
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,
pps_loop_filter_across_slices_enabled_flag,
lists_modification_present_flag,
log2_parallel_merge_level_minus2,
slice_segment_header_extension_present_flag,
})
}
#[cfg(test)]
#[path = "hevc_vps_sps_pps_tests.rs"]
mod tests;