Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 87 additions & 40 deletions examples/set_buffer_size.rs
Original file line number Diff line number Diff line change
@@ -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<AtomicBool>,
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<f32>) {
if self.first_callback.swap(false, Ordering::SeqCst) {
println!(
"Actual buffer size granted by OS: {}",
output.buffer.num_samples()
);
}
struct MyCallback {
first_callback: Arc<AtomicBool>,
sine_wave: SineWave,
}

impl AudioOutputCallback for MyCallback {
fn on_output_data(&mut self, context: AudioCallbackContext, mut output: AudioOutput<f32>) {
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<D>(driver: D) -> anyhow::Result<()>
where
D: AudioDriver,
D::Device: AudioOutputDevice,
<D::Device as AudioDevice>::Error: std::error::Error + Send + Sync + 'static,
<D::Device as AudioOutputDevice>::StreamHandle<MyCallback>: AudioStreamHandle<MyCallback>,
<<D::Device as AudioOutputDevice>::StreamHandle<MyCallback> as AudioStreamHandle<MyCallback>>::Error:
std::error::Error + Send + Sync + 'static,
{
env_logger::init();

let driver = CoreAudioDriver;
let device = driver
let full_backend_name = std::any::type_name::<D>();
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());

Expand All @@ -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 {
Expand All @@ -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).");
}
25 changes: 18 additions & 7 deletions src/backends/alsa/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ impl AudioDevice for AlsaDevice {
log::info!("TODO: enumerate configurations");
None::<[StreamConfig; 0]>
}

fn buffer_size_range(&self) -> Result<(Option<usize>, Option<usize>), 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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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:#?}");

Expand All @@ -153,7 +164,7 @@ impl AlsaDevice {
Ok(StreamConfig {
samplerate: samplerate as _,
channels,
buffer_size_range: (None, None),
buffer_size_range: self.buffer_size_range()?,
Comment on lines -156 to +167
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to avoid doing an API lookup here, (None, None) already means "the entire supported buffer range". People that need the data can themselves call buffer_size_range() if they want to.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also valid for the PipeWire and WASAPI backends

exclusive: false,
})
}
Expand Down
6 changes: 4 additions & 2 deletions src/backends/alsa/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ impl<Callback: 'static + Send> AlsaStream<Callback> {
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}");
Expand All @@ -71,7 +73,7 @@ impl<Callback: 'static + Send> AlsaStream<Callback> {
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);
Expand Down
2 changes: 1 addition & 1 deletion src/backends/coreaudio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
Expand Down
18 changes: 17 additions & 1 deletion src/backends/pipewire/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ impl AudioDevice for PipewireDevice {
fn enumerate_configurations(&self) -> Option<impl IntoIterator<Item = StreamConfig>> {
Some([])
}

fn buffer_size_range(&self) -> Result<(Option<usize>, Option<usize>), 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 {
Expand Down Expand Up @@ -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()?,
})
}

Expand Down
2 changes: 2 additions & 0 deletions src/backends/pipewire/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ pub mod driver;
pub mod error;
pub mod stream;
mod utils;

pub use driver::PipewireDriver;
1 change: 0 additions & 1 deletion src/backends/pipewire/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 0 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading