desktop: Move cpal audio backend to frontend-utils

This commit is contained in:
Mads Marquart 2024-09-14 05:21:18 +02:00 committed by TÖRÖK Attila
parent ac3f8991ea
commit 0f2b66a808
8 changed files with 52 additions and 20 deletions

2
Cargo.lock generated
View File

@ -4397,6 +4397,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-io", "async-io",
"bytemuck",
"cpal",
"futures-lite", "futures-lite",
"macro_rules_attribute", "macro_rules_attribute",
"reqwest", "reqwest",

View File

@ -52,6 +52,7 @@ naga = { version = "22.1.0", features = ["wgsl-out"] }
wgpu = "22.1.0" wgpu = "22.1.0"
egui = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093" } egui = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093" }
clap = { version = "4.5.17", features = ["derive"] } clap = { version = "4.5.17", features = ["derive"] }
cpal = "0.15.3"
anyhow = "1.0" anyhow = "1.0"
slotmap = "1.0.7" slotmap = "1.0.7"
async-channel = "2.3.1" async-channel = "2.3.1"

View File

@ -12,7 +12,7 @@ workspace = true
[dependencies] [dependencies]
clap = { workspace = true } clap = { workspace = true }
cpal = "0.15.3" cpal = { workspace = true }
egui = { workspace = true } egui = { workspace = true }
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "f4697bc007447c6c2674beb4e25f599fb7afa093", default-features = false, features = ["image"] } 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"] } 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_render_wgpu = { path = "../render/wgpu", features = ["clap"] }
ruffle_video_software = { path = "../video/software", optional = true } ruffle_video_software = { path = "../video/software", optional = true }
ruffle_video_external = { path = "../video/external", 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 = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }
tracing-appender = "0.2.3" tracing-appender = "0.2.3"

View File

@ -1,10 +1,8 @@
mod audio;
mod external_interface; mod external_interface;
mod fscommand; mod fscommand;
mod navigator; mod navigator;
mod ui; mod ui;
pub use audio::CpalAudioBackend;
pub use external_interface::DesktopExternalInterfaceProvider; pub use external_interface::DesktopExternalInterfaceProvider;
pub use fscommand::DesktopFSCommandProvider; pub use fscommand::DesktopFSCommandProvider;
pub use navigator::DesktopNavigatorInterface; pub use navigator::DesktopNavigatorInterface;

View File

