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