From 32a3fc3783a89fed481eaa4d487d56b7d6d5438d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" 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 e6e0ef29a0..1ac0b0b347 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 f6e860d6a1..febbb89da8 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))