Skip to main content

oximedia_codec/
codec_caps.rs

1//! Codec capability discovery and hardware acceleration detection.
2//!
3//! Provides a stub-based capability model for querying which hardware
4//! accelerators are available on the current platform, and a registry
5//! for looking up codec capabilities by codec identifier.
6
7#![allow(dead_code)]
8
9/// Hardware acceleration backend type.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum HwAccelType {
12    /// Software (no hardware acceleration).
13    None,
14    /// NVIDIA CUDA / NVENC / NVDEC.
15    Cuda,
16    /// Intel Quick Sync Video.
17    Qsv,
18    /// VA-API (Linux open-source GPU acceleration).
19    Vaapi,
20    /// Apple VideoToolbox.
21    VideoToolbox,
22    /// AMD AMF (Advanced Media Framework).
23    Amf,
24}
25
26impl HwAccelType {
27    /// Returns a stub availability check.
28    ///
29    /// In production this would probe the system; here it always returns `false`
30    /// for `None` and `true` for every named accelerator (for testability).
31    pub fn is_available_stub(self) -> bool {
32        !matches!(self, HwAccelType::None)
33    }
34
35    /// Human-readable name of the accelerator.
36    pub fn name(self) -> &'static str {
37        match self {
38            HwAccelType::None => "Software",
39            HwAccelType::Cuda => "CUDA",
40            HwAccelType::Qsv => "Intel QSV",
41            HwAccelType::Vaapi => "VA-API",
42            HwAccelType::VideoToolbox => "VideoToolbox",
43            HwAccelType::Amf => "AMD AMF",
44        }
45    }
46}
47
48/// Capabilities record for a single codec.
49#[derive(Debug, Clone)]
50pub struct CodecCaps {
51    /// Codec identifier string (e.g. `"h264"`, `"av1"`).
52    pub codec_id: String,
53    /// Maximum supported width in pixels.
54    pub max_width: u32,
55    /// Maximum supported height in pixels.
56    pub max_height: u32,
57    /// Supported hardware accelerators.
58    pub hw_accels: Vec<HwAccelType>,
59    /// Whether the codec supports B-frames.
60    pub b_frames: bool,
61    /// Whether the codec supports lossless mode.
62    pub lossless: bool,
63}
64
65impl CodecCaps {
66    /// Create a new capability record.
67    pub fn new(codec_id: impl Into<String>) -> Self {
68        Self {
69            codec_id: codec_id.into(),
70            max_width: 7680,
71            max_height: 4320,
72            hw_accels: Vec::new(),
73            b_frames: false,
74            lossless: false,
75        }
76    }
77
78    /// Builder: set maximum resolution.
79    pub fn with_max_resolution(mut self, w: u32, h: u32) -> Self {
80        self.max_width = w;
81        self.max_height = h;
82        self
83    }
84
85    /// Builder: add a hardware accelerator.
86    pub fn with_hw_accel(mut self, accel: HwAccelType) -> Self {
87        self.hw_accels.push(accel);
88        self
89    }
90
91    /// Builder: enable B-frame support.
92    pub fn with_b_frames(mut self) -> Self {
93        self.b_frames = true;
94        self
95    }
96
97    /// Builder: enable lossless mode.
98    pub fn with_lossless(mut self) -> Self {
99        self.lossless = true;
100        self
101    }
102
103    /// Returns `true` if this codec supports the given hardware accelerator.
104    pub fn supports_hw_accel(&self, accel: HwAccelType) -> bool {
105        self.hw_accels.contains(&accel)
106    }
107
108    /// Returns `true` if the codec can encode frames of the given dimensions.
109    pub fn supports_resolution(&self, width: u32, height: u32) -> bool {
110        width <= self.max_width && height <= self.max_height
111    }
112}
113
114/// A registry of [`CodecCaps`] keyed by codec identifier string.
115#[derive(Debug, Default)]
116pub struct CodecCapsRegistry {
117    entries: Vec<CodecCaps>,
118}
119
120impl CodecCapsRegistry {
121    /// Create an empty registry.
122    pub fn new() -> Self {
123        Self {
124            entries: Vec::new(),
125        }
126    }
127
128    /// Register a codec's capabilities.
129    ///
130    /// If an entry with the same `codec_id` already exists it is replaced.
131    pub fn register(&mut self, caps: CodecCaps) {
132        if let Some(existing) = self
133            .entries
134            .iter_mut()
135            .find(|c| c.codec_id == caps.codec_id)
136        {
137            *existing = caps;
138        } else {
139            self.entries.push(caps);
140        }
141    }
142
143    /// Find the capabilities for a codec by its identifier.
144    pub fn find(&self, codec_id: &str) -> Option<&CodecCaps> {
145        self.entries.iter().find(|c| c.codec_id == codec_id)
146    }
147
148    /// Returns the number of registered codecs.
149    pub fn len(&self) -> usize {
150        self.entries.len()
151    }
152
153    /// Returns `true` if the registry contains no entries.
154    pub fn is_empty(&self) -> bool {
155        self.entries.is_empty()
156    }
157
158    /// Returns all registered codec IDs.
159    pub fn codec_ids(&self) -> Vec<&str> {
160        self.entries.iter().map(|c| c.codec_id.as_str()).collect()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_hw_accel_none_not_available() {
170        assert!(!HwAccelType::None.is_available_stub());
171    }
172
173    #[test]
174    fn test_hw_accel_cuda_available() {
175        assert!(HwAccelType::Cuda.is_available_stub());
176    }
177
178    #[test]
179    fn test_hw_accel_vaapi_available() {
180        assert!(HwAccelType::Vaapi.is_available_stub());
181    }
182
183    #[test]
184    fn test_hw_accel_name() {
185        assert_eq!(HwAccelType::Cuda.name(), "CUDA");
186        assert_eq!(HwAccelType::Qsv.name(), "Intel QSV");
187        assert_eq!(HwAccelType::VideoToolbox.name(), "VideoToolbox");
188    }
189
190    #[test]
191    fn test_codec_caps_default_max_resolution() {
192        let caps = CodecCaps::new("av1");
193        assert_eq!(caps.max_width, 7680);
194        assert_eq!(caps.max_height, 4320);
195    }
196
197    #[test]
198    fn test_codec_caps_supports_hw_accel_true() {
199        let caps = CodecCaps::new("h264").with_hw_accel(HwAccelType::Cuda);
200        assert!(caps.supports_hw_accel(HwAccelType::Cuda));
201    }
202
203    #[test]
204    fn test_codec_caps_supports_hw_accel_false() {
205        let caps = CodecCaps::new("av1");
206        assert!(!caps.supports_hw_accel(HwAccelType::Cuda));
207    }
208
209    #[test]
210    fn test_codec_caps_supports_resolution_within() {
211        let caps = CodecCaps::new("vp9").with_max_resolution(3840, 2160);
212        assert!(caps.supports_resolution(1920, 1080));
213    }
214
215    #[test]
216    fn test_codec_caps_supports_resolution_too_large() {
217        let caps = CodecCaps::new("vp9").with_max_resolution(3840, 2160);
218        assert!(!caps.supports_resolution(7680, 4320));
219    }
220
221    #[test]
222    fn test_codec_caps_lossless_flag() {
223        let caps = CodecCaps::new("av1").with_lossless();
224        assert!(caps.lossless);
225    }
226
227    #[test]
228    fn test_registry_register_and_find() {
229        let mut reg = CodecCapsRegistry::new();
230        reg.register(CodecCaps::new("av1"));
231        assert!(reg.find("av1").is_some());
232    }
233
234    #[test]
235    fn test_registry_find_missing() {
236        let reg = CodecCapsRegistry::new();
237        assert!(reg.find("h264").is_none());
238    }
239
240    #[test]
241    fn test_registry_replaces_existing() {
242        let mut reg = CodecCapsRegistry::new();
243        reg.register(CodecCaps::new("av1").with_max_resolution(1920, 1080));
244        reg.register(CodecCaps::new("av1").with_max_resolution(3840, 2160));
245        let caps = reg.find("av1").expect("should succeed");
246        assert_eq!(caps.max_width, 3840);
247    }
248
249    #[test]
250    fn test_registry_len_and_is_empty() {
251        let mut reg = CodecCapsRegistry::new();
252        assert!(reg.is_empty());
253        reg.register(CodecCaps::new("av1"));
254        assert_eq!(reg.len(), 1);
255        assert!(!reg.is_empty());
256    }
257
258    #[test]
259    fn test_registry_codec_ids() {
260        let mut reg = CodecCapsRegistry::new();
261        reg.register(CodecCaps::new("av1"));
262        reg.register(CodecCaps::new("vp9"));
263        let ids = reg.codec_ids();
264        assert!(ids.contains(&"av1"));
265        assert!(ids.contains(&"vp9"));
266    }
267}