Skip to main content

rusty_h264_encoder/
params.rs

1//! Sequence and picture parameter sets (SPS / PPS) generation.
2//!
3//! Follows the H.264 spec syntax (§7.3.2.1.1 / §7.3.2.2) restricted to the
4//! Constrained Baseline feature set: `frame_mbs_only_flag = 1`, no scaling
5//! matrices, CAVLC entropy coding, no chroma/luma bit-depth extensions.
6
7use crate::config::EncoderConfig;
8use rusty_h264_common::{BitWriter, NalUnit, NalUnitType};
9
10/// Sequence parameter set, carrying only the fields a CBP encoder emits.
11#[derive(Debug, Clone)]
12pub struct Sps {
13    pub profile_idc: u8,
14    pub constraint_set1_flag: bool,
15    pub level_idc: u8,
16    pub seq_parameter_set_id: u32,
17    pub log2_max_frame_num_minus4: u32,
18    pub pic_order_cnt_type: u32,
19    pub log2_max_pic_order_cnt_lsb_minus4: u32,
20    pub max_num_ref_frames: u32,
21    pub pic_width_in_mbs_minus1: u32,
22    pub pic_height_in_map_units_minus1: u32,
23    /// Cropping in chroma-sample units (right, bottom) when the coded MB grid
24    /// overshoots the requested luma resolution.
25    pub frame_crop_right: u32,
26    pub frame_crop_bottom: u32,
27}
28
29impl Sps {
30    /// Derives the SPS from an encoder configuration.
31    pub fn from_config(cfg: &EncoderConfig) -> Self {
32        let mb_w = cfg.mb_width();
33        let mb_h = cfg.mb_height();
34        // Crop offsets are expressed in units of CropUnitX/Y. For 4:2:0 and
35        // frame_mbs_only_flag=1, CropUnitX=2, CropUnitY=2.
36        let crop_right = (mb_w * 16 - cfg.width) / 2;
37        let crop_bottom = (mb_h * 16 - cfg.height) / 2;
38        Self {
39            profile_idc: cfg.profile.profile_idc(),
40            constraint_set1_flag: true, // constrained baseline
41            level_idc: cfg.level_idc,
42            seq_parameter_set_id: 0,
43            log2_max_frame_num_minus4: 0, // log2_max_frame_num = 4
44            pic_order_cnt_type: 0,
45            log2_max_pic_order_cnt_lsb_minus4: 0, // log2_max_poc_lsb = 4
46            max_num_ref_frames: cfg.num_ref_frames.max(1),
47            pic_width_in_mbs_minus1: (mb_w - 1) as u32,
48            pic_height_in_map_units_minus1: (mb_h - 1) as u32,
49            frame_crop_right: crop_right as u32,
50            frame_crop_bottom: crop_bottom as u32,
51        }
52    }
53
54    /// Writes the SPS RBSP (without NAL header) including trailing bits.
55    pub fn write_rbsp(&self, w: &mut BitWriter) {
56        w.write_bits(self.profile_idc as u32, 8);
57        // constraint_set0..5 flags + 2 reserved zero bits = u(8).
58        let mut constraints = 0u32;
59        if self.constraint_set1_flag {
60            constraints |= 1 << 6; // constraint_set1_flag is bit position 6 (MSB-first)
61        }
62        w.write_bits(constraints, 8);
63        w.write_bits(self.level_idc as u32, 8);
64        w.write_ue(self.seq_parameter_set_id);
65        // CBP (profile_idc 66) => no chroma_format_idc / scaling-list block.
66        w.write_ue(self.log2_max_frame_num_minus4);
67        w.write_ue(self.pic_order_cnt_type);
68        if self.pic_order_cnt_type == 0 {
69            w.write_ue(self.log2_max_pic_order_cnt_lsb_minus4);
70        }
71        w.write_ue(self.max_num_ref_frames);
72        w.write_bit(false); // gaps_in_frame_num_value_allowed_flag
73        w.write_ue(self.pic_width_in_mbs_minus1);
74        w.write_ue(self.pic_height_in_map_units_minus1);
75        w.write_bit(true); // frame_mbs_only_flag = 1
76        w.write_bit(false); // direct_8x8_inference_flag
77        let cropping = self.frame_crop_right != 0 || self.frame_crop_bottom != 0;
78        w.write_bit(cropping); // frame_cropping_flag
79        if cropping {
80            w.write_ue(0); // frame_crop_left_offset
81            w.write_ue(self.frame_crop_right);
82            w.write_ue(0); // frame_crop_top_offset
83            w.write_ue(self.frame_crop_bottom);
84        }
85        w.write_bit(false); // vui_parameters_present_flag
86        w.rbsp_trailing_bits();
87    }
88
89    /// Builds the SPS as a complete NAL unit.
90    pub fn to_nal(&self) -> NalUnit {
91        let mut w = BitWriter::new();
92        self.write_rbsp(&mut w);
93        NalUnit::new(3, NalUnitType::Sps, w.into_bytes())
94    }
95}
96
97/// Picture parameter set for a CAVLC, single-slice-group CBP encoder.
98#[derive(Debug, Clone)]
99pub struct Pps {
100    pub pic_parameter_set_id: u32,
101    pub seq_parameter_set_id: u32,
102    pub num_ref_idx_l0_default_active_minus1: u32,
103    pub pic_init_qp_minus26: i32,
104    pub deblocking_filter_control_present_flag: bool,
105}
106
107impl Pps {
108    /// Derives the PPS from an encoder configuration.
109    pub fn from_config(cfg: &EncoderConfig) -> Self {
110        Self {
111            pic_parameter_set_id: 0,
112            seq_parameter_set_id: 0,
113            num_ref_idx_l0_default_active_minus1: cfg.num_ref_frames.max(1) - 1,
114            pic_init_qp_minus26: cfg.qp as i32 - 26,
115            // We signal deblocking control in the slice so we can disable the
116            // in-loop filter (not yet implemented); this keeps our (non-filtered)
117            // reconstruction bit-identical to a reference decoder's.
118            deblocking_filter_control_present_flag: true,
119        }
120    }
121
122    /// Writes the PPS RBSP (without NAL header) including trailing bits.
123    pub fn write_rbsp(&self, w: &mut BitWriter) {
124        w.write_ue(self.pic_parameter_set_id);
125        w.write_ue(self.seq_parameter_set_id);
126        w.write_bit(false); // entropy_coding_mode_flag = 0 (CAVLC)
127        w.write_bit(false); // bottom_field_pic_order_in_frame_present_flag
128        w.write_ue(0); // num_slice_groups_minus1
129        w.write_ue(self.num_ref_idx_l0_default_active_minus1);
130        w.write_ue(0); // num_ref_idx_l1_default_active_minus1
131        w.write_bit(false); // weighted_pred_flag
132        w.write_bits(0, 2); // weighted_bipred_idc
133        w.write_se(self.pic_init_qp_minus26);
134        w.write_se(0); // pic_init_qs_minus26
135        w.write_se(0); // chroma_qp_index_offset
136        w.write_bit(self.deblocking_filter_control_present_flag);
137        w.write_bit(false); // constrained_intra_pred_flag
138        w.write_bit(false); // redundant_pic_cnt_present_flag
139        w.rbsp_trailing_bits();
140    }
141
142    /// Builds the PPS as a complete NAL unit.
143    pub fn to_nal(&self) -> NalUnit {
144        let mut w = BitWriter::new();
145        self.write_rbsp(&mut w);
146        NalUnit::new(3, NalUnitType::Pps, w.into_bytes())
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use rusty_h264_common::{nal::emulation_unprevent, BitReader};
154
155    #[test]
156    fn sps_roundtrips_through_reader() {
157        let cfg = EncoderConfig::new(1920, 1080); // 1080 not a multiple of 16 -> cropping
158        let sps = Sps::from_config(&cfg);
159        let nal = sps.to_nal();
160
161        let rbsp = emulation_unprevent(&nal.rbsp);
162        let mut r = BitReader::new(&rbsp);
163        assert_eq!(r.read_bits(8).unwrap(), 66); // profile_idc
164        let constraints = r.read_bits(8).unwrap();
165        assert_eq!((constraints >> 6) & 1, 1); // constraint_set1_flag
166        assert_eq!(r.read_bits(8).unwrap(), 30); // level_idc
167        assert_eq!(r.read_ue().unwrap(), 0); // sps id
168        assert_eq!(r.read_ue().unwrap(), 0); // log2_max_frame_num_minus4
169        assert_eq!(r.read_ue().unwrap(), 0); // poc type
170        assert_eq!(r.read_ue().unwrap(), 0); // log2_max_poc_lsb_minus4
171        assert_eq!(r.read_ue().unwrap(), 1); // max_num_ref_frames
172        assert!(!r.read_bit().unwrap()); // gaps
173        assert_eq!(r.read_ue().unwrap(), 119); // 1920/16 - 1
174        assert_eq!(r.read_ue().unwrap(), 67); // ceil(1080/16)-1 = 68-1
175        assert!(r.read_bit().unwrap()); // frame_mbs_only
176        assert!(!r.read_bit().unwrap()); // direct_8x8
177        assert!(r.read_bit().unwrap()); // cropping present (1080)
178        assert_eq!(r.read_ue().unwrap(), 0); // crop left
179        assert_eq!(r.read_ue().unwrap(), 0); // crop right
180        assert_eq!(r.read_ue().unwrap(), 0); // crop top
181        assert_eq!(r.read_ue().unwrap(), 4); // crop bottom: (1088-1080)/2
182    }
183
184    #[test]
185    fn pps_roundtrips_through_reader() {
186        let cfg = EncoderConfig::new(640, 480);
187        let pps = Pps::from_config(&cfg);
188        let nal = pps.to_nal();
189
190        let rbsp = emulation_unprevent(&nal.rbsp);
191        let mut r = BitReader::new(&rbsp);
192        assert_eq!(r.read_ue().unwrap(), 0); // pps id
193        assert_eq!(r.read_ue().unwrap(), 0); // sps id
194        assert!(!r.read_bit().unwrap()); // entropy_coding_mode (CAVLC)
195        assert!(!r.read_bit().unwrap()); // bottom_field
196        assert_eq!(r.read_ue().unwrap(), 0); // num_slice_groups_minus1
197        assert_eq!(r.read_ue().unwrap(), 0); // num_ref_idx_l0
198        assert_eq!(r.read_ue().unwrap(), 0); // num_ref_idx_l1
199        assert!(!r.read_bit().unwrap()); // weighted_pred
200        assert_eq!(r.read_bits(2).unwrap(), 0); // weighted_bipred_idc
201        assert_eq!(r.read_se().unwrap(), 0); // pic_init_qp_minus26 (qp 26)
202    }
203}