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")]
281fn detect_nvenc() -> bool {
282 std::path::Path::new("/dev/nvidia0").exists()
286 && std::path::Path::new("/dev/nvidia-modeset").exists()
287}
288
289#[cfg(not(target_os = "linux"))]
290fn detect_nvenc() -> bool {
291 false
292}
293
294#[cfg(target_os = "linux")]
308fn detect_vaapi() -> bool {
309 use std::path::Path;
310
311 if !Path::new("/dev/dri/renderD128").exists() {
313 return false;
314 }
315
316 if !Path::new("/dev/dri/card0").exists() {
319 if !Path::new("/dev/dri/renderD129").exists() {
321 return false;
322 }
323 }
324
325 let driver_paths = [
328 "/usr/lib/dri/i965_drv_video.so",
329 "/usr/lib/dri/iHD_drv_video.so",
330 "/usr/lib/dri/radeonsi_drv_video.so",
331 "/usr/lib/dri/nouveau_drv_video.so",
332 "/usr/lib/x86_64-linux-gnu/dri/i965_drv_video.so",
333 "/usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so",
334 "/usr/lib/x86_64-linux-gnu/dri/radeonsi_drv_video.so",
335 ];
336 for p in &driver_paths {
337 if Path::new(p).exists() {
338 return true;
339 }
340 }
341
342 true
345}
346
347#[cfg(not(target_os = "linux"))]
348fn detect_vaapi() -> bool {
349 false
350}
351
352#[cfg(target_os = "linux")]
354fn detect_vdpau() -> bool {
355 use std::path::Path;
356 if !Path::new("/dev/dri/renderD128").exists() {
358 return false;
359 }
360 let vdpau_paths = [
361 "/usr/lib/vdpau/libvdpau_nvidia.so",
362 "/usr/lib/vdpau/libvdpau_r600.so",
363 "/usr/lib/vdpau/libvdpau_radeonsi.so",
364 "/usr/lib/x86_64-linux-gnu/vdpau/libvdpau_nvidia.so",
365 "/usr/lib/x86_64-linux-gnu/vdpau/libvdpau_radeonsi.so",
366 ];
367 vdpau_paths.iter().any(|p| Path::new(p).exists())
368}
369
370#[cfg(not(target_os = "linux"))]
371fn detect_vdpau() -> bool {
372 false
373}
374
375#[cfg(target_os = "linux")]
381fn detect_qsv() -> bool {
382 use std::path::Path;
383 if !Path::new("/dev/dri/renderD128").exists() {
384 return false;
385 }
386 let intel_paths = [
388 "/usr/lib/dri/i965_drv_video.so",
389 "/usr/lib/dri/iHD_drv_video.so",
390 "/usr/lib/x86_64-linux-gnu/dri/i965_drv_video.so",
391 "/usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so",
392 ];
393 intel_paths.iter().any(|p| Path::new(p).exists())
394}
395
396#[cfg(target_os = "windows")]
397fn detect_qsv() -> bool {
398 false
401}
402
403#[cfg(not(any(target_os = "linux", target_os = "windows")))]
404fn detect_qsv() -> bool {
405 false
406}
407
408#[cfg(target_os = "linux")]
412fn detect_amd() -> bool {
413 use std::path::Path;
414 if !Path::new("/dev/dri/renderD128").exists() {
415 return false;
416 }
417 let amd_paths = [
418 "/usr/lib/dri/radeonsi_drv_video.so",
419 "/usr/lib/x86_64-linux-gnu/dri/radeonsi_drv_video.so",
420 ];
421 amd_paths.iter().any(|p| Path::new(p).exists())
422}
423
424#[cfg(target_os = "windows")]
425fn detect_amd() -> bool {
426 false
427}
428
429#[cfg(not(any(target_os = "linux", target_os = "windows")))]
430fn detect_amd() -> bool {
431 false
432}
433
434#[cfg(target_os = "macos")]
440fn detect_videotoolbox() -> bool {
441 let framework_paths = [
444 "/System/Library/Frameworks/VideoToolbox.framework",
445 "/System/Library/Frameworks/CoreMedia.framework",
446 ];
447 framework_paths
448 .iter()
449 .all(|p| std::path::Path::new(p).exists())
450}
451
452#[cfg(not(target_os = "macos"))]
453fn detect_videotoolbox() -> bool {
454 false
455}
456
457#[cfg(target_os = "windows")]
462fn detect_d3d11() -> bool {
463 true
465}
466
467#[cfg(not(target_os = "windows"))]
468fn detect_d3d11() -> bool {
469 false
470}
471
472fn detect_vulkan() -> bool {
479 #[cfg(target_os = "linux")]
480 {
481 let vulkan_icd_paths = [
482 "/usr/share/vulkan/icd.d",
483 "/etc/vulkan/icd.d",
484 "/usr/local/share/vulkan/icd.d",
485 ];
486 vulkan_icd_paths
487 .iter()
488 .any(|p| std::path::Path::new(p).exists())
489 }
490 #[cfg(not(target_os = "linux"))]
491 {
492 false
493 }
494}
495
496impl HwEncoder {
497 #[must_use]
499 pub fn new(
500 accel_type: HwAccelType,
501 codec: impl Into<String>,
502 encoder_name: impl Into<String>,
503 ) -> Self {
504 Self {
505 accel_type,
506 codec: codec.into(),
507 encoder_name: encoder_name.into(),
508 available: false,
509 max_resolution: (7680, 4320), features: Vec::new(),
511 }
512 }
513
514 #[must_use]
516 pub fn available(mut self, available: bool) -> Self {
517 self.available = available;
518 self
519 }
520
521 #[must_use]
523 pub fn max_resolution(mut self, width: u32, height: u32) -> Self {
524 self.max_resolution = (width, height);
525 self
526 }
527
528 #[must_use]
530 pub fn with_feature(mut self, feature: HwFeature) -> Self {
531 self.features.push(feature);
532 self
533 }
534
535 #[must_use]
537 pub fn supports_feature(&self, feature: HwFeature) -> bool {
538 self.features.contains(&feature)
539 }
540}
541
542#[must_use]
544pub fn get_available_encoders() -> Vec<HwEncoder> {
545 let mut encoders = Vec::new();
546
547 for accel_type in detect_available_hw_accel() {
548 for codec in accel_type.supported_codecs() {
549 if let Some(encoder_name) = accel_type.encoder_name(codec) {
550 let encoder = HwEncoder::new(accel_type, codec, encoder_name).available(true);
551 encoders.push(encoder);
552 }
553 }
554 }
555
556 encoders
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562
563 #[test]
564 fn test_hw_accel_type_platform_name() {
565 assert_eq!(HwAccelType::Nvenc.platform_name(), "NVIDIA NVENC");
566 assert_eq!(HwAccelType::Qsv.platform_name(), "Intel Quick Sync");
567 assert_eq!(HwAccelType::Vaapi.platform_name(), "VAAPI");
568 }
569
570 #[test]
571 fn test_hw_accel_supported_codecs() {
572 let codecs = HwAccelType::Nvenc.supported_codecs();
573 assert!(codecs.contains(&"h264"));
574 assert!(codecs.contains(&"h265"));
575 }
576
577 #[test]
578 fn test_hw_accel_encoder_name() {
579 assert_eq!(
580 HwAccelType::Nvenc.encoder_name("h264"),
581 Some("h264_nvenc".to_string())
582 );
583 assert_eq!(
584 HwAccelType::Qsv.encoder_name("h265"),
585 Some("hevc_qsv".to_string())
586 );
587 }
588
589 #[test]
590 fn test_hw_accel_config() {
591 let config = HwAccelConfig::new(HwAccelType::Nvenc)
592 .allow_fallback(false)
593 .decode(true)
594 .encode(true)
595 .device_id(0);
596
597 assert_eq!(config.preferred_type, HwAccelType::Nvenc);
598 assert!(!config.allow_fallback);
599 assert!(config.decode);
600 assert!(config.encode);
601 assert_eq!(config.device_id, Some(0));
602 }
603
604 #[test]
605 fn test_detect_available_hw_accel() {
606 let available = detect_available_hw_accel();
607 assert!(available.contains(&HwAccelType::None)); }
609
610 #[test]
611 fn test_hw_encoder_creation() {
612 let encoder = HwEncoder::new(HwAccelType::Nvenc, "h264", "h264_nvenc")
613 .available(true)
614 .max_resolution(3840, 2160)
615 .with_feature(HwFeature::TenBit)
616 .with_feature(HwFeature::Lookahead);
617
618 assert_eq!(encoder.accel_type, HwAccelType::Nvenc);
619 assert_eq!(encoder.codec, "h264");
620 assert!(encoder.available);
621 assert_eq!(encoder.max_resolution, (3840, 2160));
622 assert!(encoder.supports_feature(HwFeature::TenBit));
623 assert!(encoder.supports_feature(HwFeature::Lookahead));
624 assert!(!encoder.supports_feature(HwFeature::Hdr));
625 }
626}