File
Blob: firmware/vendor/str0m/src/packet/h264_profile.rs
| 1 | use crate::util::BitPattern; |
| 2 | |
| 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
| 4 | pub(crate) struct H264ProfileLevel { |
| 5 | profile: H264Profile, |
| 6 | level_idc: H264LevelIdc, |
| 7 | } |
| 8 | |
| 9 | impl 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 | |
| 147 | impl 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)] |
| 183 | pub(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)] |
| 204 | enum 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 | |
| 215 | impl 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] |
| 240 | pub(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 | |
| 260 | impl 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 | |
| 286 | impl PartialOrd for H264LevelIdc { |
| 287 | fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { |
| 288 | Some(self.cmp(other)) |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | impl Ord for H264LevelIdc { |
| 293 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { |
| 294 | self.ordinal().cmp(&other.ordinal()) |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | impl 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)] |
| 329 | mod 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 | } |