Skip to main content

ff_encode/
hardware.rs

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