Skip to content

Commit 31ee599

Browse files
authored
fix(aaudio): timestamps and buffer sizing (#1165)
1 parent 1e45fbf commit 31ee599

6 files changed

Lines changed: 55 additions & 81 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
silently returning an empty list.
3636
- **AAudio**: Bump MSRV to 1.85.
3737
- **AAudio**: Buffers with default sizes are now dynamically tuned.
38+
- **AAudio**: `SupportedBufferSize` now reports `min: 1`.
3839
- **ALSA**: Device disconnection now stops the stream with `StreamError::DeviceNotAvailable`
3940
instead of looping.
4041
- **ALSA**: Polling errors trigger underrun recovery instead of looping.
@@ -81,7 +82,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8182
- Reintroduce `audio_thread_priority` feature.
8283
- Fix numeric overflows in calls to create `StreamInstant` in ASIO, CoreAudio and JACK.
8384
- **AAudio**: Fix thread lock when a stream is dropped before it fully starts.
84-
- **AAudio**: Fix invalid capture and playback timestamps.
85+
- **AAudio**: Fix capture and playback timestamps falling back to time-zero on error.
86+
- **AAudio**: Fix capture and playback timestamp not accounting for audio pipeline buffer depth.
87+
- **AAudio**: Fix signed overflow in `buffer_capacity_in_frames` for large fixed buffer sizes.
8588
- **ALSA**: Fix capture stream hanging or spinning on overruns.
8689
- **ALSA**: Fix non-monotonic `StreamInstant` during stream startup.
8790
- **ALSA**: Fix spurious timestamp errors during stream startup.

src/host/aaudio/convert.rs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,44 @@ pub fn now_stream_instant() -> StreamInstant {
1818
StreamInstant::new(ts.tv_sec as u64, ts.tv_nsec as u32)
1919
}
2020

21-
/// Returns the [`StreamInstant`] of the most recent audio frame transferred by `stream`.
22-
pub fn stream_instant(stream: &ndk::audio::AudioStream) -> StreamInstant {
23-
let ts = stream
24-
.timestamp(ndk::audio::Clockid::Monotonic)
25-
.unwrap_or(ndk::audio::Timestamp {
26-
frame_position: 0,
27-
time_nanoseconds: 0,
28-
});
29-
StreamInstant::from_nanos(ts.time_nanoseconds as u64)
21+
/// Projects a hardware timestamp anchor to the instant of a specific frame position.
22+
fn stream_instant_from_anchor(
23+
anchor_frame: i64,
24+
anchor_nanos: i64,
25+
app_frame: i64,
26+
sample_rate: u32,
27+
) -> StreamInstant {
28+
let offset_nanos =
29+
(app_frame as i128 - anchor_frame as i128) * 1_000_000_000 / sample_rate as i128;
30+
StreamInstant::from_nanos((anchor_nanos as i128 + offset_nanos).max(0) as u64)
31+
}
32+
33+
/// Returns the [`StreamInstant`] for when the first frame of the current output callback will
34+
/// be presented at the DAC.
35+
pub fn output_stream_instant(stream: &ndk::audio::AudioStream, sample_rate: u32) -> StreamInstant {
36+
match stream.timestamp(ndk::audio::Clockid::Monotonic) {
37+
Ok(ts) => stream_instant_from_anchor(
38+
ts.frame_position,
39+
ts.time_nanoseconds,
40+
stream.frames_written(),
41+
sample_rate,
42+
),
43+
Err(_) => now_stream_instant(),
44+
}
45+
}
46+
47+
/// Returns the [`StreamInstant`] for when the first frame of the current input callback was
48+
/// captured at the ADC.
49+
pub fn input_stream_instant(stream: &ndk::audio::AudioStream, sample_rate: u32) -> StreamInstant {
50+
match stream.timestamp(ndk::audio::Clockid::Monotonic) {
51+
Ok(ts) => stream_instant_from_anchor(
52+
ts.frame_position,
53+
ts.time_nanoseconds,
54+
stream.frames_read(),
55+
sample_rate,
56+
),
57+
Err(_) => now_stream_instant(),
58+
}
3059
}
3160

3261
impl From<ndk::audio::AudioError> for StreamError {

src/host/aaudio/java_interface/audio_manager.rs

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
use super::{
2-
utils::{
3-
get_context, get_property, get_system_property, get_system_service, with_attached, JNIEnv,
4-
JObject, JResult,
5-
},
6-
AudioManager, Context,
2+
utils::{get_context, get_system_property, with_attached, JNIEnv, JResult},
3+
AudioManager,
74
};
85

96
impl AudioManager {
10-
/// Get the frames per buffer using Android Java API
11-
pub fn get_frames_per_buffer() -> Result<i32, String> {
12-
let context = get_context();
13-
14-
with_attached(context, |env, context| get_frames_per_buffer(env, &context))
15-
.map_err(|error| error.to_string())
16-
}
17-
187
/// Get the AAudio mixer burst count from system property
198
pub fn get_mixer_bursts() -> Result<i32, String> {
209
let context = get_context();
@@ -24,23 +13,6 @@ impl AudioManager {
2413
}
2514
}
2615

27-
fn get_frames_per_buffer<'j>(env: &mut JNIEnv<'j>, context: &JObject<'j>) -> JResult<i32> {
28-
let audio_manager = get_system_service(env, context, Context::AUDIO_SERVICE)?;
29-
30-
let frames_per_buffer = get_property(
31-
env,
32-
&audio_manager,
33-
AudioManager::PROPERTY_OUTPUT_FRAMES_PER_BUFFER,
34-
)?;
35-
36-
let frames_per_buffer_string = String::from(env.get_string(&frames_per_buffer)?);
37-
38-
// TODO: Use jni::errors::Error::ParseFailed instead of jni::errors::Error::JniCall once jni > v0.21.1 is released
39-
frames_per_buffer_string
40-
.parse::<i32>()
41-
.map_err(|_| jni::errors::Error::JniCall(jni::errors::JniError::Unknown))
42-
}
43-
4416
fn get_mixer_bursts<'j>(env: &mut JNIEnv<'j>) -> JResult<i32> {
4517
let mixer_bursts = get_system_property(env, "aaudio.mixer_bursts", "2")?;
4618

src/host/aaudio/java_interface/definitions.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ impl PackageManager {
2121
pub(crate) struct AudioManager;
2222

2323
impl AudioManager {
24-
pub const PROPERTY_OUTPUT_FRAMES_PER_BUFFER: &'static str =
25-
"android.media.property.OUTPUT_FRAMES_PER_BUFFER";
26-
2724
pub const GET_DEVICES_INPUTS: i32 = 1 << 0;
2825
pub const GET_DEVICES_OUTPUTS: i32 = 1 << 1;
2926
pub const GET_DEVICES_ALL: i32 = Self::GET_DEVICES_INPUTS | Self::GET_DEVICES_OUTPUTS;

src/host/aaudio/java_interface/utils.rs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,6 @@ pub fn call_method_string_arg_ret_bool<'j>(
9696
.z()
9797
}
9898

99-
pub fn call_method_string_arg_ret_string<'j>(
100-
env: &mut JNIEnv<'j>,
101-
subject: &JObject<'j>,
102-
name: &str,
103-
arg: impl AsRef<str>,
104-
) -> JResult<JString<'j>> {
105-
Ok(env
106-
.call_method(
107-
subject,
108-
name,
109-
"(Ljava/lang/String;)Ljava/lang/String;",
110-
&[(&env.new_string(arg)?).into()],
111-
)?
112-
.l()?
113-
.into())
114-
}
115-
11699
pub fn call_method_string_arg_ret_object<'j>(
117100
env: &mut JNIEnv<'j>,
118101
subject: &JObject<'j>,
@@ -157,14 +140,6 @@ pub fn get_system_service<'j>(
157140
call_method_string_arg_ret_object(env, subject, "getSystemService", name)
158141
}
159142

160-
pub fn get_property<'j>(
161-
env: &mut JNIEnv<'j>,
162-
subject: &JObject<'j>,
163-
name: &str,
164-
) -> JResult<JString<'j>> {
165-
call_method_string_arg_ret_string(env, subject, "getProperty", name)
166-
}
167-
168143
/// Read an Android system property
169144
pub fn get_system_property<'j>(
170145
env: &mut JNIEnv<'j>,

