diff options
author | Paul Adenot <[email protected]> | 2022-12-14 18:28:31 +0100 |
---|---|---|
committer | Paul Adenot <[email protected]> | 2022-12-14 18:30:05 +0100 |
commit | e2faad03178a88e1b55388cf5c4cce89f3049935 (patch) | |
tree | c5d395285f0491dc738763793bb4b5015c8e5374 /src/cubeb_aaudio.cpp | |
parent | fb5c20db458836af6733e4717cd5991b53e85179 (diff) | |
download | cubeb-e2faad03178a88e1b55388cf5c4cce89f3049935.tar.gz cubeb-e2faad03178a88e1b55388cf5c4cce89f3049935.zip |
Generalize latency reporting to make it work in input-only and duplex modes
Diffstat (limited to 'src/cubeb_aaudio.cpp')
-rw-r--r-- | src/cubeb_aaudio.cpp | 206 |
1 files changed, 107 insertions, 99 deletions
diff --git a/src/cubeb_aaudio.cpp b/src/cubeb_aaudio.cpp index 5c63735..a749d8a 100644 --- a/src/cubeb_aaudio.cpp +++ b/src/cubeb_aaudio.cpp @@ -116,12 +116,14 @@ enum class stream_state { }; struct AAudioTimingInfo { - // The number of frames sent to the engine. - uint64_t frame_index; // The timestamp at which the audio engine last called the calback. uint64_t tstamp; - // The current output latency in frames. - uint32_t latency; + // The number of output frames sent to the engine. + uint64_t output_frame_index; + // The current output latency in frames. 0 if there is no output stream. + uint32_t output_latency; + // The current input latency in frames. 0 if there is no input stream. + uint32_t input_latency; }; struct cubeb_stream { @@ -130,6 +132,7 @@ struct cubeb_stream { void * user_ptr{}; std::atomic<bool> in_use{false}; + std::atomic<bool> latency_metrics_available{false}; std::atomic<stream_state> state{stream_state::INIT}; triple_buffer<AAudioTimingInfo> timing_info; @@ -152,8 +155,6 @@ struct cubeb_stream { std::atomic<float> volume{1.f}; unsigned out_channels{}; unsigned out_frame_size{}; - int64_t latest_output_latency = 0; - int64_t latest_input_latency = 0; bool voice_input; bool voice_output; uint64_t previous_clock; @@ -521,6 +522,86 @@ apply_volume(cubeb_stream * stm, void * audio_data, uint32_t num_frames) } } +uint64_t +now_ns() +{ + using namespace std::chrono; + return duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()) + .count(); +} + +// To be called from the real-time audio callback +uint64_t +aaudio_get_latency(cubeb_stream * stm, aaudio_direction_t direction, + uint64_t tstamp_ns) +{ + bool is_output = direction == AAUDIO_DIRECTION_OUTPUT; + int64_t hw_frame_index; + int64_t hw_tstamp; + AAudioStream * stream = is_output ? stm->ostream : stm->istream; + // For an output stream (resp. input stream), get the number of frames + // written to (resp read from) the hardware. + int64_t app_frame_index = is_output + ? WRAP(AAudioStream_getFramesWritten)(stream) + : WRAP(AAudioStream_getFramesRead)(stream); + + // Get a timestamp for a particular frame index written to or read from the + // hardware. + auto result = WRAP(AAudioStream_getTimestamp)(stream, CLOCK_MONOTONIC, + &hw_frame_index, &hw_tstamp); + if (result != AAUDIO_OK) { + LOG("AAudioStream_getTimestamp failure."); + return 0; + } + + // Compute the difference between the app and the hardware indices. + int64_t frame_index_delta = app_frame_index - hw_frame_index; + // Convert to ns + int64_t frame_time_delta = (frame_index_delta * 1e9) / stm->sample_rate; + // Extrapolate from the known timestamp for a particular frame presented. + int64_t app_frame_hw_time = hw_tstamp + frame_time_delta; + // For an output stream, the latency is positive, for an input stream, it's + // negative. + int64_t latency_ns = + is_output ? app_frame_hw_time - tstamp_ns : tstamp_ns - app_frame_hw_time; + int64_t latency_frames = stm->sample_rate * latency_ns / 1e9; + + LOGV("Latency in frames (%s): %d (%dms)", is_output ? "output" : "input", + latency_frames, latency_ns / 1e6); + + return latency_frames; +} + +void +compute_and_report_latency_metrics(cubeb_stream * stm) +{ + AAudioTimingInfo info = {}; + + info.tstamp = now_ns(); + + if (stm->ostream) { + uint64_t latency_frames = + aaudio_get_latency(stm, AAUDIO_DIRECTION_OUTPUT, info.tstamp); + if (latency_frames) { + info.output_latency = latency_frames; + info.output_frame_index = + WRAP(AAudioStream_getFramesWritten)(stm->ostream); + } + } + if (stm->istream) { + uint64_t latency_frames = + aaudio_get_latency(stm, AAUDIO_DIRECTION_INPUT, info.tstamp); + if (latency_frames) { + info.input_latency = latency_frames; + } + } + + if (info.output_latency || info.input_latency) { + stm->latency_metrics_available = true; + stm->timing_info.write(info); + } +} + // Returning AAUDIO_CALLBACK_RESULT_STOP seems to put the stream in // an invalid state. Seems like an AAudio bug/bad documentation. // We therefore only return it on error. @@ -566,6 +647,8 @@ aaudio_duplex_data_cb(AAudioStream * astream, void * user_data, return AAUDIO_CALLBACK_RESULT_STOP; } + compute_and_report_latency_metrics(stm); + // This can happen shortly after starting the stream. AAudio might immediately // begin to buffer output but not have any input ready yet. We could // block AAudioStream_read (passing a timeout > 0) but that leads to issues @@ -604,55 +687,6 @@ aaudio_duplex_data_cb(AAudioStream * astream, void * user_data, return AAUDIO_CALLBACK_RESULT_CONTINUE; } -uint64_t -now_ns() -{ - using namespace std::chrono; - return duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()) - .count(); -} - -// To be called from the real-time audio callback -uint64_t -aaudio_get_latency(cubeb_stream * stm, aaudio_direction_t direction, - uint64_t tstamp_ns) -{ - bool is_output = direction == AAUDIO_DIRECTION_OUTPUT; - int64_t hw_frame_index; - int64_t hw_tstamp; - AAudioStream * stream = is_output ? stm->ostream : stm->istream; - // For an output stream (resp. input stream), get the number of frames - // written to (resp read from) the hardware. - int64_t app_frame_index = is_output - ? WRAP(AAudioStream_getFramesWritten)(stream) - : WRAP(AAudioStream_getFramesRead)(stream); - - // Get a timestamp for a particular frame index written to or read from the - // hardware. - auto result = WRAP(AAudioStream_getTimestamp)(stream, CLOCK_MONOTONIC, - &hw_frame_index, &hw_tstamp); - if (result != AAUDIO_OK) { - LOG("AAudioStream_getTimestamp failure."); - return 0; - } - - // Compute the difference between the app and the hardware indices. - int64_t frame_index_delta = app_frame_index - hw_frame_index; - // Convert to ns - int64_t frame_time_delta = (frame_index_delta * 1e9) / stm->sample_rate; - // Extrapolate from the known timestamp for a particular frame presented. - int64_t app_frame_hw_time = hw_tstamp + frame_time_delta; - // For an output stream, the latency is positive, for an input stream, it's - // negative. - int64_t latency_ns = - is_output ? app_frame_hw_time - tstamp_ns : tstamp_ns - app_frame_hw_time; - int64_t latency_frames = stm->sample_rate * latency_ns / 1e9; - - LOGV("Latency in frames: %d (%dms)", latency_frames, latency_ns / 1e6); - - return latency_frames; -} - static aaudio_data_callback_result_t aaudio_output_data_cb(AAudioStream * astream, void * user_data, void * audio_data, int32_t num_frames) @@ -679,16 +713,7 @@ aaudio_output_data_cb(AAudioStream * astream, void * user_data, return AAUDIO_CALLBACK_RESULT_CONTINUE; } - uint64_t tstamp_ns = now_ns(); - uint64_t latency_frames = - aaudio_get_latency(stm, AAUDIO_DIRECTION_OUTPUT, tstamp_ns); - if (latency_frames != 0) { - AAudioTimingInfo info; - info.tstamp = tstamp_ns; - info.latency = latency_frames; - info.frame_index = WRAP(AAudioStream_getFramesWritten)(stm->ostream); - stm->timing_info.write(info); - } + compute_and_report_latency_metrics(stm); long done_frames = cubeb_resampler_fill(stm->resampler, NULL, NULL, audio_data, num_frames); @@ -734,9 +759,12 @@ aaudio_input_data_cb(AAudioStream * astream, void * user_data, return AAUDIO_CALLBACK_RESULT_CONTINUE; } + compute_and_report_latency_metrics(stm); + long input_frame_count = num_frames; long done_frames = cubeb_resampler_fill(stm->resampler, audio_data, &input_frame_count, NULL, 0); + if (done_frames < 0 || done_frames > num_frames) { LOG("Error in data callback or resampler: %ld", done_frames); stm->state.store(stream_state::ERROR); @@ -1350,11 +1378,11 @@ aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position) } AAudioTimingInfo info = stm->timing_info.read(); - LOGV("AAudioTimingInfo idx:%lu tstamp:%lu latency:%u", info.frame_index, - info.tstamp, info.latency); + LOGV("AAudioTimingInfo idx:%lu tstamp:%lu latency:%u", + info.output_frame_index, info.tstamp, info.output_latency); // Interpolate client side since the last callback. int64_t interpolation = stm->sample_rate * (now_ns() - info.tstamp) / 1e9; - *position = info.frame_index + interpolation - info.latency; + *position = info.output_frame_index + interpolation - info.output_latency; if (*position < stm->previous_clock) { *position = stm->previous_clock; } else { @@ -1369,30 +1397,20 @@ aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position) static int aaudio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) { - int64_t pos; - int64_t ns; - aaudio_result_t res; - if (!stm->ostream) { LOG("error: aaudio_stream_get_latency on input-only stream"); return CUBEB_ERROR; } - res = - WRAP(AAudioStream_getTimestamp)(stm->ostream, CLOCK_MONOTONIC, &pos, &ns); - if (res != AAUDIO_OK) { - LOG("aaudio_stream_get_latency, AAudioStream_getTimestamp: %s, returning " - "memoized value", - WRAP(AAudio_convertResultToText)(res)); - // Expected when the stream is paused. - *latency = stm->latest_output_latency; + if (!stm->latency_metrics_available) { + LOG("Not timing info yet (output)"); return CUBEB_OK; } - int64_t read = WRAP(AAudioStream_getFramesRead)(stm->ostream); + AAudioTimingInfo info = stm->timing_info.read(); - *latency = stm->latest_output_latency = read - pos; - LOG("aaudio_stream_get_latency, %u", *latency); + *latency = info.output_latency; + LOG("aaudio_stream_get_latency, %u frames", *latency); return CUBEB_OK; } @@ -1400,30 +1418,20 @@ aaudio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) static int aaudio_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency) { - int64_t pos; - int64_t ns; - aaudio_result_t res; - if (!stm->istream) { - LOG("error: aaudio_stream_get_input_latency on an ouput-only stream"); + LOG("error: aaudio_stream_get_input_latency on an output-only stream"); return CUBEB_ERROR; } - res = - WRAP(AAudioStream_getTimestamp)(stm->istream, CLOCK_MONOTONIC, &pos, &ns); - if (res != AAUDIO_OK) { - // Expected when the stream is paused. - LOG("aaudio_stream_get_input_latency, AAudioStream_getTimestamp: %s, " - "returning memoized value", - WRAP(AAudio_convertResultToText)(res)); - *latency = stm->latest_input_latency; + if (!stm->latency_metrics_available) { + LOG("Not timing info yet (input)"); return CUBEB_OK; } - int64_t written = WRAP(AAudioStream_getFramesWritten)(stm->istream); + AAudioTimingInfo info = stm->timing_info.read(); - *latency = stm->latest_input_latency = written - pos; - LOG("aaudio_stream_get_input_latency, %u", *latency); + *latency = info.input_latency; + LOG("aaudio_stream_get_latency, %u frames", *latency); return CUBEB_OK; } |