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 available hardware encoders.
39    ///
40    /// Queries FFmpeg for available hardware encoders on the system.
41    /// This is useful for UI to show which hardware acceleration options
42    /// the user can select.
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 available = HardwareEncoder::available();
52    /// for hw in available {
53    ///     println!("Available: {:?}", hw);
54    /// }
55    /// ```
56    #[must_use]
57    pub fn available() -> &'static [Self] {
58        static AVAILABLE: OnceLock<Vec<HardwareEncoder>> = OnceLock::new();
59
60        AVAILABLE.get_or_init(|| {
61            let mut result = Vec::new();
62
63            // Auto and None are always available
64            result.push(Self::Auto);
65            result.push(Self::None);
66
67            // Check each hardware encoder type
68            if Self::Nvenc.is_available() {
69                result.push(Self::Nvenc);
70            }
71            if Self::Qsv.is_available() {
72                result.push(Self::Qsv);
73            }
74            if Self::Amf.is_available() {
75                result.push(Self::Amf);
76            }
77            if Self::VideoToolbox.is_available() {
78                result.push(Self::VideoToolbox);
79            }
80            if Self::Vaapi.is_available() {
81                result.push(Self::Vaapi);
82            }
83
84            result
85        })
86    }
87
88    /// Check if this hardware encoder is available.
89    ///
90    /// Queries FFmpeg to determine if the hardware encoder is available
91    /// on the current system. This checks for both H.264 and H.265 support.
92    ///
93    /// # Examples
94    ///
95    /// ```no_run
96    /// use ff_encode::HardwareEncoder;
97    ///
98    /// if HardwareEncoder::Nvenc.is_available() {
99    ///     println!("NVENC is available on this system");
100    /// }
101    /// ```
102    #[must_use]
103    pub fn is_available(self) -> bool {
104        match self {
105            // Auto and None are always available
106            Self::Auto | Self::None => true,
107
108            // Check hardware encoder availability
109            Self::Nvenc => is_encoder_available("h264_nvenc") || is_encoder_available("hevc_nvenc"),
110            Self::Qsv => is_encoder_available("h264_qsv") || is_encoder_available("hevc_qsv"),
111            Self::Amf => is_encoder_available("h264_amf") || is_encoder_available("hevc_amf"),
112            Self::VideoToolbox => {
113                is_encoder_available("h264_videotoolbox")
114                    || is_encoder_available("hevc_videotoolbox")
115            }
116            Self::Vaapi => is_encoder_available("h264_vaapi") || is_encoder_available("hevc_vaapi"),
117        }
118    }
119}
120
121/// Helper function to check if an encoder is available.
122///
123/// # Arguments
124///
125/// * `name` - The encoder name to check (e.g., "h264_nvenc", "hevc_qsv")
126///
127/// # Returns
128///
129/// Returns `true` if the encoder is available, `false` otherwise.
130fn is_encoder_available(name: &str) -> bool {
131    unsafe {
132        ff_sys::ensure_initialized();
133
134        let Ok(c_name) = CString::new(name) else {
135            return false;
136        };
137
138        ff_sys::avcodec::find_encoder_by_name(c_name.as_ptr()).is_some()
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_default_hardware_encoder() {
148        assert_eq!(HardwareEncoder::default(), HardwareEncoder::Auto);
149    }
150
151    #[test]
152    fn test_auto_and_none_always_available() {
153        // Auto and None should always be available
154        assert!(HardwareEncoder::Auto.is_available());
155        assert!(HardwareEncoder::None.is_available());
156    }
157
158    #[test]
159    fn test_available_returns_at_least_auto_and_none() {
160        let available = HardwareEncoder::available();
161        assert!(available.contains(&HardwareEncoder::Auto));
162        assert!(available.contains(&HardwareEncoder::None));
163        assert!(available.len() >= 2);
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}