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}