//! 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(r: &mut BitReader) -> Result> { 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 { 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, pub cpb_size_value_minus1: Vec, pub cbr_flag: Vec, 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 { 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, 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, pub vcl_hrd: Option, pub low_delay_hrd: bool, pub pic_struct_present: bool, pub bitstream_restriction: Option, } 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 { 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 { 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, 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, } impl Sps { pub fn parse(rbsp: &[u8]) -> Result { 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 { 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]) -> Result { 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 { 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 { 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> { 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()); } }