Skip to main content

oximedia_transcode/
hw_accel.rs

1//! Hardware acceleration detection and configuration.
2
3use serde::{Deserialize, Serialize};
4
5/// Hardware acceleration types supported.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum HwAccelType {
8    /// No hardware acceleration.
9    None,
10    /// NVIDIA NVENC (CUDA).
11    Nvenc,
12    /// Intel Quick Sync Video.
13    Qsv,
14    /// AMD VCE/VCN.
15    Amd,
16    /// Apple `VideoToolbox`.
17    VideoToolbox,
18    /// Vulkan acceleration.
19    Vulkan,
20    /// Direct3D 11.
21    D3d11,
22    /// VAAPI (Linux).
23    Vaapi,
24    /// VDPAU (Linux, legacy).
25    Vdpau,
26}
27
28/// Hardware encoder information.
29#[derive(Debug, Clone)]
30pub struct HwEncoder {
31    /// Encoder type.
32    pub accel_type: HwAccelType,
33    /// Codec name.
34    pub codec: String,
35    /// Encoder name.
36    pub encoder_name: String,
37    /// Whether the encoder is available.
38    pub available: bool,
39    /// Maximum resolution supported.
40    pub max_resolution: (u32, u32),
41    /// Supported features.
42    pub features: Vec<HwFeature>,
43}
44
45/// Hardware encoder features.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum HwFeature {
48    /// Supports 10-bit encoding.
49    TenBit,
50    /// Supports HDR encoding.
51    Hdr,
52    /// Supports B-frames.
53    BFrames,
54    /// Supports look-ahead.
55    Lookahead,
56    /// Supports temporal AQ.
57    TemporalAq,
58    /// Supports spatial AQ.
59    SpatialAq,
60    /// Supports weighted prediction.
61    WeightedPred,
62    /// Supports custom quantization matrices.
63    CustomQuant,
64}
65
66/// Hardware acceleration configuration.
67#[derive(Debug, Clone)]
68pub struct HwAccelConfig {
69    /// Preferred acceleration type.
70    pub preferred_type: HwAccelType,
71    /// Fallback to software if hardware unavailable.
72    pub allow_fallback: bool,
73    /// Use hardware for decoding.
74    pub decode: bool,
75    /// Use hardware for encoding.
76    pub encode: bool,
77    /// Device ID to use (for multi-GPU systems).
78    pub device_id: Option<u32>,
79}
80
81impl Default for HwAccelConfig {
82    fn default() -> Self {
83        Self {
84            preferred_type: HwAccelType::None,
85            allow_fallback: true,
86            decode: false,
87            encode: false,
88            device_id: None,
89        }
90    }
91}
92
93impl HwAccelConfig {
94    /// Creates a new hardware acceleration config.
95    #[must_use]
96    pub fn new(accel_type: HwAccelType) -> Self {
97        Self {
98            preferred_type: accel_type,
99            allow_fallback: true,
100            decode: true,
101            encode: true,
102            device_id: None,
103        }
104    }
105
106    /// Sets whether to allow fallback to software.
107    #[must_use]
108    pub fn allow_fallback(mut self, allow: bool) -> Self {
109        self.allow_fallback = allow;
110        self
111    }
112
113    /// Sets whether to use hardware for decoding.
114    #[must_use]
115    pub fn decode(mut self, enable: bool) -> Self {
116        self.decode = enable;
117        self
118    }
119
120    /// Sets whether to use hardware for encoding.
121    #[must_use]
122    pub fn encode(mut self, enable: bool) -> Self {
123        self.encode = enable;
124        self
125    }
126
127    /// Sets the device ID.
128    #[must_use]
129    pub fn device_id(mut self, id: u32) -> Self {
130        self.device_id = Some(id);
131        self
132    }
133}
134
135impl HwAccelType {
136    /// Gets the platform name.
137    #[must_use]
138    pub fn platform_name(self) -> &'static str {
139        match self {
140            Self::None => "software",
141            Self::Nvenc => "NVIDIA NVENC",
142            Self::Qsv => "Intel Quick Sync",
143            Self::Amd => "AMD VCE/VCN",
144            Self::VideoToolbox => "Apple VideoToolbox",
145            Self::Vulkan => "Vulkan",
146            Self::D3d11 => "Direct3D 11",
147            Self::Vaapi => "VAAPI",
148            Self::Vdpau => "VDPAU",
149        }
150    }
151
152    /// Checks if this acceleration type is available on the current platform.
153    #[must_use]
154    pub fn is_available(self) -> bool {
155        // Placeholder - would perform actual detection
156        match self {
157            Self::None => true,
158            Self::Nvenc => detect_nvenc(),
159            Self::Qsv => detect_qsv(),
160            Self::Amd => detect_amd(),
161            Self::VideoToolbox => detect_videotoolbox(),
162            Self::Vulkan => detect_vulkan(),
163            Self::D3d11 => detect_d3d11(),
164            Self::Vaapi => detect_vaapi(),
165            Self::Vdpau => detect_vdpau(),
166        }
167    }
168
169    /// Gets supported codecs for this acceleration type.
170    #[must_use]
171    pub fn supported_codecs(self) -> Vec<&'static str> {
172        match self {
173            Self::None => vec!["h264", "vp8", "vp9", "av1", "theora"],
174            Self::Nvenc => vec!["h264", "h265", "av1"],
175            Self::Qsv => vec!["h264", "h265", "vp9", "av1"],
176            Self::Amd => vec!["h264", "h265", "av1"],
177            Self::VideoToolbox => vec!["h264", "h265"],
178            Self::Vulkan => vec!["h264", "h265"],
179            Self::D3d11 => vec!["h264", "h265", "vp9"],
180            Self::Vaapi => vec!["h264", "h265", "vp8", "vp9", "av1"],
181            Self::Vdpau => vec!["h264", "h265"],
182        }
183    }
184
185    /// Gets the encoder name for a given codec.
186    #[must_use]
187    pub fn encoder_name(self, codec: &str) -> Option<String> {
188        match self {
189            Self::None => Some(codec.to_string()),
190            Self::Nvenc => match codec {
191                "h264" => Some("h264_nvenc".to_string()),
192                "h265" => Some("hevc_nvenc".to_string()),
193                "av1" => Some("av1_nvenc".to_string()),
194                _ => None,
195            },
196            Self::Qsv => match codec {
197                "h264" => Some("h264_qsv".to_string()),
198                "h265" => Some("hevc_qsv".to_string()),
199                "vp9" => Some("vp9_qsv".to_string()),
200                "av1" => Some("av1_qsv".to_string()),
201                _ => None,
202            },
203            Self::Amd => match codec {
204                "h264" => Some("h264_amf".to_string()),
205                "h265" => Some("hevc_amf".to_string()),
206                "av1" => Some("av1_amf".to_string()),
207                _ => None,
208            },
209            Self::VideoToolbox => match codec {
210                "h264" => Some("h264_videotoolbox".to_string()),
211                "h265" => Some("hevc_videotoolbox".to_string()),
212                _ => None,
213            },
214            Self::Vulkan => match codec {
215                "h264" => Some("h264_vulkan".to_string()),
216                "h265" => Some("hevc_vulkan".to_string()),
217                _ => None,
218            },
219            Self::D3d11 => match codec {
220                "h264" => Some("h264_d3d11va".to_string()),
221                "h265" => Some("hevc_d3d11va".to_string()),
222                "vp9" => Some("vp9_d3d11va".to_string()),
223                _ => None,
224            },
225            Self::Vaapi => match codec {
226                "h264" => Some("h264_vaapi".to_string()),
227                "h265" => Some("hevc_vaapi".to_string()),
228                "vp8" => Some("vp8_vaapi".to_string()),
229                "vp9" => Some("vp9_vaapi".to_string()),
230                "av1" => Some("av1_vaapi".to_string()),
231                _ => None,
232            },
233            Self::Vdpau => match codec {
234                "h264" => Some("h264_vdpau".to_string()),
235                "h265" => Some("hevc_vdpau".to_string()),
236                _ => None,
237            },
238        }
239    }
240}
241
242/// Detects available hardware acceleration on the system.
243#[must_use]
244pub fn detect_available_hw_accel() -> Vec<HwAccelType> {
245    let mut available = vec![HwAccelType::None];
246
247    for accel_type in &[
248        HwAccelType::Nvenc,
249        HwAccelType::Qsv,
250        HwAccelType::Amd,
251        HwAccelType::VideoToolbox,
252        HwAccelType::Vulkan,
253        HwAccelType::D3d11,
254        HwAccelType::Vaapi,
255        HwAccelType::Vdpau,
256    ] {
257        if accel_type.is_available() {
258            available.push(*accel_type);
259        }
260    }
261
262    available
263}
264
265/// Detects the best hardware acceleration for a given codec.
266#[must_use]
267pub fn detect_best_hw_accel_for_codec(codec: &str) -> Option<HwAccelType> {
268    detect_available_hw_accel()
269        .into_iter()
270        .find(|&accel_type| accel_type.supported_codecs().contains(&codec))
271}
272
273// Platform-specific detection functions
274#[cfg(target_os = "linux")]
275fn detect_nvenc() -> bool {
276    std::path::Path::new("/dev/nvidia0").exists()
277}
278
279#[cfg(not(target_os = "linux"))]
280fn detect_nvenc() -> bool {
281    false // Would use platform-specific detection on Windows
282}
283
284#[cfg(target_os = "linux")]
285fn detect_vaapi() -> bool {
286    std::path::Path::new("/dev/dri/renderD128").exists()
287}
288
289#[cfg(not(target_os = "linux"))]
290fn detect_vaapi() -> bool {
291    false
292}
293
294#[cfg(target_os = "linux")]
295fn detect_vdpau() -> bool {
296    std::path::Path::new("/usr/lib/vdpau").exists()
297}
298
299#[cfg(not(target_os = "linux"))]
300fn detect_vdpau() -> bool {
301    false
302}
303
304#[cfg(target_os = "windows")]
305fn detect_qsv() -> bool {
306    // Would check for Intel GPU
307    false
308}
309
310#[cfg(not(target_os = "windows"))]
311fn detect_qsv() -> bool {
312    false
313}
314
315#[cfg(target_os = "windows")]
316fn detect_amd() -> bool {
317    // Would check for AMD GPU
318    false
319}
320
321#[cfg(not(target_os = "windows"))]
322fn detect_amd() -> bool {
323    false
324}
325
326#[cfg(target_os = "macos")]
327fn detect_videotoolbox() -> bool {
328    true // Available on all modern Macs
329}
330
331#[cfg(not(target_os = "macos"))]
332fn detect_videotoolbox() -> bool {
333    false
334}
335
336#[cfg(target_os = "windows")]
337fn detect_d3d11() -> bool {
338    true // Available on Windows 7+
339}
340
341#[cfg(not(target_os = "windows"))]
342fn detect_d3d11() -> bool {
343    false
344}
345
346fn detect_vulkan() -> bool {
347    // Would check for Vulkan support
348    false
349}
350
351impl HwEncoder {
352    /// Creates a new hardware encoder info.
353    #[must_use]
354    pub fn new(
355        accel_type: HwAccelType,
356        codec: impl Into<String>,
357        encoder_name: impl Into<String>,
358    ) -> Self {
359        Self {
360            accel_type,
361            codec: codec.into(),
362            encoder_name: encoder_name.into(),
363            available: false,
364            max_resolution: (7680, 4320), // 8K
365            features: Vec::new(),
366        }
367    }
368
369    /// Sets whether the encoder is available.
370    #[must_use]
371    pub fn available(mut self, available: bool) -> Self {
372        self.available = available;
373        self
374    }
375
376    /// Sets the maximum resolution.
377    #[must_use]
378    pub fn max_resolution(mut self, width: u32, height: u32) -> Self {
379        self.max_resolution = (width, height);
380        self
381    }
382
383    /// Adds a supported feature.
384    #[must_use]
385    pub fn with_feature(mut self, feature: HwFeature) -> Self {
386        self.features.push(feature);
387        self
388    }
389
390    /// Checks if a feature is supported.
391    #[must_use]
392    pub fn supports_feature(&self, feature: HwFeature) -> bool {
393        self.features.contains(&feature)
394    }
395}
396
397/// Gets information about all available hardware encoders.
398#[must_use]
399pub fn get_available_encoders() -> Vec<HwEncoder> {
400    let mut encoders = Vec::new();
401
402    for accel_type in detect_available_hw_accel() {
403        for codec in accel_type.supported_codecs() {
404            if let Some(encoder_name) = accel_type.encoder_name(codec) {
405                let encoder = HwEncoder::new(accel_type, codec, encoder_name).available(true);
406                encoders.push(encoder);
407            }
408        }
409    }
410
411    encoders
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    #[test]
419    fn test_hw_accel_type_platform_name() {
420        assert_eq!(HwAccelType::Nvenc.platform_name(), "NVIDIA NVENC");
421        assert_eq!(HwAccelType::Qsv.platform_name(), "Intel Quick Sync");
422        assert_eq!(HwAccelType::Vaapi.platform_name(), "VAAPI");
423    }
424
425    #[test]
426    fn test_hw_accel_supported_codecs() {
427        let codecs = HwAccelType::Nvenc.supported_codecs();
428        assert!(codecs.contains(&"h264"));
429        assert!(codecs.contains(&"h265"));
430    }
431
432    #[test]
433    fn test_hw_accel_encoder_name() {
434        assert_eq!(
435            HwAccelType::Nvenc.encoder_name("h264"),
436            Some("h264_nvenc".to_string())
437        );
438        assert_eq!(
439            HwAccelType::Qsv.encoder_name("h265"),
440            Some("hevc_qsv".to_string())
441        );
442    }
443
444    #[test]
445    fn test_hw_accel_config() {
446        let config = HwAccelConfig::new(HwAccelType::Nvenc)
447            .allow_fallback(false)
448            .decode(true)
449            .encode(true)
450            .device_id(0);
451
452        assert_eq!(config.preferred_type, HwAccelType::Nvenc);
453        assert!(!config.allow_fallback);
454        assert!(config.decode);
455        assert!(config.encode);
456        assert_eq!(config.device_id, Some(0));
457    }
458
459    #[test]
460    fn test_detect_available_hw_accel() {
461        let available = detect_available_hw_accel();
462        assert!(available.contains(&HwAccelType::None)); // Always available
463    }
464
465    #[test]
466    fn test_hw_encoder_creation() {
467        let encoder = HwEncoder::new(HwAccelType::Nvenc, "h264", "h264_nvenc")
468            .available(true)
469            .max_resolution(3840, 2160)
470            .with_feature(HwFeature::TenBit)
471            .with_feature(HwFeature::Lookahead);
472
473        assert_eq!(encoder.accel_type, HwAccelType::Nvenc);
474        assert_eq!(encoder.codec, "h264");
475        assert!(encoder.available);
476        assert_eq!(encoder.max_resolution, (3840, 2160));
477        assert!(encoder.supports_feature(HwFeature::TenBit));
478        assert!(encoder.supports_feature(HwFeature::Lookahead));
479        assert!(!encoder.supports_feature(HwFeature::Hdr));
480    }
481}