scuffle_h264/sps/
sps_ext.rs

1use std::io;
2
3use scuffle_bytes_util::{BitReader, BitWriter};
4use scuffle_expgolomb::{BitReaderExpGolombExt, BitWriterExpGolombExt, size_of_exp_golomb, size_of_signed_exp_golomb};
5
6/// The Sequence Parameter Set extension.
7/// ISO/IEC-14496-10-2022 - 7.3.2
8#[derive(Debug, Clone, PartialEq)]
9pub struct SpsExtended {
10    /// The `chroma_format_idc` as a u8. This is the chroma sampling relative
11    /// to the luma sampling specified in subclause 6.2.
12    ///
13    /// The value of this ranges from \[0, 3\].
14    ///
15    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
16    /// The smallest encoding would be for `0` which is encoded as `1`, which is a single bit.
17    /// The largest encoding would be for `3` which is encoded as `0 0100`, which is 5 bits.
18    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
19    ///
20    /// For more information:
21    ///
22    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
23    pub chroma_format_idc: u8,
24
25    /// The `separate_colour_plane_flag` is a single bit.
26    ///
27    /// 0 means the the color components aren't coded separately and `ChromaArrayType` is set to `chroma_format_idc`.
28    ///
29    /// 1 means the 3 color components of the 4:4:4 chroma format are coded separately and
30    /// `ChromaArrayType` is set to 0.
31    ///
32    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
33    pub separate_color_plane_flag: bool,
34
35    /// The `bit_depth_luma_minus8` as a u8. This is the chroma sampling relative
36    /// to the luma sampling specified in subclause 6.2.
37    ///
38    /// The value of this ranges from \[0, 6\].
39    ///
40    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
41    /// The smallest encoding would be for `0` which is encoded as `1`, which is a single bit.
42    /// The largest encoding would be for `6` which is encoded as `0 0111`, which is 5 bits.
43    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
44    ///
45    /// For more information:
46    ///
47    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
48    pub bit_depth_luma_minus8: u8,
49
50    /// The `bit_depth_chroma_minus8` as a u8. This is the chroma sampling
51    /// relative to the luma sampling specified in subclause 6.2.
52    ///
53    /// The value of this ranges from \[0, 6\].
54    ///
55    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
56    /// The smallest encoding would be for `0` which is encoded as `1`, which is a single bit.
57    /// The largest encoding would be for `6` which is encoded as `0 0111`, which is 5 bits.
58    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
59    ///
60    /// For more information:
61    ///
62    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
63    pub bit_depth_chroma_minus8: u8,
64
65    /// The `qpprime_y_zero_transform_bypass_flag` is a single bit.
66    ///
67    /// 0 means the transform coefficient decoding and picture construction processes wont
68    /// use the transform bypass operation.
69    ///
70    /// 1 means that when QP'_Y is 0 then a transform bypass operation for the transform
71    /// coefficient decoding and picture construction processes will be applied before
72    /// the deblocking filter process from subclause 8.5.
73    ///
74    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
75    pub qpprime_y_zero_transform_bypass_flag: bool,
76
77    /// The `scaling_matrix`. If the length is nonzero, then
78    /// `seq_scaling_matrix_present_flag` must have been set.
79    pub scaling_matrix: Vec<Vec<i64>>,
80}
81
82impl Default for SpsExtended {
83    fn default() -> Self {
84        Self::DEFAULT
85    }
86}
87
88impl SpsExtended {
89    // default values defined in 7.4.2.1.1
90    const DEFAULT: SpsExtended = SpsExtended {
91        chroma_format_idc: 1,
92        separate_color_plane_flag: false,
93        bit_depth_luma_minus8: 0,
94        bit_depth_chroma_minus8: 0,
95        qpprime_y_zero_transform_bypass_flag: false,
96        scaling_matrix: vec![],
97    };
98
99    /// Parses an extended SPS from a bitstream.
100    /// Returns an `SpsExtended` struct.
101    pub fn parse<T: io::Read>(reader: &mut BitReader<T>) -> io::Result<Self> {
102        let chroma_format_idc = reader.read_exp_golomb()? as u8;
103        // Defaults to false: ISO/IEC-14496-10-2022 - 7.4.2.1.1
104        let mut separate_color_plane_flag = false;
105        if chroma_format_idc == 3 {
106            separate_color_plane_flag = reader.read_bit()?;
107        }
108
109        let bit_depth_luma_minus8 = reader.read_exp_golomb()? as u8;
110        let bit_depth_chroma_minus8 = reader.read_exp_golomb()? as u8;
111        let qpprime_y_zero_transform_bypass_flag = reader.read_bit()?;
112        let seq_scaling_matrix_present_flag = reader.read_bit()?;
113        let mut scaling_matrix: Vec<Vec<i64>> = vec![];
114
115        if seq_scaling_matrix_present_flag {
116            // We need to read the scaling matrices here, but we don't need them
117            // for decoding, so we just skip them.
118            let count = if chroma_format_idc != 3 { 8 } else { 12 };
119            for i in 0..count {
120                let bit = reader.read_bit()?;
121                scaling_matrix.push(vec![]);
122                if bit {
123                    let size = if i < 6 { 16 } else { 64 };
124                    let mut next_scale = 8;
125                    for _ in 0..size {
126                        let delta_scale = reader.read_signed_exp_golomb()?;
127                        scaling_matrix[i].push(delta_scale);
128                        next_scale = (next_scale + delta_scale + 256) % 256;
129                        if next_scale == 0 {
130                            break;
131                        }
132                    }
133                }
134            }
135        }
136
137        Ok(SpsExtended {
138            chroma_format_idc,
139            separate_color_plane_flag,
140            bit_depth_luma_minus8,
141            bit_depth_chroma_minus8,
142            qpprime_y_zero_transform_bypass_flag,
143            scaling_matrix,
144        })
145    }
146
147    /// Builds the SPSExtended struct into a byte stream.
148    /// Returns a built byte stream.
149    pub fn build<T: io::Write>(&self, writer: &mut BitWriter<T>) -> io::Result<()> {
150        writer.write_exp_golomb(self.chroma_format_idc as u64)?;
151
152        if self.chroma_format_idc == 3 {
153            writer.write_bit(self.separate_color_plane_flag)?;
154        }
155
156        writer.write_exp_golomb(self.bit_depth_luma_minus8 as u64)?;
157        writer.write_exp_golomb(self.bit_depth_chroma_minus8 as u64)?;
158        writer.write_bit(self.qpprime_y_zero_transform_bypass_flag)?;
159
160        writer.write_bit(!self.scaling_matrix.is_empty())?;
161
162        for vec in &self.scaling_matrix {
163            writer.write_bit(!vec.is_empty())?;
164
165            for expg in vec {
166                writer.write_signed_exp_golomb(*expg)?;
167            }
168        }
169        Ok(())
170    }
171
172    /// Returns the total bits of the SpsExtended struct.
173    ///
174    /// Note that this isn't the bytesize since aligning it may cause some values to be different.
175    pub fn bitsize(&self) -> u64 {
176        size_of_exp_golomb(self.chroma_format_idc as u64) +
177        (self.chroma_format_idc == 3) as u64 +
178        size_of_exp_golomb(self.bit_depth_luma_minus8 as u64) +
179        size_of_exp_golomb(self.bit_depth_chroma_minus8 as u64) +
180        1 + // qpprime_y_zero_transform_bypass_flag
181        1 + // scaling_matrix.is_empty()
182        // scaling matrix
183        self.scaling_matrix.len() as u64 +
184        self.scaling_matrix.iter().flat_map(|inner| inner.iter()).map(|&x| size_of_signed_exp_golomb(x)).sum::<u64>()
185    }
186
187    /// Returns the total bytes of the SpsExtended struct.
188    ///
189    /// Note that this calls [`SpsExtended::bitsize()`] and calculates the number of bytes
190    /// including any necessary padding such that the bitstream is byte aligned.
191    pub fn bytesize(&self) -> u64 {
192        self.bitsize().div_ceil(8)
193    }
194}
195
196#[cfg(test)]
197#[cfg_attr(all(test, coverage_nightly), coverage(off))]
198mod tests {
199    use scuffle_bytes_util::{BitReader, BitWriter};
200    use scuffle_expgolomb::BitWriterExpGolombExt;
201
202    use crate::sps::SpsExtended;
203
204    #[test]
205    fn test_build_size_sps_ext_chroma_not_3_and_no_scaling_matrix_and_size() {
206        // create data bitstream for sps_ext
207        let mut data = Vec::new();
208        let mut writer = BitWriter::new(&mut data);
209
210        writer.write_exp_golomb(1).unwrap();
211        writer.write_exp_golomb(2).unwrap();
212        writer.write_exp_golomb(4).unwrap();
213        writer.write_bit(true).unwrap();
214        writer.write_bit(false).unwrap();
215
216        writer.finish().unwrap();
217
218        // parse bitstream
219        let mut reader = BitReader::new_from_slice(&mut data);
220        let sps_ext = SpsExtended::parse(&mut reader).unwrap();
221
222        // create a writer for the builder
223        let mut buf = Vec::new();
224        let mut writer2 = BitWriter::new(&mut buf);
225
226        // build from the example result
227        sps_ext.build(&mut writer2).unwrap();
228        writer2.finish().unwrap();
229
230        assert_eq!(buf, data);
231
232        // now we re-parse so we can compare the bit sizes.
233        // create a reader for the parser
234        let mut reader2 = BitReader::new_from_slice(buf);
235        let rebuilt_sps_ext = SpsExtended::parse(&mut reader2).unwrap();
236
237        // now we can check the size:
238        assert_eq!(rebuilt_sps_ext.bitsize(), sps_ext.bitsize());
239        assert_eq!(rebuilt_sps_ext.bytesize(), sps_ext.bytesize());
240    }
241
242    #[test]
243    fn test_build_size_sps_ext_chroma_3_and_scaling_matrix() {
244        // create bitstream for sps_ext
245        let mut data = Vec::new();
246        let mut writer = BitWriter::new(&mut data);
247
248        // set chroma_format_idc = 3
249        writer.write_exp_golomb(3).unwrap();
250        // separate_color_plane_flag since chroma_format_idc = 3
251        writer.write_bit(true).unwrap();
252        writer.write_exp_golomb(2).unwrap();
253        writer.write_exp_golomb(4).unwrap();
254        writer.write_bit(true).unwrap();
255        // set seq_scaling_matrix_present_flag
256        writer.write_bit(true).unwrap();
257
258        // scaling matrix loop happens 12 times since chroma_format_idc is 3
259        // loop 1 of 12
260        writer.write_bit(true).unwrap();
261        // subloop 1 of 64
262        // next_scale starts as 8, we add 1 so it's 9
263        writer.write_signed_exp_golomb(1).unwrap();
264        // subloop 2 of 64
265        // next_scale is 9, we add 2 so it's 11
266        writer.write_signed_exp_golomb(2).unwrap();
267        // subloop 3 of 64
268        // next_scale is 11, we add 3 so it's 14
269        writer.write_signed_exp_golomb(3).unwrap();
270        // subloop 4 of 64: we want to break out of the loop now
271        // next_scale is 14, we subtract 14 so it's 0, triggering a break
272        writer.write_signed_exp_golomb(-14).unwrap();
273
274        // loop 2 of 12
275        writer.write_bit(true).unwrap();
276        // subloop 1 of 64
277        // next_scale starts at 8, we add 3 so it's 11
278        writer.write_signed_exp_golomb(3).unwrap();
279        // subloop 2 of 64
280        // next_scale is 11, we add 5 so it's 16
281        writer.write_signed_exp_golomb(5).unwrap();
282        // subloop 3 of 64; we want to break out of the loop now
283        // next_scale is 16, we subtract 16 so it's 0, triggering a break
284        writer.write_signed_exp_golomb(-16).unwrap();
285
286        // loop 3 of 12
287        writer.write_bit(true).unwrap();
288        // subloop 1 of 64
289        // next_scale starts at 8, we add 1 so it's 9
290        writer.write_signed_exp_golomb(1).unwrap();
291        // subloop 2 of 64; we want to break out of the loop now
292        // next_scale is 9, we subtract 9 so it's 0, triggering a break
293        writer.write_signed_exp_golomb(-9).unwrap();
294
295        // loop 4 of 12
296        writer.write_bit(true).unwrap();
297        // subloop 1 of 64; we want to break out of the loop now
298        // next scale starts at 8, we subtract 8 so it's 0, triggering a break
299        writer.write_signed_exp_golomb(-8).unwrap();
300
301        // loop 5 thru 11: try writing nothing
302        writer.write_bits(0, 7).unwrap();
303
304        // loop 12 of 12: try writing something
305        writer.write_bit(true).unwrap();
306        // subloop 1 of 64; we want to break out of the loop now
307        // next scale starts at 8, we subtract 8 so it's 0, triggering a break
308        writer.write_signed_exp_golomb(-8).unwrap();
309
310        writer.finish().unwrap();
311
312        // parse bitstream
313        let mut reader = BitReader::new_from_slice(&mut data);
314        let sps_ext = SpsExtended::parse(&mut reader).unwrap();
315
316        // create a writer for the builder
317        let mut buf = Vec::new();
318        let mut writer2 = BitWriter::new(&mut buf);
319
320        // build from the example result
321        sps_ext.build(&mut writer2).unwrap();
322        writer2.finish().unwrap();
323
324        assert_eq!(buf, data);
325
326        // now we re-parse so we can compare the bit sizes.
327        // create a reader for the parser
328        let mut reader2 = BitReader::new_from_slice(buf);
329        let rebuilt_sps_ext = SpsExtended::parse(&mut reader2).unwrap();
330
331        // now we can check the size:
332        assert_eq!(rebuilt_sps_ext.bitsize(), sps_ext.bitsize());
333        assert_eq!(rebuilt_sps_ext.bytesize(), sps_ext.bytesize());
334    }
335}