Skip to main content

ff_encode/shared/
hardware.rs

1//! Hardware encoder definitions.
2
3use std::sync::OnceLock;
4
5/// Hardware encoder type.
6///
7/// Specifies which hardware acceleration to use for encoding.
8/// Hardware encoding is generally faster and more power-efficient than software encoding,
9/// but may have slightly lower quality at the same bitrate.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11#[non_exhaustive]
12pub enum HardwareEncoder {
13    /// Auto-detect available hardware encoder
14    #[default]
15    Auto,
16
17    /// Software encoding only (no hardware acceleration)
18    None,
19
20    /// NVIDIA NVENC
21    Nvenc,
22
23    /// Intel Quick Sync Video
24    Qsv,
25
26    /// AMD Advanced Media Framework (AMF, formerly VCE)
27    Amf,
28
29    /// Apple `VideoToolbox`
30    VideoToolbox,
31
32    /// VA-API (Linux)
33    Vaapi,
34}
35
36impl HardwareEncoder {
37    /// Get the list of concrete hardware encoder backends available on this system.
38    ///
39    /// Returns only actual hardware backends (NVENC, QSV, AMF, `VideoToolbox`,
40    /// VA-API). The control variants [`Auto`](Self::Auto) and [`None`](Self::None)
41    /// are intentionally excluded — they are configuration options, not hardware
42    /// backends.
43    ///
44    /// The result is cached on first call for performance.
45    ///
46    /// # Examples
47    ///
48    /// ```no_run
49    /// use ff_encode::HardwareEncoder;
50    ///
51    /// let backends = HardwareEncoder::available();
52    /// if backends.is_empty() {
53    ///     println!("No hardware encoders detected");
54    /// } else {
55    ///     for hw in backends {
56    ///         println!("Available: {:?}", hw);
57    ///     }
58    /// }
59    /// ```
60    #[must_use]
61    pub fn available() -> &'static [Self] {
62        static AVAILABLE: OnceLock<Vec<HardwareEncoder>> = OnceLock::new();
63
64        AVAILABLE.get_or_init(|| {
65            let mut result = Vec::new();
66
67            // Check each concrete hardware encoder type.
68            // Auto and None are control variants, not hardware backends — excluded.
69            if Self::Nvenc.is_available() {
70                result.push(Self::Nvenc);
71            }
72            if Self::Qsv.is_available() {
73                result.push(Self::Qsv);
74            }
75            if Self::Amf.is_available() {
76                result.push(Self::Amf);
77            }
78            if Self::VideoToolbox.is_available() {
79                result.push(Self::VideoToolbox);
80            }
81            if Self::Vaapi.is_available() {
82                result.push(Self::Vaapi);
83            }
84
85            result
86        })
87    }
88
89    /// Check if this hardware encoder is available.
90    ///
91    /// Queries `FFmpeg` to determine if the hardware encoder is available
92    /// on the current system. This checks for both H.264 and H.265 support.
93    ///
94    /// # Examples
95    ///
96    /// ```no_run
97    /// use ff_encode::HardwareEncoder;
98    ///
99    /// if HardwareEncoder::Nvenc.is_available() {
100    ///     println!("NVENC is available on this system");
101    /// }
102    /// ```
103    #[must_use]
104    pub fn is_available(self) -> bool {
105        match self {
106            // Auto and None are always available
107            Self::Auto | Self::None => true,
108
109            // Check hardware encoder availability
110            Self::Nvenc => is_encoder_available("h264_nvenc") || is_encoder_available("hevc_nvenc"),
111            Self::Qsv => is_encoder_available("h264_qsv") || is_encoder_available("hevc_qsv"),
112            Self::Amf => is_encoder_available("h264_amf") || is_encoder_available("hevc_amf"),
113            Self::VideoToolbox => {
114                is_encoder_available("h264_videotoolbox")
115                    || is_encoder_available("hevc_videotoolbox")
116            }
117            Self::Vaapi => is_encoder_available("h264_vaapi") || is_encoder_available("hevc_vaapi"),
118        }
119    }
120}
121
122/// Helper function to check if an encoder is available.
123///
124/// # Arguments
125///
126/// * `name` - The encoder name to check (e.g., "`h264_nvenc`", "`hevc_qsv`")
127///
128/// # Returns
129///
130/// Returns `true` if the encoder is available, `false` otherwise.
131fn is_encoder_available(name: &str) -> bool {
132    ff_sys::ensure_initialized();
133    ff_sys::Codec::find_encoder_by_name(name).is_some()
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_default_hardware_encoder() {
142        assert_eq!(HardwareEncoder::default(), HardwareEncoder::Auto);
143    }
144
145    #[test]
146    fn test_auto_and_none_always_available() {
147        // Auto and None should always be available
148        assert!(HardwareEncoder::Auto.is_available());
149        assert!(HardwareEncoder::None.is_available());
150    }
151
152    #[test]
153    fn available_should_contain_only_hardware_backends() {
154        let available = HardwareEncoder::available();
155        assert!(
156            !available.contains(&HardwareEncoder::Auto),
157            "Auto is a control variant, not a hardware backend"
158        );
159        assert!(
160            !available.contains(&HardwareEncoder::None),
161            "None is a control variant, not a hardware backend"
162        );
163        // May be empty on systems with no hardware encoders — that is correct.
164    }
165
166    #[test]
167    fn test_hardware_encoder_availability() {
168        // This test just checks that the functions don't panic
169        // Actual availability depends on system hardware
170        let _nvenc = HardwareEncoder::Nvenc.is_available();
171        let _qsv = HardwareEncoder::Qsv.is_available();
172        let _amf = HardwareEncoder::Amf.is_available();
173        let _videotoolbox = HardwareEncoder::VideoToolbox.is_available();
174        let _vaapi = HardwareEncoder::Vaapi.is_available();
175    }
176}