Skip to content

Commit 55589ec

Browse files
authored
refactor: make buffer_size() return Result (#1142)
1 parent 3e3bfe7 commit 55589ec

20 files changed

Lines changed: 134 additions & 73 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4242
- **ASIO**: Timestamps now include driver-reported hardware latency.
4343
- **ASIO**: Hardware latency is now re-queried when the driver reports `kAsioLatenciesChanged`.
4444
- **ASIO**: Stream error callback now receives `StreamError::BufferUnderrun` on `kAsioResyncRequest`.
45-
- **ASIO**: Stream error callback now receives `StreamError::StreamInvalidated` when the driver reports a sample rate change (`sampleRateDidChange`) of 1 Hz or more from the configured rate.
45+
- **ASIO**: Stream error callback now receives `StreamError::StreamInvalidated` when the driver reports a sample
46+
rate change (`sampleRateDidChange`) of 1 Hz or more from the configured rate.
47+
- **AudioWorklet**: `BufferSize::Fixed` now sets `renderSizeHint` on the `AudioContext`.
4648
- **CoreAudio**: Timestamps now include device latency and safety offset.
4749
- **JACK**: Timestamps now use the precise hardware deadline.
4850
- **Linux/BSD**: Default host in order from first to last available now is: PipeWire, PulseAudio, ALSA.

examples/custom.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ struct StreamControls {
3333
pause: AtomicBool,
3434
}
3535

36+
const CHANNEL_COUNT: cpal::ChannelCount = 2;
37+
const BUFFER_SIZE: cpal::FrameCount = 4096;
38+
3639
impl HostTrait for MyHost {
3740
type Device = MyDevice;
3841
type Devices = std::iter::Once<MyDevice>;
@@ -81,10 +84,13 @@ impl DeviceTrait for MyDevice {
8184
&self,
8285
) -> Result<Self::SupportedOutputConfigs, cpal::SupportedStreamConfigsError> {
8386
Ok(std::iter::once(cpal::SupportedStreamConfigRange::new(
84-
2,
87+
CHANNEL_COUNT,
8588
44100,
8689
44100,
87-
cpal::SupportedBufferSize::Unknown,
90+
cpal::SupportedBufferSize::Range {
91+
min: BUFFER_SIZE,
92+
max: BUFFER_SIZE,
93+
},
8894
cpal::SampleFormat::F32,
8995
)))
9096
}
@@ -99,9 +105,12 @@ impl DeviceTrait for MyDevice {
99105
&self,
100106
) -> Result<cpal::SupportedStreamConfig, cpal::DefaultStreamConfigError> {
101107
Ok(cpal::SupportedStreamConfig::new(
102-
2,
108+
CHANNEL_COUNT,
103109
44100,
104-
cpal::SupportedBufferSize::Unknown,
110+
cpal::SupportedBufferSize::Range {
111+
min: BUFFER_SIZE,
112+
max: BUFFER_SIZE,
113+
},
105114
cpal::SampleFormat::I16,
106115
))
107116
}
@@ -145,7 +154,7 @@ impl DeviceTrait for MyDevice {
145154
let start = Instant::now();
146155
let thread_controls = controls.clone();
147156
let handle = std::thread::spawn(move || {
148-
let mut buffer = [0.0_f32; 4096];
157+
let mut buffer = [0.0_f32; BUFFER_SIZE as usize * CHANNEL_COUNT as usize];
149158
while !thread_controls.exit.load(Ordering::Relaxed) {
150159
std::thread::sleep(std::time::Duration::from_secs_f32(
151160
buffer.len() as f32 / 44100.0,
@@ -203,6 +212,10 @@ impl StreamTrait for MyStream {
203212
let elapsed = self.start.elapsed();
204213
cpal::StreamInstant::new(elapsed.as_secs(), elapsed.subsec_nanos())
205214
}
215+
216+
fn buffer_size(&self) -> Result<cpal::FrameCount, cpal::StreamError> {
217+
Ok(BUFFER_SIZE)
218+
}
206219
}
207220

208221
// streams are expected to stop when dropped

src/host/aaudio/mod.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -721,19 +721,15 @@ impl StreamTrait for Stream {
721721
now_stream_instant()
722722
}
723723

724-
fn buffer_size(&self) -> Option<crate::FrameCount> {
725-
let stream = self.inner.lock().ok()?;
724+
fn buffer_size(&self) -> Result<crate::FrameCount, crate::StreamError> {
725+
let stream = self.inner.lock().unwrap();
726726

727727
// frames_per_data_callback is only set for BufferSize::Fixed; for Default AAudio
728728
// schedules callbacks at the burst size, so that is the best available estimate.
729729
let frames = match stream.frames_per_data_callback() {
730730
Some(size) if size > 0 => size,
731731
_ => stream.frames_per_burst(),
732732
};
733-
if frames > 0 {
734-
Some(frames as crate::FrameCount)
735-
} else {
736-
None
737-
}
733+
Ok(frames as crate::FrameCount)
738734
}
739735
}

src/host/alsa/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,8 +1289,8 @@ impl StreamTrait for Stream {
12891289
.expect("stream duration exceeded `StreamInstant` range")
12901290
}
12911291

1292-
fn buffer_size(&self) -> Option<FrameCount> {
1293-
Some(self.inner.period_frames as FrameCount)
1292+
fn buffer_size(&self) -> Result<FrameCount, crate::StreamError> {
1293+
Ok(self.inner.period_frames as FrameCount)
12941294
}
12951295
}
12961296

src/host/asio/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ impl StreamTrait for Stream {
160160
Stream::now(self)
161161
}
162162

163-
fn buffer_size(&self) -> Option<crate::FrameCount> {
163+
fn buffer_size(&self) -> Result<crate::FrameCount, crate::StreamError> {
164164
Stream::buffer_size(self)
165165
}
166166
}

src/host/asio/stream.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,14 @@ impl Stream {
8181
Ok(())
8282
}
8383

84-
pub fn buffer_size(&self) -> Option<crate::FrameCount> {
85-
let streams = self.asio_streams.lock().ok()?;
86-
streams
84+
pub fn buffer_size(&self) -> Result<crate::FrameCount, crate::StreamError> {
85+
let streams = self.asio_streams.lock().unwrap();
86+
Ok(streams
8787
.output
8888
.as_ref()
8989
.or(streams.input.as_ref())
90-
.map(|s| s.buffer_size as crate::FrameCount)
90+
.expect("ASIO stream has neither input nor output")
91+
.buffer_size as crate::FrameCount)
9192
}
9293
}
9394

src/host/audioworklet/mod.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
66
mod dependent_module;
77
use js_sys::wasm_bindgen;
8+
use std::sync::{
9+
atomic::{AtomicU64, Ordering},
10+
Arc,
11+
};
812

913
use crate::dependent_module;
1014
use wasm_bindgen::prelude::*;
@@ -30,6 +34,7 @@ pub struct Host;
3034

3135
pub struct Stream {
3236
audio_context: web_sys::AudioContext,
37+
buffer_size_frames: Arc<AtomicU64>,
3338
}
3439

3540
pub use crate::iter::{SupportedInputConfigs, SupportedOutputConfigs};
@@ -41,6 +46,9 @@ const MAX_SAMPLE_RATE: SampleRate = 96_000;
4146
const DEFAULT_SAMPLE_RATE: SampleRate = 44_100;
4247
const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
4348

49+
// https://webaudio.github.io/web-audio-api/#render-quantum-size
50+
const DEFAULT_RENDER_SIZE: u64 = 128;
51+
4452
impl Host {
4553
pub fn new() -> Result<Self, crate::HostUnavailable> {
4654
if Self::is_available() {
@@ -195,6 +203,13 @@ impl DeviceTrait for Device {
195203

196204
let stream_opts = web_sys::AudioContextOptions::new();
197205
stream_opts.set_sample_rate(config.sample_rate as f32);
206+
if let crate::BufferSize::Fixed(n) = config.buffer_size {
207+
let _ = js_sys::Reflect::set(
208+
stream_opts.as_ref(),
209+
&JsValue::from_str("renderSizeHint"),
210+
&JsValue::from_f64(n as f64),
211+
);
212+
}
198213

199214
let audio_context = web_sys::AudioContext::new_with_context_options(&stream_opts).map_err(
200215
|err| -> BuildStreamError {
@@ -213,6 +228,12 @@ impl DeviceTrait for Device {
213228
destination.set_channel_count(config.channels as u32);
214229
}
215230

231+
let initial_quantum = match config.buffer_size {
232+
crate::BufferSize::Fixed(n) => n as u64,
233+
crate::BufferSize::Default => DEFAULT_RENDER_SIZE,
234+
};
235+
let buffer_size_frames = Arc::new(AtomicU64::new(initial_quantum));
236+
let buffer_size_frames_cb = buffer_size_frames.clone();
216237
let ctx = audio_context.clone();
217238
wasm_bindgen_futures::spawn_local(async move {
218239
let result: Result<(), JsValue> = async move {
@@ -255,6 +276,7 @@ impl DeviceTrait for Device {
255276
&wasm_bindgen::memory(),
256277
&WasmAudioProcessor::new(Box::new(
257278
move |interleaved_data, frame_size, sample_rate, now| {
279+
buffer_size_frames_cb.store(frame_size as u64, Ordering::Relaxed);
258280
let data = interleaved_data.as_mut_ptr() as *mut ();
259281
let mut data = unsafe {
260282
Data::from_parts(data, interleaved_data.len(), sample_format)
@@ -295,11 +317,18 @@ impl DeviceTrait for Device {
295317
}
296318
});
297319

298-
Ok(Stream { audio_context })
320+
Ok(Stream {
321+
audio_context,
322+
buffer_size_frames,
323+
})
299324
}
300325
}
301326

302327
impl StreamTrait for Stream {
328+
fn buffer_size(&self) -> Result<crate::FrameCount, crate::StreamError> {
329+
Ok(self.buffer_size_frames.load(Ordering::Relaxed) as crate::FrameCount)
330+
}
331+
303332
fn play(&self) -> Result<(), PlayStreamError> {
304333
match self.audio_context.resume() {
305334
Ok(_) => Ok(()),

src/host/coreaudio/ios/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,8 @@ impl StreamTrait for Stream {
285285
host_time_to_stream_instant(m_host_time).expect("mach_timebase_info failed")
286286
}
287287

288-
fn buffer_size(&self) -> Option<crate::FrameCount> {
289-
Some(get_device_buffer_frames() as crate::FrameCount)
288+
fn buffer_size(&self) -> Result<crate::FrameCount, crate::StreamError> {
289+
Ok(get_device_buffer_frames() as crate::FrameCount)
290290
}
291291
}
292292

src/host/coreaudio/macos/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,12 +268,12 @@ impl StreamTrait for Stream {
268268
host_time_to_stream_instant(m_host_time).expect("mach_timebase_info failed")
269269
}
270270

271-
fn buffer_size(&self) -> Option<crate::FrameCount> {
272-
let stream = self.inner.lock().ok()?;
271+
fn buffer_size(&self) -> Result<crate::FrameCount, crate::StreamError> {
272+
let stream = self.inner.lock().unwrap();
273273

274274
device::get_device_buffer_frame_size(&stream.audio_unit)
275-
.ok()
276275
.map(|size| size as crate::FrameCount)
276+
.map_err(|_| crate::StreamError::DeviceNotAvailable)
277277
}
278278
}
279279

src/host/custom/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ trait StreamErased: Send + Sync {
178178
fn play(&self) -> Result<(), PlayStreamError>;
179179
fn pause(&self) -> Result<(), PauseStreamError>;
180180
fn now(&self) -> StreamInstant;
181+
fn buffer_size(&self) -> Result<crate::FrameCount, crate::StreamError>;
181182
}
182183

183184
fn device_to_erased(d: impl DeviceErased + 'static) -> Device {
@@ -317,6 +318,10 @@ where
317318
fn now(&self) -> StreamInstant {
318319
<T as StreamTrait>::now(self)
319320
}
321+
322+
fn buffer_size(&self) -> Result<crate::FrameCount, crate::StreamError> {
323+
<T as StreamTrait>::buffer_size(self)
324+
}
320325
}
321326

322327
// implementations of HostTrait, DeviceTrait, and StreamTrait for custom versions
@@ -444,4 +449,8 @@ impl StreamTrait for Stream {
444449
fn now(&self) -> StreamInstant {
445450
self.0.now()
446451
}
452+
453+
fn buffer_size(&self) -> Result<crate::FrameCount, crate::StreamError> {
454+
self.0.buffer_size()
455+
}
447456
}

0 commit comments

Comments
 (0)