Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
106 changes: 70 additions & 36 deletions examples/set_buffer_size.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,86 @@
//! 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) {
println!(
"Actual buffer size granted by OS: {}",
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 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 device = driver
.default_device(DeviceType::OUTPUT)
.expect("Failed to query for default output device")
Expand Down Expand Up @@ -80,8 +119,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).");
}
11 changes: 10 additions & 1 deletion 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 @@ -153,7 +162,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
Copy Markdown
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
Copy Markdown
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
22 changes: 20 additions & 2 deletions src/backends/pipewire/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ 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| {
props
.get("api.alsa.period-size-range")
.map(|range_str| {
let mut parts = range_str.split_whitespace();
let min = parts.next().and_then(|s| s.parse().ok());
let max = parts.next().and_then(|s| s.parse().ok());
(min, max)
})
.unwrap_or((None, None))
})
.unwrap_or((None, None)))
}
}

impl AudioInputDevice for PipewireDevice {
Expand All @@ -77,7 +95,7 @@ impl AudioInputDevice for PipewireDevice {
samplerate: 48000.0,
channels: 0b11,
exclusive: false,
buffer_size_range: (None, None),
buffer_size_range: self.buffer_size_range()?,
})
}

Expand All @@ -104,7 +122,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
64 changes: 56 additions & 8 deletions src/backends/wasapi/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
Channel, DeviceType, StreamConfig,
};
use std::borrow::Cow;
use windows::core::Interface;
use windows::Win32::Media::Audio;

/// Type of devices available from the WASAPI driver.
Expand Down Expand Up @@ -54,6 +55,59 @@ impl AudioDevice for WasapiDevice {
fn enumerate_configurations(&self) -> Option<impl IntoIterator<Item = StreamConfig>> {
None::<[StreamConfig; 0]>
}

fn buffer_size_range(&self) -> Result<(Option<usize>, Option<usize>), Self::Error> {
let audio_client = self.device.activate::<Audio::IAudioClient>()?;
let format = unsafe { audio_client.GetMixFormat()?.read_unaligned() };
let samplerate = format.nSamplesPerSec;

// Attempt IAudioClient3/IAudioClient2 to get the buffer size range.
if let Ok(client) = audio_client.cast::<Audio::IAudioClient3>() {
let mut min_buffer_duration = 0;
let mut max_buffer_duration = 0;
// Based on the stream implementation, we assume event driven mode.
let event_driven = true;
unsafe {
client.GetBufferSizeLimits(
&format,
event_driven.into(),
&mut min_buffer_duration,
&mut max_buffer_duration,
)?;
}
// Convert from 100-nanosecond units to frames.
let to_frames = |period| (period as u64 * samplerate as u64 / 10_000_000) as usize;
return Ok((
Some(to_frames(min_buffer_duration)),
Some(to_frames(max_buffer_duration)),
));
}
if let Ok(client) = audio_client.cast::<Audio::IAudioClient2>() {
let mut min_buffer_duration = 0;
let mut max_buffer_duration = 0;
let event_driven = true;
unsafe {
client.GetBufferSizeLimits(
&format,
event_driven.into(),
&mut min_buffer_duration,
&mut max_buffer_duration,
)?;
}
let to_frames = |period| (period as u64 * samplerate as u64 / 10_000_000) as usize;
return Ok((
Some(to_frames(min_buffer_duration)),
Some(to_frames(max_buffer_duration)),
));
}

// Fallback to GetBufferSize for older WASAPI versions.
let frame_size = unsafe { audio_client.GetBufferSize() }.ok();
Ok((
frame_size.map(|v| v as usize),
frame_size.map(|v| v as usize),
))
}
}

impl AudioInputDevice for WasapiDevice {
Expand All @@ -62,14 +116,11 @@ impl AudioInputDevice for WasapiDevice {
fn default_input_config(&self) -> Result<StreamConfig, Self::Error> {
let audio_client = self.device.activate::<Audio::IAudioClient>()?;
let format = unsafe { audio_client.GetMixFormat()?.read_unaligned() };
let frame_size = unsafe { audio_client.GetBufferSize() }
.map(|i| i as usize)
.ok();
Ok(StreamConfig {
channels: 0u32.with_indices(0..format.nChannels as _),
exclusive: false,
samplerate: format.nSamplesPerSec as _,
buffer_size_range: (frame_size, frame_size),
buffer_size_range: self.buffer_size_range()?,
})
}

Expand All @@ -92,14 +143,11 @@ impl AudioOutputDevice for WasapiDevice {
fn default_output_config(&self) -> Result<StreamConfig, Self::Error> {
let audio_client = self.device.activate::<Audio::IAudioClient>()?;
let format = unsafe { audio_client.GetMixFormat()?.read_unaligned() };
let frame_size = unsafe { audio_client.GetBufferSize() }
.map(|i| i as usize)
.ok();
Ok(StreamConfig {
channels: 0u32.with_indices(0..format.nChannels as _),
exclusive: false,
samplerate: format.nSamplesPerSec as _,
buffer_size_range: (frame_size, frame_size),
buffer_size_range: self.buffer_size_range()?,
})
}

Expand Down
2 changes: 1 addition & 1 deletion src/backends/wasapi/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ pub(crate) fn is_output_config_supported(
device: WasapiMMDevice,
stream_config: &StreamConfig,
) -> bool {
let mut try_ = || unsafe {
let try_ = || unsafe {
let audio_client: Audio::IAudioClient = device.activate()?;
let sharemode = if stream_config.exclusive {
Audio::AUDCLNT_SHAREMODE_EXCLUSIVE
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