src/host/aaudio/mod.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ use std::vec::IntoIter as VecIntoIter;
1111

1212
extern crate ndk;
1313

14-
use convert::{now_stream_instant, stream_instant};
14+
use convert::{input_stream_instant, now_stream_instant, output_stream_instant};
1515
use java_interface::{AudioDeviceInfo, AudioManager};
1616

1717
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
1818
use crate::{
1919
BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError,
2020
DeviceDescription, DeviceDescriptionBuilder, DeviceDirection, DeviceId, DeviceIdError,
21-
DeviceNameError, DeviceType, DevicesError, InputCallbackInfo, InputStreamTimestamp,
21+
DeviceNameError, DeviceType, DevicesError, FrameCount, InputCallbackInfo, InputStreamTimestamp,
2222
InterfaceType, OutputCallbackInfo, OutputStreamTimestamp, PauseStreamError, PlayStreamError,
2323
SampleFormat, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
2424
SupportedStreamConfigRange, SupportedStreamConfigsError,
@@ -199,13 +199,9 @@ impl HostTrait for Host {
199199
}
200200

201201
fn buffer_size_range() -> SupportedBufferSize {
202-
if let Ok(min_buffer_size) = AudioManager::get_frames_per_buffer() {
203-
SupportedBufferSize::Range {
204-
min: min_buffer_size as u32,
205-
max: i32::MAX as u32,
206-
}
207-
} else {
208-
SupportedBufferSize::Unknown
202+
SupportedBufferSize::Range {
203+
min: 1,
204+
max: i32::MAX as FrameCount,
209205
}
210206
}
211207

@@ -296,8 +292,8 @@ fn configure_for_device(
296292
if let BufferSize::Fixed(size) = config.buffer_size {
297293
// For fixed sizes, the user explicitly wants control over the callback size.
298294
builder = builder
299-
.frames_per_data_callback(size as i32)
300-
.buffer_capacity_in_frames(2 * size as i32);
295+
.frames_per_data_callback(size.min(i32::MAX as FrameCount) as i32)
296+
.buffer_capacity_in_frames(size.saturating_mul(2).min(i32::MAX as FrameCount) as i32);
301297
}
302298

303299
builder
@@ -317,12 +313,13 @@ where
317313
{
318314
let builder = configure_for_device(builder, device, config);
319315
let channel_count = config.channels as i32;
316+
let sample_rate = config.sample_rate;
320317
let stream = builder
321318
.data_callback(Box::new(move |stream, data, num_frames| {
322319
let cb_info = InputCallbackInfo {
323320
timestamp: InputStreamTimestamp {
324321
callback: now_stream_instant(),
325-
capture: stream_instant(stream),
322+
capture: input_stream_instant(stream, sample_rate),
326323
},
327324
};
328325
(data_callback)(
@@ -366,6 +363,7 @@ where
366363
{
367364
let builder = configure_for_device(builder, device, config);
368365
let channel_count = config.channels as i32;
366+
let sample_rate = config.sample_rate;
369367
let tune_dynamically = config.buffer_size == BufferSize::Default;
370368

371369
let tuning = Arc::new(BufferTuningState::default());
@@ -377,7 +375,7 @@ where
377375
let cb_info = OutputCallbackInfo {
378376
timestamp: OutputStreamTimestamp {
379377
callback: now_stream_instant(),
380-
playback: stream_instant(stream),
378+
playback: output_stream_instant(stream, sample_rate),
381379
},
382380
};
383381
(data_callback)(

0 commit comments

Comments
 (0)