1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum HwAccelType {
8 None,
10 Nvenc,
12 Qsv,
14 Amd,
16 VideoToolbox,
18 Vulkan,
20 D3d11,
22 Vaapi,
24 Vdpau,
26}
27
28#[derive(Debug, Clone)]
30pub struct HwEncoder {
31 pub accel_type: HwAccelType,
33 pub codec: String,
35 pub encoder_name: String,
37 pub available: bool,
39 pub max_resolution: (u32, u32),
41 pub features: Vec<HwFeature>,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum HwFeature {
48 TenBit,
50 Hdr,
52 BFrames,
54 Lookahead,
56 TemporalAq,
58 SpatialAq,
60 WeightedPred,
62 CustomQuant,
64}
65
66#[derive(Debug, Clone)]
68pub struct HwAccelConfig {
69 pub preferred_type: HwAccelType,
71 pub allow_fallback: bool,
73 pub decode: bool,
75 pub encode: bool,
77 pub device_id: Option<u32>,
79}
80
81impl Default for HwAccelConfig {
82 fn default() -> Self {
83 Self {
84 preferred_type: HwAccelType::None,
85 allow_fallback: true,
86 decode: false,
87 encode: false,
88 device_id: None,
89 }
90 }
91}
92
93impl HwAccelConfig {
94 #[must_use]
96 pub fn new(accel_type: HwAccelType) -> Self {
97 Self {
98 preferred_type: accel_type,
99 allow_fallback: true,
100 decode: true,
101 encode: true,
102 device_id: None,
103 }
104 }
105
106 #[must_use]
108 pub fn allow_fallback(mut self, allow: bool) -> Self {
109 self.allow_fallback = allow;
110 self
111 }
112
113 #[must_use]
115 pub fn decode(mut self, enable: bool) -> Self {
116 self.decode = enable;
117 self
118 }
119
120 #[must_use]
122 pub fn encode(mut self, enable: bool) -> Self {
123 self.encode = enable;
124 self
125 }
126
127 #[must_use]
129 pub fn device_id(mut self, id: u32) -> Self {
130 self.device_id = Some(id);
131 self
132 }
133}
134
135impl HwAccelType {
136 #[must_use]
138 pub fn platform_name(self) -> &'static str {
139 match self {
140 Self::None => "software",
141 Self::Nvenc => "NVIDIA NVENC",
142 Self::Qsv => "Intel Quick Sync",
143 Self::Amd => "AMD VCE/VCN",
144 Self::VideoToolbox => "Apple VideoToolbox",
145 Self::Vulkan => "Vulkan",
146 Self::D3d11 => "Direct3D 11",
147 Self::Vaapi => "VAAPI",
148 Self::Vdpau => "VDPAU",
149 }
150 }
151
152 #[must_use]
154 pub fn is_available(self) -> bool {
155 match self {
157 Self::None => true,
158 Self::Nvenc => detect_nvenc(),
159 Self::Qsv => detect_qsv(),
160 Self::Amd => detect_amd(),
161 Self::VideoToolbox => detect_videotoolbox(),
162 Self::Vulkan => detect_vulkan(),
163 Self::D3d11 => detect_d3d11(),
164 Self::Vaapi => detect_vaapi(),
165 Self::Vdpau => detect_vdpau(),
166 }
167 }
168
169 #[must_use]
171 pub fn supported_codecs(self) -> Vec<&'static str> {
172 match self {
173 Self::None => vec!["h264", "vp8", "vp9", "av1", "theora"],
174 Self::Nvenc => vec!["h264", "h265", "av1"],
175 Self::Qsv => vec!["h264", "h265", "vp9", "av1"],
176 Self::Amd => vec!["h264", "h265", "av1"],
177 Self::VideoToolbox => vec!["h264", "h265"],
178 Self::Vulkan => vec!["h264", "h265"],
179 Self::D3d11 => vec!["h264", "h265", "vp9"],
180 Self::Vaapi => vec!["h264", "h265", "vp8", "vp9", "av1"],
181 Self::Vdpau => vec!["h264", "h265"],
182 }
183 }
184
185 #[must_use]
187 pub fn encoder_name(self, codec: &str) -> Option<String> {
188 match self {
189 Self::None => Some(codec.to_string()),
190 Self::Nvenc => match codec {
191 "h264" => Some("h264_nvenc".to_string()),
192 "h265" => Some("hevc_nvenc".to_string()),
193 "av1" => Some("av1_nvenc".to_string()),
194 _ => None,
195 },
196 Self::Qsv => match codec {
197 "h264" => Some("h264_qsv".to_string()),
198 "h265" => Some("hevc_qsv".to_string()),
199 "vp9" => Some("vp9_qsv".to_string()),
200 "av1" => Some("av1_qsv".to_string()),
201 _ => None,
202 },
203 Self::Amd => match codec {
204 "h264" => Some("h264_amf".to_string()),
205 "h265" => Some("hevc_amf".to_string()),
206 "av1" => Some("av1_amf".to_string()),
207 _ => None,
208 },
209 Self::VideoToolbox => match codec {
210 "h264" => Some("h264_videotoolbox".to_string()),
211 "h265" => Some("hevc_videotoolbox".to_string()),
212 _ => None,
213 },
214 Self::Vulkan => match codec {
215 "h264" => Some("h264_vulkan".to_string()),
216 "h265" => Some("hevc_vulkan".to_string()),
217 _ => None,
218 },
219 Self::D3d11 => match codec {
220 "h264" => Some("h264_d3d11va".to_string()),
221 "h265" => Some("hevc_d3d11va".to_string()),
222 "vp9" => Some("vp9_d3d11va".to_string()),
223 _ => None,
224 },
225 Self::Vaapi => match codec {
226 "h264" => Some("h264_vaapi".to_string()),
227 "h265" => Some("hevc_vaapi".to_string()),
228 "vp8" => Some("vp8_vaapi".to_string()),
229 "vp9" => Some("vp9_vaapi".to_string()),
230 "av1" => Some("av1_vaapi".to_string()),
231 _ => None,
232 },
233 Self::Vdpau => match codec {
234 "h264" => Some("h264_vdpau".to_string()),
235 "h265" => Some("hevc_vdpau".to_string()),
236 _ => None,
237 },
238 }
239 }
240}
241
242#[must_use]
244pub fn detect_available_hw_accel() -> Vec<HwAccelType> {
245 let mut available = vec![HwAccelType::None];
246
247 for accel_type in &[
248 HwAccelType::Nvenc,
249 HwAccelType::Qsv,
250 HwAccelType::Amd,
251 HwAccelType::VideoToolbox,
252 HwAccelType::Vulkan,
253 HwAccelType::D3d11,
254 HwAccelType::Vaapi,
255 HwAccelType::Vdpau,
256 ] {
257 if accel_type.is_available() {
258 available.push(*accel_type);
259 }
260 }
261
262 available
263}
264
265#[must_use]
267pub fn detect_best_hw_accel_for_codec(codec: &str) -> Option<HwAccelType> {
268 detect_available_hw_accel()
269 .into_iter()
270 .find(|&accel_type| accel_type.supported_codecs().contains(&codec))
271}
272
273#[cfg(target_os = "linux")]
275fn detect_nvenc() -> bool {
276 std::path::Path::new("/dev/nvidia0").exists()
277}
278
279#[cfg(not(target_os = "linux"))]
280fn detect_nvenc() -> bool {
281 false }
283
284#[cfg(target_os = "linux")]
285fn detect_vaapi() -> bool {
286 std::path::Path::new("/dev/dri/renderD128").exists()
287}
288
289#[cfg(not(target_os = "linux"))]
290fn detect_vaapi() -> bool {
291 false
292}
293
294#[cfg(target_os = "linux")]
295fn detect_vdpau() -> bool {
296 std::path::Path::new("/usr/lib/vdpau").exists()
297}
298
299#[cfg(not(target_os = "linux"))]
300fn detect_vdpau() -> bool {
301 false
302}
303
304#[cfg(target_os = "windows")]
305fn detect_qsv() -> bool {
306 false
308}
309
310#[cfg(not(target_os = "windows"))]
311fn detect_qsv() -> bool {
312 false
313}
314
315#[cfg(target_os = "windows")]
316fn detect_amd() -> bool {
317 false
319}
320
321#[cfg(not(target_os = "windows"))]
322fn detect_amd() -> bool {
323 false
324}
325
326#[cfg(target_os = "macos")]
327fn detect_videotoolbox() -> bool {
328 true }
330
331#[cfg(not(target_os = "macos"))]
332fn detect_videotoolbox() -> bool {
333 false
334}
335
336#[cfg(target_os = "windows")]
337fn detect_d3d11() -> bool {
338 true }
340
341#[cfg(not(target_os = "windows"))]
342fn detect_d3d11() -> bool {
343 false
344}
345
346fn detect_vulkan() -> bool {
347 false
349}
350
351impl HwEncoder {
352 #[must_use]
354 pub fn new(
355 accel_type: HwAccelType,
356 codec: impl Into<String>,
357 encoder_name: impl Into<String>,
358 ) -> Self {
359 Self {
360 accel_type,
361 codec: codec.into(),
362 encoder_name: encoder_name.into(),
363 available: false,
364 max_resolution: (7680, 4320), features: Vec::new(),
366 }
367 }
368
369 #[must_use]
371 pub fn available(mut self, available: bool) -> Self {
372 self.available = available;
373 self
374 }
375
376 #[must_use]
378 pub fn max_resolution(mut self, width: u32, height: u32) -> Self {
379 self.max_resolution = (width, height);
380 self
381 }
382
383 #[must_use]
385 pub fn with_feature(mut self, feature: HwFeature) -> Self {
386 self.features.push(feature);
387 self
388 }
389
390 #[must_use]
392 pub fn supports_feature(&self, feature: HwFeature) -> bool {
393 self.features.contains(&feature)
394 }
395}
396
397#[must_use]
399pub fn get_available_encoders() -> Vec<HwEncoder> {
400 let mut encoders = Vec::new();
401
402 for accel_type in detect_available_hw_accel() {
403 for codec in accel_type.supported_codecs() {
404 if let Some(encoder_name) = accel_type.encoder_name(codec) {
405 let encoder = HwEncoder::new(accel_type, codec, encoder_name).available(true);
406 encoders.push(encoder);
407 }
408 }
409 }
410
411 encoders
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn test_hw_accel_type_platform_name() {
420 assert_eq!(HwAccelType::Nvenc.platform_name(), "NVIDIA NVENC");
421 assert_eq!(HwAccelType::Qsv.platform_name(), "Intel Quick Sync");
422 assert_eq!(HwAccelType::Vaapi.platform_name(), "VAAPI");
423 }
424
425 #[test]
426 fn test_hw_accel_supported_codecs() {
427 let codecs = HwAccelType::Nvenc.supported_codecs();
428 assert!(codecs.contains(&"h264"));
429 assert!(codecs.contains(&"h265"));
430 }
431
432 #[test]
433 fn test_hw_accel_encoder_name() {
434 assert_eq!(
435 HwAccelType::Nvenc.encoder_name("h264"),
436 Some("h264_nvenc".to_string())
437 );
438 assert_eq!(
439 HwAccelType::Qsv.encoder_name("h265"),
440 Some("hevc_qsv".to_string())
441 );
442 }
443
444 #[test]
445 fn test_hw_accel_config() {
446 let config = HwAccelConfig::new(HwAccelType::Nvenc)
447 .allow_fallback(false)
448 .decode(true)
449 .encode(true)
450 .device_id(0);
451
452 assert_eq!(config.preferred_type, HwAccelType::Nvenc);
453 assert!(!config.allow_fallback);
454 assert!(config.decode);
455 assert!(config.encode);
456 assert_eq!(config.device_id, Some(0));
457 }
458
459 #[test]
460 fn test_detect_available_hw_accel() {
461 let available = detect_available_hw_accel();
462 assert!(available.contains(&HwAccelType::None)); }
464
465 #[test]
466 fn test_hw_encoder_creation() {
467 let encoder = HwEncoder::new(HwAccelType::Nvenc, "h264", "h264_nvenc")
468 .available(true)
469 .max_resolution(3840, 2160)
470 .with_feature(HwFeature::TenBit)
471 .with_feature(HwFeature::Lookahead);
472
473 assert_eq!(encoder.accel_type, HwAccelType::Nvenc);
474 assert_eq!(encoder.codec, "h264");
475 assert!(encoder.available);
476 assert_eq!(encoder.max_resolution, (3840, 2160));
477 assert!(encoder.supports_feature(HwFeature::TenBit));
478 assert!(encoder.supports_feature(HwFeature::Lookahead));
479 assert!(!encoder.supports_feature(HwFeature::Hdr));
480 }
481}