scuffle_h264/sps/
color_config.rs

1use std::io;
2
3use byteorder::ReadBytesExt;
4use scuffle_bytes_util::{BitReader, BitWriter};
5
6use crate::VideoFormat;
7
8/// The color config for SPS. ISO/IEC-14496-10-2022 - E.2.1
9#[derive(Debug, Clone, PartialEq)]
10pub struct ColorConfig {
11    /// The `video_format` is comprised of 3 bits stored as a u8.
12    ///
13    /// Refer to the `VideoFormat` nutype enum for more info.
14    ///
15    /// ISO/IEC-14496-10-2022 - E.2.1 Table E-2
16    pub video_format: VideoFormat,
17
18    /// The `video_full_range_flag` is a single bit indicating the black level and range of
19    /// luma and chroma signals.
20    ///
21    /// This field is passed into the `ColorConfig`.
22    /// ISO/IEC-14496-10-2022 - E.2.1
23    pub video_full_range_flag: bool,
24
25    /// The `colour_primaries` byte as a u8. If `color_description_present_flag` is not set,
26    /// the value defaults to 2. ISO/IEC-14496-10-2022 - E.2.1 Table E-3
27    pub color_primaries: u8,
28
29    /// The `transfer_characteristics` byte as a u8. If `color_description_present_flag` is not set,
30    /// the value defaults to 2. ISO/IEC-14496-10-2022 - E.2.1 Table E-4
31    pub transfer_characteristics: u8,
32
33    /// The `matrix_coefficients` byte as a u8. If `color_description_present_flag` is not set,
34    /// the value defaults to 2. ISO/IEC-14496-10-2022 - E.2.1 Table E-5
35    pub matrix_coefficients: u8,
36}
37
38impl ColorConfig {
39    /// Parses the fields defined when the `video_signal_type_present_flag == 1` from a bitstream.
40    /// Returns a `ColorConfig` struct.
41    pub fn parse<T: io::Read>(reader: &mut BitReader<T>) -> io::Result<Self> {
42        let video_format = reader.read_bits(3)? as u8;
43        let video_full_range_flag = reader.read_bit()?;
44
45        let color_primaries;
46        let transfer_characteristics;
47        let matrix_coefficients;
48
49        let color_description_present_flag = reader.read_bit()?;
50        if color_description_present_flag {
51            color_primaries = reader.read_u8()?;
52            transfer_characteristics = reader.read_u8()?;
53            matrix_coefficients = reader.read_u8()?;
54        } else {
55            color_primaries = 2; // UNSPECIFIED
56            transfer_characteristics = 2; // UNSPECIFIED
57            matrix_coefficients = 2; // UNSPECIFIED
58        }
59
60        Ok(ColorConfig {
61            video_format: VideoFormat(video_format), // defalut value is 5 E.2.1 Table E-2
62            video_full_range_flag,
63            color_primaries,
64            transfer_characteristics,
65            matrix_coefficients,
66        })
67    }
68
69    /// Builds the ColorConfig struct into a byte stream.
70    /// Returns a built byte stream.
71    pub fn build<T: io::Write>(&self, writer: &mut BitWriter<T>) -> io::Result<()> {
72        writer.write_bits(self.video_format.0 as u64, 3)?;
73        writer.write_bit(self.video_full_range_flag)?;
74
75        match (self.color_primaries, self.transfer_characteristics, self.matrix_coefficients) {
76            (2, 2, 2) => {
77                writer.write_bit(false)?;
78            }
79            (color_priamries, transfer_characteristics, matrix_coefficients) => {
80                writer.write_bit(true)?;
81                writer.write_bits(color_priamries as u64, 8)?;
82                writer.write_bits(transfer_characteristics as u64, 8)?;
83                writer.write_bits(matrix_coefficients as u64, 8)?;
84            }
85        }
86        Ok(())
87    }
88
89    /// Returns the total bits of the ColorConfig struct.
90    ///
91    /// Note that this isn't the bytesize since aligning it may cause some values to be different.
92    pub fn bitsize(&self) -> u64 {
93        3 + // video_format
94        1 + // video_full_range_flag
95        1 + // color_description_present_flag
96        match (self.color_primaries, self.transfer_characteristics, self.matrix_coefficients) {
97            (2, 2, 2) => 0,
98            _ => 24
99        }
100    }
101
102    /// Returns the total bytes of the ColorConfig struct.
103    ///
104    /// Note that this calls [`ColorConfig::bitsize()`] and calculates the number of bytes
105    /// including any necessary padding such that the bitstream is byte aligned.
106    pub fn bytesize(&self) -> u64 {
107        self.bitsize().div_ceil(8)
108    }
109}
110
111#[cfg(test)]
112#[cfg_attr(all(test, coverage_nightly), coverage(off))]
113mod tests {
114    use scuffle_bytes_util::{BitReader, BitWriter};
115
116    use crate::sps::ColorConfig;
117
118    #[test]
119    fn test_build_size_color_config() {
120        // create bitstream for color_config
121        let mut data = Vec::new();
122        let mut writer = BitWriter::new(&mut data);
123
124        writer.write_bits(4, 3).unwrap();
125        writer.write_bit(true).unwrap();
126
127        // color_desc_present_flag
128        writer.write_bit(true).unwrap();
129        writer.write_bits(2, 8).unwrap();
130        writer.write_bits(6, 8).unwrap();
131        writer.write_bits(1, 8).unwrap();
132        writer.finish().unwrap();
133
134        // parse bitstream
135        let mut reader = BitReader::new_from_slice(&mut data);
136        let color_config = ColorConfig::parse(&mut reader).unwrap();
137
138        // create a writer for the builder
139        let mut buf = Vec::new();
140        let mut writer2 = BitWriter::new(&mut buf);
141
142        // build from the example result
143        color_config.build(&mut writer2).unwrap();
144        writer2.finish().unwrap();
145
146        assert_eq!(buf, data);
147        // now we re-parse so we can compare the bit sizes.
148        // create a reader for the parser
149        let mut reader2 = BitReader::new_from_slice(buf);
150        let rebuilt_color_config = ColorConfig::parse(&mut reader2).unwrap();
151
152        // now we can check the size:
153        assert_eq!(rebuilt_color_config.bitsize(), color_config.bitsize());
154        assert_eq!(rebuilt_color_config.bytesize(), color_config.bytesize());
155    }
156
157    #[test]
158    fn test_build_size_color_config_no_desc() {
159        // create bitstream for color_config
160        let mut data = Vec::new();
161        let mut writer = BitWriter::new(&mut data);
162
163        writer.write_bits(4, 3).unwrap();
164        writer.write_bit(true).unwrap();
165
166        // color_desc_present_flag
167        writer.write_bit(false).unwrap();
168        writer.finish().unwrap();
169
170        // parse bitstream
171        let mut reader = BitReader::new_from_slice(&mut data);
172        let color_config = ColorConfig::parse(&mut reader).unwrap();
173
174        // create a writer for the builder
175        let mut buf = Vec::new();
176        let mut writer2 = BitWriter::new(&mut buf);
177
178        // build from the example result
179        color_config.build(&mut writer2).unwrap();
180        writer2.finish().unwrap();
181
182        assert_eq!(buf, data);
183
184        // now we re-parse so we can compare the bit sizes.
185        // create a reader for the parser
186        let mut reader2 = BitReader::new_from_slice(buf);
187        let rebuilt_color_config = ColorConfig::parse(&mut reader2).unwrap();
188
189        // now we can check the size:
190        assert_eq!(rebuilt_color_config.bitsize(), color_config.bitsize());
191        assert_eq!(rebuilt_color_config.bytesize(), color_config.bytesize());
192    }
193}