diff --git a/Cargo.lock b/Cargo.lock index 5d3a6be..c4f6ffe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1065,6 +1065,7 @@ dependencies = [ "i18n-embed", "i18n-embed-fl", "lazy_static", + "lexopt", "libcosmic", "log", "paste", @@ -2840,6 +2841,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +[[package]] +name = "lexopt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" + [[package]] name = "libc" version = "0.2.153" diff --git a/Cargo.toml b/Cargo.toml index c50ce5e..8df0d0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ rust-embed = "6" # Logging env_logger = "0.10" log = "0.4" +lexopt = "0.3" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic.git" diff --git a/src/config.rs b/src/config.rs index d10b36c..33405d5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,8 @@ use cosmic::{ }; use serde::{Deserialize, Serialize}; +use crate::wrappers::HWDeviceType; + pub const CONFIG_VERSION: u64 = 1; #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -28,12 +30,14 @@ impl AppTheme { #[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Config { pub app_theme: AppTheme, + pub hw_decoder: HWDeviceType, } impl Default for Config { fn default() -> Self { Self { app_theme: AppTheme::System, + hw_decoder: HWDeviceType::default(), } } } diff --git a/src/main.rs b/src/main.rs index e348921..c24456a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,8 @@ mod localize; use player::{PlayerMessage, VideoFrame, VideoQueue}; mod player; +mod wrappers; + /// Runs application with these settings #[rustfmt::skip] fn main() -> Result<(), Box> { diff --git a/src/wrappers.rs b/src/wrappers.rs new file mode 100644 index 0000000..38b2de2 --- /dev/null +++ b/src/wrappers.rs @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::str::FromStr; + +use ffmpeg_next::ffi::{av_hwdevice_iterate_types, AVHWDeviceType}; +use serde::{ + de::{value::Error as DeError, Error as DeErrorTrait, Unexpected}, + Deserialize, Serialize, +}; + +/// Delegate type for [`ffmpeg_next::ffi::AVHWDeviceType`] for configs. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum HWDeviceType { + None, + /// Compute Unified Device Architecture + /// Nvidia only. + /// https://developer.nvidia.com/video-codec-sdk + Cuda, + /// Direct3D 11 Video API + /// https://learn.microsoft.com/en-us/windows/win32/medfound/direct3d-11-video-apis + D3d11va, + /// Direct3D 12 Video API + /// https://learn.microsoft.com/en-us/windows/win32/medfound/direct3d-12-video-overview + D3d12va, + /// DirectX Video Acceleration 2.0 + /// https://learn.microsoft.com/en-us/windows/win32/medfound/about-dxva-2-0 + Dxva2, + Drm, + /// MediaCodec + /// Android only + /// https://developer.android.com/reference/android/media/MediaCodec + MediaCodec, + /// OpenCL + /// Only used in filters + /// https://www.khronos.org/opencl/ + OpenCl, + /// Intel Quick Sync Video + /// https://www.intel.com/content/www/us/en/developer/tools/vpl/overview.html + Qsv, + /// Video Acceleration API + /// https://www.intel.com/content/www/us/en/developer/articles/technical/linuxmedia-vaapi.html + Vaapi, + /// Video Decode and Presentation API for Unix + /// https://www.freedesktop.org/wiki/Software/VDPAU/ + Vdpau, + /// Video Toolbox + /// https://developer.apple.com/documentation/videotoolbox + VideoToolbox, + /// Vulkan + Vulkan, +} + +impl HWDeviceType { + /// Hardware device names for user facing interfaces (logging, configs). + pub const fn name(self) -> &'static str { + match self { + Self::None => "None", + Self::Cuda => "CUDA", + Self::Dxva2 => "DirectX Video Acceleration 2.0", + Self::D3d11va => "DirectX 11 Video Acceleration", + Self::D3d12va => "DirectX 12 Video Acceleration", + Self::Drm => "Direct Rendering Manager (DRM)", + Self::MediaCodec => "MediaCodec", + Self::OpenCl => "OpenCL", + Self::Qsv => "Intel Quick Video Sync", + Self::Vaapi => "VA-API", + Self::Vdpau => "VDPAU", + Self::VideoToolbox => "VideoToolbox", + Self::Vulkan => "Vulkan", + } + } + + /// Supported hardware decoders + pub fn supported_devices() -> SupportedDeviceIter { + SupportedDeviceIter::default() + } +} + +impl FromStr for HWDeviceType { + type Err = DeError; + + // av_hwdevice_find_type_by_name returns None for invalid device type names, but this type + // is used for deserializing configs (etc.) so the error is preserved. + fn from_str(s: &str) -> Result { + match s { + "none" => Ok(Self::None), + "cuda" => Ok(Self::Cuda), + "dxva2" => Ok(Self::Dxva2), + "d3d11va" => Ok(Self::D3d11va), + "d3d12va" => Ok(Self::D3d12va), + "drm" => Ok(Self::Drm), + "mediacodec" => Ok(Self::MediaCodec), + "opencl" => Ok(Self::OpenCl), + "qsv" => Ok(Self::Qsv), + "vaapi" => Ok(Self::Vaapi), + "vdpau" => Ok(Self::Vdpau), + "videotoolbox" => Ok(Self::VideoToolbox), + "vulkan" => Ok(Self::Vulkan), + _ => Err(DeError::invalid_value( + Unexpected::Str(s), + &"valid hardware decoder", + )), + } + } +} + +impl From for HWDeviceType { + fn from(value: AVHWDeviceType) -> Self { + match value { + AVHWDeviceType::AV_HWDEVICE_TYPE_NONE => Self::None, + AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA => Self::Cuda, + AVHWDeviceType::AV_HWDEVICE_TYPE_DXVA2 => Self::Dxva2, + AVHWDeviceType::AV_HWDEVICE_TYPE_D3D11VA => Self::D3d11va, + // This variant exists in ffmpeg's C lib but not in Rust's crate yet. + // AVHWDeviceType::AV_HWDEVICE_TYPE_D3D12VA => Self::D3d12va + AVHWDeviceType::AV_HWDEVICE_TYPE_DRM => Self::Drm, + AVHWDeviceType::AV_HWDEVICE_TYPE_MEDIACODEC => Self::MediaCodec, + AVHWDeviceType::AV_HWDEVICE_TYPE_OPENCL => Self::OpenCl, + AVHWDeviceType::AV_HWDEVICE_TYPE_QSV => Self::Qsv, + AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI => Self::Vaapi, + AVHWDeviceType::AV_HWDEVICE_TYPE_VDPAU => Self::Vdpau, + AVHWDeviceType::AV_HWDEVICE_TYPE_VIDEOTOOLBOX => Self::VideoToolbox, + AVHWDeviceType::AV_HWDEVICE_TYPE_VULKAN => Self::Vulkan, + } + } +} + +impl Default for HWDeviceType { + fn default() -> Self { + Self::Vaapi + } +} + +pub struct SupportedDeviceIter { + current: AVHWDeviceType, +} + +impl Default for SupportedDeviceIter { + fn default() -> Self { + // SAFETY: FFmpeg's documentation states that the iterator is delimited by AV_HWDEVICE_TYPE_NONE. + let current = unsafe { av_hwdevice_iterate_types(AVHWDeviceType::AV_HWDEVICE_TYPE_NONE) }; + Self { current } + } +} + +impl Iterator for SupportedDeviceIter { + type Item = HWDeviceType; + + fn next(&mut self) -> Option { + if self.current == AVHWDeviceType::AV_HWDEVICE_TYPE_NONE { + None + } else { + let prev = self.current; + self.current = unsafe { av_hwdevice_iterate_types(prev) }; + + Some(prev.into()) + } + } +}