diff --git a/examples/set_buffer_size.rs b/examples/set_buffer_size.rs index 7e86ecd..7932dbb 100644 --- a/examples/set_buffer_size.rs +++ b/examples/set_buffer_size.rs @@ -1,51 +1,103 @@ -//! This example demonstrates how to request a specific buffer size from the CoreAudio backend. -//! Probably only works on macOS. +//! This example demonstrates how to request a specific buffer size from the audio backends. + +use interflow::channel_map::{ChannelMap32, CreateBitset}; +use interflow::prelude::*; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use util::sine::SineWave; mod util; -#[cfg(os_coreaudio)] fn main() -> anyhow::Result<()> { - use interflow::backends::coreaudio::CoreAudioDriver; - use interflow::channel_map::{ChannelMap32, CreateBitset}; - use interflow::prelude::*; - use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }; - use util::sine::SineWave; - - struct MyCallback { - first_callback: Arc, - sine_wave: SineWave, + #[cfg(target_os = "macos")] + { + use interflow::backends::coreaudio::CoreAudioDriver; + run(CoreAudioDriver) + } + #[cfg(target_os = "windows")] + { + use interflow::backends::wasapi::WasapiDriver; + run(WasapiDriver::default()) + } + #[cfg(all(target_os = "linux", not(feature = "pipewire")))] + { + use interflow::backends::alsa::AlsaDriver; + run(AlsaDriver::default()) } + #[cfg(all(target_os = "linux", feature = "pipewire"))] + { + use interflow::backends::pipewire::PipewireDriver; + run(PipewireDriver::new()?) + } + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] + { + println!("This example is not available on this platform."); + Ok(()) + } +} - impl AudioOutputCallback for MyCallback { - fn on_output_data(&mut self, context: AudioCallbackContext, mut output: AudioOutput) { - if self.first_callback.swap(false, Ordering::SeqCst) { - println!( - "Actual buffer size granted by OS: {}", - output.buffer.num_samples() - ); - } +struct MyCallback { + first_callback: Arc, + sine_wave: SineWave, +} + +impl AudioOutputCallback for MyCallback { + fn on_output_data(&mut self, context: AudioCallbackContext, mut output: AudioOutput) { + if self.first_callback.swap(false, Ordering::SeqCst) { + let (min_buf, max_buf) = context.stream_config.buffer_size_range; + let buffer_size = min_buf.unwrap_or(0); + println!("Actual buffer size granted by OS: {}", buffer_size); + println!( + "Period size (frames per callback): {}", + output.buffer.num_samples() + ); + } - for mut frame in output.buffer.as_interleaved_mut().rows_mut() { - let sample = self - .sine_wave - .next_sample(context.stream_config.samplerate as f32); - for channel_sample in &mut frame { - *channel_sample = sample; - } + for mut frame in output.buffer.as_interleaved_mut().rows_mut() { + let sample = self + .sine_wave + .next_sample(context.stream_config.samplerate as f32); + for channel_sample in &mut frame { + *channel_sample = sample; } } } +} +fn run(driver: D) -> anyhow::Result<()> +where + D: AudioDriver, + D::Device: AudioOutputDevice, + ::Error: std::error::Error + Send + Sync + 'static, + ::StreamHandle: AudioStreamHandle, + <::StreamHandle as AudioStreamHandle>::Error: + std::error::Error + Send + Sync + 'static, +{ env_logger::init(); - let driver = CoreAudioDriver; - let device = driver + let full_backend_name = std::any::type_name::(); + let backend_name = full_backend_name + .split("::") + .last() + .unwrap_or(full_backend_name); + println!("Using backend: {}", backend_name); + + let mut device = driver .default_device(DeviceType::OUTPUT) - .expect("Failed to query for default output device") - .expect("No default output device found on this system"); + .expect("Failed to query for default output device"); + + if device.is_none() { + println!("No default output device found, falling back to first available device."); + device = driver + .list_devices() + .expect("Failed to query for devices") + .into_iter() + .find(|d| d.device_type().contains(DeviceType::OUTPUT)); + } + + let device = device.expect("No output devices found on this system"); println!("Using device: {}", device.name()); @@ -64,7 +116,7 @@ fn main() -> anyhow::Result<()> { samplerate: 48000.0, channels: ChannelMap32::from_indices([0, 1]), buffer_size_range: (Some(requested_buffer_size), Some(requested_buffer_size)), - exclusive: false, + exclusive: true, }; let callback = MyCallback { @@ -80,8 +132,3 @@ fn main() -> anyhow::Result<()> { stream.eject()?; Ok(()) } - -#[cfg(not(os_coreaudio))] -fn main() { - println!("This example is only available on platforms that support CoreAudio (e.g. macOS)."); -} diff --git a/src/backends/alsa/device.rs b/src/backends/alsa/device.rs index 509b3b8..a15a8e8 100644 --- a/src/backends/alsa/device.rs +++ b/src/backends/alsa/device.rs @@ -57,6 +57,15 @@ impl AudioDevice for AlsaDevice { log::info!("TODO: enumerate configurations"); None::<[StreamConfig; 0]> } + + fn buffer_size_range(&self) -> Result<(Option, Option), Self::Error> { + // Query ALSA hardware parameters for the buffer size range. + let hwp = pcm::HwParams::any(&self.pcm)?; + Ok(( + Some(hwp.get_buffer_size_min()? as usize), + Some(hwp.get_buffer_size_max()? as usize), + )) + } } impl AudioInputDevice for AlsaDevice { @@ -116,12 +125,12 @@ impl AlsaDevice { let hwp = pcm::HwParams::any(&self.pcm)?; hwp.set_channels(config.channels as _)?; hwp.set_rate(config.samplerate as _, alsa::ValueOr::Nearest)?; - if let Some(min) = config.buffer_size_range.0 { - hwp.set_buffer_size_min(min as _)?; - } - if let Some(max) = config.buffer_size_range.1 { - hwp.set_buffer_size_max(max as _)?; + + if let Some(buffer_size) = config.buffer_size_range.0.or(config.buffer_size_range.1) { + let buffer_size = hwp.set_buffer_size_near(buffer_size as i64)?; + hwp.set_period_size_near(buffer_size / 2, alsa::ValueOr::Nearest)?; } + hwp.set_format(pcm::Format::float())?; hwp.set_access(pcm::Access::RWInterleaved)?; Ok(hwp) @@ -139,7 +148,9 @@ impl AlsaDevice { log::debug!("Apply config: hwp {hwp:#?}"); - swp.set_start_threshold(hwp.get_buffer_size()?)?; + let (_, period_size) = self.pcm.get_params()?; + swp.set_avail_min(period_size as i64)?; + swp.set_start_threshold(period_size as i64)?; self.pcm.sw_params(&swp)?; log::debug!("Apply config: swp {swp:#?}"); @@ -153,7 +164,7 @@ impl AlsaDevice { Ok(StreamConfig { samplerate: samplerate as _, channels, - buffer_size_range: (None, None), + buffer_size_range: self.buffer_size_range()?, exclusive: false, }) } diff --git a/src/backends/alsa/stream.rs b/src/backends/alsa/stream.rs index ac649c1..11054f7 100644 --- a/src/backends/alsa/stream.rs +++ b/src/backends/alsa/stream.rs @@ -60,8 +60,10 @@ impl AlsaStream { buf }; let (hwp, _, io) = device.apply_config(&stream_config)?; - let (_, period_size) = device.pcm.get_params()?; + let (buffer_size, period_size) = device.pcm.get_params()?; + let buffer_size = buffer_size as usize; let period_size = period_size as usize; + log::info!("Buffer size : {buffer_size}"); log::info!("Period size : {period_size}"); let num_channels = hwp.get_channels()? as usize; log::info!("Num channels: {num_channels}"); @@ -71,7 +73,7 @@ impl AlsaStream { samplerate, channels: ChannelMap32::default() .with_indices(std::iter::repeat(1).take(num_channels)), - buffer_size_range: (Some(period_size), Some(period_size)), + buffer_size_range: (Some(buffer_size), Some(buffer_size)), exclusive: false, }; let mut timestamp = Timestamp::new(samplerate); diff --git a/src/backends/coreaudio.rs b/src/backends/coreaudio.rs index 93dd7a6..67747cf 100644 --- a/src/backends/coreaudio.rs +++ b/src/backends/coreaudio.rs @@ -278,7 +278,7 @@ impl AudioInputDevice for CoreAudioDevice { Ok(StreamConfig { channels: 0b11, samplerate, - buffer_size_range: self.buffer_size_range()?, + buffer_size_range: (None, None), exclusive: false, }) } diff --git a/src/backends/pipewire/device.rs b/src/backends/pipewire/device.rs index 18c9bef..178b421 100644 --- a/src/backends/pipewire/device.rs +++ b/src/backends/pipewire/device.rs @@ -67,6 +67,22 @@ impl AudioDevice for PipewireDevice { fn enumerate_configurations(&self) -> Option> { Some([]) } + + fn buffer_size_range(&self) -> Result<(Option, Option), Self::Error> { + // The buffer size range is stored in a string property from the ALSA compatibility API. + Ok(self + .properties()? + .map(|props| { + let min = props + .get("default.clock.min-quantum") + .and_then(|s| s.parse().ok()); + let max = props + .get("default.clock.max-quantum") + .and_then(|s| s.parse().ok()); + (min, max) + }) + .unwrap_or((None, None))) + } } impl AudioInputDevice for PipewireDevice { @@ -104,7 +120,7 @@ impl AudioOutputDevice for PipewireDevice { samplerate: 48000.0, channels: 0b11, exclusive: false, - buffer_size_range: (None, None), + buffer_size_range: self.buffer_size_range()?, }) } diff --git a/src/backends/pipewire/mod.rs b/src/backends/pipewire/mod.rs index f571420..c871392 100644 --- a/src/backends/pipewire/mod.rs +++ b/src/backends/pipewire/mod.rs @@ -3,3 +3,5 @@ pub mod driver; pub mod error; pub mod stream; mod utils; + +pub use driver::PipewireDriver; diff --git a/src/backends/pipewire/stream.rs b/src/backends/pipewire/stream.rs index d24cd41..b74dfe7 100644 --- a/src/backends/pipewire/stream.rs +++ b/src/backends/pipewire/stream.rs @@ -9,7 +9,6 @@ use crate::{ use libspa::buffer::Data; use libspa::param::audio::{AudioFormat, AudioInfoRaw}; use libspa::pod::Pod; -use libspa::utils::Direction; use libspa_sys::{SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format}; use pipewire::context::Context; use pipewire::keys; diff --git a/src/lib.rs b/src/lib.rs index d76ad36..ff15c34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,6 @@ use bitflags::bitflags; use std::borrow::Cow; -use std::fmt; -use std::fmt::Formatter; use crate::audio_buffer::{AudioMut, AudioRef}; use crate::channel_map::ChannelMap32;