Skip to main content

oximedia_codec/
profile_level.rs

1#![allow(dead_code)]
2//! Codec profile and level definitions.
3//!
4//! Defines the profile/level/tier system used by modern video codecs to
5//! describe capability requirements. Provides constraint checking so that
6//! an encoder can verify the output conforms to a chosen profile/level pair.
7
8use std::fmt;
9
10/// Video codec family.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub enum CodecFamily {
13    /// AV1 codec.
14    Av1,
15    /// H.264 / AVC codec.
16    H264,
17    /// H.265 / HEVC codec.
18    H265,
19    /// VP9 codec.
20    Vp9,
21}
22
23impl fmt::Display for CodecFamily {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::Av1 => write!(f, "AV1"),
27            Self::H264 => write!(f, "H.264"),
28            Self::H265 => write!(f, "H.265"),
29            Self::Vp9 => write!(f, "VP9"),
30        }
31    }
32}
33
34/// Generic codec profile.
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
36pub enum Profile {
37    /// Main profile (8-bit 4:2:0).
38    Main,
39    /// Main 10 profile (10-bit 4:2:0).
40    Main10,
41    /// High profile (8-bit 4:2:0 with advanced coding tools).
42    High,
43    /// Professional profile (4:2:2 / 4:4:4).
44    Professional,
45    /// Baseline profile (constrained feature set).
46    Baseline,
47}
48
49impl fmt::Display for Profile {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            Self::Main => write!(f, "Main"),
53            Self::Main10 => write!(f, "Main10"),
54            Self::High => write!(f, "High"),
55            Self::Professional => write!(f, "Professional"),
56            Self::Baseline => write!(f, "Baseline"),
57        }
58    }
59}
60
61/// Generic codec level as a numeric identifier.
62///
63/// The numeric value follows codec conventions (e.g., H.264 level 5.1 = 51).
64#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
65pub struct Level(pub u16);
66
67impl Level {
68    /// Level 3.0.
69    pub const L3_0: Self = Self(30);
70    /// Level 3.1.
71    pub const L3_1: Self = Self(31);
72    /// Level 4.0.
73    pub const L4_0: Self = Self(40);
74    /// Level 4.1.
75    pub const L4_1: Self = Self(41);
76    /// Level 5.0.
77    pub const L5_0: Self = Self(50);
78    /// Level 5.1.
79    pub const L5_1: Self = Self(51);
80    /// Level 5.2.
81    pub const L5_2: Self = Self(52);
82    /// Level 6.0.
83    pub const L6_0: Self = Self(60);
84    /// Level 6.1.
85    pub const L6_1: Self = Self(61);
86}
87
88impl fmt::Display for Level {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "{}.{}", self.0 / 10, self.0 % 10)
91    }
92}
93
94/// Codec tier (main or high).
95#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
96pub enum Tier {
97    /// Main tier (lower bitrate constraints).
98    Main,
99    /// High tier (higher bitrate constraints).
100    High,
101}
102
103impl Default for Tier {
104    fn default() -> Self {
105        Self::Main
106    }
107}
108
109/// Constraints imposed by a profile/level combination.
110#[derive(Clone, Debug)]
111pub struct LevelConstraints {
112    /// Maximum picture width in pixels.
113    pub max_width: u32,
114    /// Maximum picture height in pixels.
115    pub max_height: u32,
116    /// Maximum luma sample rate (samples per second).
117    pub max_sample_rate: u64,
118    /// Maximum bitrate in bits per second for main tier.
119    pub max_bitrate_main: u64,
120    /// Maximum bitrate in bits per second for high tier.
121    pub max_bitrate_high: u64,
122    /// Maximum number of reference frames.
123    pub max_ref_frames: u8,
124    /// Maximum number of tile columns.
125    pub max_tile_cols: u8,
126    /// Maximum number of tile rows.
127    pub max_tile_rows: u8,
128}
129
130impl LevelConstraints {
131    /// Maximum bitrate for the given tier.
132    pub fn max_bitrate(&self, tier: Tier) -> u64 {
133        match tier {
134            Tier::Main => self.max_bitrate_main,
135            Tier::High => self.max_bitrate_high,
136        }
137    }
138
139    /// Maximum total pixel count (width * height).
140    pub fn max_pixels(&self) -> u64 {
141        u64::from(self.max_width) * u64::from(self.max_height)
142    }
143}
144
145/// Get level constraints for a given codec and level.
146pub fn get_constraints(codec: CodecFamily, level: Level) -> LevelConstraints {
147    match codec {
148        CodecFamily::Av1 => av1_constraints(level),
149        CodecFamily::H264 => h264_constraints(level),
150        CodecFamily::H265 => h265_constraints(level),
151        CodecFamily::Vp9 => vp9_constraints(level),
152    }
153}
154
155fn av1_constraints(level: Level) -> LevelConstraints {
156    match level.0 {
157        50 => LevelConstraints {
158            max_width: 4096,
159            max_height: 2304,
160            max_sample_rate: 66_846_720,
161            max_bitrate_main: 30_000_000,
162            max_bitrate_high: 60_000_000,
163            max_ref_frames: 7,
164            max_tile_cols: 8,
165            max_tile_rows: 8,
166        },
167        51 => LevelConstraints {
168            max_width: 4096,
169            max_height: 2304,
170            max_sample_rate: 133_693_440,
171            max_bitrate_main: 40_000_000,
172            max_bitrate_high: 100_000_000,
173            max_ref_frames: 7,
174            max_tile_cols: 8,
175            max_tile_rows: 8,
176        },
177        60 => LevelConstraints {
178            max_width: 8192,
179            max_height: 4352,
180            max_sample_rate: 267_386_880,
181            max_bitrate_main: 60_000_000,
182            max_bitrate_high: 180_000_000,
183            max_ref_frames: 7,
184            max_tile_cols: 16,
185            max_tile_rows: 16,
186        },
187        _ => default_constraints(),
188    }
189}
190
191fn h264_constraints(level: Level) -> LevelConstraints {
192    match level.0 {
193        40 => LevelConstraints {
194            max_width: 2048,
195            max_height: 1024,
196            max_sample_rate: 62_914_560,
197            max_bitrate_main: 20_000_000,
198            max_bitrate_high: 25_000_000,
199            max_ref_frames: 4,
200            max_tile_cols: 1,
201            max_tile_rows: 1,
202        },
203        51 => LevelConstraints {
204            max_width: 4096,
205            max_height: 2304,
206            max_sample_rate: 251_658_240,
207            max_bitrate_main: 135_000_000,
208            max_bitrate_high: 240_000_000,
209            max_ref_frames: 16,
210            max_tile_cols: 1,
211            max_tile_rows: 1,
212        },
213        _ => default_constraints(),
214    }
215}
216
217fn h265_constraints(level: Level) -> LevelConstraints {
218    match level.0 {
219        40 => LevelConstraints {
220            max_width: 2048,
221            max_height: 1080,
222            max_sample_rate: 62_914_560,
223            max_bitrate_main: 12_000_000,
224            max_bitrate_high: 30_000_000,
225            max_ref_frames: 6,
226            max_tile_cols: 5,
227            max_tile_rows: 5,
228        },
229        51 => LevelConstraints {
230            max_width: 4096,
231            max_height: 2160,
232            max_sample_rate: 267_386_880,
233            max_bitrate_main: 40_000_000,
234            max_bitrate_high: 160_000_000,
235            max_ref_frames: 6,
236            max_tile_cols: 10,
237            max_tile_rows: 10,
238        },
239        _ => default_constraints(),
240    }
241}
242
243fn vp9_constraints(level: Level) -> LevelConstraints {
244    match level.0 {
245        40 => LevelConstraints {
246            max_width: 2048,
247            max_height: 1080,
248            max_sample_rate: 62_914_560,
249            max_bitrate_main: 18_000_000,
250            max_bitrate_high: 36_000_000,
251            max_ref_frames: 3,
252            max_tile_cols: 4,
253            max_tile_rows: 4,
254        },
255        51 => LevelConstraints {
256            max_width: 4096,
257            max_height: 2160,
258            max_sample_rate: 267_386_880,
259            max_bitrate_main: 36_000_000,
260            max_bitrate_high: 120_000_000,
261            max_ref_frames: 3,
262            max_tile_cols: 8,
263            max_tile_rows: 8,
264        },
265        _ => default_constraints(),
266    }
267}
268
269fn default_constraints() -> LevelConstraints {
270    LevelConstraints {
271        max_width: 1920,
272        max_height: 1080,
273        max_sample_rate: 31_457_280,
274        max_bitrate_main: 10_000_000,
275        max_bitrate_high: 20_000_000,
276        max_ref_frames: 4,
277        max_tile_cols: 1,
278        max_tile_rows: 1,
279    }
280}
281
282/// Result of a constraint compliance check.
283#[derive(Clone, Debug, PartialEq, Eq)]
284pub struct ComplianceResult {
285    /// Whether the configuration passes all checks.
286    pub passed: bool,
287    /// List of violated constraints.
288    pub violations: Vec<String>,
289}
290
291impl ComplianceResult {
292    /// Create a passing result with no violations.
293    pub fn pass() -> Self {
294        Self {
295            passed: true,
296            violations: Vec::new(),
297        }
298    }
299
300    /// Create a failing result with the given violations.
301    pub fn fail(violations: Vec<String>) -> Self {
302        Self {
303            passed: false,
304            violations,
305        }
306    }
307}
308
309/// Check whether a given encoding configuration fits within a level's constraints.
310#[allow(clippy::cast_precision_loss)]
311pub fn check_compliance(
312    codec: CodecFamily,
313    level: Level,
314    tier: Tier,
315    width: u32,
316    height: u32,
317    framerate: f64,
318    bitrate: u64,
319) -> ComplianceResult {
320    let constraints = get_constraints(codec, level);
321    let mut violations = Vec::new();
322
323    if width > constraints.max_width {
324        violations.push(format!(
325            "Width {width} exceeds max {max}",
326            max = constraints.max_width
327        ));
328    }
329    if height > constraints.max_height {
330        violations.push(format!(
331            "Height {height} exceeds max {max}",
332            max = constraints.max_height
333        ));
334    }
335
336    let sample_rate = (u64::from(width) * u64::from(height)) as f64 * framerate;
337    if sample_rate > constraints.max_sample_rate as f64 {
338        violations.push(format!(
339            "Sample rate {sample_rate:.0} exceeds max {}",
340            constraints.max_sample_rate
341        ));
342    }
343
344    let max_br = constraints.max_bitrate(tier);
345    if bitrate > max_br {
346        violations.push(format!(
347            "Bitrate {bitrate} exceeds max {max_br} for tier {tier:?}"
348        ));
349    }
350
351    if violations.is_empty() {
352        ComplianceResult::pass()
353    } else {
354        ComplianceResult::fail(violations)
355    }
356}
357
358/// Find the minimum level that satisfies the given parameters.
359#[allow(clippy::cast_precision_loss)]
360pub fn find_minimum_level(
361    codec: CodecFamily,
362    tier: Tier,
363    width: u32,
364    height: u32,
365    framerate: f64,
366    bitrate: u64,
367) -> Option<Level> {
368    let candidates = [
369        Level::L3_0,
370        Level::L3_1,
371        Level::L4_0,
372        Level::L4_1,
373        Level::L5_0,
374        Level::L5_1,
375        Level::L5_2,
376        Level::L6_0,
377        Level::L6_1,
378    ];
379    for lvl in &candidates {
380        let result = check_compliance(codec, *lvl, tier, width, height, framerate, bitrate);
381        if result.passed {
382            return Some(*lvl);
383        }
384    }
385    None
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    #[test]
393    fn test_codec_family_display() {
394        assert_eq!(CodecFamily::Av1.to_string(), "AV1");
395        assert_eq!(CodecFamily::H264.to_string(), "H.264");
396        assert_eq!(CodecFamily::H265.to_string(), "H.265");
397        assert_eq!(CodecFamily::Vp9.to_string(), "VP9");
398    }
399
400    #[test]
401    fn test_profile_display() {
402        assert_eq!(Profile::Main.to_string(), "Main");
403        assert_eq!(Profile::Main10.to_string(), "Main10");
404        assert_eq!(Profile::Baseline.to_string(), "Baseline");
405    }
406
407    #[test]
408    fn test_level_display() {
409        assert_eq!(Level::L5_1.to_string(), "5.1");
410        assert_eq!(Level::L4_0.to_string(), "4.0");
411        assert_eq!(Level::L6_1.to_string(), "6.1");
412    }
413
414    #[test]
415    fn test_level_ordering() {
416        assert!(Level::L3_0 < Level::L4_0);
417        assert!(Level::L5_0 < Level::L5_1);
418        assert!(Level::L5_1 < Level::L6_0);
419    }
420
421    #[test]
422    fn test_tier_default() {
423        assert_eq!(Tier::default(), Tier::Main);
424    }
425
426    #[test]
427    fn test_av1_level_50_constraints() {
428        let c = get_constraints(CodecFamily::Av1, Level::L5_0);
429        assert_eq!(c.max_width, 4096);
430        assert_eq!(c.max_height, 2304);
431        assert_eq!(c.max_ref_frames, 7);
432    }
433
434    #[test]
435    fn test_max_bitrate_tier() {
436        let c = get_constraints(CodecFamily::Av1, Level::L5_0);
437        assert_eq!(c.max_bitrate(Tier::Main), 30_000_000);
438        assert_eq!(c.max_bitrate(Tier::High), 60_000_000);
439    }
440
441    #[test]
442    fn test_max_pixels() {
443        let c = get_constraints(CodecFamily::Av1, Level::L5_0);
444        assert_eq!(c.max_pixels(), 4096 * 2304);
445    }
446
447    #[test]
448    fn test_compliance_pass() {
449        let result = check_compliance(
450            CodecFamily::Av1,
451            Level::L5_1,
452            Tier::Main,
453            1920,
454            1080,
455            60.0,
456            10_000_000,
457        );
458        assert!(result.passed);
459        assert!(result.violations.is_empty());
460    }
461
462    #[test]
463    fn test_compliance_fail_width() {
464        let result = check_compliance(
465            CodecFamily::Av1,
466            Level::L5_0,
467            Tier::Main,
468            8192,
469            4320,
470            30.0,
471            10_000_000,
472        );
473        assert!(!result.passed);
474        assert!(!result.violations.is_empty());
475    }
476
477    #[test]
478    fn test_compliance_fail_bitrate() {
479        let result = check_compliance(
480            CodecFamily::Av1,
481            Level::L5_0,
482            Tier::Main,
483            1920,
484            1080,
485            30.0,
486            999_000_000,
487        );
488        assert!(!result.passed);
489        assert!(result.violations.iter().any(|v| v.contains("Bitrate")));
490    }
491
492    #[test]
493    fn test_find_minimum_level() {
494        let lvl = find_minimum_level(CodecFamily::Av1, Tier::Main, 1920, 1080, 30.0, 8_000_000);
495        assert!(lvl.is_some());
496    }
497
498    #[test]
499    fn test_find_minimum_level_none() {
500        // Absurdly high bitrate that no level supports
501        let lvl = find_minimum_level(
502            CodecFamily::Av1,
503            Tier::Main,
504            8192,
505            4320,
506            120.0,
507            999_999_999_999,
508        );
509        assert!(lvl.is_none());
510    }
511
512    #[test]
513    fn test_compliance_result_constructors() {
514        let pass = ComplianceResult::pass();
515        assert!(pass.passed);
516        assert!(pass.violations.is_empty());
517
518        let fail = ComplianceResult::fail(vec!["bad".to_string()]);
519        assert!(!fail.passed);
520        assert_eq!(fail.violations.len(), 1);
521    }
522}