Skip to content

Commit c6af26e

Browse files
committed
Implement buffer_size_range for pipewire, alsa, and wasapi & update
example
1 parent 8388334 commit c6af26e

8 files changed

Lines changed: 159 additions & 51 deletions

File tree

examples/set_buffer_size.rs

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,86 @@
1-
//! This example demonstrates how to request a specific buffer size from the CoreAudio backend.
2-
//! Probably only works on macOS.
1+
//! This example demonstrates how to request a specific buffer size from the audio backends.
2+
3+
use interflow::channel_map::{ChannelMap32, CreateBitset};
4+
use interflow::prelude::*;
5+
use std::sync::{
6+
atomic::{AtomicBool, Ordering},
7+
Arc,
8+
};
9+
use util::sine::SineWave;
310

411
mod util;
512

6-
#[cfg(os_coreaudio)]
713
fn main() -> anyhow::Result<()> {
8-
use interflow::backends::coreaudio::CoreAudioDriver;
9-
use interflow::channel_map::{ChannelMap32, CreateBitset};
10-
use interflow::prelude::*;
11-
use std::sync::{
12-
atomic::{AtomicBool, Ordering},
13-
Arc,
14-
};
15-
use util::sine::SineWave;
16-
17-
struct MyCallback {
18-
first_callback: Arc<AtomicBool>,
19-
sine_wave: SineWave,
14+
#[cfg(target_os = "macos")]
15+
{
16+
use interflow::backends::coreaudio::CoreAudioDriver;
17+
run(CoreAudioDriver)
18+
}
19+
#[cfg(target_os = "windows")]
20+
{
21+
use interflow::backends::wasapi::WasapiDriver;
22+
run(WasapiDriver::default())
2023
}
24+
#[cfg(all(target_os = "linux", not(feature = "pipewire")))]
25+
{
26+
use interflow::backends::alsa::AlsaDriver;
27+
run(AlsaDriver::default())
28+
}
29+
#[cfg(all(target_os = "linux", feature = "pipewire"))]
30+
{
31+
use interflow::backends::pipewire::PipewireDriver;
32+
run(PipewireDriver::new()?)
33+
}
34+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
35+
{
36+
println!("This example is not available on this platform.");
37+
Ok(())
38+
}
39+
}
2140

22-
impl AudioOutputCallback for MyCallback {
23-
fn on_output_data(&mut self, context: AudioCallbackContext, mut output: AudioOutput<f32>) {
24-
if self.first_callback.swap(false, Ordering::SeqCst) {
25-
println!(
26-
"Actual buffer size granted by OS: {}",
27-
output.buffer.num_samples()
28-
);
29-
}
41+
struct MyCallback {
42+
first_callback: Arc<AtomicBool>,
43+
sine_wave: SineWave,
44+
}
45+
46+
impl AudioOutputCallback for MyCallback {
47+
fn on_output_data(&mut self, context: AudioCallbackContext, mut output: AudioOutput<f32>) {
48+
if self.first_callback.swap(false, Ordering::SeqCst) {
49+
println!(
50+
"Actual buffer size granted by OS: {}",
51+
output.buffer.num_samples()
52+
);
53+
}
3054

31-
for mut frame in output.buffer.as_interleaved_mut().rows_mut() {
32-
let sample = self
33-
.sine_wave
34-
.next_sample(context.stream_config.samplerate as f32);
35-
for channel_sample in &mut frame {
36-
*channel_sample = sample;
37-
}
55+
for mut frame in output.buffer.as_interleaved_mut().rows_mut() {
56+
let sample = self
57+
.sine_wave
58+
.next_sample(context.stream_config.samplerate as f32);
59+
for channel_sample in &mut frame {
60+
*channel_sample = sample;
3861
}
3962
}
4063
}
64+
}
4165

66+
fn run<D>(driver: D) -> anyhow::Result<()>
67+
where
68+
D: AudioDriver,
69+
D::Device: AudioOutputDevice,
70+
<D::Device as AudioDevice>::Error: std::error::Error + Send + Sync + 'static,
71+
<D::Device as AudioOutputDevice>::StreamHandle<MyCallback>: AudioStreamHandle<MyCallback>,
72+
<<D::Device as AudioOutputDevice>::StreamHandle<MyCallback> as AudioStreamHandle<MyCallback>>::Error:
73+
std::error::Error + Send + Sync + 'static,
74+
{
4275
env_logger::init();
4376

44-
let driver = CoreAudioDriver;
77+
let full_backend_name = std::any::type_name::<D>();
78+
let backend_name = full_backend_name
79+
.split("::")
80+
.last()
81+
.unwrap_or(full_backend_name);
82+
println!("Using backend: {}", backend_name);
83+
4584
let device = driver
4685
.default_device(DeviceType::OUTPUT)
4786
.expect("Failed to query for default output device")
@@ -80,8 +119,3 @@ fn main() -> anyhow::Result<()> {
80119
stream.eject()?;
81120
Ok(())
82121
}
83-
84-
#[cfg(not(os_coreaudio))]
85-
fn main() {
86-
println!("This example is only available on platforms that support CoreAudio (e.g. macOS).");
87-
}

src/backends/alsa/device.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ impl AudioDevice for AlsaDevice {
5757
log::info!("TODO: enumerate configurations");
5858
None::<[StreamConfig; 0]>
5959
}
60+
61+
fn buffer_size_range(&self) -> Result<(Option<usize>, Option<usize>), Self::Error> {
62+
// Query ALSA hardware parameters for the buffer size range.
63+
let hwp = pcm::HwParams::any(&self.pcm)?;
64+
Ok((
65+
Some(hwp.get_buffer_size_min()? as usize),
66+
Some(hwp.get_buffer_size_max()? as usize),
67+
))
68+
}
6069
}
6170

6271
impl AudioInputDevice for AlsaDevice {
@@ -153,7 +162,7 @@ impl AlsaDevice {
153162
Ok(StreamConfig {
154163
samplerate: samplerate as _,
155164
channels,
156-
buffer_size_range: (None, None),
165+
buffer_size_range: self.buffer_size_range()?,
157166
exclusive: false,
158167
})
159168
}

src/backends/pipewire/device.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,24 @@ impl AudioDevice for PipewireDevice {
6767
fn enumerate_configurations(&self) -> Option<impl IntoIterator<Item = StreamConfig>> {
6868
Some([])
6969
}
70+
71+
fn buffer_size_range(&self) -> Result<(Option<usize>, Option<usize>), Self::Error> {
72+
// The buffer size range is stored in a string property from the ALSA compatibility API.
73+
Ok(self
74+
.properties()?
75+
.map(|props| {
76+
props
77+
.get("api.alsa.period-size-range")
78+
.map(|range_str| {
79+
let mut parts = range_str.split_whitespace();
80+
let min = parts.next().and_then(|s| s.parse().ok());
81+
let max = parts.next().and_then(|s| s.parse().ok());
82+
(min, max)
83+
})
84+
.unwrap_or((None, None))
85+
})
86+
.unwrap_or((None, None)))
87+
}
7088
}
7189

7290
impl AudioInputDevice for PipewireDevice {
@@ -77,7 +95,7 @@ impl AudioInputDevice for PipewireDevice {
7795
samplerate: 48000.0,
7896
channels: 0b11,
7997
exclusive: false,
80-
buffer_size_range: (None, None),
98+
buffer_size_range: self.buffer_size_range()?,
8199
})
82100
}
83101

@@ -104,7 +122,7 @@ impl AudioOutputDevice for PipewireDevice {
104122
samplerate: 48000.0,
105123
channels: 0b11,
106124
exclusive: false,
107-
buffer_size_range: (None, None),
125+
buffer_size_range: self.buffer_size_range()?,
108126
})
109127
}
110128

src/backends/pipewire/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ pub mod driver;
33
pub mod error;
44
pub mod stream;
55
mod utils;
6+
7+
pub use driver::PipewireDriver;

src/backends/pipewire/stream.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use crate::{
99
use libspa::buffer::Data;
1010
use libspa::param::audio::{AudioFormat, AudioInfoRaw};
1111
use libspa::pod::Pod;
12-
use libspa::utils::Direction;
1312
use libspa_sys::{SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format};
1413
use pipewire::context::Context;
1514
use pipewire::keys;

src/backends/wasapi/device.rs

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
Channel, DeviceType, StreamConfig,
88
};
99
use std::borrow::Cow;
10+
use windows::core::Interface;
1011
use windows::Win32::Media::Audio;
1112

1213
/// Type of devices available from the WASAPI driver.
@@ -54,6 +55,59 @@ impl AudioDevice for WasapiDevice {
5455
fn enumerate_configurations(&self) -> Option<impl IntoIterator<Item = StreamConfig>> {
5556
None::<[StreamConfig; 0]>
5657
}
58+
59+
fn buffer_size_range(&self) -> Result<(Option<usize>, Option<usize>), Self::Error> {
60+
let audio_client = self.device.activate::<Audio::IAudioClient>()?;
61+
let format = unsafe { audio_client.GetMixFormat()?.read_unaligned() };
62+
let samplerate = format.nSamplesPerSec;
63+
64+
// Attempt IAudioClient3/IAudioClient2 to get the buffer size range.
65+
if let Ok(client) = audio_client.cast::<Audio::IAudioClient3>() {
66+
let mut min_buffer_duration = 0;
67+
let mut max_buffer_duration = 0;
68+
// Based on the stream implementation, we assume event driven mode.
69+
let event_driven = true;
70+
unsafe {
71+
client.GetBufferSizeLimits(
72+
&format,
73+
event_driven.into(),
74+
&mut min_buffer_duration,
75+
&mut max_buffer_duration,
76+
)?;
77+
}
78+
// Convert from 100-nanosecond units to frames.
79+
let to_frames = |period| (period as u64 * samplerate as u64 / 10_000_000) as usize;
80+
return Ok((
81+
Some(to_frames(min_buffer_duration)),
82+
Some(to_frames(max_buffer_duration)),
83+
));
84+
}
85+
if let Ok(client) = audio_client.cast::<Audio::IAudioClient2>() {
86+
let mut min_buffer_duration = 0;
87+
let mut max_buffer_duration = 0;
88+
let event_driven = true;
89+
unsafe {
90+
client.GetBufferSizeLimits(
91+
&format,
92+
event_driven.into(),
93+
&mut min_buffer_duration,
94+
&mut max_buffer_duration,
95+
)?;
96+
}
97+
let to_frames = |period| (period as u64 * samplerate as u64 / 10_000_000) as usize;
98+
return Ok((
99+
Some(to_frames(min_buffer_duration)),
100+
Some(to_frames(max_buffer_duration)),
101+
));
102+
}
103+
104+
// Fallback to GetBufferSize for older WASAPI versions.
105+
let frame_size = unsafe { audio_client.GetBufferSize() }.ok();
106+
Ok((
107+
frame_size.map(|v| v as usize),
108+
frame_size.map(|v| v as usize),
109+
))
110+
}
57111
}
58112

59113
impl AudioInputDevice for WasapiDevice {
@@ -62,14 +116,11 @@ impl AudioInputDevice for WasapiDevice {
62116
fn default_input_config(&self) -> Result<StreamConfig, Self::Error> {
63117
let audio_client = self.device.activate::<Audio::IAudioClient>()?;
64118
let format = unsafe { audio_client.GetMixFormat()?.read_unaligned() };
65-
let frame_size = unsafe { audio_client.GetBufferSize() }
66-
.map(|i| i as usize)
67-
.ok();
68119
Ok(StreamConfig {
69120
channels: 0u32.with_indices(0..format.nChannels as _),
70121
exclusive: false,
71122
samplerate: format.nSamplesPerSec as _,
72-
buffer_size_range: (frame_size, frame_size),
123+
buffer_size_range: self.buffer_size_range()?,
73124
})
74125
}
75126

@@ -92,14 +143,11 @@ impl AudioOutputDevice for WasapiDevice {
92143
fn default_output_config(&self) -> Result<StreamConfig, Self::Error> {
93144
let audio_client = self.device.activate::<Audio::IAudioClient>()?;
94145
let format = unsafe { audio_client.GetMixFormat()?.read_unaligned() };
95-
let frame_size = unsafe { audio_client.GetBufferSize() }
96-
.map(|i| i as usize)
97-
.ok();
98146
Ok(StreamConfig {
99147
channels: 0u32.with_indices(0..format.nChannels as _),
100148
exclusive: false,
101149
samplerate: format.nSamplesPerSec as _,
102-
buffer_size_range: (frame_size, frame_size),
150+
buffer_size_range: self.buffer_size_range()?,
103151
})
104152
}
105153

src/backends/wasapi/stream.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ pub(crate) fn is_output_config_supported(
483483
device: WasapiMMDevice,
484484
stream_config: &StreamConfig,
485485
) -> bool {
486-
let mut try_ = || unsafe {
486+
let try_ = || unsafe {
487487
let audio_client: Audio::IAudioClient = device.activate()?;
488488
let sharemode = if stream_config.exclusive {
489489
Audio::AUDCLNT_SHAREMODE_EXCLUSIVE

src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
use bitflags::bitflags;
55
use std::borrow::Cow;
6-
use std::fmt;
7-
use std::fmt::Formatter;
86

97
use crate::audio_buffer::{AudioMut, AudioRef};
108
use crate::channel_map::ChannelMap32;

0 commit comments

Comments
 (0)