WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 858feb1

Browse files
committed
feat: add Stream::buffer_size() and improve AAudio buffer configuration
Add buffer_size() method to Stream trait that returns the number of frames passed to each data callback invocation (actual size or upper limit depending on platform). AAudio improvements: - BufferSize::Default now explicitly configures using optimal burst size from AudioManager, following Android low-latency audio best practices - buffer_size() query falls back to burst size if frames_per_data_callback was not explicitly set - Refactored buffer configuration to eliminate code duplication Addresses #1042 Relates to #964, #942
1 parent 5d57176 commit 858feb1

File tree

12 files changed

+125
-9
lines changed

12 files changed

+125
-9
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
12+
- `StreamTrait::buffer_size` method to query the callback buffer size (actual size or upper limit depending on platform).
13+
- **AAudio**: `BufferSize::Default` now explicitly configures using the optimal burst size from AudioManager.
14+
815
## [0.17.0] - 2025-12-20
916

1017
### Added
@@ -1017,6 +1024,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10171024

10181025
- Initial commit.
10191026

1027+
[Unreleased]: https://github.com/RustAudio/cpal/compare/v0.17.0...HEAD
10201028
[0.17.0]: https://github.com/RustAudio/cpal/compare/v0.16.0...v0.17.0
10211029
[0.16.0]: https://github.com/RustAudio/cpal/compare/v0.15.3...v0.16.0
10221030
[0.15.3]: https://github.com/RustAudio/cpal/compare/v0.15.2...v0.15.3

src/host/aaudio/mod.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,22 @@ fn configure_for_device(
277277
};
278278
builder = builder.sample_rate(config.sample_rate.try_into().unwrap());
279279

280-
// Note: Buffer size validation is not needed - the native AAudio API validates buffer sizes
281-
// when `open_stream()` is called.
282-
match &config.buffer_size {
283-
BufferSize::Default => builder,
284-
BufferSize::Fixed(size) => builder
285-
.frames_per_data_callback(*size as i32)
286-
.buffer_capacity_in_frames((*size * 2) as i32), // Double-buffering
280+
let buffer_size = match config.buffer_size {
281+
BufferSize::Default => {
282+
// Use the optimal burst size from AudioManager:
283+
// https://developer.android.com/ndk/guides/audio/audio-latency#buffer-size
284+
AudioManager::get_frames_per_buffer().ok()
285+
}
286+
BufferSize::Fixed(size) => Some(size),
287+
};
288+
289+
if let Some(size) = buffer_size {
290+
builder
291+
.frames_per_data_callback(size as i32)
292+
.buffer_capacity_in_frames((size * 2) as i32) // Double-buffering
293+
} else {
294+
// If we couldn't determine a buffer size, let AAudio choose defaults
295+
builder
287296
}
288297
}
289298

@@ -606,4 +615,20 @@ impl StreamTrait for Stream {
606615
.map_err(PauseStreamError::from),
607616
}
608617
}
618+
619+
fn buffer_size(&self) -> Option<crate::FrameCount> {
620+
let stream = match self {
621+
Self::Input(stream) => stream.lock().ok()?,
622+
Self::Output(stream) => stream.lock().ok()?,
623+
};
624+
625+
// If frames_per_data_callback was not explicitly set (returning 0),
626+
// fall back to the burst size as that's what AAudio uses by default.
627+
match stream.get_frames_per_data_callback() {
628+
Some(size) if size > 0 => Some(size as crate::FrameCount),
629+
_ => stream
630+
.get_frames_per_burst()
631+
.map(|f| f as crate::FrameCount),
632+
}
633+
}
609634
}

src/host/alsa/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,9 @@ impl StreamTrait for Stream {
12201220
self.inner.channel.pause(true).ok();
12211221
Ok(())
12221222
}
1223+
fn buffer_size(&self) -> Option<FrameCount> {
1224+
Some(self.inner.period_frames as FrameCount)
1225+
}
12231226
}
12241227

12251228
// Convert ALSA frames to FrameCount, clamping to valid range.

src/host/asio/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,8 @@ impl StreamTrait for Stream {
153153
fn pause(&self) -> Result<(), PauseStreamError> {
154154
Stream::pause(self)
155155
}
156+
157+
fn buffer_size(&self) -> Option<crate::FrameCount> {
158+
Stream::buffer_size(self)
159+
}
156160
}

src/host/asio/stream.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ impl Stream {
3737
self.playing.store(false, Ordering::SeqCst);
3838
Ok(())
3939
}
40+
41+
pub fn buffer_size(&self) -> Option<crate::FrameCount> {
42+
let streams = self.asio_streams.lock().ok()?;
43+
streams
44+
.output
45+
.as_ref()
46+
.or(streams.input.as_ref())
47+
.map(|s| s.buffer_size as crate::FrameCount)
48+
}
4049
}
4150

