From 0f2b66a8086888a542ac80a1df1a5b732c3a79c2 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 14 Sep 2024 05:21:18 +0200 Subject: [PATCH] desktop: Move cpal audio backend to frontend-utils --- Cargo.lock | 2 + Cargo.toml | 1 + desktop/Cargo.toml | 4 +- desktop/src/backends.rs | 2 - desktop/src/player.rs | 7 ++-- frontend-utils/Cargo.toml | 15 ++++++- frontend-utils/src/backends.rs | 2 + .../src/backends/audio.rs | 39 +++++++++++++------ 8 files changed, 52 insertions(+), 20 deletions(-) rename {desktop => frontend-utils}/src/backends/audio.rs (75%) diff --git a/Cargo.lock b/Cargo.lock index e53b2f7d8..c3dc6469b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4397,6 +4397,8 @@ version = "0.1.0" dependencies = [ "async-channel", "async-io", + "bytemuck", + "cpal", "futures-lite", "macro_rules_attribute", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 339e7c0a5..eb43abf4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ naga = { version = "22.1.0", features = ["wgsl-out"] } wgpu = "22.1.0" egui = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093" } clap = { version = "4.5.17", features = ["derive"] } +cpal = "0.15.3" anyhow = "1.0" slotmap = "1.0.7" async-channel = "2.3.1" diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index c31b47739..0f8967131 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] clap = { workspace = true } -cpal = "0.15.3" +cpal = { workspace = true } egui = { workspace = true } egui_extras = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093", default-features = false, features = ["image"] } egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093", features = ["winit"] } @@ -24,7 +24,7 @@ ruffle_render = { path = "../render", features = ["clap"] } ruffle_render_wgpu = { path = "../render/wgpu", features = ["clap"] } ruffle_video_software = { path = "../video/software", optional = true } ruffle_video_external = { path = "../video/external", optional = true } -ruffle_frontend_utils = { path = "../frontend-utils" } +ruffle_frontend_utils = { path = "../frontend-utils", features = ["cpal"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } tracing-appender = "0.2.3" diff --git a/desktop/src/backends.rs b/desktop/src/backends.rs index 5361d8f9c..5efcd056c 100644 --- a/desktop/src/backends.rs +++ b/desktop/src/backends.rs @@ -1,10 +1,8 @@ -mod audio; mod external_interface; mod fscommand; mod navigator; mod ui; -pub use audio::CpalAudioBackend; pub use external_interface::DesktopExternalInterfaceProvider; pub use fscommand::DesktopFSCommandProvider; pub use navigator::DesktopNavigatorInterface; diff --git a/desktop/src/player.rs b/desktop/src/player.rs index 7d1e02b8b..5599986a5 100644 --- a/desktop/src/player.rs +++ b/desktop/src/player.rs @@ -1,6 +1,6 @@ use crate::backends::{ - CpalAudioBackend, DesktopExternalInterfaceProvider, DesktopFSCommandProvider, - DesktopNavigatorInterface, DesktopUiBackend, + DesktopExternalInterfaceProvider, DesktopFSCommandProvider, DesktopNavigatorInterface, + DesktopUiBackend, }; use crate::custom_event::RuffleEvent; use crate::gui::{FilePicker, MovieView}; @@ -11,6 +11,7 @@ use ruffle_core::backend::navigator::{OpenURLMode, SocketMode}; use ruffle_core::config::Letterbox; use ruffle_core::events::{GamepadButton, KeyCode}; use ruffle_core::{DefaultFont, LoadBehavior, Player, PlayerBuilder, PlayerEvent}; +use ruffle_frontend_utils::backends::audio::CpalAudioBackend; use ruffle_frontend_utils::backends::executor::{AsyncExecutor, PollRequester}; use ruffle_frontend_utils::backends::navigator::ExternalNavigatorBackend; use ruffle_frontend_utils::bundle::source::BundleSourceError; @@ -134,7 +135,7 @@ impl ActivePlayer { ) -> Self { let mut builder = PlayerBuilder::new(); - match CpalAudioBackend::new(&preferences) { + match CpalAudioBackend::new(preferences.output_device_name().as_deref()) { Ok(audio) => { builder = builder.with_audio(audio); } diff --git a/frontend-utils/Cargo.toml b/frontend-utils/Cargo.toml index 10c2668ed..00f583378 100644 --- a/frontend-utils/Cargo.toml +++ b/frontend-utils/Cargo.toml @@ -10,12 +10,15 @@ version.workspace = true [lints] workspace = true +[features] +cpal = ["dep:cpal", "dep:bytemuck"] + [dependencies] toml_edit = { version = "0.22.20", features = ["parse"] } url = { workspace = true } tracing = { workspace = true } thiserror = { workspace = true } -zip = { version = "2.2.0", default-features = false, features = ["deflate"]} +zip = { version = "2.2.0", default-features = false, features = ["deflate"] } urlencoding = "2.1.3" ruffle_core = { path = "../core", default-features = false } ruffle_render = { path = "../render", default-features = false } @@ -23,8 +26,16 @@ async-channel = { workspace = true } slotmap = { workspace = true } async-io = "2.3.4" futures-lite = "2.3.0" -reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls", "cookies", "charset", "http2", "macos-system-configuration"] } +reqwest = { version = "0.12.7", default-features = false, features = [ + "rustls-tls", + "cookies", + "charset", + "http2", + "macos-system-configuration", +] } tokio = { workspace = true, features = ["net"] } +cpal = { workspace = true, optional = true } +bytemuck = { workspace = true, optional = true } [dev-dependencies] tempfile = "3" diff --git a/frontend-utils/src/backends.rs b/frontend-utils/src/backends.rs index cac692ff5..ed1020a2c 100644 --- a/frontend-utils/src/backends.rs +++ b/frontend-utils/src/backends.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "cpal")] +pub mod audio; pub mod executor; pub mod navigator; pub mod storage; diff --git a/desktop/src/backends/audio.rs b/frontend-utils/src/backends/audio.rs similarity index 75% rename from desktop/src/backends/audio.rs rename to frontend-utils/src/backends/audio.rs index 27885a3d7..c0941bee5 100644 --- a/desktop/src/backends/audio.rs +++ b/frontend-utils/src/backends/audio.rs @@ -1,12 +1,29 @@ -use crate::preferences::GlobalPreferences; -use anyhow::{anyhow, Context, Error}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::SampleFormat; use ruffle_core::backend::audio::{ swf, AudioBackend, AudioMixer, DecodeError, RegisterError, SoundHandle, SoundInstanceHandle, SoundStreamInfo, SoundTransform, }; use ruffle_core::impl_audio_mixer_backend; +#[derive(Debug, thiserror::Error)] +pub enum CpalError { + #[error("No audio devices available")] + NoDevices, + + #[error("Failed to get default output config")] + DefaultStream(#[from] cpal::DefaultStreamConfigError), + + #[error("Unsupported sample format {0:?}")] + UnsupportedSampleFormat(SampleFormat), + + #[error("Couldn't play the audio stream")] + Play(#[from] cpal::PlayStreamError), + + #[error("Failed to construct audio stream")] + Build(#[from] cpal::BuildStreamError), +} + pub struct CpalAudioBackend { #[allow(dead_code)] device: cpal::Device, @@ -17,16 +34,16 @@ pub struct CpalAudioBackend { } impl CpalAudioBackend { - pub fn new(preferences: &GlobalPreferences) -> Result { + pub fn new(preferred_device_name: Option<&str>) -> Result { // Create CPAL audio device. let host = cpal::default_host(); - let device = get_suitable_output_device(preferences, &host) - .ok_or_else(|| anyhow!("No audio devices available"))?; + let device = + get_suitable_output_device(preferred_device_name, &host).ok_or(CpalError::NoDevices)?; // Create audio stream for device. let config = device .default_output_config() - .context("Failed to get default output config")?; + .map_err(CpalError::DefaultStream)?; let sample_format = config.sample_format(); let config = cpal::StreamConfig::from(config); let mixer = AudioMixer::new(config.channels as u8, config.sample_rate.0); @@ -63,11 +80,11 @@ impl CpalAudioBackend { error_handler, None, ), - _ => anyhow::bail!("Unsupported sample format {sample_format:?}"), + _ => return Err(CpalError::UnsupportedSampleFormat(sample_format)), }? }; - stream.play().context("Couldn't play the audio stream")?; + stream.play().map_err(CpalError::Play)?; Ok(Self { device, @@ -91,14 +108,14 @@ impl AudioBackend for CpalAudioBackend { } fn get_suitable_output_device( - preferences: &GlobalPreferences, + preferred_device_name: Option<&str>, host: &cpal::Host, ) -> Option { // First let's check for any user preference... - if let Some(preferred_device_name) = preferences.output_device_name() { + if let Some(preferred_device_name) = preferred_device_name { if let Ok(mut devices) = host.output_devices() { if let Some(device) = - devices.find(|device| device.name().ok().as_deref() == Some(&preferred_device_name)) + devices.find(|device| device.name().ok().as_deref() == Some(preferred_device_name)) { return Some(device); }