aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorPaul Adenot <[email protected]>2022-12-14 18:28:31 +0100
committerPaul Adenot <[email protected]>2022-12-14 18:30:05 +0100
commite2faad03178a88e1b55388cf5c4cce89f3049935 (patch)
treec5d395285f0491dc738763793bb4b5015c8e5374 /src
parentfb5c20db458836af6733e4717cd5991b53e85179 (diff)
downloadcubeb-e2faad03178a88e1b55388cf5c4cce89f3049935.tar.gz
cubeb-e2faad03178a88e1b55388cf5c4cce89f3049935.zip
Generalize latency reporting to make it work in input-only and duplex modes
Diffstat (limited to 'src')
-rw-r--r--src/cubeb_aaudio.cpp206
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;
}