4251
impl Device {

src/host/coreaudio/ios/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,14 @@ impl StreamTrait for Stream {
274274
let err = BackendSpecificError { description };
275275
return Err(err.into());
276276
}
277-
278277
stream.playing = false;
279278
}
280279
Ok(())
281280
}
281+
282+
fn buffer_size(&self) -> Option<crate::FrameCount> {
283+
Some(get_device_buffer_frames() as crate::FrameCount)
284+
}
282285
}
283286

284287
struct StreamInner {

src/host/coreaudio/macos/device.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,9 @@ fn setup_callback_vars(
10071007
///
10081008
/// Buffer frame size is a device-level property that always uses Scope::Global + Element::Output,
10091009
/// regardless of whether the audio unit is configured for input or output streams.
1010-
fn get_device_buffer_frame_size(audio_unit: &AudioUnit) -> Result<usize, coreaudio::Error> {
1010+
pub(crate) fn get_device_buffer_frame_size(
1011+
audio_unit: &AudioUnit,
1012+
) -> Result<usize, coreaudio::Error> {
10111013
// Device-level property: always use Scope::Global + Element::Output
10121014
// This is consistent with how we set the buffer size and query the buffer size range
10131015
let frames: u32 = audio_unit.get_property(

src/host/coreaudio/macos/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,14 @@ impl StreamTrait for Stream {
261261

262262
stream.pause()
263263
}
264+
265+
fn buffer_size(&self) -> Option<crate::FrameCount> {
266+
let stream = self.inner.lock().ok()?;
267+
268+
device::get_device_buffer_frame_size(&stream.audio_unit)
269+
.ok()
270+
.map(|size| size as crate::FrameCount)
271+
}
264272
}
265273

266274
#[cfg(test)]

src/host/jack/stream.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ impl StreamTrait for Stream {
220220
self.playing.store(false, Ordering::SeqCst);
221221
Ok(())
222222
}
223+
224+
fn buffer_size(&self) -> Option<crate::FrameCount> {
225+
Some(self.async_client.as_client().buffer_size() as crate::FrameCount)
226+
}
223227
}
224228

225229
type InputDataCallback = Box<dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static>;

src/host/wasapi/stream.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ pub struct Stream {
2929
// This event is signalled after a new entry is added to `commands`, so that the `run()`
3030
// method can be notified.
3131
pending_scheduled_event: Foundation::HANDLE,
32+
33+
// Number of frames in the WASAPI buffer.
34+
//
35+
// Note: the actual callback size is variable and may be less than this value.
36+
max_frames_in_buffer: u32,
3237
}
3338

3439
// SAFETY: Windows Event HANDLEs are safe to send between threads - they are designed for
@@ -115,6 +120,8 @@ impl Stream {
115120
.expect("cpal: could not create input stream event");
116121
let (tx, rx) = channel();
117122

123+
let max_frames_in_buffer = stream_inner.max_frames_in_buffer;
124+
118125
let run_context = RunContext {
119126
handles: vec![pending_scheduled_event, stream_inner.event],
120127
stream: stream_inner,
@@ -130,6 +137,7 @@ impl Stream {
130137
thread: Some(thread),
131138
commands: tx,
132139
pending_scheduled_event,
140+
max_frames_in_buffer,
133141
}
134142
}
135143

@@ -148,6 +156,8 @@ impl Stream {
148156
.expect("cpal: could not create output stream event");
149157
let (tx, rx) = channel();
150158

159+
let max_frames_in_buffer = stream_inner.max_frames_in_buffer;
160+
151161
let run_context = RunContext {
152162
handles: vec![pending_scheduled_event, stream_inner.event],
153163
stream: stream_inner,
@@ -163,6 +173,7 @@ impl Stream {
163173
thread: Some(thread),
164174
commands: tx,
165175
pending_scheduled_event,
176+
max_frames_in_buffer,
166177
}
167178
}
168179

@@ -198,6 +209,12 @@ impl StreamTrait for Stream {
198209
.map_err(|_| crate::error::PauseStreamError::DeviceNotAvailable)?;
199210
Ok(())
200211
}
212+
213+
fn buffer_size(&self) -> Option<crate::FrameCount> {
214+
// WASAPI uses event-driven callbacks with variable callback sizes.
215+
// We return the total buffer size allocated by Windows as an upper bound.
216+
Some(self.max_frames_in_buffer as crate::FrameCount)
217+
}
201218
}
202219

203220
impl Drop for StreamInner {

0 commit comments

Comments
 (0)