Files
deckcraft/crates/h264/src/params.rs
T
Nikita 6873a25635 Codecs: a hostile number is a format error, not an overflow
H.264 and HEVC quantiser offsets, Matroska block times, Opus granule timing and QuickTime PCM frame sizes are range-checked before the arithmetic.
2026-10-13 20:16:51 +03:00

824 lines
30 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Sequence and picture parameter sets (7.3.2.1, 7.3.2.2) including VUI and scaling lists.
use crate::error::{Result, ensure, unsupported};
use crate::tables::*;
use deckcraft_bitstream::BitReader;
/// Scaling matrices after applying fall-back rules, stored in raster order (weightScale).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScalingMatrices {
/// [Intra Y, Intra Cb, Intra Cr, Inter Y, Inter Cb, Inter Cr], raster order.
pub m4: [[u8; 16]; 6],
/// [Intra Y, Inter Y, Intra Cb, Inter Cb, Intra Cr, Inter Cr], raster order.
pub m8: [[u8; 64]; 6],
}
impl ScalingMatrices {
pub fn flat() -> Self {
Self { m4: [[16; 16]; 6], m8: [[16; 64]; 6] }
}
pub fn is_flat(&self) -> bool {
self.m4.iter().all(|m| m.iter().all(|&v| v == 16)) && self.m8.iter().all(|m| m.iter().all(|&v| v == 16))
}
}
/// Lists as parsed, in zig-zag order, before inverse scanning.
#[derive(Clone, Debug)]
struct ZzLists {
l4: [[u8; 16]; 6],
l8: [[u8; 64]; 6],
}
fn zz4_to_raster(l: &[u8; 16]) -> [u8; 16] {
let mut out = [0; 16];
for (k, &v) in l.iter().enumerate() {
out[ZIGZAG4[k] as usize] = v;
}
out
}
fn zz8_to_raster(l: &[u8; 64]) -> [u8; 64] {
let mut out = [0; 64];
for (k, &v) in l.iter().enumerate() {
out[ZIGZAG8[k] as usize] = v;
}
out
}
impl ZzLists {
fn to_matrices(&self) -> ScalingMatrices {
let mut m = ScalingMatrices::flat();
for i in 0..6 {
m.m4[i] = zz4_to_raster(&self.l4[i]);
m.m8[i] = zz8_to_raster(&self.l8[i]);
}
m
}
fn flat() -> Self {
Self { l4: [[16; 16]; 6], l8: [[16; 64]; 6] }
}
}
/// Result of parsing one scaling_list(): Some(list) or None meaning "use default".
fn parse_scaling_list<const N: usize>(r: &mut BitReader) -> Result<Option<[u8; N]>> {
let mut list = [0u8; N];
let mut last = 8i32;
let mut next = 8i32;
for (j, item) in list.iter_mut().enumerate() {
if next != 0 {
let delta = r.read_se()?;
ensure!((-128..=127).contains(&delta), "delta_scale out of range");
next = (last + delta + 256) % 256;
if j == 0 && next == 0 {
return Ok(None);
}
}
*item = if next == 0 { last as u8 } else { next as u8 };
last = *item as i32;
}
Ok(Some(list))
}
/// Parse scaling lists with a fall-back rule. `fallback` gives the lists used for rule B
/// (sequence-level) or None for rule A.
fn parse_scaling_lists(r: &mut BitReader, count: usize, fallback: Option<&ZzLists>) -> Result<ZzLists> {
let mut out = ZzLists::flat();
for i in 0..count.max(8) {
let present = if i < count { r.read_flag()? } else { false };
if i < 6 {
let parsed = if present { Some(parse_scaling_list::<16>(r)?) } else { None };
out.l4[i] = match parsed {
Some(Some(l)) => l,
Some(None) => {
if i < 3 {
DEFAULT_4X4_INTRA
} else {
DEFAULT_4X4_INTER
}
}
None => match (i, fallback) {
(0, None) => DEFAULT_4X4_INTRA,
(3, None) => DEFAULT_4X4_INTER,
(0 | 3, Some(f)) => f.l4[i],
_ => out.l4[i - 1],
},
};
} else {
let k = i - 6;
if k >= 6 {
break;
}
let parsed = if present { Some(parse_scaling_list::<64>(r)?) } else { None };
out.l8[k] = match parsed {
Some(Some(l)) => l,
Some(None) => {
if k % 2 == 0 {
DEFAULT_8X8_INTRA
} else {
DEFAULT_8X8_INTER
}
}
None => match (k, fallback) {
(0, None) => DEFAULT_8X8_INTRA,
(1, None) => DEFAULT_8X8_INTER,
(0 | 1, Some(f)) => f.l8[k],
_ => out.l8[k - 2],
},
};
}
}
// 8x8 chroma lists that were never signalled (count == 8) follow rule A/B chains as well.
if count <= 8 {
for k in 2..6 {
out.l8[k] = out.l8[k - 2];
}
}
Ok(out)
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct HrdParameters {
pub cpb_cnt: u32,
pub bit_rate_scale: u8,
pub cpb_size_scale: u8,
pub bit_rate_value_minus1: Vec<u32>,
pub cpb_size_value_minus1: Vec<u32>,
pub cbr_flag: Vec<bool>,
pub initial_cpb_removal_delay_length: u8,
pub cpb_removal_delay_length: u8,
pub dpb_output_delay_length: u8,
pub time_offset_length: u8,
}
fn parse_hrd(r: &mut BitReader) -> Result<HrdParameters> {
let cpb_cnt = r.read_ue_plus(1)?;
ensure!(cpb_cnt <= 32, "cpb_cnt_minus1 out of range");
let mut h = HrdParameters { cpb_cnt, bit_rate_scale: r.read_bits(4)? as u8, cpb_size_scale: r.read_bits(4)? as u8, ..Default::default() };
for _ in 0..cpb_cnt {
h.bit_rate_value_minus1.push(r.read_ue()?);
h.cpb_size_value_minus1.push(r.read_ue()?);
h.cbr_flag.push(r.read_flag()?);
}
h.initial_cpb_removal_delay_length = r.read_bits(5)? as u8 + 1;
h.cpb_removal_delay_length = r.read_bits(5)? as u8 + 1;
h.dpb_output_delay_length = r.read_bits(5)? as u8 + 1;
h.time_offset_length = r.read_bits(5)? as u8;
Ok(h)
}
/// Video usability information (Annex E).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Vui {
pub aspect_ratio_idc: u8,
pub sar: (u16, u16),
pub overscan_appropriate: Option<bool>,
pub video_format: u8,
pub full_range: bool,
pub colour_primaries: u8,
pub transfer_characteristics: u8,
pub matrix_coefficients: u8,
pub chroma_loc: Option<(u32, u32)>,
pub timing: Option<(u32, u32, bool)>,
pub nal_hrd: Option<HrdParameters>,
pub vcl_hrd: Option<HrdParameters>,
pub low_delay_hrd: bool,
pub pic_struct_present: bool,
pub bitstream_restriction: Option<BitstreamRestriction>,
}
impl Default for Vui {
fn default() -> Self {
Self {
aspect_ratio_idc: 0,
sar: (0, 0),
overscan_appropriate: None,
video_format: 5,
full_range: false,
colour_primaries: 2,
transfer_characteristics: 2,
matrix_coefficients: 2,
chroma_loc: None,
timing: None,
nal_hrd: None,
vcl_hrd: None,
low_delay_hrd: false,
pic_struct_present: false,
bitstream_restriction: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BitstreamRestriction {
pub motion_vectors_over_pic_boundaries: bool,
pub max_bytes_per_pic_denom: u32,
pub max_bits_per_mb_denom: u32,
pub log2_max_mv_length_horizontal: u32,
pub log2_max_mv_length_vertical: u32,
pub max_num_reorder_frames: u32,
pub max_dec_frame_buffering: u32,
}
/// Table E-1 sample aspect ratios.
const SAR_TABLE: [(u16, u16); 17] = [
(0, 0),
(1, 1),
(12, 11),
(10, 11),
(16, 11),
(40, 33),
(24, 11),
(20, 11),
(32, 11),
(80, 33),
(18, 11),
(15, 11),
(64, 33),
(160, 99),
(4, 3),
(3, 2),
(2, 1),
];
fn parse_vui(r: &mut BitReader) -> Result<Vui> {
let mut v = Vui::default();
if r.read_flag()? {
v.aspect_ratio_idc = r.read_bits(8)? as u8;
if v.aspect_ratio_idc == 255 {
v.sar = (r.read_bits(16)? as u16, r.read_bits(16)? as u16);
} else if (v.aspect_ratio_idc as usize) < SAR_TABLE.len() {
v.sar = SAR_TABLE[v.aspect_ratio_idc as usize];
}
}
if r.read_flag()? {
v.overscan_appropriate = Some(r.read_flag()?);
}
if r.read_flag()? {
v.video_format = r.read_bits(3)? as u8;
v.full_range = r.read_flag()?;
if r.read_flag()? {
v.colour_primaries = r.read_bits(8)? as u8;
v.transfer_characteristics = r.read_bits(8)? as u8;
v.matrix_coefficients = r.read_bits(8)? as u8;
}
}
if r.read_flag()? {
v.chroma_loc = Some((r.read_ue()?, r.read_ue()?));
}
if r.read_flag()? {
let n = r.read_bits(32)?;
let t = r.read_bits(32)?;
let fixed = r.read_flag()?;
v.timing = Some((n, t, fixed));
}
if r.read_flag()? {
v.nal_hrd = Some(parse_hrd(r)?);
}
if r.read_flag()? {
v.vcl_hrd = Some(parse_hrd(r)?);
}
if v.nal_hrd.is_some() || v.vcl_hrd.is_some() {
v.low_delay_hrd = r.read_flag()?;
}
v.pic_struct_present = r.read_flag()?;
// Some streams truncate the VUI after this point; treat EOF as "absent".
if r.bits_left() > 0 && r.read_flag()? {
let br = (|| -> Result<BitstreamRestriction> {
Ok(BitstreamRestriction {
motion_vectors_over_pic_boundaries: r.read_flag()?,
max_bytes_per_pic_denom: r.read_ue()?,
max_bits_per_mb_denom: r.read_ue()?,
log2_max_mv_length_horizontal: r.read_ue()?,
log2_max_mv_length_vertical: r.read_ue()?,
max_num_reorder_frames: r.read_ue()?,
max_dec_frame_buffering: r.read_ue()?,
})
})();
v.bitstream_restriction = br.ok();
}
Ok(v)
}
/// Sequence parameter set.
#[derive(Clone, Debug)]
pub struct Sps {
pub profile_idc: u8,
pub constraint_flags: u8,
pub level_idc: u8,
pub id: u32,
pub chroma_format_idc: u32,
pub separate_colour_plane: bool,
pub bit_depth_luma: u32,
pub bit_depth_chroma: u32,
pub qpprime_y_zero_transform_bypass: bool,
pub seq_scaling_matrix_present: bool,
scaling_lists: ZzLists,
pub scaling: ScalingMatrices,
pub log2_max_frame_num: u32,
pub pic_order_cnt_type: u32,
pub log2_max_poc_lsb: u32,
pub delta_pic_order_always_zero: bool,
pub offset_for_non_ref_pic: i32,
pub offset_for_top_to_bottom_field: i32,
pub offset_for_ref_frame: Vec<i32>,
pub max_num_ref_frames: u32,
pub gaps_in_frame_num_allowed: bool,
pub pic_width_in_mbs: u32,
pub pic_height_in_map_units: u32,
pub frame_mbs_only: bool,
pub mb_adaptive_frame_field: bool,
pub direct_8x8_inference: bool,
/// (left, right, top, bottom) in crop units as coded.
pub frame_crop: Option<(u32, u32, u32, u32)>,
pub vui: Option<Vui>,
}
impl Sps {
pub fn parse(rbsp: &[u8]) -> Result<Sps> {
let mut r = BitReader::new(rbsp);
let profile_idc = r.read_bits(8)? as u8;
let constraint_flags = r.read_bits(8)? as u8;
let level_idc = r.read_bits(8)? as u8;
let id = r.read_ue()?;
ensure!(id < 32, "seq_parameter_set_id {id} out of range");
let mut chroma_format_idc = 1;
let mut separate_colour_plane = false;
let mut bit_depth_luma = 8;
let mut bit_depth_chroma = 8;
let mut bypass = false;
let mut seq_scaling_matrix_present = false;
let mut scaling_lists = ZzLists::flat();
if matches!(profile_idc, 100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135) {
chroma_format_idc = r.read_ue()?;
ensure!(chroma_format_idc <= 3, "chroma_format_idc out of range");
if chroma_format_idc == 3 {
separate_colour_plane = r.read_flag()?;
}
bit_depth_luma = r.read_ue_plus(8)?;
bit_depth_chroma = r.read_ue_plus(8)?;
ensure!(bit_depth_luma <= 14 && bit_depth_chroma <= 14, "bit depth out of range");
bypass = r.read_flag()?;
seq_scaling_matrix_present = r.read_flag()?;
if seq_scaling_matrix_present {
let count = if chroma_format_idc != 3 { 8 } else { 12 };
scaling_lists = parse_scaling_lists(&mut r, count, None)?;
}
}
let log2_max_frame_num = r.read_ue_plus(4)?;
ensure!(log2_max_frame_num <= 16, "log2_max_frame_num out of range");
let pic_order_cnt_type = r.read_ue()?;
ensure!(pic_order_cnt_type <= 2, "pic_order_cnt_type out of range");
let mut log2_max_poc_lsb = 0;
let mut delta_pic_order_always_zero = false;
let mut offset_for_non_ref_pic = 0;
let mut offset_for_top_to_bottom_field = 0;
let mut offset_for_ref_frame = Vec::new();
if pic_order_cnt_type == 0 {
log2_max_poc_lsb = r.read_ue_plus(4)?;
ensure!(log2_max_poc_lsb <= 16, "log2_max_pic_order_cnt_lsb out of range");
} else if pic_order_cnt_type == 1 {
delta_pic_order_always_zero = r.read_flag()?;
offset_for_non_ref_pic = r.read_se()?;
offset_for_top_to_bottom_field = r.read_se()?;
let n = r.read_ue()?;
ensure!(n <= 255, "num_ref_frames_in_pic_order_cnt_cycle out of range");
for _ in 0..n {
offset_for_ref_frame.push(r.read_se()?);
}
}
let max_num_ref_frames = r.read_ue()?;
ensure!(max_num_ref_frames <= 16, "max_num_ref_frames out of range");
let gaps_in_frame_num_allowed = r.read_flag()?;
let pic_width_in_mbs = r.read_ue_plus(1)?;
let pic_height_in_map_units = r.read_ue_plus(1)?;
ensure!(pic_width_in_mbs <= 1024 && pic_height_in_map_units <= 1024, "picture too large");
let frame_mbs_only = r.read_flag()?;
let mb_adaptive_frame_field = if !frame_mbs_only { r.read_flag()? } else { false };
let direct_8x8_inference = r.read_flag()?;
let frame_crop = if r.read_flag()? { Some((r.read_ue()?, r.read_ue()?, r.read_ue()?, r.read_ue()?)) } else { None };
let vui = if r.read_flag()? { Some(parse_vui(&mut r)?) } else { None };
let scaling = if seq_scaling_matrix_present { scaling_lists.to_matrices() } else { ScalingMatrices::flat() };
let sps = Sps {
profile_idc,
constraint_flags,
level_idc,
id,
chroma_format_idc,
separate_colour_plane,
bit_depth_luma,
bit_depth_chroma,
qpprime_y_zero_transform_bypass: bypass,
seq_scaling_matrix_present,
scaling_lists,
scaling,
log2_max_frame_num,
pic_order_cnt_type,
log2_max_poc_lsb,
delta_pic_order_always_zero,
offset_for_non_ref_pic,
offset_for_top_to_bottom_field,
offset_for_ref_frame,
max_num_ref_frames,
gaps_in_frame_num_allowed,
pic_width_in_mbs,
pic_height_in_map_units,
frame_mbs_only,
mb_adaptive_frame_field,
direct_8x8_inference,
frame_crop,
vui,
};
if let Some((l, rr, t, b)) = sps.frame_crop {
let (cx, cy) = sps.crop_unit();
let crop = |a: u32, b: u32, unit: u32| (u64::from(a) + u64::from(b)) * u64::from(unit);
ensure!(crop(l, rr, cx) < u64::from(sps.width()) && crop(t, b, cy) < u64::from(sps.height()), "frame cropping exceeds picture size");
}
Ok(sps)
}
/// ChromaArrayType.
pub fn chroma_array_type(&self) -> u32 {
if self.separate_colour_plane { 0 } else { self.chroma_format_idc }
}
pub fn frame_height_in_mbs(&self) -> u32 {
(2 - self.frame_mbs_only as u32) * self.pic_height_in_map_units
}
/// Decoded (uncropped) luma width.
pub fn width(&self) -> u32 {
self.pic_width_in_mbs * 16
}
pub fn height(&self) -> u32 {
self.frame_height_in_mbs() * 16
}
pub fn max_frame_num(&self) -> u32 {
1 << self.log2_max_frame_num
}
/// (CropUnitX, CropUnitY).
pub fn crop_unit(&self) -> (u32, u32) {
let (sub_w, sub_h) = match self.chroma_array_type() {
1 => (2, 2),
2 => (2, 1),
_ => (1, 1),
};
if self.chroma_array_type() == 0 { (1, 2 - self.frame_mbs_only as u32) } else { (sub_w, sub_h * (2 - self.frame_mbs_only as u32)) }
}
/// Cropping rectangle in luma samples: (x, y, width, height).
pub fn crop_rect(&self) -> (u32, u32, u32, u32) {
match self.frame_crop {
None => (0, 0, self.width(), self.height()),
Some((l, r, t, b)) => {
let (cx, cy) = self.crop_unit();
(l * cx, t * cy, self.width() - (l + r) * cx, self.height() - (t + b) * cy)
}
}
}
/// MaxDpbFrames from Table A-1 (and the VUI max_dec_frame_buffering when present).
pub fn max_dpb_frames(&self) -> usize {
let max_dpb_mbs: u32 = match self.level_idc {
9 => 396,
10 => 396,
11 => {
if self.constraint_flags & 0x10 != 0 && !matches!(self.profile_idc, 100 | 110 | 122 | 244 | 44) {
396
} else {
900
}
}
12 | 13 | 20 => 2376,
21 => 4752,
22 | 30 => 8100,
31 => 18000,
32 => 20480,
40 | 41 => 32768,
42 => 34816,
50 => 110400,
51 | 52 => 184320,
60..=62 => 696320,
_ => 184320,
};
let frame_mbs = self.pic_width_in_mbs * self.frame_height_in_mbs();
let mut n = (max_dpb_mbs / frame_mbs.max(1)).clamp(1, 16) as usize;
if let Some(br) = self.vui.as_ref().and_then(|v| v.bitstream_restriction.as_ref()) {
n = (br.max_dec_frame_buffering as usize).min(16);
}
n.max(self.max_num_ref_frames as usize).max(1)
}
/// Number of frames that may precede a frame in decoding order and follow it in output order.
pub fn max_num_reorder_frames(&self) -> usize {
if let Some(br) = self.vui.as_ref().and_then(|v| v.bitstream_restriction.as_ref()) {
return br.max_num_reorder_frames as usize;
}
// Profiles that cannot reorder (no B slices) with constraint_set3 (intra) etc.
if matches!(self.profile_idc, 44 | 86 | 100 | 110 | 122 | 244) && self.constraint_flags & 0x10 != 0 {
return 0;
}
self.max_dpb_frames()
}
pub fn check_supported(&self) -> Result<()> {
if !self.frame_mbs_only {
return unsupported("interlaced (field / MBAFF) coding");
}
if self.chroma_array_type() != 1 {
return unsupported(format!("chroma_format_idc {} (only 4:2:0 is implemented)", self.chroma_format_idc));
}
if self.bit_depth_luma != 8 || self.bit_depth_chroma != 8 {
return unsupported(format!("bit depth luma {} chroma {} (only 8-bit is implemented)", self.bit_depth_luma, self.bit_depth_chroma));
}
if self.qpprime_y_zero_transform_bypass {
return unsupported("qpprime_y_zero_transform_bypass (lossless)");
}
Ok(())
}
}
/// Picture parameter set.
#[derive(Clone, Debug)]
pub struct Pps {
pub id: u32,
pub sps_id: u32,
pub entropy_coding_mode: bool,
pub bottom_field_pic_order_in_frame_present: bool,
pub num_slice_groups: u32,
pub slice_group_map_type: u32,
pub num_ref_idx_l0_default_active: u32,
pub num_ref_idx_l1_default_active: u32,
pub weighted_pred: bool,
pub weighted_bipred_idc: u32,
pub pic_init_qp: i32,
pub pic_init_qs: i32,
pub chroma_qp_index_offset: i32,
pub deblocking_filter_control_present: bool,
pub constrained_intra_pred: bool,
pub redundant_pic_cnt_present: bool,
pub transform_8x8_mode: bool,
pub pic_scaling_matrix_present: bool,
pub second_chroma_qp_index_offset: i32,
/// Effective scaling matrices (after PPS/SPS fall-back), raster order.
pub scaling: ScalingMatrices,
}
/// `26 + minus26`, after `minus26` is inside −(26 + QpBdOffset) ..= 25.
fn qp_plus_26(minus26: i32, qp_bd: i32, name: &str) -> Result<i32> {
ensure!((-(26 + qp_bd)..=25).contains(&minus26), "{name} out of range");
Ok(26 + minus26)
}
impl Pps {
/// Parse a PPS. Needs the SPS table because the tail depends on chroma_format_idc.
pub fn parse(rbsp: &[u8], spss: &[Option<Sps>]) -> Result<Pps> {
let mut r = BitReader::new(rbsp);
let id = r.read_ue()?;
ensure!(id < 256, "pic_parameter_set_id out of range");
let sps_id = r.read_ue()?;
ensure!(sps_id < 32, "seq_parameter_set_id out of range");
let sps = spss
.get(sps_id as usize)
.and_then(|s| s.as_ref())
.ok_or_else(|| crate::Error::MissingParameterSet(format!("SPS {sps_id} for PPS {id}")))?;
let entropy_coding_mode = r.read_flag()?;
let bottom_field_pic_order_in_frame_present = r.read_flag()?;
let num_slice_groups = r.read_ue_plus(1)?;
ensure!(num_slice_groups <= 8, "num_slice_groups out of range");
let mut slice_group_map_type = 0;
if num_slice_groups > 1 {
// Parsed for completeness; FMO decoding is unsupported.
slice_group_map_type = r.read_ue()?;
match slice_group_map_type {
0 => {
for _ in 0..num_slice_groups {
r.read_ue()?;
}
}
2 => {
for _ in 0..num_slice_groups - 1 {
r.read_ue()?;
r.read_ue()?;
}
}
3..=5 => {
r.read_flag()?;
r.read_ue()?;
}
6 => {
let n = r.read_ue_plus(1)?;
let bits = 32 - (num_slice_groups - 1).leading_zeros();
for _ in 0..n {
r.read_bits(bits)?;
}
}
_ => {}
}
}
let num_ref_idx_l0_default_active = r.read_ue_plus(1)?;
let num_ref_idx_l1_default_active = r.read_ue_plus(1)?;
ensure!(num_ref_idx_l0_default_active <= 32 && num_ref_idx_l1_default_active <= 32, "num_ref_idx_default_active out of range");
let weighted_pred = r.read_flag()?;
let weighted_bipred_idc = r.read_bits(2)?;
// pic_init_qp_minus26 is −(26 + QpBdOffsetY) ..= 25. Check that before adding 26: a hostile
// Exp-Golomb value overflows the addition in debug builds.
let qp_bd = 6 * sps.bit_depth_luma.saturating_sub(8) as i32;
let pic_init_qp = qp_plus_26(r.read_se()?, qp_bd, "pic_init_qp_minus26")?;
let pic_init_qs = qp_plus_26(r.read_se()?, qp_bd, "pic_init_qs_minus26")?;
let chroma_qp_index_offset = r.read_se()?;
ensure!((-12..=12).contains(&chroma_qp_index_offset), "chroma_qp_index_offset out of range");
let deblocking_filter_control_present = r.read_flag()?;
let constrained_intra_pred = r.read_flag()?;
let redundant_pic_cnt_present = r.read_flag()?;
let mut transform_8x8_mode = false;
let mut pic_scaling_matrix_present = false;
let mut second_chroma_qp_index_offset = chroma_qp_index_offset;
let mut scaling = sps.scaling.clone();
if r.more_rbsp_data() {
transform_8x8_mode = r.read_flag()?;
pic_scaling_matrix_present = r.read_flag()?;
if pic_scaling_matrix_present {
let count = 6 + if sps.chroma_format_idc != 3 { 2 } else { 6 } * transform_8x8_mode as usize;
// Fall-back rule B uses the sequence-level lists (or Flat when the SPS has none, but then
// rule A applies per 7.4.2.2).
let lists = if sps.seq_scaling_matrix_present {
parse_scaling_lists(&mut r, count, Some(&sps.scaling_lists))?
} else {
parse_scaling_lists(&mut r, count, None)?
};
scaling = lists.to_matrices();
}
second_chroma_qp_index_offset = r.read_se()?;
ensure!((-12..=12).contains(&second_chroma_qp_index_offset), "second_chroma_qp_index_offset out of range");
}
Ok(Pps {
id,
sps_id,
entropy_coding_mode,
bottom_field_pic_order_in_frame_present,
num_slice_groups,
slice_group_map_type,
num_ref_idx_l0_default_active,
num_ref_idx_l1_default_active,
weighted_pred,
weighted_bipred_idc,
pic_init_qp,
pic_init_qs,
chroma_qp_index_offset,
deblocking_filter_control_present,
constrained_intra_pred,
redundant_pic_cnt_present,
transform_8x8_mode,
pic_scaling_matrix_present,
second_chroma_qp_index_offset,
scaling,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use deckcraft_bitstream::BitWriter;
#[test]
fn scaling_list_default_signal() {
// delta_scale = -8 on the first entry => nextScale 0 => use default.
let mut w = BitWriter::new();
w.write_se(-8);
w.rbsp_trailing();
let b = w.finish();
let mut r = BitReader::new(&b);
assert_eq!(parse_scaling_list::<16>(&mut r).unwrap(), None);
}
#[test]
fn scaling_list_repeat_last() {
// 8 -> +2 = 10, then +0 = 10, then -10 => 0 => repeat 10 for the rest.
let mut w = BitWriter::new();
w.write_se(2);
w.write_se(0);
w.write_se(-10);
w.rbsp_trailing();
let b = w.finish();
let mut r = BitReader::new(&b);
let l = parse_scaling_list::<16>(&mut r).unwrap().unwrap();
assert_eq!(l, [10; 16]);
}
#[test]
fn parse_minimal_baseline_sps() {
// profile 66, level 30, sps_id 0, log2_max_frame_num_minus4 0, poc type 2, 1 ref, 11x9 MBs.
let mut w = BitWriter::new();
w.write_bits(66, 8);
w.write_bits(0xC0, 8);
w.write_bits(30, 8);
w.write_ue(0);
w.write_ue(0);
w.write_ue(2);
w.write_ue(1);
w.write_bit(false);
w.write_ue(10);
w.write_ue(8);
w.write_bit(true); // frame_mbs_only
w.write_bit(true); // direct_8x8
w.write_bit(false); // cropping
w.write_bit(false); // vui
w.rbsp_trailing();
let sps = Sps::parse(&w.finish()).unwrap();
assert_eq!((sps.width(), sps.height()), (176, 144));
assert_eq!(sps.pic_order_cnt_type, 2);
assert_eq!(sps.crop_rect(), (0, 0, 176, 144));
}
/// The baseline SPS of `parse_minimal_baseline_sps` with a chosen width and optional cropping.
fn sps_with(width_minus1: u32, crop: Option<[u32; 4]>) -> Vec<u8> {
let mut w = BitWriter::new();
w.write_bits(66, 8);
w.write_bits(0xC0, 8);
w.write_bits(30, 8);
w.write_ue(0);
w.write_ue(0);
w.write_ue(2);
w.write_ue(1);
w.write_bit(false);
w.write_ue(width_minus1);
w.write_ue(8);
w.write_bit(true); // frame_mbs_only
w.write_bit(true); // direct_8x8
w.write_bit(crop.is_some());
if let Some(c) = crop {
c.iter().for_each(|&v| w.write_ue(v));
}
w.write_bit(false); // vui
w.rbsp_trailing();
w.finish()
}
#[test]
fn hostile_sps_values_are_errors_not_overflows() {
// pic_width_in_mbs_minus1 = u32::MAX: `+ 1` used to overflow (wrapping to 0 in release).
assert!(Sps::parse(&sps_with(u32::MAX, None)).is_err());
// Crop offsets whose sum or product exceed u32.
assert!(Sps::parse(&sps_with(10, Some([u32::MAX - 1, u32::MAX - 1, 0, 0]))).is_err());
assert!(Sps::parse(&sps_with(10, Some([0x8000_0000, 0, 0, 0]))).is_err());
assert!(Sps::parse(&sps_with(10, Some([1, 1, 1, 1]))).is_ok());
}
fn pps_bits(qp_minus26: i32, qs_minus26: i32) -> Vec<u8> {
let mut w = BitWriter::new();
w.write_ue(0);
w.write_ue(0);
w.write_bit(false);
w.write_bit(false);
w.write_ue(0);
w.write_ue(0);
w.write_ue(0);
w.write_bit(false);
w.write_bits(0, 2);
w.write_se(qp_minus26);
w.write_se(qs_minus26);
w.write_se(0);
w.write_bit(false);
w.write_bit(false);
w.write_bit(false);
w.rbsp_trailing();
w.finish()
}
fn sps_table() -> Vec<Option<Sps>> {
vec![Sps::parse(&sps_with(10, None)).ok()]
}
#[test]
fn pic_init_qp_is_checked_before_adding_26() {
let table = sps_table();
assert_eq!(Pps::parse(&pps_bits(0, 0), &table).unwrap().pic_init_qp, 26);
assert_eq!(Pps::parse(&pps_bits(25, 0), &table).unwrap().pic_init_qp, 51);
assert_eq!(Pps::parse(&pps_bits(-26, 0), &table).unwrap().pic_init_qp, 0);
assert!(Pps::parse(&pps_bits(26, 0), &table).is_err());
assert!(Pps::parse(&pps_bits(-27, 0), &table).is_err());
assert!(Pps::parse(&pps_bits(i32::MAX, 0), &table).is_err());
assert!(Pps::parse(&pps_bits(0, i32::MAX), &table).is_err());
}
#[test]
fn slice_qp_addition_does_not_overflow() {
let table = sps_table();
let pps = Pps::parse(&pps_bits(0, 0), &table).unwrap();
let sps = table[0].clone().unwrap();
let nal = crate::slice::NalHeader { nal_ref_idc: 0, nal_unit_type: crate::slice::nal_type::SLICE };
let rbsp = |delta: i32| {
let mut w = BitWriter::new();
w.write_ue(0);
w.write_ue(2); // I
w.write_ue(0);
w.write_bits(0, sps.log2_max_frame_num);
w.write_se(delta);
w.rbsp_trailing();
w.finish()
};
let parse = |delta: i32| crate::slice::SliceHeader::parse(&rbsp(delta), nal, |_| Ok((&pps, table[0].as_ref().unwrap())));
let (sh, _, _) = parse(0).unwrap();
assert_eq!(sh.qp(&pps), 26);
assert!(parse(26).is_err());
assert!(parse(i32::MAX).is_err());
}
}