From bbbe5bb0b29ed64cc7dd191d7a72fe24bba0d284 Mon Sep 17 00:00:00 2001 From: John Lin Date: Thu, 4 Jul 2024 18:47:57 -0700 Subject: [PATCH] [AAudio] offset position by number of written frames. When reinitializating, `cubeb_stream::previous_clock` isn't reset properly but stream gets reset and restarts `AAudioTimingInfo::output_frame_index` from zero. Therefore, `aaudio_stream_get_position()` always reports previous clock rather than actual position until it is surpassed later. To estimate the position correctly, Save the number of frames sent to stream before it's reset, and use the value as the basis for future position estimation. --- src/cubeb_aaudio.cpp | 61 ++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/cubeb_aaudio.cpp b/src/cubeb_aaudio.cpp index 02b88a3..a863622 100644 --- a/src/cubeb_aaudio.cpp +++ b/src/cubeb_aaudio.cpp @@ -139,10 +139,14 @@ struct AAudioTimingInfo { /* To guess the current position of the stream when it's playing, the elapsed * time between the last callback and now is used. However, when the stream was * stopped and there was no new callback after playing restarted yet, the time - * spent in stopped state should be excluded. + * spent in stopped state should be excluded. It's also necessary to track the + * number of audio frames written to stream before reinitialization so it can be + * used to offset the position later, because + * `AAudioTimingInfo.output_frame_index` will restart from zero after + * reinitializing. * This class defines an internal state machine that takes the stream state - * changes and callback emissions as events to changes it own statesm and - * calculates played time accordingly. + * changes and callback emissions as events to changes it own states and + * estimates played time accordingly. * * A simplified |stream_state| transitions of playing looks like: * INIT -> [STARTING/STARTED -> callback* -> STOPPING/STOPPED]* -> SHUTDOWN|INIT @@ -161,7 +165,7 @@ struct AAudioTimingInfo { * - Resume -(STARTING)-> Resume * - Pause -(INIT)-> None */ -class position_interpolator { +class position_estimate { public: // Called with the current time when stopping the stream. void stop(uint64_t timestamp) @@ -187,7 +191,8 @@ public: } // Calculate how much time the stream bas been playing since last callback. - uint64_t compute(uint64_t now, uint64_t last_callback_timestamp) + uint64_t elapsed_time_since_callback(uint64_t now, + uint64_t last_callback_timestamp) { if (in_state()) { if (callback_timestamp != last_callback_timestamp) { @@ -214,6 +219,18 @@ public: } } + // Called when reinitializing stream. The input parameter is how many frames + // have already been written to AAudio since the first initialization. + void reinit(uint64_t position) + { + init_position = position; + state = None{}; + callback_timestamp = 0; + } + + // Frame index when last reinitialized. + uint64_t initial_position() { return init_position; } + private: template void set_state() { state.emplace(); } @@ -254,8 +271,10 @@ private: uint64_t pause_time; // Elapsed time from stopping to starting stream. }; std::variant state; - // Keep track input callback timestamp to detect callback emission. + // Track input callback timestamp to detect callback emission. uint64_t callback_timestamp{0}; + // Track number of written frames to adjust position after reinitialization. + uint64_t init_position{0}; }; struct cubeb_stream { @@ -296,7 +315,7 @@ struct cubeb_stream { bool voice_input{}; bool voice_output{}; uint64_t previous_clock{}; - position_interpolator interpolator; + position_estimate pos_estimate; }; struct cubeb { @@ -1020,12 +1039,18 @@ reinitialize_stream(cubeb_stream * stm) state == stream_state::STARTING || state == stream_state::DRAINING; int err = aaudio_stream_stop_locked(stm, lock); + // get total number of written frames before destroying the stream. + uint64_t total_frames = stm->pos_estimate.initial_position() + + WRAP(AAudioStream_getFramesWritten)(stm->ostream); // error ignored. aaudio_stream_destroy_locked(stm, lock); err = aaudio_stream_init_impl(stm, lock); assert(stm->in_use.load()); + // set the new initial position. + stm->pos_estimate.reinit(total_frames); + if (err != CUBEB_OK) { aaudio_stream_destroy_locked(stm, lock); LOG("aaudio_stream_init_impl error while reiniting: %s", @@ -1173,7 +1198,8 @@ aaudio_stream_destroy_locked(cubeb_stream * stm, lock_guard & lock) } stm->timing_info.invalidate(); - stm->interpolator = {}; + stm->previous_clock = 0; + stm->pos_estimate = {}; if (stm->resampler) { cubeb_resampler_destroy(stm->resampler); @@ -1544,7 +1570,7 @@ aaudio_stream_start_locked(cubeb_stream * stm, lock_guard & lock) } if (success) { - stm->interpolator.start(now_ns()); + stm->pos_estimate.start(now_ns()); stm->context->state.waiting.store(true); stm->context->state.cond.notify_one(); } @@ -1653,7 +1679,7 @@ aaudio_stream_stop_locked(cubeb_stream * stm, lock_guard & lock) } if (success) { - stm->interpolator.stop(now_ns()); + stm->pos_estimate.stop(now_ns()); stm->context->state.waiting.store(true); stm->context->state.cond.notify_one(); } @@ -1668,6 +1694,7 @@ aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position) lock_guard lock(stm->mutex); stream_state state = stm->state.load(); + uint64_t init_position = stm->pos_estimate.initial_position(); AAudioStream * stream = stm->ostream ? stm->ostream : stm->istream; switch (state) { case stream_state::ERROR: @@ -1678,7 +1705,7 @@ aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position) case stream_state::STOPPING: // getTimestamp is only valid when the stream is playing. // Simply return the number of frames passed to aaudio - *position = WRAP(AAudioStream_getFramesRead)(stream); + *position = init_position + WRAP(AAudioStream_getFramesRead)(stream); if (*position < stm->previous_clock) { *position = stm->previous_clock; } else { @@ -1696,7 +1723,7 @@ aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position) // No callback yet, the stream hasn't really started. if (stm->previous_clock == 0 && !stm->timing_info.updated()) { LOG("Not timing info yet"); - *position = 0; + *position = init_position; return CUBEB_OK; } @@ -1704,10 +1731,12 @@ aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position) LOGV("AAudioTimingInfo idx:%lu tstamp:%lu latency:%u", info.output_frame_index, info.tstamp, info.output_latency); // Interpolate client side since the last callback. - uint64_t interpolation = stm->sample_rate * - stm->interpolator.compute(now_ns(), info.tstamp) / - NS_PER_S; - *position = info.output_frame_index + interpolation - info.output_latency; + uint64_t interpolation = + (stm->sample_rate * + stm->pos_estimate.elapsed_time_since_callback(now_ns(), info.tstamp) / + NS_PER_S); + *position = init_position + info.output_frame_index + interpolation - + info.output_latency; if (*position < stm->previous_clock) { *position = stm->previous_clock; } else {