Skip to content

Commit 5656daa

Browse files
committed
fix(aaudio): use system mixer bursts for buffer capacity
1 parent 3b4254d commit 5656daa

3 files changed

Lines changed: 69 additions & 14 deletions

File tree

src/host/aaudio/java_interface/audio_manager.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::{
22
utils::{
3-
get_context, get_property, get_system_service, with_attached, JNIEnv, JObject, JResult,
3+
get_context, get_property, get_system_property, get_system_service, with_attached, JNIEnv,
4+
JObject, JResult,
45
},
56
AudioManager, Context,
67
};
@@ -13,6 +14,15 @@ impl AudioManager {
1314
with_attached(context, |env, context| get_frames_per_buffer(env, &context))
1415
.map_err(|error| error.to_string())
1516
}
17+
18+
/// Get the AAudio mixer burst count from system property
19+
/// Returns the value from aaudio.mixer_bursts property, defaulting to 2
20+
pub fn get_mixer_bursts() -> Result<i32, String> {
21+
let context = get_context();
22+
23+
with_attached(context, |env, _context| get_mixer_bursts(env))
24+
.map_err(|error| error.to_string())
25+
}
1626
}
1727

1828
fn get_frames_per_buffer<'j>(env: &mut JNIEnv<'j>, context: &JObject<'j>) -> JResult<i32> {
@@ -31,3 +41,14 @@ fn get_frames_per_buffer<'j>(env: &mut JNIEnv<'j>, context: &JObject<'j>) -> JRe
3141
.parse::<i32>()
3242
.map_err(|_| jni::errors::Error::JniCall(jni::errors::JniError::Unknown))
3343
}
44+
45+
fn get_mixer_bursts<'j>(env: &mut JNIEnv<'j>) -> JResult<i32> {
46+
let mixer_bursts = get_system_property(env, "aaudio.mixer_bursts", "2")?;
47+
48+
let mixer_bursts_string = String::from(env.get_string(&mixer_bursts)?);
49+
50+
// TODO: Use jni::errors::Error::ParseFailed instead of jni::errors::Error::JniCall once jni > v0.21.1 is released
51+
mixer_bursts_string
52+
.parse::<i32>()
53+
.map_err(|_| jni::errors::Error::JniCall(jni::errors::JniError::Unknown))
54+
}

src/host/aaudio/java_interface/utils.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,26 @@ pub fn get_property<'j>(
165165
call_method_string_arg_ret_string(env, subject, "getProperty", name)
166166
}
167167

168+
/// Read an Android system property
169+
pub fn get_system_property<'j>(
170+
env: &mut JNIEnv<'j>,
171+
name: &str,
172+
default_value: &str,
173+
) -> JResult<JString<'j>> {
174+
Ok(env
175+
.call_static_method(
176+
"android/os/SystemProperties",
177+
"get",
178+
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
179+
&[
180+
(&env.new_string(name)?).into(),
181+
(&env.new_string(default_value)?).into(),
182+
],
183+
)?
184+
.l()?
185+
.into())
186+
}
187+
168188
pub fn get_devices<'j>(
169189
env: &mut JNIEnv<'j>,
170190
subject: &JObject<'j>,

src/host/aaudio/mod.rs

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

280-
let size = match config.buffer_size {
280+
match config.buffer_size {
281281
BufferSize::Default => {
282-
// Use the optimal burst size from AudioManager:
283-
// https://developer.android.com/ndk/guides/audio/audio-latency#buffer-size
284-
match AudioManager::get_frames_per_buffer() {
285-
Ok(size) if size > 0 => size as u32,
286-
_ => 256,
287-
}
288-
}
289-
BufferSize::Fixed(size) => size,
290-
};
282+
// Following the pattern from Oboe and Google's AAudio samples, we only set the buffer
283+
// capacity and let AAudio choose the optimal callback size dynamically. See:
284+
// - https://developer.android.com/ndk/reference/group/audio
285+
// - https://developer.android.com/ndk/guides/audio/audio-latency#buffer-size
286+
let burst = match AudioManager::get_frames_per_buffer() {
287+
Ok(size) if size > 0 => size,
288+
_ => 256, // default from Android docs
289+
};
291290

292-
builder
293-
.frames_per_data_callback(size as i32)
294-
.buffer_capacity_in_frames((size * 2) as i32) // Double-buffering
291+
// Determine the buffer capacity multiplier. This matches AOSP's
292+
// AAudioServiceEndpointPlay buffer sizing strategy.
293+
let mixer_bursts = match AudioManager::get_mixer_bursts() {
294+
Ok(bursts) if bursts > 1 => bursts,
295+
_ => 2, // double-buffering: default from AOSP
296+
};
297+
298+
let capacity = burst * mixer_bursts;
299+
builder.buffer_capacity_in_frames(capacity)
300+
}
301+
BufferSize::Fixed(size) => {
302+
// For fixed sizes, the user explicitly wants control over the callback size,
303+
// so we set both the callback size and capacity (with double-buffering).
304+
builder
305+
.frames_per_data_callback(size as i32)
306+
.buffer_capacity_in_frames((size * 2) as i32)
307+
}
308+
}
295309
}
296310

297311
fn build_input_stream<D, E>(

0 commit comments

Comments
 (0)