Skip to main content

rusty_h264_decoder/
params.rs

1//! SPS/PPS parsing for Constrained Baseline.
2//!
3//! Parses any conformant Baseline SPS/PPS and **rejects** (never misparses or
4//! panics on) profiles and tools outside Constrained Baseline.
5
6use crate::DecodeError;
7use rusty_h264_common::BitReader;
8
9/// Profiles that carry the High-profile SPS prefix (`chroma_format_idc`,
10/// bit-depths, scaling matrices). Decoding their SPS with the Baseline layout
11/// would shift every later field — so we reject them up front rather than
12/// misparse. (Spec Table A-1 / §7.3.2.1.1.)
13const HIGH_PROFILE_IDCS: &[u8] = &[
14    100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135,
15];
16
17/// Upper bound on the coded frame size, in macroblocks. Above this we reject the
18/// SPS rather than attempt a multi-gigabyte allocation from a hostile header.
19/// (≈ 4× H.264 Level 5.2's MaxFS of 36 864 MBs — generous but finite.)
20const MAX_FRAME_MBS: u64 = 36_864 * 4;
21
22/// Default 4×4 scaling lists in zig-zag order (spec Table 7-3).
23pub(crate) const DEFAULT_4X4_INTRA: [u8; 16] =
24    [6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, 42];
25pub(crate) const DEFAULT_4X4_INTER: [u8; 16] =
26    [10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34];
27/// Default 8×8 scaling lists in zig-zag order (spec Table 7-4).
28pub(crate) const DEFAULT_8X8_INTRA: [u8; 64] = [
29    6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, 23, 23, 23, 23, 23, 25, 25, 25,
30    25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31,
31    31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42,
32];
33pub(crate) const DEFAULT_8X8_INTER: [u8; 64] = [
34    9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, 21, 21, 21, 21, 21, 22, 22, 22,
35    22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27,
36    27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35,
37];
38
39/// Parses a `scaling_list` of `size` coefficients (spec §7.3.2.1.1.1), filling
40/// `out` (zig-zag order) and returning `use_default`. Consumes the exact bits so
41/// the rest of the SPS/PPS stays aligned even when we ignore the weights.
42fn parse_scaling_list(r: &mut BitReader, out: &mut [u8], size: usize) -> Result<bool, DecodeError> {
43    let mut last_scale = 8i32;
44    let mut next_scale = 8i32;
45    let mut use_default = false;
46    for (j, slot) in out.iter_mut().enumerate().take(size) {
47        if next_scale != 0 {
48            let delta = r.read_se()?;
49            next_scale = (last_scale + delta + 256).rem_euclid(256);
50            if j == 0 && next_scale == 0 {
51                use_default = true;
52            }
53        }
54        let v = if next_scale == 0 { last_scale } else { next_scale };
55        *slot = v as u8;
56        last_scale = v;
57    }
58    Ok(use_default)
59}
60
61/// Parsed sequence parameter set fields the decoder needs.
62#[derive(Debug, Clone)]
63pub struct Sps {
64    pub profile_idc: u8,
65    pub level_idc: u8,
66    pub seq_parameter_set_id: u32,
67    pub log2_max_frame_num: u32,
68    pub pic_order_cnt_type: u32,
69    pub log2_max_pic_order_cnt_lsb: u32,
70    /// `delta_pic_order_always_zero_flag` (only meaningful for POC type 1).
71    pub delta_pic_order_always_zero: bool,
72    /// `gaps_in_frame_num_value_allowed_flag`: when set, `frame_num` may skip
73    /// values and the decoder must synthesize placeholder reference frames.
74    pub gaps_in_frame_num_allowed: bool,
75    /// `direct_8x8_inference_flag`: when set, B direct/skip derives one motion per
76    /// 8×8 from the co-located corner 4×4 (vs per-4×4).
77    pub direct_8x8_inference: bool,
78    pub max_num_ref_frames: u32,
79    pub pic_width_in_mbs: usize,
80    pub pic_height_in_mbs: usize,
81    pub frame_crop_left: u32,
82    pub frame_crop_right: u32,
83    pub frame_crop_top: u32,
84    pub frame_crop_bottom: u32,
85    /// `chroma_format_idc` (1 = 4:2:0; the only value we decode).
86    pub chroma_format_idc: u32,
87    /// Sequence scaling lists in zig-zag order (six 4×4, two 8×8); `16`
88    /// everywhere = flat (no weighting). High-profile only.
89    pub scaling_4x4: [[u8; 16]; 6],
90    pub scaling_8x8: [[u8; 64]; 2],
91    /// Whether custom scaling matrices are active (else flat dequant).
92    pub has_scaling: bool,
93}
94
95impl Sps {
96    /// Coded luma width/height in samples (MB grid * 16).
97    pub fn coded_width(&self) -> usize {
98        self.pic_width_in_mbs * 16
99    }
100    pub fn coded_height(&self) -> usize {
101        self.pic_height_in_mbs * 16
102    }
103
104    /// Displayed luma width after cropping (CropUnitX = 2 for 4:2:0).
105    pub fn display_width(&self) -> usize {
106        self.coded_width() - 2 * (self.frame_crop_left + self.frame_crop_right) as usize
107    }
108    /// Displayed luma height after cropping (CropUnitY = 2 for 4:2:0, frame-only).
109    pub fn display_height(&self) -> usize {
110        self.coded_height() - 2 * (self.frame_crop_top + self.frame_crop_bottom) as usize
111    }
112
113    /// Parses an SPS RBSP (emulation bytes already removed). Rejects anything
114    /// outside Constrained Baseline cleanly; never panics.
115    pub fn parse(rbsp: &[u8]) -> Result<Self, DecodeError> {
116        let mut r = BitReader::new(rbsp);
117        let profile_idc = r.read_bits(8)? as u8;
118        let _constraints = r.read_bits(8)?;
119        let level_idc = r.read_bits(8)? as u8;
120        let seq_parameter_set_id = r.read_ue()?;
121        // High/Main-prefix profiles add chroma_format_idc, bit-depths, and the
122        // sequence scaling matrices here (spec §7.3.2.1.1, after seq_parameter_set_id).
123        // Parse the 4:2:0 / 8-bit subset; reject the rest cleanly.
124        let mut chroma_format_idc = 1u32;
125        let mut scaling_4x4 = [[16u8; 16]; 6];
126        let mut scaling_8x8 = [[16u8; 64]; 2];
127        let mut has_scaling = false;
128        if HIGH_PROFILE_IDCS.contains(&profile_idc) {
129            chroma_format_idc = r.read_ue()?;
130            if chroma_format_idc == 3 {
131                let _separate_colour_plane = r.read_bit()?;
132            }
133            if chroma_format_idc != 1 {
134                return Err(DecodeError::Unsupported("non-4:2:0 chroma"));
135            }
136            if r.read_ue()? != 0 || r.read_ue()? != 0 {
137                return Err(DecodeError::Unsupported("bit depth > 8"));
138            }
139            let _qpprime_y_zero_transform_bypass = r.read_bit()?;
140            if r.read_bit()? {
141                // seq_scaling_matrix_present_flag — six 4×4 then two 8×8 (4:2:0),
142                // with fall-back rule set A for absent / use-default lists
143                // (spec §8.5.9 Table 8-?, §7.4.2.1.1.1).
144                has_scaling = true;
145                for i in 0..8 {
146                    let present = r.read_bit()?;
147                    if i < 6 {
148                        if present {
149                            let dflt = parse_scaling_list(&mut r, &mut scaling_4x4[i], 16)?;
150                            if dflt {
151                                scaling_4x4[i] = if i < 3 { DEFAULT_4X4_INTRA } else { DEFAULT_4X4_INTER };
152                            }
153                        } else {
154                            scaling_4x4[i] = match i {
155                                0 => DEFAULT_4X4_INTRA,
156                                3 => DEFAULT_4X4_INTER,
157                                _ => scaling_4x4[i - 1], // fall back to the previous list
158                            };
159                        }
160                    } else if present {
161                        let dflt = parse_scaling_list(&mut r, &mut scaling_8x8[i - 6], 64)?;
162                        if dflt {
163                            scaling_8x8[i - 6] = if i == 6 { DEFAULT_8X8_INTRA } else { DEFAULT_8X8_INTER };
164                        }
165                    } else {
166                        scaling_8x8[i - 6] = if i == 6 { DEFAULT_8X8_INTRA } else { DEFAULT_8X8_INTER };
167                    }
168                }
169            }
170        }
171        // CBP/Baseline: no chroma_format_idc / scaling-list section.
172        // Spec §7.4.2.1.1: log2_max_frame_num_minus4 ∈ [0,12] → log2_max_frame_num ≤ 16.
173        // Reject anything larger: an attacker-inflated value makes MaxFrameNum = 1<<n
174        // billions, which would drive the frame-num-gap loop unbounded.
175        let log2_max_frame_num = r.read_ue()? + 4;
176        if log2_max_frame_num > 16 {
177            return Err(DecodeError::Unsupported("invalid log2_max_frame_num"));
178        }
179        let pic_order_cnt_type = r.read_ue()?;
180        let mut log2_max_pic_order_cnt_lsb = 0;
181        let mut delta_pic_order_always_zero = false;
182        if pic_order_cnt_type == 0 {
183            log2_max_pic_order_cnt_lsb = r.read_ue()? + 4;
184            if log2_max_pic_order_cnt_lsb > 16 {
185                return Err(DecodeError::Unsupported("invalid log2_max_pic_order_cnt_lsb"));
186            }
187        } else if pic_order_cnt_type == 1 {
188            // Parse the type-1 cycle so later fields stay aligned; CBP output
189            // order is decode order, so only the always-zero flag is retained
190            // (the slice header needs it to know whether delta_pic_order_cnt is
191            // present).
192            delta_pic_order_always_zero = r.read_bit()?;
193            let _offset_for_non_ref_pic = r.read_se()?;
194            let _offset_for_top_to_bottom = r.read_se()?;
195            let n = r.read_ue()?;
196            if n > 255 {
197                return Err(DecodeError::Unsupported("oversized poc cycle"));
198            }
199            for _ in 0..n {
200                let _offset = r.read_se()?;
201            }
202        } else if pic_order_cnt_type != 2 {
203            return Err(DecodeError::Unsupported("invalid pic_order_cnt_type"));
204        }
205        let max_num_ref_frames = r.read_ue()?;
206        let gaps_in_frame_num_allowed = r.read_bit()?;
207        let pic_width_in_mbs = (r.read_ue()? as u64 + 1) as usize;
208        let pic_height_in_mbs = (r.read_ue()? as u64 + 1) as usize;
209        // Guard against a hostile SPS demanding a giant allocation.
210        if (pic_width_in_mbs as u64) * (pic_height_in_mbs as u64) > MAX_FRAME_MBS {
211            return Err(DecodeError::Unsupported("frame too large"));
212        }
213        let frame_mbs_only_flag = r.read_bit()?;
214        if !frame_mbs_only_flag {
215            return Err(DecodeError::Unsupported("interlace / field coding"));
216        }
217        let direct_8x8_inference = r.read_bit()?;
218        let cropping = r.read_bit()?;
219        let (mut cl, mut cr, mut ct, mut cb) = (0, 0, 0, 0);
220        if cropping {
221            cl = r.read_ue()?;
222            cr = r.read_ue()?;
223            ct = r.read_ue()?;
224            cb = r.read_ue()?;
225            // Reject crop windows that exceed the coded frame (would underflow
226            // the display-size subtraction in into_frame / display_*).
227            if (cl + cr) as usize * 2 >= pic_width_in_mbs * 16
228                || (ct + cb) as usize * 2 >= pic_height_in_mbs * 16
229            {
230                return Err(DecodeError::Unsupported("crop exceeds frame"));
231            }
232        }
233        // vui_parameters_present_flag and trailing bits ignored.
234        Ok(Self {
235            profile_idc,
236            level_idc,
237            seq_parameter_set_id,
238            log2_max_frame_num,
239            pic_order_cnt_type,
240            log2_max_pic_order_cnt_lsb,
241            delta_pic_order_always_zero,
242            gaps_in_frame_num_allowed,
243            direct_8x8_inference,
244            max_num_ref_frames,
245            pic_width_in_mbs,
246            pic_height_in_mbs,
247            frame_crop_left: cl,
248            frame_crop_right: cr,
249            frame_crop_top: ct,
250            frame_crop_bottom: cb,
251            chroma_format_idc,
252            scaling_4x4,
253            scaling_8x8,
254            has_scaling,
255        })
256    }
257}
258
259/// Parsed picture parameter set fields the decoder needs.
260#[derive(Debug, Clone)]
261pub struct Pps {
262    pub pic_parameter_set_id: u32,
263    pub seq_parameter_set_id: u32,
264    pub entropy_coding_mode_flag: bool,
265    /// `bottom_field_pic_order_in_frame_present_flag` (a.k.a. pic_order_present):
266    /// when set, slice headers carry an extra `delta_pic_order_cnt` value.
267    pub bottom_field_pic_order_present: bool,
268    pub num_ref_idx_l0_default: u32,
269    pub num_ref_idx_l1_default: u32,
270    pub weighted_pred: bool,
271    pub weighted_bipred_idc: u8,
272    pub pic_init_qp: i32,
273    /// Signed offset applied when mapping luma QP to chroma QP (§8.5.8).
274    pub chroma_qp_index_offset: i32,
275    pub deblocking_filter_control_present_flag: bool,
276    pub constrained_intra_pred_flag: bool,
277    pub redundant_pic_cnt_present_flag: bool,
278    /// `transform_8x8_mode_flag` (High PPS extension): when set, macroblocks may
279    /// signal `transform_size_8x8_flag` to use the 8×8 transform.
280    pub transform_8x8_mode_flag: bool,
281    /// `second_chroma_qp_index_offset` (High PPS extension) — the Cr QP offset;
282    /// defaults to `chroma_qp_index_offset` (the Cb offset) when absent.
283    pub second_chroma_qp_index_offset: i32,
284    /// `pic_scaling_matrix_present_flag`: per-picture scaling lists overriding
285    /// the SPS ones (fall-back rule B). When false the SPS lists apply.
286    pub pic_scaling_matrix_present: bool,
287    /// Parsed PPS scaling lists (zig-zag order) and per-list present flags. Only
288    /// meaningful when `pic_scaling_matrix_present`; absent lists resolve against
289    /// the SPS at slice time.
290    pub scaling_4x4: [[u8; 16]; 6],
291    pub scaling_8x8: [[u8; 64]; 2],
292    pub scaling_present_4x4: [bool; 6],
293    pub scaling_present_8x8: [bool; 2],
294}
295
296impl Pps {
297    /// Parses a PPS RBSP. Rejects FMO/slice-groups cleanly; never panics.
298    pub fn parse(rbsp: &[u8]) -> Result<Self, DecodeError> {
299        let mut r = BitReader::new(rbsp);
300        let pic_parameter_set_id = r.read_ue()?;
301        let seq_parameter_set_id = r.read_ue()?;
302        let entropy_coding_mode_flag = r.read_bit()?;
303        let bottom_field_pic_order_present = r.read_bit()?;
304        let num_slice_groups_minus1 = r.read_ue()?;
305        if num_slice_groups_minus1 != 0 {
306            // FMO: a slice_group map follows here that we neither parse nor
307            // support — reject before the syntax shifts under us.
308            return Err(DecodeError::Unsupported("slice groups (FMO)"));
309        }
310        let num_ref_idx_l0_default = r.read_ue()? + 1;
311        let num_ref_idx_l1_default = r.read_ue()? + 1;
312        let weighted_pred = r.read_bit()?;
313        let weighted_bipred_idc = r.read_bits(2)? as u8;
314        let pic_init_qp = 26 + r.read_se()?;
315        let _pic_init_qs = r.read_se()?;
316        let chroma_qp_index_offset = r.read_se()?;
317        let deblocking_filter_control_present_flag = r.read_bit()?;
318        let constrained_intra_pred_flag = r.read_bit()?;
319        let redundant_pic_cnt_present_flag = r.read_bit()?;
320        // High-profile PPS extension (present iff there is more RBSP data).
321        let mut transform_8x8_mode_flag = false;
322        let mut second_chroma_qp_index_offset = chroma_qp_index_offset;
323        let mut pic_scaling_matrix_present = false;
324        let mut scaling_4x4 = [[16u8; 16]; 6];
325        let mut scaling_8x8 = [[16u8; 64]; 2];
326        let mut scaling_present_4x4 = [false; 6];
327        let mut scaling_present_8x8 = [false; 2];
328        if r.more_rbsp_data() {
329            transform_8x8_mode_flag = r.read_bit()?;
330            if r.read_bit()? {
331                // pic_scaling_matrix_present_flag: 6 4×4 lists + (2 8×8 when the
332                // 8×8 transform is enabled, for 4:2:0). Absent lists resolve via
333                // fall-back rule B against the SPS at slice time.
334                pic_scaling_matrix_present = true;
335                let n = 6 + if transform_8x8_mode_flag { 2 } else { 0 };
336                for i in 0..n {
337                    let present = r.read_bit()?;
338                    if i < 6 {
339                        scaling_present_4x4[i] = present;
340                        if present {
341                            let dflt = parse_scaling_list(&mut r, &mut scaling_4x4[i], 16)?;
342                            if dflt {
343                                scaling_4x4[i] =
344                                    if i < 3 { DEFAULT_4X4_INTRA } else { DEFAULT_4X4_INTER };
345                            }
346                        }
347                    } else {
348                        scaling_present_8x8[i - 6] = present;
349                        if present {
350                            let dflt = parse_scaling_list(&mut r, &mut scaling_8x8[i - 6], 64)?;
351                            if dflt {
352                                scaling_8x8[i - 6] =
353                                    if i == 6 { DEFAULT_8X8_INTRA } else { DEFAULT_8X8_INTER };
354                            }
355                        }
356                    }
357                }
358            }
359            second_chroma_qp_index_offset = r.read_se()?;
360        }
361        Ok(Self {
362            pic_parameter_set_id,
363            seq_parameter_set_id,
364            entropy_coding_mode_flag,
365            bottom_field_pic_order_present,
366            num_ref_idx_l0_default,
367            num_ref_idx_l1_default,
368            weighted_pred,
369            weighted_bipred_idc,
370            pic_init_qp,
371            chroma_qp_index_offset,
372            deblocking_filter_control_present_flag,
373            constrained_intra_pred_flag,
374            redundant_pic_cnt_present_flag,
375            transform_8x8_mode_flag,
376            second_chroma_qp_index_offset,
377            pic_scaling_matrix_present,
378            scaling_4x4,
379            scaling_8x8,
380            scaling_present_4x4,
381            scaling_present_8x8,
382        })
383    }
384}