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}