#![forbid(unsafe_code)]
use mediaway_sw::h264::{BitReader, H264Error, NalUnitType};
use smallvec::SmallVec;
use crate::vulkan::dpb::Dpb;
use crate::vulkan::h264_params::{H264ParamError, H264Pps, H264Sps};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum H264SliceType {
P,
B,
I,
Sp,
Si,
}
impl H264SliceType {
#[must_use]
const fn from_raw(slice_type: u32) -> Self {
match slice_type % 5 {
0 => Self::P,
1 => Self::B,
3 => Self::Sp,
4 => Self::Si,
_ => Self::I,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RefPicListModification {
pub idc: u32,
pub value: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct H264SliceHeader {
pub first_mb_in_slice: u32,
pub slice_type: H264SliceType,
pub pic_parameter_set_id: u32,
pub frame_num: u32,
pub idr_pic_id: Option<u32>,
pub pic_order_cnt_lsb: u32,
pub num_ref_idx_l0_active: u32,
pub ref_pic_list_modifications_l0: SmallVec<[RefPicListModification; 4]>,
pub slice_qp_delta: i32,
}
impl H264SliceHeader {
pub fn parse(
reader: &mut BitReader<'_>,
sps: &H264Sps,
pps: &H264Pps,
nal_unit_type: NalUnitType,
nal_ref_idc: u8,
) -> Result<Self, H264ParamError> {
let first_mb_in_slice = reader.read_ue()?;
if first_mb_in_slice != 0 {
return Err(H264ParamError::Unsupported {
reason: "multi-slice pictures are not supported (first_mb_in_slice must be 0)",
});
}
let slice_type = H264SliceType::from_raw(reader.read_ue()?);
if matches!(
slice_type,
H264SliceType::B | H264SliceType::Sp | H264SliceType::Si
) {
return Err(H264ParamError::Unsupported {
reason: "only I- and P-slices are supported this round",
});
}
let pic_parameter_set_id = reader.read_ue()?;
let frame_num = reader.read_bits(sps.log2_max_frame_num)?;
let is_idr = matches!(nal_unit_type, NalUnitType::IdrSlice);
let idr_pic_id = if is_idr {
Some(reader.read_ue()?)
} else {
None
};
let pic_order_cnt_lsb = reader.read_bits(sps.log2_max_pic_order_cnt_lsb)?;
let mut num_ref_idx_l0_active = 0u32;
let mut ref_pic_list_modifications_l0 = SmallVec::new();
if matches!(slice_type, H264SliceType::P) {
let num_ref_idx_active_override_flag = reader.read_bit()? != 0;
num_ref_idx_l0_active = if num_ref_idx_active_override_flag {
reader
.read_ue()?
.checked_add(1)
.ok_or(H264Error::FieldOverflow)?
} else {
pps.num_ref_idx_l0_default_active
};
let ref_pic_list_modification_flag_l0 = reader.read_bit()? != 0;
if ref_pic_list_modification_flag_l0 {
loop {
let idc = reader.read_ue()?;
if idc == 3 {
break;
}
if idc == 2 {
return Err(H264ParamError::Unsupported {
reason: "long-term reference picture modification is not supported",
});
}
let value = reader.read_ue()?;
ref_pic_list_modifications_l0.push(RefPicListModification { idc, value });
}
}
}
if nal_ref_idc != 0 {
parse_dec_ref_pic_marking(reader, is_idr)?;
}
let slice_qp_delta = reader.read_se()?;
if pps.deblocking_filter_control_present {
let disable_deblocking_filter_idc = reader.read_ue()?;
if disable_deblocking_filter_idc != 1 {
let _slice_alpha_c0_offset_div2 = reader.read_se()?;
let _slice_beta_offset_div2 = reader.read_se()?;
}
}
Ok(Self {
first_mb_in_slice,
slice_type,
pic_parameter_set_id,
frame_num,
idr_pic_id,
pic_order_cnt_lsb,
num_ref_idx_l0_active,
ref_pic_list_modifications_l0,
slice_qp_delta,
})
}
}
fn parse_dec_ref_pic_marking(
reader: &mut BitReader<'_>,
is_idr: bool,
) -> Result<(), H264ParamError> {
if is_idr {
let _no_output_of_prior_pics_flag = reader.read_bit()?;
let _long_term_reference_flag = reader.read_bit()?;
return Ok(());
}
let adaptive_ref_pic_marking_mode_flag = reader.read_bit()? != 0;
if adaptive_ref_pic_marking_mode_flag {
return Err(H264ParamError::Unsupported {
reason: "adaptive_ref_pic_marking_mode_flag (MMCO) is not supported; sliding window only",
});
}
Ok(())
}
#[must_use]
pub fn default_ref_pic_list0(dpb: &Dpb) -> Vec<usize> {
let mut refs: Vec<(usize, i32)> = dpb
.occupied_slots()
.filter(|(_, slot)| slot.used_for_reference)
.map(|(index, slot)| (index, slot.frame_num_wrap))
.collect();
refs.sort_by_key(|&(_, frame_num_wrap)| std::cmp::Reverse(frame_num_wrap));
refs.into_iter().map(|(index, _)| index).collect()
}
#[must_use]
pub fn apply_ref_pic_list_modifications(
mut ref_pic_list0: Vec<usize>,
dpb: &Dpb,
modifications: &[RefPicListModification],
current_pic_num: i32,
max_pic_num: i32,
) -> Vec<usize> {
let mut pred_pic_num = current_pic_num;
let mut insert_at = 0usize;
for modification in modifications {
if modification.idc != 0 && modification.idc != 1 {
continue;
}
let abs_diff = i32::try_from(modification.value.wrapping_add(1)).unwrap_or(i32::MAX);
let pic_num_no_wrap = if modification.idc == 0 {
let value = pred_pic_num - abs_diff;
if value < 0 {
value + max_pic_num
} else {
value
}
} else {
let value = pred_pic_num + abs_diff;
if value >= max_pic_num {
value - max_pic_num
} else {
value
}
};
pred_pic_num = pic_num_no_wrap;
let pic_num = if pic_num_no_wrap > current_pic_num {
pic_num_no_wrap - max_pic_num
} else {
pic_num_no_wrap
};
let target = dpb
.occupied_slots()
.find(|(_, slot)| slot.used_for_reference && slot.frame_num_wrap == pic_num)
.map(|(index, _)| index);
if let Some(target_index) = target {
ref_pic_list0.retain(|&index| index != target_index);
let at = insert_at.min(ref_pic_list0.len());
ref_pic_list0.insert(at, target_index);
insert_at += 1;
}
}
ref_pic_list0
}
#[cfg(test)]
#[path = "h264_slice_tests.rs"]
mod tests;