1#![allow(dead_code)]
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
10pub enum CodecLevel {
11 Level30,
13 Level31,
15 Level40,
17 Level41,
19 Level50,
21 Level51,
23 Level52,
25}
26
27impl CodecLevel {
28 #[allow(clippy::cast_precision_loss)]
30 #[must_use]
31 pub fn as_f32(self) -> f32 {
32 match self {
33 CodecLevel::Level30 => 3.0,
34 CodecLevel::Level31 => 3.1,
35 CodecLevel::Level40 => 4.0,
36 CodecLevel::Level41 => 4.1,
37 CodecLevel::Level50 => 5.0,
38 CodecLevel::Level51 => 5.1,
39 CodecLevel::Level52 => 5.2,
40 }
41 }
42
43 #[must_use]
45 pub fn max_pixels(self) -> u64 {
46 match self {
47 CodecLevel::Level30 => 1_280 * 720,
48 CodecLevel::Level31 => 1_280 * 720,
49 CodecLevel::Level40 => 1_920 * 1_080,
50 CodecLevel::Level41 => 1_920 * 1_080,
51 CodecLevel::Level50 => 3_840 * 2_160,
52 CodecLevel::Level51 => 3_840 * 2_160,
53 CodecLevel::Level52 => 7_680 * 4_320,
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct CodecProfile {
61 pub codec: String,
63 pub level: CodecLevel,
65 max_bitrate_mbps_val: f64,
67 hw_encodable: bool,
69 supports_10bit: bool,
71}
72
73impl CodecProfile {
74 pub fn new(
76 codec: impl Into<String>,
77 level: CodecLevel,
78 max_bitrate_mbps: f64,
79 hw_encodable: bool,
80 ) -> Self {
81 Self {
82 codec: codec.into(),
83 level,
84 max_bitrate_mbps_val: max_bitrate_mbps,
85 hw_encodable,
86 supports_10bit: false,
87 }
88 }
89
90 #[must_use]
92 pub fn with_10bit(mut self) -> Self {
93 self.supports_10bit = true;
94 self
95 }
96
97 #[must_use]
99 pub fn max_bitrate_mbps(&self) -> f64 {
100 self.max_bitrate_mbps_val
101 }
102
103 #[must_use]
105 pub fn is_hardware_encodable(&self) -> bool {
106 self.hw_encodable
107 }
108
109 #[must_use]
111 pub fn supports_10bit(&self) -> bool {
112 self.supports_10bit
113 }
114
115 #[must_use]
117 pub fn max_pixels(&self) -> u64 {
118 self.level.max_pixels()
119 }
120
121 #[must_use]
123 pub fn supports_resolution(&self, width: u32, height: u32) -> bool {
124 let pixels = u64::from(width) * u64::from(height);
125 pixels <= self.level.max_pixels()
126 }
127}
128
129#[derive(Debug, Default)]
131pub struct CodecProfileSelector {
132 profiles: Vec<CodecProfile>,
133}
134
135impl CodecProfileSelector {
136 #[must_use]
138 pub fn new() -> Self {
139 Self::default()
140 }
141
142 #[must_use]
144 pub fn with_h264_defaults() -> Self {
145 let mut s = Self::new();
146 s.add(CodecProfile::new("h264", CodecLevel::Level31, 10.0, true));
147 s.add(CodecProfile::new("h264", CodecLevel::Level41, 50.0, true));
148 s.add(CodecProfile::new("h264", CodecLevel::Level51, 240.0, true));
149 s
150 }
151
152 #[must_use]
154 pub fn with_hevc_defaults() -> Self {
155 let mut s = Self::new();
156 s.add(CodecProfile::new("hevc", CodecLevel::Level41, 40.0, true).with_10bit());
157 s.add(CodecProfile::new("hevc", CodecLevel::Level51, 160.0, true).with_10bit());
158 s.add(CodecProfile::new("hevc", CodecLevel::Level52, 640.0, false).with_10bit());
159 s
160 }
161
162 pub fn add(&mut self, profile: CodecProfile) {
164 self.profiles.push(profile);
165 }
166
167 #[must_use]
170 pub fn select_for_resolution(&self, width: u32, height: u32) -> Option<&CodecProfile> {
171 self.profiles
172 .iter()
173 .filter(|p| p.supports_resolution(width, height))
174 .min_by(|a, b| a.level.cmp(&b.level))
175 }
176
177 #[must_use]
179 pub fn all_for_resolution(&self, width: u32, height: u32) -> Vec<&CodecProfile> {
180 self.profiles
181 .iter()
182 .filter(|p| p.supports_resolution(width, height))
183 .collect()
184 }
185
186 #[must_use]
188 pub fn profile_count(&self) -> usize {
189 self.profiles.len()
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_codec_level_ordering() {
199 assert!(CodecLevel::Level30 < CodecLevel::Level31);
200 assert!(CodecLevel::Level41 < CodecLevel::Level50);
201 assert!(CodecLevel::Level51 < CodecLevel::Level52);
202 }
203
204 #[test]
205 fn test_codec_level_as_f32() {
206 let v = CodecLevel::Level41.as_f32();
207 assert!((v - 4.1_f32).abs() < 0.001);
208 }
209
210 #[test]
211 fn test_codec_level_max_pixels_1080p() {
212 assert_eq!(CodecLevel::Level41.max_pixels(), 1_920 * 1_080);
213 }
214
215 #[test]
216 fn test_codec_level_max_pixels_4k() {
217 assert_eq!(CodecLevel::Level51.max_pixels(), 3_840 * 2_160);
218 }
219
220 #[test]
221 fn test_profile_max_bitrate() {
222 let p = CodecProfile::new("h264", CodecLevel::Level41, 50.0, true);
223 assert!((p.max_bitrate_mbps() - 50.0).abs() < f64::EPSILON);
224 }
225
226 #[test]
227 fn test_profile_hw_encodable() {
228 let p = CodecProfile::new("h264", CodecLevel::Level41, 50.0, true);
229 assert!(p.is_hardware_encodable());
230 let p2 = CodecProfile::new("av1", CodecLevel::Level51, 100.0, false);
231 assert!(!p2.is_hardware_encodable());
232 }
233
234 #[test]
235 fn test_profile_10bit_default_false() {
236 let p = CodecProfile::new("h264", CodecLevel::Level41, 50.0, true);
237 assert!(!p.supports_10bit());
238 }
239
240 #[test]
241 fn test_profile_with_10bit() {
242 let p = CodecProfile::new("hevc", CodecLevel::Level51, 160.0, true).with_10bit();
243 assert!(p.supports_10bit());
244 }
245
246 #[test]
247 fn test_profile_supports_resolution_1080p() {
248 let p = CodecProfile::new("h264", CodecLevel::Level41, 50.0, true);
249 assert!(p.supports_resolution(1920, 1080));
250 assert!(!p.supports_resolution(3840, 2160));
251 }
252
253 #[test]
254 fn test_selector_h264_defaults_count() {
255 let sel = CodecProfileSelector::with_h264_defaults();
256 assert_eq!(sel.profile_count(), 3);
257 }
258
259 #[test]
260 fn test_selector_select_for_1080p_returns_lowest_level() {
261 let sel = CodecProfileSelector::with_h264_defaults();
262 let p = sel
263 .select_for_resolution(1920, 1080)
264 .expect("should succeed in test");
265 assert_eq!(p.level, CodecLevel::Level41);
267 }
268
269 #[test]
270 fn test_selector_select_for_720p() {
271 let sel = CodecProfileSelector::with_h264_defaults();
272 let p = sel
273 .select_for_resolution(1280, 720)
274 .expect("should succeed in test");
275 assert_eq!(p.level, CodecLevel::Level31);
276 }
277
278 #[test]
279 fn test_selector_select_for_8k_returns_none_h264() {
280 let sel = CodecProfileSelector::with_h264_defaults();
281 assert!(sel.select_for_resolution(7680, 4320).is_none());
283 }
284
285 #[test]
286 fn test_selector_hevc_supports_4k() {
287 let sel = CodecProfileSelector::with_hevc_defaults();
288 let p = sel
289 .select_for_resolution(3840, 2160)
290 .expect("should succeed in test");
291 assert_eq!(p.codec, "hevc");
292 assert!(p.supports_10bit());
293 }
294}