From 99595eeeedcfb0154df84949015fd31a3e43d75f Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Wed, 14 Feb 2024 03:09:44 -0500 Subject: [PATCH 1/3] Wrap FFmpeg's `AVHWDeviceType` and av_hwdevice_iterate_types --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/config.rs | 4 ++ src/main.rs | 2 + src/wrappers.rs | 159 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+) create mode 100644 src/wrappers.rs 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()) + } + } +} From 028fd832960c9735434be2fdf952c4c39574700f Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Thu, 15 Feb 2024 23:38:23 -0500 Subject: [PATCH 2/3] Allow config to be updated with CLI args --- src/config.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 29 ++++++++++++++------------ src/wrappers.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 120 insertions(+), 15 deletions(-) diff --git a/src/config.rs b/src/config.rs index 33405d5..77f4ad1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,9 @@ use cosmic::{ cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry}, theme, }; +use lexopt::prelude::*; use serde::{Deserialize, Serialize}; +use std::{path::PathBuf, process}; use crate::wrappers::HWDeviceType; @@ -41,3 +43,52 @@ impl Default for Config { } } } + +impl Config { + pub fn with_args(&mut self, args: &mut Args) { + if let Some(decoder) = args.decoder { + self.hw_decoder = decoder; + } + } +} + +pub struct Args { + pub paths: Vec, + pub decoder: Option, +} + +impl Args { + pub fn parse_args() -> Result { + let mut paths = Vec::new(); + let mut decoder = None; + + let mut parser = lexopt::Parser::from_env(); + while let Some(arg) = parser.next()? { + match arg { + Long("list-hwdec") => { + println!("Supported hardware decoders:"); + for hwdec in HWDeviceType::supported_devices() { + println!("\t* [{}] {hwdec}", hwdec.short_name()); + } + process::exit(0); + } + Long("hwdec") => { + decoder = Some(parser.value()?.parse()?); + } + Value(path) => { + let path = path.parse()?; + paths.push(path); + } + _ => return Err(arg.unexpected()), + } + } + + if paths.is_empty() { + return Err(lexopt::Error::MissingValue { + option: Some("missing video path".into()), + }); + } + + Ok(Self { paths, decoder }) + } +} diff --git a/src/main.rs b/src/main.rs index c24456a..68a120d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,14 +16,12 @@ use cosmic::{ use std::{ any::TypeId, collections::HashMap, - env, - path::PathBuf, - process, + env, process, sync::{mpsc, Arc, Mutex}, time::{Duration, Instant}, }; -use config::{AppTheme, Config, CONFIG_VERSION}; +use config::{AppTheme, Args, Config, CONFIG_VERSION}; mod config; use key_bind::{key_binds, KeyBind}; @@ -42,16 +40,26 @@ fn main() -> Result<(), Box> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); localize::localize(); + + let mut args = match Args::parse_args() { + Ok(args) => args, + Err(e) => { + log::error!("{e}"); + process::exit(1); + } + }; let (config_handler, config) = match cosmic_config::Config::new(App::APP_ID, CONFIG_VERSION) { Ok(config_handler) => { - let config = match Config::get_entry(&config_handler) { + let mut config = match Config::get_entry(&config_handler) { Ok(ok) => ok, Err((errs, config)) => { log::info!("errors loading config: {:?}", errs); config } }; + // Update config with command line args + config.with_args(&mut args); (Some(config_handler), config) } Err(err) => { @@ -60,14 +68,9 @@ fn main() -> Result<(), Box> { } }; - //TODO: support multiple paths - let path = match env::args().skip(1).next() { - Some(arg) => PathBuf::from(arg), - None => { - log::error!("no argument provided"); - process::exit(1); - } - }; + //TODO: support using multiple paths + let Args { mut paths, .. } = args; + let path = paths.pop().unwrap(); let (player_tx, video_queue_lock) = player::run(path); diff --git a/src/wrappers.rs b/src/wrappers.rs index 38b2de2..2bbc617 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::str::FromStr; +use std::{fmt, iter::FusedIterator, str::FromStr}; use ffmpeg_next::ffi::{av_hwdevice_iterate_types, AVHWDeviceType}; use serde::{ @@ -25,6 +25,8 @@ pub enum HWDeviceType { /// DirectX Video Acceleration 2.0 /// https://learn.microsoft.com/en-us/windows/win32/medfound/about-dxva-2-0 Dxva2, + /// Direct Rendering Manager + /// https://dri.freedesktop.org/wiki/DRM/ Drm, /// MediaCodec /// Android only @@ -70,7 +72,26 @@ impl HWDeviceType { } } - /// Supported hardware decoders + /// Short name for CLI arguments + pub const fn short_name(self) -> &'static str { + match self { + Self::None => "none", + Self::Cuda => "cuda", + Self::Dxva2 => "dxva2", + Self::D3d11va => "d3d11va", + Self::D3d12va => "d3d12va", + Self::Drm => "drm", + Self::MediaCodec => "mediacodec", + Self::OpenCl => "opencl", + Self::Qsv => "qsv", + Self::Vaapi => "vaapi", + Self::Vdpau => "vdpau", + Self::VideoToolbox => "videotoolbox", + Self::Vulkan => "vulkan", + } + } + + /// System's supported hardware decoders pub fn supported_devices() -> SupportedDeviceIter { SupportedDeviceIter::default() } @@ -104,6 +125,12 @@ impl FromStr for HWDeviceType { } } +impl fmt::Display for HWDeviceType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + impl From for HWDeviceType { fn from(value: AVHWDeviceType) -> Self { match value { @@ -131,6 +158,7 @@ impl Default for HWDeviceType { } } +/// Iterator over system's supported hardware decoders. pub struct SupportedDeviceIter { current: AVHWDeviceType, } @@ -147,13 +175,36 @@ impl Iterator for SupportedDeviceIter { type Item = HWDeviceType; fn next(&mut self) -> Option { + // None is a sentinel value that indicates the iterator is exhausted if self.current == AVHWDeviceType::AV_HWDEVICE_TYPE_NONE { None } else { let prev = self.current; + // SAFETY: The docs and examples state that the iterator yields the next value + // when the previous is passed in. self.current = unsafe { av_hwdevice_iterate_types(prev) }; Some(prev.into()) } } } + +impl FusedIterator for SupportedDeviceIter {} + +#[cfg(test)] +mod tests { + use std::hint::black_box; + + use super::*; + + // The iterator's yielded values aren't important since hardware decoders vary by system + // This is just a sanity check to ensure the iterator works + #[test] + fn supported_device_iter_doesnt_seg_fault() { + for decoder in HWDeviceType::supported_devices() { + black_box(decoder); + } + + let _decoders: Vec<_> = black_box(HWDeviceType::supported_devices().collect()); + } +} From e078fe05ddd908d8e016ba67b4ddd5bd9a4902f1 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Fri, 16 Feb 2024 03:15:23 -0500 Subject: [PATCH 3/3] Pass HW decoder choice to player --- src/config.rs | 10 +-- src/hardware.rs | 7 ++ src/{wrappers.rs => hardware/device_type.rs} | 90 +++++++------------- src/hardware/iter.rs | 58 +++++++++++++ src/main.rs | 5 +- src/player.rs | 17 ++-- 6 files changed, 115 insertions(+), 72 deletions(-) create mode 100644 src/hardware.rs rename src/{wrappers.rs => hardware/device_type.rs} (74%) create mode 100644 src/hardware/iter.rs diff --git a/src/config.rs b/src/config.rs index 77f4ad1..2337e11 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,7 +8,7 @@ use lexopt::prelude::*; use serde::{Deserialize, Serialize}; use std::{path::PathBuf, process}; -use crate::wrappers::HWDeviceType; +use crate::hardware::DeviceType; pub const CONFIG_VERSION: u64 = 1; @@ -32,14 +32,14 @@ impl AppTheme { #[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Config { pub app_theme: AppTheme, - pub hw_decoder: HWDeviceType, + pub hw_decoder: DeviceType, } impl Default for Config { fn default() -> Self { Self { app_theme: AppTheme::System, - hw_decoder: HWDeviceType::default(), + hw_decoder: DeviceType::default(), } } } @@ -54,7 +54,7 @@ impl Config { pub struct Args { pub paths: Vec, - pub decoder: Option, + pub decoder: Option, } impl Args { @@ -67,7 +67,7 @@ impl Args { match arg { Long("list-hwdec") => { println!("Supported hardware decoders:"); - for hwdec in HWDeviceType::supported_devices() { + for hwdec in DeviceType::supported_devices() { println!("\t* [{}] {hwdec}", hwdec.short_name()); } process::exit(0); diff --git a/src/hardware.rs b/src/hardware.rs new file mode 100644 index 0000000..143259d --- /dev/null +++ b/src/hardware.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-3.0-only + +pub mod device_type; +pub mod iter; + +pub use device_type::DeviceType; +pub use iter::SupportedDeviceIter; diff --git a/src/wrappers.rs b/src/hardware/device_type.rs similarity index 74% rename from src/wrappers.rs rename to src/hardware/device_type.rs index 2bbc617..8e82c68 100644 --- a/src/wrappers.rs +++ b/src/hardware/device_type.rs @@ -1,16 +1,18 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{fmt, iter::FusedIterator, str::FromStr}; +use std::{fmt, str::FromStr}; -use ffmpeg_next::ffi::{av_hwdevice_iterate_types, AVHWDeviceType}; +use ffmpeg_next::ffi::AVHWDeviceType; use serde::{ de::{value::Error as DeError, Error as DeErrorTrait, Unexpected}, Deserialize, Serialize, }; +use super::iter::SupportedDeviceIter; + /// Delegate type for [`ffmpeg_next::ffi::AVHWDeviceType`] for configs. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum HWDeviceType { +pub enum DeviceType { None, /// Compute Unified Device Architecture /// Nvidia only. @@ -52,7 +54,7 @@ pub enum HWDeviceType { Vulkan, } -impl HWDeviceType { +impl DeviceType { /// Hardware device names for user facing interfaces (logging, configs). pub const fn name(self) -> &'static str { match self { @@ -97,7 +99,7 @@ impl HWDeviceType { } } -impl FromStr for HWDeviceType { +impl FromStr for DeviceType { type Err = DeError; // av_hwdevice_find_type_by_name returns None for invalid device type names, but this type @@ -125,13 +127,13 @@ impl FromStr for HWDeviceType { } } -impl fmt::Display for HWDeviceType { +impl fmt::Display for DeviceType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name()) } } -impl From for HWDeviceType { +impl From for DeviceType { fn from(value: AVHWDeviceType) -> Self { match value { AVHWDeviceType::AV_HWDEVICE_TYPE_NONE => Self::None, @@ -152,59 +154,29 @@ impl From for HWDeviceType { } } -impl Default for HWDeviceType { +impl From for AVHWDeviceType { + fn from(value: DeviceType) -> Self { + match value { + DeviceType::None => Self::AV_HWDEVICE_TYPE_NONE, + DeviceType::Cuda => Self::AV_HWDEVICE_TYPE_CUDA, + DeviceType::D3d11va => Self::AV_HWDEVICE_TYPE_D3D11VA, + // NOTE: Next FFmpeg release + DeviceType::D3d12va => Self::AV_HWDEVICE_TYPE_NONE, + DeviceType::Dxva2 => Self::AV_HWDEVICE_TYPE_DXVA2, + DeviceType::Drm => Self::AV_HWDEVICE_TYPE_DRM, + DeviceType::MediaCodec => Self::AV_HWDEVICE_TYPE_MEDIACODEC, + DeviceType::OpenCl => Self::AV_HWDEVICE_TYPE_OPENCL, + DeviceType::Qsv => Self::AV_HWDEVICE_TYPE_QSV, + DeviceType::Vaapi => Self::AV_HWDEVICE_TYPE_VAAPI, + DeviceType::Vdpau => Self::AV_HWDEVICE_TYPE_VDPAU, + DeviceType::VideoToolbox => Self::AV_HWDEVICE_TYPE_VIDEOTOOLBOX, + DeviceType::Vulkan => Self::AV_HWDEVICE_TYPE_VULKAN, + } + } +} + +impl Default for DeviceType { fn default() -> Self { Self::Vaapi } } - -/// Iterator over system's supported hardware decoders. -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 { - // None is a sentinel value that indicates the iterator is exhausted - if self.current == AVHWDeviceType::AV_HWDEVICE_TYPE_NONE { - None - } else { - let prev = self.current; - // SAFETY: The docs and examples state that the iterator yields the next value - // when the previous is passed in. - self.current = unsafe { av_hwdevice_iterate_types(prev) }; - - Some(prev.into()) - } - } -} - -impl FusedIterator for SupportedDeviceIter {} - -#[cfg(test)] -mod tests { - use std::hint::black_box; - - use super::*; - - // The iterator's yielded values aren't important since hardware decoders vary by system - // This is just a sanity check to ensure the iterator works - #[test] - fn supported_device_iter_doesnt_seg_fault() { - for decoder in HWDeviceType::supported_devices() { - black_box(decoder); - } - - let _decoders: Vec<_> = black_box(HWDeviceType::supported_devices().collect()); - } -} diff --git a/src/hardware/iter.rs b/src/hardware/iter.rs new file mode 100644 index 0000000..0a52f7e --- /dev/null +++ b/src/hardware/iter.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::iter::FusedIterator; + +use ffmpeg_next::ffi::{av_hwdevice_iterate_types, AVHWDeviceType}; + +use super::device_type::DeviceType; + +/// Iterator over system's supported hardware decoders. +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 = DeviceType; + + fn next(&mut self) -> Option { + // None is a sentinel value that indicates the iterator is exhausted + if self.current == AVHWDeviceType::AV_HWDEVICE_TYPE_NONE { + None + } else { + let prev = self.current; + // SAFETY: The docs and examples state that the iterator yields the next value + // when the previous is passed in. + self.current = unsafe { av_hwdevice_iterate_types(prev) }; + + Some(prev.into()) + } + } +} + +impl FusedIterator for SupportedDeviceIter {} + +#[cfg(test)] +mod tests { + use std::hint::black_box; + + use super::*; + + // The iterator's yielded values aren't important since hardware decoders vary by system + // This is just a sanity check to ensure the iterator works + #[test] + fn supported_device_iter_doesnt_seg_fault() { + for decoder in DeviceType::supported_devices() { + black_box(decoder); + } + + let _decoders: Vec<_> = black_box(DeviceType::supported_devices().collect()); + } +} diff --git a/src/main.rs b/src/main.rs index 68a120d..bf665ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ mod localize; use player::{PlayerMessage, VideoFrame, VideoQueue}; mod player; -mod wrappers; +mod hardware; /// Runs application with these settings #[rustfmt::skip] @@ -72,7 +72,8 @@ fn main() -> Result<(), Box> { let Args { mut paths, .. } = args; let path = paths.pop().unwrap(); - let (player_tx, video_queue_lock) = player::run(path); + // TODO: Update video player config when it's updated via the app + let (player_tx, video_queue_lock) = player::run(path, config.clone()); let mut settings = Settings::default(); settings = settings.theme(config.app_theme.theme()); diff --git a/src/player.rs b/src/player.rs index b4b2f8a..2bf1473 100644 --- a/src/player.rs +++ b/src/player.rs @@ -28,6 +28,8 @@ use std::{ time::{Duration, Instant}, }; +use crate::config::Config; + //TODO: calculate presentation time of end of queue pub struct AudioQueue { pub channels: usize, @@ -207,6 +209,7 @@ fn ffmpeg_thread>( path: P, player_rx: mpsc::Receiver, video_queue_lock: Arc>, + config: Config, ) -> Result<(), Box> { let (audio_config, cpal_stream, audio_queue_lock) = cpal(); @@ -227,21 +230,23 @@ fn ffmpeg_thread>( let mut hw_device_ctx = ptr::null_mut(); unsafe { //TODO: support other types - let hw_device_kind = ffi::AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI; + let hw_device_kind = config.hw_decoder; if ffi::av_hwdevice_ctx_create( &mut hw_device_ctx, - hw_device_kind, + hw_device_kind.into(), ptr::null(), ptr::null_mut(), 0, ) == 0 { - log::info!("using vaapi decoding"); + log::info!("using {hw_device_kind} decoding"); (&mut *video_decoder_context.as_mut_ptr()).hw_device_ctx = ffi::av_buffer_ref(hw_device_ctx); } else { //TODO: support other hardware devices - log::warn!("failed to use vaapi decoding, falling back to software decoding"); + log::warn!( + "failed to use {hw_device_kind} decoding, falling back to software decoding" + ); } } @@ -630,7 +635,7 @@ fn ffmpeg_thread>( Ok(()) } -pub fn run(path: PathBuf) -> (mpsc::Sender, Arc>) { +pub fn run(path: PathBuf, config: Config) -> (mpsc::Sender, Arc>) { ffmpeg::init().unwrap(); let (player_tx, player_rx) = mpsc::channel(); @@ -640,7 +645,7 @@ pub fn run(path: PathBuf) -> (mpsc::Sender, Arc thread::Builder::new() .name("ffmpeg".to_string()) .spawn(move || { - ffmpeg_thread(path, player_rx, video_queue_lock).unwrap(); + ffmpeg_thread(path, player_rx, video_queue_lock, config).unwrap(); }) .unwrap(); }