use crate::bitstream::{BitError, BitReader};
use crate::scaling_list::{parse_scaling_list, ScalingListError};
use crate::sps::ScalingListEntry;
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum PpsError {
#[error("bitstream read failed: {0}")]
Bitstream(#[from] BitError),
#[error("pic_parameter_set_id out of range (got {0}, max 255)")]
PpsIdOutOfRange(u32),
#[error("seq_parameter_set_id out of range (got {0}, max 31)")]
SpsIdOutOfRange(u32),
#[error("num_slice_groups_minus1 out of range (got {0}, max 7)")]
NumSliceGroupsOutOfRange(u32),
#[error("slice_group_map_type out of range (got {0}, max 6)")]
SliceGroupMapTypeOutOfRange(u32),
#[error("weighted_bipred_idc out of range (got {0}, max 2)")]
WeightedBipredIdcOutOfRange(u32),
#[error("chroma_qp_index_offset out of range (got {0}, must be -12..=12)")]
ChromaQpIndexOffsetOutOfRange(i32),
#[error("second_chroma_qp_index_offset out of range (got {0}, must be -12..=12)")]
SecondChromaQpIndexOffsetOutOfRange(i32),
#[error("pic_size_in_map_units_minus1 out of range (got {0}, max {1})")]
PicSizeInMapUnitsOutOfRange(u32, u32),
#[error("scaling_list: {0}")]
ScalingList(#[from] ScalingListError),
}
const MAX_PIC_SIZE_IN_MAP_UNITS_MINUS1: u32 = (1u32 << 22) - 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PicScalingLists {
pub entries: Vec<ScalingListEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SliceGroupMap {
Interleaved { run_length_minus1: Vec<u32> },
Dispersed,
Foreground {
top_left: Vec<u32>,
bottom_right: Vec<u32>,
},
Changing {
slice_group_map_type: u32, change_direction_flag: bool,
change_rate_minus1: u32,
},
Explicit {
pic_size_in_map_units_minus1: u32,
slice_group_id: Vec<u32>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PpsExtension {
pub transform_8x8_mode_flag: bool,
pub pic_scaling_matrix_present_flag: bool,
pub pic_scaling_lists: Option<PicScalingLists>,
pub second_chroma_qp_index_offset: i32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pps {
pub pic_parameter_set_id: u32,
pub seq_parameter_set_id: u32,
pub entropy_coding_mode_flag: bool,
pub bottom_field_pic_order_in_frame_present_flag: bool,
pub num_slice_groups_minus1: u32,
pub slice_group_map: Option<SliceGroupMap>,
pub num_ref_idx_l0_default_active_minus1: u32,
pub num_ref_idx_l1_default_active_minus1: u32,
pub weighted_pred_flag: bool,
pub weighted_bipred_idc: u32,
pub pic_init_qp_minus26: i32,
pub pic_init_qs_minus26: i32,
pub chroma_qp_index_offset: i32,
pub deblocking_filter_control_present_flag: bool,
pub constrained_intra_pred_flag: bool,
pub redundant_pic_cnt_present_flag: bool,
pub extension: Option<PpsExtension>,
}
impl Pps {
pub fn parse(rbsp: &[u8]) -> Result<Self, PpsError> {
Self::parse_with_chroma_format(rbsp, 1)
}
pub fn parse_with_chroma_format(rbsp: &[u8], chroma_format_idc: u32) -> Result<Self, PpsError> {
let mut r = BitReader::new(rbsp);
let pic_parameter_set_id = r.ue()?;
if pic_parameter_set_id > 255 {
return Err(PpsError::PpsIdOutOfRange(pic_parameter_set_id));
}
let seq_parameter_set_id = r.ue()?;
if seq_parameter_set_id > 31 {
return Err(PpsError::SpsIdOutOfRange(seq_parameter_set_id));
}
let entropy_coding_mode_flag = r.u(1)? == 1;
let bottom_field_pic_order_in_frame_present_flag = r.u(1)? == 1;
let num_slice_groups_minus1 = r.ue()?;
if num_slice_groups_minus1 > 7 {
return Err(PpsError::NumSliceGroupsOutOfRange(num_slice_groups_minus1));
}
let slice_group_map = if num_slice_groups_minus1 > 0 {
let slice_group_map_type = r.ue()?;
if slice_group_map_type > 6 {
return Err(PpsError::SliceGroupMapTypeOutOfRange(slice_group_map_type));
}
let map = match slice_group_map_type {
0 => {
let mut run_length_minus1 =
Vec::with_capacity(num_slice_groups_minus1 as usize + 1);
for _ in 0..=num_slice_groups_minus1 {
run_length_minus1.push(r.ue()?);
}
SliceGroupMap::Interleaved { run_length_minus1 }
}
1 => SliceGroupMap::Dispersed,
2 => {
let mut top_left = Vec::with_capacity(num_slice_groups_minus1 as usize);
let mut bottom_right = Vec::with_capacity(num_slice_groups_minus1 as usize);
for _ in 0..num_slice_groups_minus1 {
top_left.push(r.ue()?);
bottom_right.push(r.ue()?);
}
SliceGroupMap::Foreground {
top_left,
bottom_right,
}
}
3..=5 => {
let change_direction_flag = r.u(1)? == 1;
let change_rate_minus1 = r.ue()?;
SliceGroupMap::Changing {
slice_group_map_type,
change_direction_flag,
change_rate_minus1,
}
}
6 => {
let pic_size_in_map_units_minus1 = r.ue()?;
if pic_size_in_map_units_minus1 > MAX_PIC_SIZE_IN_MAP_UNITS_MINUS1 {
return Err(PpsError::PicSizeInMapUnitsOutOfRange(
pic_size_in_map_units_minus1,
MAX_PIC_SIZE_IN_MAP_UNITS_MINUS1,
));
}
let v = ceil_log2(num_slice_groups_minus1 + 1);
let count = pic_size_in_map_units_minus1 as usize + 1;
let mut slice_group_id = Vec::with_capacity(count);
for _ in 0..count {
let id = if v == 0 { 0 } else { r.u(v)? };
slice_group_id.push(id);
}
SliceGroupMap::Explicit {
pic_size_in_map_units_minus1,
slice_group_id,
}
}
_ => unreachable!("slice_group_map_type bound-checked above"),
};
Some(map)
} else {
None
};
let num_ref_idx_l0_default_active_minus1 = r.ue()?;
let num_ref_idx_l1_default_active_minus1 = r.ue()?;
let weighted_pred_flag = r.u(1)? == 1;
let weighted_bipred_idc = r.u(2)?;
if weighted_bipred_idc > 2 {
return Err(PpsError::WeightedBipredIdcOutOfRange(weighted_bipred_idc));
}
let pic_init_qp_minus26 = r.se()?;
let pic_init_qs_minus26 = r.se()?;
let chroma_qp_index_offset = r.se()?;
if !(-12..=12).contains(&chroma_qp_index_offset) {
return Err(PpsError::ChromaQpIndexOffsetOutOfRange(
chroma_qp_index_offset,
));
}
let deblocking_filter_control_present_flag = r.u(1)? == 1;
let constrained_intra_pred_flag = r.u(1)? == 1;
let redundant_pic_cnt_present_flag = r.u(1)? == 1;
let extension = if r.more_rbsp_data() {
let transform_8x8_mode_flag = r.u(1)? == 1;
let pic_scaling_matrix_present_flag = r.u(1)? == 1;
let pic_scaling_lists = if pic_scaling_matrix_present_flag {
let extra_8x8 = if !transform_8x8_mode_flag {
0
} else if chroma_format_idc != 3 {
2
} else {
6
};
let n = 6 + extra_8x8;
let mut entries = Vec::with_capacity(n);
for i in 0..n {
let present = r.u(1)? == 1;
if !present {
entries.push(ScalingListEntry::NotPresent);
} else {
let size = if i < 6 { 16 } else { 64 };
let result = parse_scaling_list(&mut r, size)?;
entries.push(if result.use_default {
ScalingListEntry::UseDefault
} else {
ScalingListEntry::Explicit(result.scaling_list)
});
}
}
Some(PicScalingLists { entries })
} else {
None
};
let second_chroma_qp_index_offset = r.se()?;
if !(-12..=12).contains(&second_chroma_qp_index_offset) {
return Err(PpsError::SecondChromaQpIndexOffsetOutOfRange(
second_chroma_qp_index_offset,
));
}
let _ = r.rbsp_trailing_bits();
Some(PpsExtension {
transform_8x8_mode_flag,
pic_scaling_matrix_present_flag,
pic_scaling_lists,
second_chroma_qp_index_offset,
})
} else {
None
};
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,
slice_group_map,
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,
extension,
})
}
pub fn second_chroma_qp_index_offset(&self) -> i32 {
self.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(self.chroma_qp_index_offset)
}
pub fn transform_8x8_mode_flag(&self) -> bool {
self.extension
.as_ref()
.is_some_and(|e| e.transform_8x8_mode_flag)
}
}
fn ceil_log2(n: u32) -> u32 {
if n <= 1 {
0
} else {
32 - (n - 1).leading_zeros()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct BitWriter {
bytes: Vec<u8>,
bit_pos: u8,
}
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
bit_pos: 0,
}
}
fn u(&mut self, bits: u32, value: u32) {
for i in (0..bits).rev() {
let bit = ((value >> i) & 1) as u8;
if self.bit_pos == 0 {
self.bytes.push(0);
}
let idx = self.bytes.len() - 1;
self.bytes[idx] |= bit << (7 - self.bit_pos);
self.bit_pos = (self.bit_pos + 1) % 8;
}
}
fn ue(&mut self, value: u32) {
let v = value + 1;
let leading = 31 - v.leading_zeros();
for _ in 0..leading {
self.u(1, 0);
}
self.u(leading + 1, v);
}
fn se(&mut self, value: i32) {
let k = if value <= 0 {
(-2 * value) as u32
} else {
(2 * value - 1) as u32
};
self.ue(k);
}
fn trailing(&mut self) {
self.u(1, 1);
while self.bit_pos != 0 {
self.u(1, 0);
}
}
fn into_bytes(self) -> Vec<u8> {
self.bytes
}
}
#[test]
fn ceil_log2_matches_spec() {
assert_eq!(ceil_log2(1), 0);
assert_eq!(ceil_log2(2), 1);
assert_eq!(ceil_log2(3), 2);
assert_eq!(ceil_log2(4), 2);
assert_eq!(ceil_log2(5), 3);
assert_eq!(ceil_log2(8), 3);
assert_eq!(ceil_log2(9), 4);
}
#[test]
fn minimal_cavlc_pps() {
let mut w = BitWriter::new();
w.ue(0); w.ue(0); w.u(1, 0); w.u(1, 0); w.ue(0); w.ue(0); w.ue(0); w.u(1, 0); w.u(2, 0); w.se(0); w.se(0); w.se(0); w.u(1, 1); w.u(1, 0); w.u(1, 0); w.trailing();
let bytes = w.into_bytes();
let pps = Pps::parse(&bytes).unwrap();
assert_eq!(pps.pic_parameter_set_id, 0);
assert_eq!(pps.seq_parameter_set_id, 0);
assert!(!pps.entropy_coding_mode_flag);
assert!(!pps.bottom_field_pic_order_in_frame_present_flag);
assert_eq!(pps.num_slice_groups_minus1, 0);
assert!(pps.slice_group_map.is_none());
assert_eq!(pps.num_ref_idx_l0_default_active_minus1, 0);
assert_eq!(pps.num_ref_idx_l1_default_active_minus1, 0);
assert!(!pps.weighted_pred_flag);
assert_eq!(pps.weighted_bipred_idc, 0);
assert_eq!(pps.pic_init_qp_minus26, 0);
assert_eq!(pps.pic_init_qs_minus26, 0);
assert_eq!(pps.chroma_qp_index_offset, 0);
assert!(pps.deblocking_filter_control_present_flag);
assert!(!pps.constrained_intra_pred_flag);
assert!(!pps.redundant_pic_cnt_present_flag);
assert!(pps.extension.is_none());
assert_eq!(pps.second_chroma_qp_index_offset(), 0);
assert!(!pps.transform_8x8_mode_flag());
}
#[test]
fn cabac_pps_with_trailing_group() {
let mut w = BitWriter::new();
w.ue(1); w.ue(2); w.u(1, 1); w.u(1, 1); w.ue(0); w.ue(2); w.ue(0); w.u(1, 1); w.u(2, 2); w.se(-3); w.se(0); w.se(-2); w.u(1, 1); w.u(1, 0); w.u(1, 1); w.u(1, 1); w.u(1, 0); w.se(-3); w.trailing();
let bytes = w.into_bytes();
let pps = Pps::parse(&bytes).unwrap();
assert_eq!(pps.pic_parameter_set_id, 1);
assert_eq!(pps.seq_parameter_set_id, 2);
assert!(pps.entropy_coding_mode_flag);
assert!(pps.bottom_field_pic_order_in_frame_present_flag);
assert_eq!(pps.num_ref_idx_l0_default_active_minus1, 2);
assert!(pps.weighted_pred_flag);
assert_eq!(pps.weighted_bipred_idc, 2);
assert_eq!(pps.pic_init_qp_minus26, -3);
assert_eq!(pps.chroma_qp_index_offset, -2);
assert!(pps.redundant_pic_cnt_present_flag);
let ext = pps.extension.clone().expect("tail present");
assert!(ext.transform_8x8_mode_flag);
assert!(!ext.pic_scaling_matrix_present_flag);
assert_eq!(ext.second_chroma_qp_index_offset, -3);
assert_eq!(pps.second_chroma_qp_index_offset(), -3);
assert!(pps.transform_8x8_mode_flag());
}
#[test]
fn pps_with_scaling_matrix_all_not_present() {
let mut w = BitWriter::new();
w.ue(0); w.ue(0); w.u(1, 0); w.u(1, 0); w.ue(0); w.ue(0); w.ue(0); w.u(1, 0); w.u(2, 0); w.se(0); w.se(0); w.se(0); w.u(1, 0); w.u(1, 0); w.u(1, 0); w.u(1, 1); w.u(1, 1); for _ in 0..8 {
w.u(1, 0);
}
w.se(0); w.trailing();
let bytes = w.into_bytes();
let pps = Pps::parse(&bytes).unwrap();
let ext = pps.extension.as_ref().expect("tail present");
assert!(ext.transform_8x8_mode_flag);
assert!(ext.pic_scaling_matrix_present_flag);
let lists = ext.pic_scaling_lists.as_ref().unwrap();
assert_eq!(lists.entries.len(), 8);
for e in &lists.entries {
assert_eq!(*e, ScalingListEntry::NotPresent);
}
}
#[test]
fn pps_with_scaling_matrix_4x4x4_loop_count() {
let mut w = BitWriter::new();
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(1, 0);
w.ue(0);
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(2, 0);
w.se(0);
w.se(0);
w.se(0);
w.u(1, 0);
w.u(1, 0);
w.u(1, 0);
w.u(1, 1); w.u(1, 1); for _ in 0..12 {
w.u(1, 0);
}
w.se(0);
w.trailing();
let bytes = w.into_bytes();
let pps = Pps::parse_with_chroma_format(&bytes, 3).unwrap();
let ext = pps.extension.as_ref().unwrap();
let lists = ext.pic_scaling_lists.as_ref().unwrap();
assert_eq!(lists.entries.len(), 12);
}
#[test]
fn pps_with_fmo_type_0_interleaved() {
let mut w = BitWriter::new();
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(1, 0);
w.ue(2); w.ue(0); w.ue(10); w.ue(20); w.ue(30); w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(2, 0);
w.se(0);
w.se(0);
w.se(0);
w.u(1, 0);
w.u(1, 0);
w.u(1, 0);
w.trailing();
let bytes = w.into_bytes();
let pps = Pps::parse(&bytes).unwrap();
assert_eq!(pps.num_slice_groups_minus1, 2);
match pps.slice_group_map {
Some(SliceGroupMap::Interleaved { run_length_minus1 }) => {
assert_eq!(run_length_minus1, vec![10, 20, 30]);
}
other => panic!("unexpected slice_group_map: {:?}", other),
}
}
#[test]
fn pps_with_fmo_type_6_explicit() {
let mut w = BitWriter::new();
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(1, 0);
w.ue(2); w.ue(6); w.ue(3); w.u(2, 0);
w.u(2, 1);
w.u(2, 2);
w.u(2, 1);
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(2, 0);
w.se(0);
w.se(0);
w.se(0);
w.u(1, 0);
w.u(1, 0);
w.u(1, 0);
w.trailing();
let bytes = w.into_bytes();
let pps = Pps::parse(&bytes).unwrap();
match pps.slice_group_map {
Some(SliceGroupMap::Explicit {
pic_size_in_map_units_minus1,
slice_group_id,
}) => {
assert_eq!(pic_size_in_map_units_minus1, 3);
assert_eq!(slice_group_id, vec![0, 1, 2, 1]);
}
other => panic!("unexpected slice_group_map: {:?}", other),
}
}
#[test]
fn pps_with_fmo_type_3_changing() {
let mut w = BitWriter::new();
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(1, 0);
w.ue(1); w.ue(3); w.u(1, 1); w.ue(7); w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(2, 0);
w.se(0);
w.se(0);
w.se(0);
w.u(1, 0);
w.u(1, 0);
w.u(1, 0);
w.trailing();
let bytes = w.into_bytes();
let pps = Pps::parse(&bytes).unwrap();
match pps.slice_group_map {
Some(SliceGroupMap::Changing {
slice_group_map_type,
change_direction_flag,
change_rate_minus1,
}) => {
assert_eq!(slice_group_map_type, 3);
assert!(change_direction_flag);
assert_eq!(change_rate_minus1, 7);
}
other => panic!("unexpected slice_group_map: {:?}", other),
}
}
#[test]
fn pps_with_fmo_type_2_foreground() {
let mut w = BitWriter::new();
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(1, 0);
w.ue(2);
w.ue(2); w.ue(0);
w.ue(99);
w.ue(5);
w.ue(77);
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(2, 0);
w.se(0);
w.se(0);
w.se(0);
w.u(1, 0);
w.u(1, 0);
w.u(1, 0);
w.trailing();
let bytes = w.into_bytes();
let pps = Pps::parse(&bytes).unwrap();
match pps.slice_group_map {
Some(SliceGroupMap::Foreground {
top_left,
bottom_right,
}) => {
assert_eq!(top_left, vec![0, 5]);
assert_eq!(bottom_right, vec![99, 77]);
}
other => panic!("unexpected slice_group_map: {:?}", other),
}
}
#[test]
fn pps_rejects_out_of_range_chroma_qp_offset() {
let mut w = BitWriter::new();
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(1, 0);
w.ue(0);
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(2, 0);
w.se(0);
w.se(0);
w.se(13); let bytes = w.into_bytes();
assert_eq!(
Pps::parse(&bytes).unwrap_err(),
PpsError::ChromaQpIndexOffsetOutOfRange(13)
);
}
#[test]
fn pps_rejects_weighted_bipred_idc_3() {
let mut w = BitWriter::new();
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(1, 0);
w.ue(0);
w.ue(0);
w.ue(0);
w.u(1, 0);
w.u(2, 3); let bytes = w.into_bytes();
assert_eq!(
Pps::parse(&bytes).unwrap_err(),
PpsError::WeightedBipredIdcOutOfRange(3)
);
}
}