Skip to content
File

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

rust406 lines
1use std::collections::HashMap;
2 
3use serde::{Deserialize, Serialize};
4 
5/// H.266/VVC profile, tier, and level combination.
6///
7/// Represents the three SDP fmtp parameters `profile-id`, `tier-flag`, and
8/// `level-id` as defined in RFC 9328 §7.2 and ITU-T H.266 Annex A.
9///
10/// Note: the parameter *names* are identical to H.265 (RFC 7798), but the
11/// value spaces differ. In particular the level-id encodings are disjoint:
12/// H.265 uses `30 × level` (e.g. 3.1 → 93) while H.266 uses
13/// `16 × major + 3 × minor` (e.g. 3.1 → 51), which is what allows SDP
14/// parsing to tell the two codecs' fmtp lines apart.
15#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct H266ProfileTierLevel {
17 profile: H266Profile,
18 tier: H266Tier,
19 level: H266Level,
20}
21 
22impl H266ProfileTierLevel {
23 // Inferred values mandated by RFC 9328 §7.2 when parameters are
24 // absent from SDP: profile-id => 1 (Main 10), tier-flag => 0,
25 // level-id => 51 (Level 3.1).
26 pub(crate) const FALLBACK: Self = Self {
27 profile: H266Profile::Main10,
28 tier: H266Tier::Main,
29 level: H266Level::Level3_1,
30 };
31 
32 /// Construct a new H266ProfileTierLevel from profile_id, tier_flag, and level_id.
33 ///
34 /// Returns `Some(Self)` only if the provided parameters identify valid values.
35 pub(crate) fn new(profile_id: u8, tier_flag: u8, level_id: u8) -> Option<Self> {
36 let profile = H266Profile::from_id(profile_id)?;
37 let tier = H266Tier::from_flag(tier_flag)?;
38 let level = H266Level::from_id(level_id)?;
39 
40 Some(Self {
41 profile,
42 tier,
43 level,
44 })
45 }
46 
47 /// Parse H266ProfileTierLevel from SDP fmtp parameters.
48 ///
49 /// Expects keys: "profile-id", "tier-flag", "level-id"
50 /// Returns None if any required parameter is missing or invalid.
51 pub(crate) fn from_fmtp(params: &HashMap<String, String>) -> Option<Self> {
52 let profile_id: u8 = params.get("profile-id")?.parse().ok()?;
53 let tier_flag: u8 = params.get("tier-flag")?.parse().ok()?;
54 let level_id: u8 = params.get("level-id")?.parse().ok()?;
55 
56 Self::new(profile_id, tier_flag, level_id)
57 }
58 
59 /// Returns the H.266 profile (Main 10, Main 10 4:4:4, etc.).
60 pub fn profile(&self) -> H266Profile {
61 self.profile
62 }
63 
64 /// Returns the H.266 tier (Main or High).
65 pub fn tier(&self) -> H266Tier {
66 self.tier
67 }
68 
69 /// Returns the H.266 level (Level 3.1, Level 4.0, etc.).
70 pub fn level(&self) -> H266Level {
71 self.level
72 }
73 
74 /// Returns a copy with the level replaced.
75 pub fn with_level(self, level: H266Level) -> Self {
76 Self { level, ..self }
77 }
78 
79 /// Returns the numeric profile_id value for SDP serialization.
80 pub fn profile_id(&self) -> u8 {
81 self.profile.to_id()
82 }
83 
84 /// Returns the numeric tier_flag value for SDP serialization.
85 pub fn tier_flag(&self) -> u8 {
86 self.tier.to_flag()
87 }
88 
89 /// Returns the numeric level_id value for SDP serialization.
90 pub fn level_id(&self) -> u8 {
91 self.level.to_id()
92 }
93}
94 
95impl From<(u8, u8, u8)> for H266ProfileTierLevel {
96 fn from(value: (u8, u8, u8)) -> Self {
97 Self::new(value.0, value.1, value.2).unwrap_or(Self::FALLBACK)
98 }
99}
100 
101/// H.266 profile as defined in ITU-T H.266 Annex A
102/// (`general_profile_idc` values, version 1 profiles).
103#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub enum H266Profile {
105 /// Main 10 profile (profile_id=1).
106 Main10,
107 /// Multilayer Main 10 profile (profile_id=17).
108 MultilayerMain10,
109 /// Main 10 4:4:4 profile (profile_id=33).
110 Main10_444,
111 /// Multilayer Main 10 4:4:4 profile (profile_id=49).
112 MultilayerMain10_444,
113 /// Main 10 Still Picture profile (profile_id=65).
114 Main10StillPicture,
115 /// Multilayer Main 10 Still Picture profile (profile_id=81).
116 MultilayerMain10StillPicture,
117 /// Main 10 4:4:4 Still Picture profile (profile_id=97).
118 Main10_444StillPicture,
119 /// Multilayer Main 10 4:4:4 Still Picture profile (profile_id=113).
120 MultilayerMain10_444StillPicture,
121}
122 
123impl H266Profile {
124 /// Convert from H.266 profile_id (general_profile_idc) value.
125 ///
126 /// See ITU-T H.266 Annex A for profile definitions.
127 fn from_id(profile_id: u8) -> Option<Self> {
128 match profile_id {
129 1 => Some(Self::Main10),
130 17 => Some(Self::MultilayerMain10),
131 33 => Some(Self::Main10_444),
132 49 => Some(Self::MultilayerMain10_444),
133 65 => Some(Self::Main10StillPicture),
134 81 => Some(Self::MultilayerMain10StillPicture),
135 97 => Some(Self::Main10_444StillPicture),
136 113 => Some(Self::MultilayerMain10_444StillPicture),
137 _ => None,
138 }
139 }
140 
141 /// Convert to H.266 profile_id (general_profile_idc) value.
142 pub fn to_id(self) -> u8 {
143 match self {
144 Self::Main10 => 1,
145 Self::MultilayerMain10 => 17,
146 Self::Main10_444 => 33,
147 Self::MultilayerMain10_444 => 49,
148 Self::Main10StillPicture => 65,
149 Self::MultilayerMain10StillPicture => 81,
150 Self::Main10_444StillPicture => 97,
151 Self::MultilayerMain10_444StillPicture => 113,
152 }
153 }
154}
155 
156/// H.266 tier (Main or High).
157#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
158pub enum H266Tier {
159 /// Main tier (tier_flag=0).
160 Main,
161 /// High tier (tier_flag=1).
162 High,
163}
164 
165impl H266Tier {
166 /// Convert from H.266 tier_flag value.
167 ///
168 /// 0 = Main tier, 1 = High tier
169 fn from_flag(tier_flag: u8) -> Option<Self> {
170 match tier_flag {
171 0 => Some(Self::Main),
172 1 => Some(Self::High),
173 _ => None,
174 }
175 }
176 
177 /// Convert to H.266 tier_flag value.
178 pub fn to_flag(self) -> u8 {
179 match self {
180 Self::Main => 0,
181 Self::High => 1,
182 }
183 }
184}
185 
186/// H.266 level as defined in ITU-T H.266 Annex A.
187///
188/// Level IDs (`general_level_idc`) encode as `16 × major + 3 × minor`
189/// (e.g., Level 3.1 = 16×3 + 3×1 = 51).
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
191#[repr(u8)]
192#[rustfmt::skip]
193pub enum H266Level {
194 /// Level 1.0 (level_id=16).
195 Level1 = 16_u8,
196 /// Level 2.0 (level_id=32).
197 Level2 = 32_u8,
198 /// Level 2.1 (level_id=35).
199 Level2_1 = 35_u8,
200 /// Level 3.0 (level_id=48).
201 Level3 = 48_u8,
202 /// Level 3.1 (level_id=51).
203 Level3_1 = 51_u8,
204 /// Level 4.0 (level_id=64).
205 Level4 = 64_u8,
206 /// Level 4.1 (level_id=67).
207 Level4_1 = 67_u8,
208 /// Level 5.0 (level_id=80).
209 Level5 = 80_u8,
210 /// Level 5.1 (level_id=83).
211 Level5_1 = 83_u8,
212 /// Level 5.2 (level_id=86).
213 Level5_2 = 86_u8,
214 /// Level 6.0 (level_id=96).
215 Level6 = 96_u8,
216 /// Level 6.1 (level_id=99).
217 Level6_1 = 99_u8,
218 /// Level 6.2 (level_id=102).
219 Level6_2 = 102_u8,
220 /// Level 6.3 (level_id=105).
221 Level6_3 = 105_u8,
222 /// Level 15.5 (level_id=255) — the special maximum level used by the
223 /// still-picture profiles (16*15 + 3*5 = 255).
224 Level15_5 = 255_u8,
225}
226 
227impl H266Level {
228 /// Returns the ordinal position (0-14) representing capability order.
229 pub fn ordinal(self) -> usize {
230 match self {
231 Self::Level1 => 0,
232 Self::Level2 => 1,
233 Self::Level2_1 => 2,
234 Self::Level3 => 3,
235 Self::Level3_1 => 4,
236 Self::Level4 => 5,
237 Self::Level4_1 => 6,
238 Self::Level5 => 7,
239 Self::Level5_1 => 8,
240 Self::Level5_2 => 9,
241 Self::Level6 => 10,
242 Self::Level6_1 => 11,
243 Self::Level6_2 => 12,
244 Self::Level6_3 => 13,
245 Self::Level15_5 => 14,
246 }
247 }
248 
249 /// Convert from H.266 level_id value.
250 fn from_id(level_id: u8) -> Option<Self> {
251 use H266Level::*;
252 match level_id {
253 x if (Level1 as u8) == x => Some(Level1),
254 x if (Level2 as u8) == x => Some(Level2),
255 x if (Level2_1 as u8) == x => Some(Level2_1),
256 x if (Level3 as u8) == x => Some(Level3),
257 x if (Level3_1 as u8) == x => Some(Level3_1),
258 x if (Level4 as u8) == x => Some(Level4),
259 x if (Level4_1 as u8) == x => Some(Level4_1),
260 x if (Level5 as u8) == x => Some(Level5),
261 x if (Level5_1 as u8) == x => Some(Level5_1),
262 x if (Level5_2 as u8) == x => Some(Level5_2),
263 x if (Level6 as u8) == x => Some(Level6),
264 x if (Level6_1 as u8) == x => Some(Level6_1),
265 x if (Level6_2 as u8) == x => Some(Level6_2),
266 x if (Level6_3 as u8) == x => Some(Level6_3),
267 x if (Level15_5 as u8) == x => Some(Level15_5),
268 _ => None,
269 }
270 }
271 
272 /// Convert to H.266 level_id value.
273 pub fn to_id(self) -> u8 {
274 self as u8
275 }
276}
277 
278impl PartialOrd for H266Level {
279 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
280 Some(self.cmp(other))
281 }
282}
283 
284impl Ord for H266Level {
285 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
286 self.ordinal().cmp(&other.ordinal())
287 }
288}
289 
290#[cfg(test)]
291mod test {
292 use super::*;
293 
294 #[test]
295 fn test_h266_profile_tier_level_new() {
296 // Valid combination
297 let ptl = H266ProfileTierLevel::new(1, 0, 51);
298 assert!(ptl.is_some());
299 let ptl = ptl.unwrap();
300 assert_eq!(ptl.profile(), H266Profile::Main10);
301 assert_eq!(ptl.tier(), H266Tier::Main);
302 assert_eq!(ptl.level(), H266Level::Level3_1);
303 
304 // Invalid profile_id
305 let ptl = H266ProfileTierLevel::new(99, 0, 51);
306 assert!(ptl.is_none());
307 
308 // Invalid tier_flag
309 let ptl = H266ProfileTierLevel::new(1, 5, 51);
310 assert!(ptl.is_none());
311 
312 // Invalid level_id (93 is an H.265 level encoding, not H.266)
313 let ptl = H266ProfileTierLevel::new(1, 0, 93);
314 assert!(ptl.is_none());
315 }
316 
317 #[test]
318 fn test_h266_profile_from_id() {
319 assert_eq!(H266Profile::from_id(1), Some(H266Profile::Main10));
320 assert_eq!(H266Profile::from_id(33), Some(H266Profile::Main10_444));
321 assert_eq!(
322 H266Profile::from_id(65),
323 Some(H266Profile::Main10StillPicture)
324 );
325 assert_eq!(
326 H266Profile::from_id(81),
327 Some(H266Profile::MultilayerMain10StillPicture)
328 );
329 assert_eq!(
330 H266Profile::from_id(113),
331 Some(H266Profile::MultilayerMain10_444StillPicture)
332 );
333 assert_eq!(H266Profile::from_id(2), None);
334 }
335 
336 #[test]
337 fn test_h266_tier_from_flag() {
338 assert_eq!(H266Tier::from_flag(0), Some(H266Tier::Main));
339 assert_eq!(H266Tier::from_flag(1), Some(H266Tier::High));
340 assert_eq!(H266Tier::from_flag(2), None);
341 }
342 
343 #[test]
344 fn test_h266_level_from_id() {
345 assert_eq!(H266Level::from_id(16), Some(H266Level::Level1));
346 assert_eq!(H266Level::from_id(51), Some(H266Level::Level3_1));
347 assert_eq!(H266Level::from_id(105), Some(H266Level::Level6_3));
348 assert_eq!(H266Level::from_id(255), Some(H266Level::Level15_5));
349 // H.265 encodings must NOT parse as H.266 levels.
350 assert_eq!(H266Level::from_id(93), None);
351 assert_eq!(H266Level::from_id(180), None);
352 }
353 
354 #[test]
355 fn test_h266_level_ordering() {
356 assert!(H266Level::Level1 < H266Level::Level2);
357 assert!(H266Level::Level3_1 < H266Level::Level4);
358 assert!(H266Level::Level6_2 < H266Level::Level6_3);
359 assert!(H266Level::Level6_3 < H266Level::Level15_5);
360 assert_eq!(H266Level::Level3_1.ordinal(), 4);
361 }
362 
363 #[test]
364 fn test_h266_profile_tier_level_from_fmtp() {
365 let mut params = HashMap::new();
366 params.insert("profile-id".to_string(), "1".to_string());
367 params.insert("tier-flag".to_string(), "0".to_string());
368 params.insert("level-id".to_string(), "51".to_string());
369 
370 let ptl = H266ProfileTierLevel::from_fmtp(&params);
371 assert!(ptl.is_some());
372 let ptl = ptl.unwrap();
373 assert_eq!(ptl.profile(), H266Profile::Main10);
374 assert_eq!(ptl.tier(), H266Tier::Main);
375 assert_eq!(ptl.level(), H266Level::Level3_1);
376 
377 // Missing parameter
378 let mut params = HashMap::new();
379 params.insert("profile-id".to_string(), "1".to_string());
380 params.insert("tier-flag".to_string(), "0".to_string());
381 let ptl = H266ProfileTierLevel::from_fmtp(&params);
382 assert!(ptl.is_none());
383 }
384 
385 #[test]
386 fn test_fallback_values() {
387 let fallback = H266ProfileTierLevel::FALLBACK;
388 assert_eq!(fallback.profile(), H266Profile::Main10);
389 assert_eq!(fallback.tier(), H266Tier::Main);
390 assert_eq!(fallback.level(), H266Level::Level3_1);
391 }
392 
393 #[test]
394 fn test_h265_h266_level_spaces_disjoint() {
395 // The disambiguation in SDP parsing relies on this property.
396 let h265_levels = [30, 60, 63, 90, 93, 120, 123, 150, 153, 156, 180, 183, 186];
397 for id in h265_levels {
398 assert_eq!(
399 H266Level::from_id(id),
400 None,
401 "level-id {id} must not be valid H.266"
402 );
403 }
404 }
405}