Skip to content
File

Blob: firmware/vendor/str0m/src/packet/h264_profile.rs

rust375 lines
1use crate::util::BitPattern;
2 
3#[derive(Debug, Copy, Clone, PartialEq, Eq)]
4pub(crate) struct H264ProfileLevel {
5 profile: H264Profile,
6 level_idc: H264LevelIdc,
7}
8 
9impl H264ProfileLevel {
10 // TODO: The default should really be Baseline and Level1
11 // according to the spec: https://tools.ietf.org/html/rfc6184#section-8.1. However, libWebRTC
12 // specifies Level3_1 to not break backwards compatibility and we copy them.
13 // When libWebRTC updates we are probably safe to do the same.
14 //
15 // See: https://webrtc.googlesource.com/src/+/refs/heads/main/api/video_codecs/h264_profile_level_id.cc#182
16 pub(crate) const FALLBACK: Self = Self {
17 profile: H264Profile::Baseline,
18 level_idc: H264LevelIdc::Level3_1,
19 };
20 
21 /// Different combinations of profile-iop and profile-idc match to the same profile.
22 /// See table 5 in https://www.rfc-editor.org/rfc/rfc6184#section-8.1
23 ///
24 /// The first value in each tuple is the profile that is matched if the profile-idc and the
25 /// BitPattern matches a given fmtp line.
26 const PROFILES: &'static [(H264Profile, H264ProfileIdc, BitPattern)] = &[
27 // Constrained Baseline
28 (
29 H264Profile::ConstrainedBaseline,
30 H264ProfileIdc::X42,
31 BitPattern::new(*b"x1xx0000"),
32 ),
33 (
34 H264Profile::ConstrainedBaseline,
35 H264ProfileIdc::X4D,
36 BitPattern::new(*b"1xxx0000"),
37 ),
38 (
39 H264Profile::ConstrainedBaseline,
40 H264ProfileIdc::X58,
41 BitPattern::new(*b"11xx0000"),
42 ),
43 // Baseline
44 (
45 H264Profile::Baseline,
46 H264ProfileIdc::X42,
47 BitPattern::new(*b"x0xx0000"),
48 ),
49 (
50 H264Profile::Baseline,
51 H264ProfileIdc::X58,
52 BitPattern::new(*b"10xx0000"),
53 ),
54 // Main
55 (
56 H264Profile::Main,
57 H264ProfileIdc::X4D,
58 BitPattern::new(*b"0x0x0000"),
59 ),
60 // Extended
61 (
62 H264Profile::Extended,
63 H264ProfileIdc::X58,
64 BitPattern::new(*b"00xx0000"),
65 ),
66 // High(No constraints)
67 (
68 H264Profile::High,
69 H264ProfileIdc::X64,
70 BitPattern::new(*b"00000000"),
71 ),
72 // Constrained High
73 (
74 H264Profile::ConstrainedHigh,
75 H264ProfileIdc::X64,
76 BitPattern::new(*b"00001100"),
77 ),
78 (
79 H264Profile::High10,
80 H264ProfileIdc::X6E,
81 BitPattern::new(*b"00000000"),
82 ),
83 (
84 H264Profile::High422,
85 H264ProfileIdc::X7A,
86 BitPattern::new(*b"00000000"),
87 ),
88 (
89 H264Profile::High444Predictive,
90 H264ProfileIdc::XF4,
91 BitPattern::new(*b"00000000"),
92 ),
93 // Intra profiles
94 (
95 H264Profile::High10Intra,
96 H264ProfileIdc::X6E,
97 BitPattern::new(*b"00010000"),
98 ),
99 (
100 H264Profile::High422Intra,
101 H264ProfileIdc::X7A,
102 BitPattern::new(*b"00010000"),
103 ),
104 (
105 H264Profile::High444Intra,
106 H264ProfileIdc::XF4,
107 BitPattern::new(*b"00010000"),
108 ),
109 (
110 H264Profile::CAVLC444Intra,
111 H264ProfileIdc::X2C,
112 BitPattern::new(*b"00010000"),
113 ),
114 ];
115 
116 /// Construct a new H264ProfileLevel.
117 ///
118 /// Returns `Some(Self)` only if the provided parameters identify a valid profile.
119 fn new(profile_idc: H264ProfileIdc, profile_iop: u8, level_idc: H264LevelIdc) -> Option<Self> {
120 Self::PROFILES
121 .iter()
122 .find_map(|&(profile, expected_pidc, iop_pattern)| {
123 // Profile IDC must match
124 if expected_pidc != profile_idc {
125 return None;
126 }
127 // The profile-iop must match the pattern for the profile
128 if !iop_pattern.matches(profile_iop) {
129 return None;
130 }
131 
132 Some(Self { profile, level_idc })
133 })
134 }
135 
136 /// Returns the H.264 profile (Baseline, Main, High, etc.).
137 pub(crate) fn profile(&self) -> H264Profile {
138 self.profile
139 }
140 
141 /// Returns the H.264 level (Level 3.1, Level 4.0, etc.).
142 pub(crate) fn level(&self) -> H264LevelIdc {
143 self.level_idc
144 }
145}
146 
147impl TryFrom<u32> for H264ProfileLevel {
148 type Error = ();
149 
150 fn try_from(value: u32) -> Result<Self, ()> {
151 const CONSTRAINT_SET3_FLAG: u8 = 0x10;
152 
153 let bytes = value.to_be_bytes();
154 
155 let profile_idc = bytes[1].try_into()?;
156 let profile_iop = bytes[2];
157 let mut profile_level = bytes[3].try_into()?;
158 
159 // When profile_idc is equal to 66, 77, or 88 (the Baseline, Main, or
160 // Extended profile), level_idc is equal to 11, and bit 4
161 // (constraint_set3_flag) of the profile-iop byte is equal to 1,
162 // the default level is Level 1b.
163 if [
164 H264ProfileIdc::X42,
165 H264ProfileIdc::X4D,
166 H264ProfileIdc::X58,
167 ]
168 .contains(&profile_idc)
169 && profile_level == H264LevelIdc::Level1
170 {
171 profile_level = if (profile_iop & CONSTRAINT_SET3_FLAG) != 0 {
172 H264LevelIdc::Level1B
173 } else {
174 H264LevelIdc::Level1
175 };
176 }
177 
178 Self::new(profile_idc, profile_iop, profile_level).ok_or(())
179 }
180}
181 
182#[derive(Debug, Copy, Clone, PartialEq, Eq)]
183pub(crate) enum H264Profile {
184 Baseline,
185 ConstrainedBaseline,
186 Main,
187 Extended,
188 High,
189 ConstrainedHigh,
190 High10,
191 High422,
192 High444Predictive,
193 High10Intra,
194 High422Intra,
195 High444Intra,
196 CAVLC444Intra,
197}
198 
199/// The various h264 profile_idc, not all of these have a name,
200/// but they are a constrained portion of `u8`, hence an
201/// enum to prevent using unspecified values.
202#[derive(Debug, Copy, Clone, PartialEq, Eq)]
203#[repr(u8)]
204enum H264ProfileIdc {
205 X2C = 44_u8,
206 X42 = 66_u8, // B
207 X4D = 77_u8, // M
208 X58 = 88_u8, // E
209 X64 = 100_u8,
210 X6E = 110_u8,
211 X7A = 122_u8,
212 XF4 = 244_u8,
213}
214 
215impl TryFrom<u8> for H264ProfileIdc {
216 type Error = ();
217 
218 fn try_from(value: u8) -> Result<Self, Self::Error> {
219 match value {
220 x if (Self::X2C as u8) == x => Ok(Self::X2C),
221 x if (Self::X42 as u8) == x => Ok(Self::X42),
222 x if (Self::X4D as u8) == x => Ok(Self::X4D),
223 x if (Self::X58 as u8) == x => Ok(Self::X58),
224 x if (Self::X64 as u8) == x => Ok(Self::X64),
225 x if (Self::X6E as u8) == x => Ok(Self::X6E),
226 x if (Self::X7A as u8) == x => Ok(Self::X7A),
227 x if (Self::XF4 as u8) == x => Ok(Self::XF4),
228 _ => Err(()),
229 }
230 }
231}
232 
233// Per libWebRTC
234// All values are equal to ten times the level number, except level 1b which is
235// special.
236// https://datatracker.ietf.org/doc/html/rfc6184#section-8.2.2
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238#[repr(u8)]
239#[rustfmt::skip]
240pub(crate) enum H264LevelIdc {
241 Level1B = 0_u8,
242 Level1 = 10_u8,
243 Level1_1 = 11_u8,
244 Level1_2 = 12_u8,
245 Level1_3 = 13_u8,
246 Level2 = 20_u8,
247 Level2_1 = 21_u8,
248 Level2_2 = 22_u8,
249 Level3 = 30_u8,
250 Level3_1 = 31_u8,
251 Level3_2 = 32_u8,
252 Level4 = 40_u8,
253 Level4_1 = 41_u8,
254 Level4_2 = 42_u8,
255 Level5 = 50_u8,
256 Level5_1 = 51_u8,
257 Level5_2 = 52_u8,
258}
259 
260impl H264LevelIdc {
261 /// Returns the ordinal position (0-16) representing capability order.
262 /// This takes into account that Level1B is between Level1 and Level1_1.
263 pub(crate) fn ordinal(self) -> usize {
264 match self {
265 Self::Level1 => 0,
266 Self::Level1B => 1,
267 Self::Level1_1 => 2,
268 Self::Level1_2 => 3,
269 Self::Level1_3 => 4,
270 Self::Level2 => 5,
271 Self::Level2_1 => 6,
272 Self::Level2_2 => 7,
273 Self::Level3 => 8,
274 Self::Level3_1 => 9,
275 Self::Level3_2 => 10,
276 Self::Level4 => 11,
277 Self::Level4_1 => 12,
278 Self::Level4_2 => 13,
279 Self::Level5 => 14,
280 Self::Level5_1 => 15,
281 Self::Level5_2 => 16,
282 }
283 }
284}
285 
286impl PartialOrd for H264LevelIdc {
287 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
288 Some(self.cmp(other))
289 }
290}
291 
292impl Ord for H264LevelIdc {
293 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
294 self.ordinal().cmp(&other.ordinal())
295 }
296}
297 
298impl TryFrom<u8> for H264LevelIdc {
299 type Error = ();
300 
301 #[rustfmt::skip]
302 fn try_from(value: u8) -> Result<Self, Self::Error> {
303 use H264LevelIdc::*;
304 
305 match value {
306 x if (Level1B as u8) == x => Ok(Level1B),
307 x if (Level1 as u8) == x => Ok(Level1),
308 x if (Level1_1 as u8) == x => Ok(Level1_1),
309 x if (Level1_2 as u8) == x => Ok(Level1_2),
310 x if (Level1_3 as u8) == x => Ok(Level1_3),
311 x if (Level2 as u8) == x => Ok(Level2),
312 x if (Level2_1 as u8) == x => Ok(Level2_1),
313 x if (Level2_2 as u8) == x => Ok(Level2_2),
314 x if (Level3 as u8) == x => Ok(Level3),
315 x if (Level3_1 as u8) == x => Ok(Level3_1),
316 x if (Level3_2 as u8) == x => Ok(Level3_2),
317 x if (Level4 as u8) == x => Ok(Level4),
318 x if (Level4_1 as u8) == x => Ok(Level4_1),
319 x if (Level4_2 as u8) == x => Ok(Level4_2),
320 x if (Level5 as u8) == x => Ok(Level5),
321 x if (Level5_1 as u8) == x => Ok(Level5_1),
322 x if (Level5_2 as u8) == x => Ok(Level5_2),
323 _ => Err(()),
324 }
325 }
326}
327 
328#[cfg(test)]
329mod test {
330 use super::*;
331 
332 #[test]
333 fn test_profile_level_parsing() {
334 struct Case {
335 profile_level_id: u32,
336 expected: Option<(H264Profile, H264LevelIdc)>,
337 msg: &'static str,
338 }
339 
340 let cases = [
341 // Test 1: High profile (no constraint flags) -> should parse
342 Case {
343 profile_level_id: 0x64001f,
344 expected: Some((H264Profile::High, H264LevelIdc::Level3_1)),
345 msg: "0x64001f should parse as High level 3.1",
346 },
347 // Test 2: Constrained High (constraint_set4_flag and
348 // constraint_set5_flag set) -> should parse
349 Case {
350 profile_level_id: 0x640c34,
351 expected: Some((H264Profile::ConstrainedHigh, H264LevelIdc::Level5_2)),
352 msg: "0x640c34 should parse as Constrained High level 5.2",
353 },
354 // Test 3: Undefined profile-iop combination -> should NOT parse
355 Case {
356 profile_level_id: 0x64101f,
357 expected: None,
358 msg: "0x64101f should not parse, profile-iop 0x10 is not defined for profile-idc 0x64",
359 },
360 ];
361 
362 for Case {
363 profile_level_id,
364 expected,
365 msg,
366 } in cases.into_iter()
367 {
368 let parsed = H264ProfileLevel::try_from(profile_level_id)
369 .ok()
370 .map(|pl| (pl.profile(), pl.level()));
371 assert_eq!(parsed, expected, "{msg}");
372 }
373 }
374}