Skip to main content

oximedia_codec/
features.rs

1//! Codec capability and feature detection.
2//!
3//! [`CodecFeatures`] provides a runtime query interface for the capabilities
4//! of a named codec (e.g. `"av1"`, `"vp9"`, `"opus"`).  The information is
5//! based on a static capability table and can be used by higher-level pipeline
6//! code to select the correct encoder/decoder configuration.
7
8#![allow(dead_code)]
9
10// ---------------------------------------------------------------------------
11// Codec identifier
12// ---------------------------------------------------------------------------
13
14/// A codec name as a borrowed or owned string.
15pub type CodecId = String;
16
17// ---------------------------------------------------------------------------
18// Codec feature flags
19// ---------------------------------------------------------------------------
20
21/// Capability flags for a specific codec.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct CodecFeatures {
24    /// Codec identifier (e.g. `"av1"`, `"vp9"`, `"opus"`).
25    pub codec: CodecId,
26    /// Whether the codec supports HDR content (PQ/HLG transfer functions).
27    pub hdr: bool,
28    /// Whether the codec supports 10-bit (or deeper) bit depth.
29    pub ten_bit: bool,
30    /// Whether the codec supports B-frames (bi-directionally predicted frames).
31    pub bframe: bool,
32    /// Whether the codec supports lossless encoding.
33    pub lossless: bool,
34    /// Whether the codec is an audio codec (as opposed to video or image).
35    pub audio: bool,
36    /// Whether the codec supports scalable video coding (SVC / temporal layers).
37    pub svc: bool,
38    /// Maximum bit depth supported (e.g. 8, 10, 12).
39    pub max_bit_depth: u8,
40}
41
42impl CodecFeatures {
43    /// Create a [`CodecFeatures`] record for a named codec.
44    ///
45    /// Codec names are matched case-insensitively.  Unknown codecs return a
46    /// conservative feature set (no HDR, no 10-bit, no B-frames).
47    ///
48    /// # Parameters
49    /// - `codec` – codec identifier string (e.g. `"av1"`, `"vp9"`, `"h264"`).
50    #[must_use]
51    pub fn new(codec: &str) -> Self {
52        let id = codec.to_lowercase();
53        match id.as_str() {
54            "av1" => Self {
55                codec: id,
56                hdr: true,
57                ten_bit: true,
58                bframe: true,
59                lossless: true,
60                audio: false,
61                svc: true,
62                max_bit_depth: 12,
63            },
64            "vp9" => Self {
65                codec: id,
66                hdr: true,
67                ten_bit: true,
68                bframe: false,
69                lossless: true,
70                audio: false,
71                svc: true,
72                max_bit_depth: 12,
73            },
74            "vp8" => Self {
75                codec: id,
76                hdr: false,
77                ten_bit: false,
78                bframe: false,
79                lossless: false,
80                audio: false,
81                svc: false,
82                max_bit_depth: 8,
83            },
84            "theora" => Self {
85                codec: id,
86                hdr: false,
87                ten_bit: false,
88                bframe: false,
89                lossless: false,
90                audio: false,
91                svc: false,
92                max_bit_depth: 8,
93            },
94            "h264" | "avc" => Self {
95                codec: id,
96                hdr: false,
97                ten_bit: true,
98                bframe: true,
99                lossless: false,
100                audio: false,
101                svc: true,
102                max_bit_depth: 10,
103            },
104            "h265" | "hevc" => Self {
105                codec: id,
106                hdr: true,
107                ten_bit: true,
108                bframe: true,
109                lossless: false,
110                audio: false,
111                svc: true,
112                max_bit_depth: 12,
113            },
114            "opus" => Self {
115                codec: id,
116                hdr: false,
117                ten_bit: false,
118                bframe: false,
119                lossless: false,
120                audio: true,
121                svc: false,
122                max_bit_depth: 16,
123            },
124            "vorbis" => Self {
125                codec: id,
126                hdr: false,
127                ten_bit: false,
128                bframe: false,
129                lossless: false,
130                audio: true,
131                svc: false,
132                max_bit_depth: 16,
133            },
134            "flac" => Self {
135                codec: id,
136                hdr: false,
137                ten_bit: false,
138                bframe: false,
139                lossless: true,
140                audio: true,
141                svc: false,
142                max_bit_depth: 24,
143            },
144            "ffv1" => Self {
145                codec: id,
146                hdr: true,
147                ten_bit: true,
148                bframe: false,
149                lossless: true,
150                audio: false,
151                svc: false,
152                max_bit_depth: 16,
153            },
154            "jpegxl" | "jxl" => Self {
155                codec: id,
156                hdr: true,
157                ten_bit: true,
158                bframe: false,
159                lossless: true,
160                audio: false,
161                svc: false,
162                max_bit_depth: 16,
163            },
164            _ => Self {
165                codec: id,
166                hdr: false,
167                ten_bit: false,
168                bframe: false,
169                lossless: false,
170                audio: false,
171                svc: false,
172                max_bit_depth: 8,
173            },
174        }
175    }
176
177    /// Returns `true` if the codec supports HDR content.
178    #[must_use]
179    pub fn supports_hdr(&self) -> bool {
180        self.hdr
181    }
182
183    /// Returns `true` if the codec supports 10-bit (or deeper) bit depth.
184    #[must_use]
185    pub fn supports_10bit(&self) -> bool {
186        self.ten_bit
187    }
188
189    /// Returns `true` if the codec supports B-frames.
190    #[must_use]
191    pub fn supports_bframe(&self) -> bool {
192        self.bframe
193    }
194
195    /// Returns `true` if the codec supports lossless encoding.
196    #[must_use]
197    pub fn supports_lossless(&self) -> bool {
198        self.lossless
199    }
200
201    /// Returns `true` if this is an audio codec.
202    #[must_use]
203    pub fn is_audio(&self) -> bool {
204        self.audio
205    }
206
207    /// Returns `true` if the codec supports scalable video coding.
208    #[must_use]
209    pub fn supports_svc(&self) -> bool {
210        self.svc
211    }
212
213    /// Returns the maximum bit depth supported by this codec.
214    #[must_use]
215    pub fn max_bit_depth(&self) -> u8 {
216        self.max_bit_depth
217    }
218}
219
220// ---------------------------------------------------------------------------
221// Tests
222// ---------------------------------------------------------------------------
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn av1_features_correct() {
230        let f = CodecFeatures::new("av1");
231        assert!(f.supports_hdr());
232        assert!(f.supports_10bit());
233        assert!(f.supports_bframe());
234        assert!(f.supports_lossless());
235        assert!(!f.is_audio());
236        assert_eq!(f.max_bit_depth(), 12);
237    }
238
239    #[test]
240    fn vp9_no_bframe() {
241        let f = CodecFeatures::new("vp9");
242        assert!(!f.supports_bframe());
243        assert!(f.supports_10bit());
244    }
245
246    #[test]
247    fn vp8_basic_only() {
248        let f = CodecFeatures::new("vp8");
249        assert!(!f.supports_hdr());
250        assert!(!f.supports_10bit());
251        assert!(!f.supports_bframe());
252        assert_eq!(f.max_bit_depth(), 8);
253    }
254
255    #[test]
256    fn opus_is_audio() {
257        let f = CodecFeatures::new("opus");
258        assert!(f.is_audio());
259        assert!(!f.supports_bframe());
260    }
261
262    #[test]
263    fn flac_lossless_audio() {
264        let f = CodecFeatures::new("flac");
265        assert!(f.supports_lossless());
266        assert!(f.is_audio());
267        assert_eq!(f.max_bit_depth(), 24);
268    }
269
270    #[test]
271    fn ffv1_lossless_video_hdr() {
272        let f = CodecFeatures::new("ffv1");
273        assert!(f.supports_hdr());
274        assert!(f.supports_lossless());
275        assert!(!f.is_audio());
276    }
277
278    #[test]
279    fn case_insensitive_lookup() {
280        let lower = CodecFeatures::new("AV1");
281        let upper = CodecFeatures::new("av1");
282        assert_eq!(lower.supports_hdr(), upper.supports_hdr());
283    }
284
285    #[test]
286    fn unknown_codec_conservative_defaults() {
287        let f = CodecFeatures::new("my_unknown_codec");
288        assert!(!f.supports_hdr());
289        assert!(!f.supports_10bit());
290        assert!(!f.supports_bframe());
291        assert_eq!(f.max_bit_depth(), 8);
292    }
293
294    #[test]
295    fn h265_hdr_and_bframe() {
296        let f = CodecFeatures::new("h265");
297        assert!(f.supports_hdr());
298        assert!(f.supports_bframe());
299    }
300}