https://github.com/libsdl-org/SDL/commit/32a3fc3783a89fed481eaa4d487d56b7d6d5438d
From 32a3fc3783a89fed481eaa4d487d56b7d6d5438d Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sat, 29 Jul 2023 19:44:23 -0400
Subject: [PATCH] aaudio: Use the callback interface.
This is allegedly lower-latency than the AAudioStream_write interface,
but more importantly, it let me set this up to block in WaitDevice.
Also turned on the low-latency performance mode, which trades battery life
for a more efficient audio thread to some unspecified degree.
---
src/audio/aaudio/SDL_aaudio.c | 108 +++++++++++++++++------------
src/audio/aaudio/SDL_aaudiofuncs.h | 16 ++---
2 files changed, 73 insertions(+), 51 deletions(-)
diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c
index e6e0ef29a08d..1ac0b0b3479e 100644
--- a/src/audio/aaudio/SDL_aaudio.c
+++ b/src/audio/aaudio/SDL_aaudio.c
@@ -37,10 +37,8 @@
struct SDL_PrivateAudioData
{
AAudioStream *stream;
-
Uint8 *mixbuf; // Raw mixing buffer
- int frame_size;
-
+ SDL_Semaphore *semaphore;
int resume; // Resume device if it was paused automatically
};
@@ -75,8 +73,13 @@ static int AAUDIO_LoadFunctions(AAUDIO_Data *data)
static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error)
{
LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error));
+ // !!! FIXME: you MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here.
+ // !!! FIXME: but we should flag the device and kill it in WaitDevice/PlayDevice.
}
+static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames);
+
+
#define LIB_AAUDIO_SO "libaaudio.so"
static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
@@ -132,21 +135,39 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
}
ctx.AAudioStreamBuilder_setFormat(builder, format);
-
- ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, hidden);
+ ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device);
+ ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device);
+ ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u",
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
- device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
+ device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames);
res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream);
- ctx.AAudioStreamBuilder_delete(builder);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res);
+ ctx.AAudioStreamBuilder_delete(builder);
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
}
+ device->sample_frames = (int) ctx.AAudioStream_getFramesPerDataCallback(hidden->stream);
+ if (device->sample_frames == AAUDIO_UNSPECIFIED) {
+ // if this happens, figure out a reasonable sample frame count, tear down this stream and force it in a new stream.
+ device->sample_frames = (int) (ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 4);
+ LOGI("AAUDIO: Got a stream with unspecified sample frames per data callback! Retrying with %d frames...", device->sample_frames);
+ ctx.AAudioStream_close(hidden->stream);
+ ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames);
+ res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream);
+ if (res != AAUDIO_OK) { // oh well, we tried.
+ LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res);
+ ctx.AAudioStreamBuilder_delete(builder);
+ return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
+ }
+ }
+
+ ctx.AAudioStreamBuilder_delete(builder);
+
device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream);
device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream);
@@ -161,9 +182,7 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format);
}
- device->sample_frames = ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2;
-
- LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u",
+ LOGI("AAudio Actually opened %u hz %u bit chan %u %s samples %u",
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames);
@@ -178,7 +197,11 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
SDL_memset(hidden->mixbuf, device->silence_value, device->buffer_size);
}
- hidden->frame_size = device->spec.channels * (SDL_AUDIO_BITSIZE(device->spec.format) / 8);
+ hidden->semaphore = SDL_CreateSemaphore(0);
+ if (!hidden->semaphore) {
+ LOGI("SDL Failed SDL_CreateSemaphore %s iscapture:%d", SDL_GetError(), iscapture);
+ return -1;
+ }
res = ctx.AAudioStream_requestStart(hidden->stream);
if (res != AAUDIO_OK) {
@@ -193,20 +216,42 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
static void AAUDIO_CloseDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
- if (hidden) {
- LOGI(__func__);
+ LOGI(__func__);
+ if (hidden) {
if (hidden->stream) {
ctx.AAudioStream_requestStop(hidden->stream);
+ // !!! FIXME: do we have to wait for the state to change to make sure all buffered audio has played, or will close do this (or will the system do this after the close)?
+ // !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again?
ctx.AAudioStream_close(hidden->stream);
}
+ if (hidden->semaphore) {
+ SDL_DestroySemaphore(hidden->semaphore);
+ }
+
SDL_free(hidden->mixbuf);
SDL_free(hidden);
device->hidden = NULL;
}
}
+// due to the way the aaudio data callback works, PlayDevice is a no-op. The callback collects audio while SDL camps in WaitDevice and
+// fires a semaphore that will unblock WaitDevice and start a new iteration, so when the callback runs again, WaitDevice is ready
+// to hand it more data.
+static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames)
+{
+ SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
+ SDL_assert(numFrames == device->sample_frames);
+ if (device->iscapture) {
+ SDL_memcpy(device->hidden->mixbuf, audioData, device->buffer_size);
+ } else {
+ SDL_memcpy(audioData, device->hidden->mixbuf, device->buffer_size);
+ }
+ SDL_PostSemaphore(device->hidden->semaphore);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
{
return device->hidden->mixbuf;
@@ -214,44 +259,20 @@ static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
static void AAUDIO_WaitDevice(SDL_AudioDevice *device)
{
- AAudioStream *stream = device->hidden->stream;
- while (!SDL_AtomicGet(&device->shutdown) && ((int) ctx.AAudioStream_getBufferSizeInFrames(stream)) < device->sample_frames) {
- SDL_Delay(1);
- }
+ SDL_WaitSemaphore(device->hidden->semaphore);
}
static void AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
- AAudioStream *stream = device->hidden->stream;
- const aaudio_result_t res = ctx.AAudioStream_write(stream, buffer, device->sample_frames, 0);
- if (res < 0) {
- LOGI("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
- } else {
- LOGI("SDL AAudio play: %d frames, wanted:%d frames", (int)res, sample_frames);
- }
-
-#if 0
- // Log under-run count
- {
- static int prev = 0;
- int32_t cnt = ctx.AAudioStream_getXRunCount(hidden->stream);
- if (cnt != prev) {
- SDL_Log("AAudio underrun: %d - total: %d", cnt - prev, cnt);
- prev = cnt;
- }
- }
-#endif
+ // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice.
}
+// no need for a FlushCapture implementation, just don't read mixbuf until the next iteration.
static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
- const aaudio_result_t res = ctx.AAudioStream_read(device->hidden->stream, buffer, device->sample_frames, 0);
- if (res < 0) {
- LOGI("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
- return -1;
- }
- LOGI("SDL AAudio capture:%d frames, wanted:%d frames", (int)res, buflen / device->hidden->frame_size);
- return res * device->hidden->frame_size;
+ const int cpy = SDL_min(buflen, device->buffer_size);
+ SDL_memcpy(buffer, device->hidden->mixbuf, cpy);
+ return cpy;
}
static void AAUDIO_Deinitialize(void)
@@ -377,6 +398,7 @@ void AAUDIO_ResumeDevices(void)
}
}
+// !!! FIXME: do we need this now that we use the callback?
/*
We can sometimes get into a state where AAudioStream_write() will just block forever until we pause and unpause.
None of the standard state queries indicate any problem in my testing. And the error callback doesn't actually get called.
diff --git a/src/audio/aaudio/SDL_aaudiofuncs.h b/src/audio/aaudio/SDL_aaudiofuncs.h
index f6e860d6a110..febbb89da812 100644
--- a/src/audio/aaudio/SDL_aaudiofuncs.h
+++ b/src/audio/aaudio/SDL_aaudiofuncs.h
@@ -32,15 +32,15 @@ SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aa
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode))
SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames))
-SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode))
+SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) /* API 28 */
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) /* API 28 */
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) /* API 28 */
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) /* API 29 */
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) /* API 28 */
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) /* API 30 */
-SDL_PROC_UNUSED(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData))
-SDL_PROC_UNUSED(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames))
+SDL_PROC(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData))
+SDL_PROC(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames))
SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder * builder, AAudioStream_errorCallback callback, void *userData))
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_openStream, (AAudioStreamBuilder * builder, AAudioStream **stream))
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_delete, (AAudioStreamBuilder * builder))
@@ -52,14 +52,14 @@ SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_requestFlush, (AAudioStream * stre
SDL_PROC(aaudio_result_t, AAudioStream_requestStop, (AAudioStream * stream))
SDL_PROC(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream * stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds))
-SDL_PROC(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
-SDL_PROC(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
+SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
+SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_setBufferSizeInFrames, (AAudioStream * stream, int32_t numFrames))
-SDL_PROC(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream))
+SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerBurst, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream))
-SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream))
-SDL_PROC(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream))
+SDL_PROC(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream))
+SDL_PROC_UNUSED(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getSampleRate, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getChannelCount, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getSamplesPerFrame, (AAudioStream * stream))