@ -1,6 +1,6 @@
use crate::backends::{ use crate::backends::{
CpalAudioBackend, DesktopExternalInterfaceProvider, DesktopFSCommandProvider, DesktopExternalInterfaceProvider, DesktopFSCommandProvider, DesktopNavigatorInterface,
DesktopNavigatorInterface, DesktopUiBackend, DesktopUiBackend,
}; };
use crate::custom_event::RuffleEvent; use crate::custom_event::RuffleEvent;
use crate::gui::{FilePicker, MovieView}; use crate::gui::{FilePicker, MovieView};
@ -11,6 +11,7 @@ use ruffle_core::backend::navigator::{OpenURLMode, SocketMode};
use ruffle_core::config::Letterbox; use ruffle_core::config::Letterbox;
use ruffle_core::events::{GamepadButton, KeyCode}; use ruffle_core::events::{GamepadButton, KeyCode};
use ruffle_core::{DefaultFont, LoadBehavior, Player, PlayerBuilder, PlayerEvent}; 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::executor::{AsyncExecutor, PollRequester};
use ruffle_frontend_utils::backends::navigator::ExternalNavigatorBackend; use ruffle_frontend_utils::backends::navigator::ExternalNavigatorBackend;
use ruffle_frontend_utils::bundle::source::BundleSourceError; use ruffle_frontend_utils::bundle::source::BundleSourceError;
@ -134,7 +135,7 @@ impl ActivePlayer {
) -> Self { ) -> Self {
let mut builder = PlayerBuilder::new(); let mut builder = PlayerBuilder::new();
match CpalAudioBackend::new(&preferences) { match CpalAudioBackend::new(preferences.output_device_name().as_deref()) {
Ok(audio) => { Ok(audio) => {
builder = builder.with_audio(audio); builder = builder.with_audio(audio);
} }

View File

@ -10,12 +10,15 @@ version.workspace = true
[lints] [lints]
workspace = true workspace = true
[features]
cpal = ["dep:cpal", "dep:bytemuck"]
[dependencies] [dependencies]
toml_edit = { version = "0.22.20", features = ["parse"] } toml_edit = { version = "0.22.20", features = ["parse"] }
url = { workspace = true } url = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
thiserror = { 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" urlencoding = "2.1.3"
ruffle_core = { path = "../core", default-features = false } ruffle_core = { path = "../core", default-features = false }
ruffle_render = { path = "../render", default-features = false } ruffle_render = { path = "../render", default-features = false }
@ -23,8 +26,16 @@ async-channel = { workspace = true }
slotmap = { workspace = true } slotmap = { workspace = true }
async-io = "2.3.4" async-io = "2.3.4"
futures-lite = "2.3.0" 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"] } tokio = { workspace = true, features = ["net"] }
cpal = { workspace = true, optional = true }
bytemuck = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
tempfile = "3" tempfile = "3"

View File

@ -1,3 +1,5 @@
#[cfg(feature = "cpal")]
pub mod audio;
pub mod executor; pub mod executor;
pub mod navigator; pub mod navigator;
pub mod storage; pub mod storage;

View File

@ -1,12 +1,29 @@
use crate::preferences::GlobalPreferences;
use anyhow::{anyhow, Context, Error};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::SampleFormat;
use ruffle_core::backend::audio::{ use ruffle_core::backend::audio::{
swf, AudioBackend, AudioMixer, DecodeError, RegisterError, SoundHandle, SoundInstanceHandle, swf, AudioBackend, AudioMixer, DecodeError, RegisterError, SoundHandle, SoundInstanceHandle,
SoundStreamInfo, SoundTransform, SoundStreamInfo, SoundTransform,
}; };
use ruffle_core::impl_audio_mixer_backend; 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 { pub struct CpalAudioBackend {
#[allow(dead_code)] #[allow(dead_code)]
device: cpal::Device, device: cpal::Device,
@ -17,16 +34,16 @@ pub struct CpalAudioBackend {
} }
impl CpalAudioBackend { impl CpalAudioBackend {
pub fn new(preferences: &GlobalPreferences) -> Result<Self, Error> { pub fn new(preferred_device_name: Option<&str>) -> Result<Self, CpalError> {
// Create CPAL audio device. // Create CPAL audio device.
let host = cpal::default_host(); let host = cpal::default_host();
let device = get_suitable_output_device(preferences, &host) let device =
.ok_or_else(|| anyhow!("No audio devices available"))?; get_suitable_output_device(preferred_device_name, &host).ok_or(CpalError::NoDevices)?;
// Create audio stream for device. // Create audio stream for device.
let config = device let config = device
.default_output_config() .default_output_config()
.context("Failed to get default output config")?; .map_err(CpalError::DefaultStream)?;
let sample_format = config.sample_format(); let sample_format = config.sample_format();
let config = cpal::StreamConfig::from(config); let config = cpal::StreamConfig::from(config);
let mixer = AudioMixer::new(config.channels as u8, config.sample_rate.0); let mixer = AudioMixer::new(config.channels as u8, config.sample_rate.0);
@ -63,11 +80,11 @@ impl CpalAudioBackend {
error_handler, error_handler,
None, 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 { Ok(Self {
device, device,
@ -91,14 +108,14 @@ impl AudioBackend for CpalAudioBackend {
} }
fn get_suitable_output_device( fn get_suitable_output_device(
preferences: &GlobalPreferences, preferred_device_name: Option<&str>,
host: &cpal::Host, host: &cpal::Host,
) -> Option<cpal::Device> { ) -> Option<cpal::Device> {
// First let's check for any user preference... // 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 Ok(mut devices) = host.output_devices() {
if let Some(device) = 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); return Some(device);
} }