| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "SDL_internal.h" |
| |
| #ifdef SDL_AUDIO_DRIVER_AAUDIO |
| |
| #include "../SDL_sysaudio.h" |
| #include "SDL_aaudio.h" |
| |
| #include "../../core/android/SDL_android.h" |
| #include <aaudio/AAudio.h> |
| |
| #if __ANDROID_API__ < 31 |
| #define AAUDIO_FORMAT_PCM_I32 4 |
| #endif |
| |
| struct SDL_PrivateAudioData |
| { |
| AAudioStream *stream; |
| int num_buffers; |
| Uint8 *mixbuf; // Raw mixing buffer |
| size_t mixbuf_bytes; // num_buffers * device->buffer_size |
| size_t callback_bytes; |
| size_t processed_bytes; |
| SDL_Semaphore *semaphore; |
| SDL_AtomicInt error_callback_triggered; |
| }; |
| |
| // Debug |
| #if 0 |
| #define LOGI(...) SDL_Log(__VA_ARGS__); |
| #else |
| #define LOGI(...) |
| #endif |
| |
| #define LIB_AAUDIO_SO "libaaudio.so" |
| |
| SDL_ELF_NOTE_DLOPEN( |
| "audio-aaudio", |
| "Support for audio through AAudio", |
| SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, |
| LIB_AAUDIO_SO |
| ) |
| |
| typedef struct AAUDIO_Data |
| { |
| SDL_SharedObject *handle; |
| #define SDL_PROC(ret, func, params) ret (*func) params; |
| #include "SDL_aaudiofuncs.h" |
| } AAUDIO_Data; |
| static AAUDIO_Data ctx; |
| |
| static bool AAUDIO_LoadFunctions(AAUDIO_Data *data) |
| { |
| #define SDL_PROC(ret, func, params) \ |
| do { \ |
| data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \ |
| if (!data->func) { \ |
| return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \ |
| } \ |
| } while (0); |
| |
| #define SDL_PROC_OPTIONAL(ret, func, params) \ |
| do { \ |
| data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); /* if it fails, okay. */ \ |
| } while (0); |
| #include "SDL_aaudiofuncs.h" |
| return true; |
| } |
| |
| |
| static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error) |
| { |
| LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error)); |
| |
| // You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here. |
| // Just flag the device so we can kill it in PlayDevice instead. |
| SDL_AudioDevice *device = (SDL_AudioDevice *) userData; |
| SDL_SetAtomicInt(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error. |
| SDL_SignalSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice. |
| } |
| |
| static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) |
| { |
| SDL_AudioDevice *device = (SDL_AudioDevice *) userData; |
| struct SDL_PrivateAudioData *hidden = device->hidden; |
| size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec); |
| size_t callback_bytes = numFrames * framesize; |
| size_t old_buffer_index = hidden->callback_bytes / device->buffer_size; |
| |
| if (device->recording) { |
| const Uint8 *input = (const Uint8 *)audioData; |
| size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes); |
| size_t size = SDL_min(available_bytes, callback_bytes); |
| size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes; |
| size_t end = (offset + size) % hidden->mixbuf_bytes; |
| SDL_assert(size <= hidden->mixbuf_bytes); |
| |
| //LOGI("Recorded %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->callback_bytes / framesize, hidden->processed_bytes / framesize); |
| |
| if (offset <= end) { |
| SDL_memcpy(&hidden->mixbuf[offset], input, size); |
| } else { |
| size_t partial = (hidden->mixbuf_bytes - offset); |
| SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial); |
| SDL_memcpy(&hidden->mixbuf[0], &input[partial], end); |
| } |
| |
| SDL_MemoryBarrierRelease(); |
| hidden->callback_bytes += size; |
| |
| if (size < callback_bytes) { |
| LOGI("Audio recording overflow, dropped %zu frames", (callback_bytes - size) / framesize); |
| } |
| } else { |
| Uint8 *output = (Uint8 *)audioData; |
| size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes); |
| size_t size = SDL_min(available_bytes, callback_bytes); |
| size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes; |
| size_t end = (offset + size) % hidden->mixbuf_bytes; |
| SDL_assert(size <= hidden->mixbuf_bytes); |
| |
| //LOGI("Playing %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->processed_bytes / framesize, hidden->callback_bytes / framesize); |
| |
| SDL_MemoryBarrierAcquire(); |
| if (offset <= end) { |
| SDL_memcpy(output, &hidden->mixbuf[offset], size); |
| } else { |
| size_t partial = (hidden->mixbuf_bytes - offset); |
| SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial); |
| SDL_memcpy(&output[partial], &hidden->mixbuf[0], end); |
| } |
| hidden->callback_bytes += size; |
| |
| if (size < callback_bytes) { |
| LOGI("Audio playback underflow, missed %zu frames", (callback_bytes - size) / framesize); |
| SDL_memset(&output[size], device->silence_value, (callback_bytes - size)); |
| } |
| } |
| |
| size_t new_buffer_index = hidden->callback_bytes / device->buffer_size; |
| while (old_buffer_index < new_buffer_index) { |
| // Trigger audio processing |
| SDL_SignalSemaphore(hidden->semaphore); |
| ++old_buffer_index; |
| } |
| |
| return AAUDIO_CALLBACK_RESULT_CONTINUE; |
| } |
| |
| static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) |
| { |
| struct SDL_PrivateAudioData *hidden = device->hidden; |
| size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes); |
| return &hidden->mixbuf[offset]; |
| } |
| |
| static bool AAUDIO_WaitDevice(SDL_AudioDevice *device) |
| { |
| while (!SDL_GetAtomicInt(&device->shutdown)) { |
| // this semaphore won't fire when the app is in the background (AAUDIO_PauseDevices was called). |
| if (SDL_WaitSemaphoreTimeout(device->hidden->semaphore, 100)) { |
| return true; // semaphore was signaled, let's go! |
| } |
| // Still waiting on the semaphore (or the system), check other things then wait again. |
| } |
| return true; |
| } |
| |
| static bool BuildAAudioStream(SDL_AudioDevice *device); |
| |
| static bool RecoverAAudioDevice(SDL_AudioDevice *device) |
| { |
| struct SDL_PrivateAudioData *hidden = device->hidden; |
| |
| // attempt to build a new stream, in case there's a new default device. |
| ctx.AAudioStream_requestStop(hidden->stream); |
| ctx.AAudioStream_close(hidden->stream); |
| hidden->stream = NULL; |
| |
| SDL_aligned_free(hidden->mixbuf); |
| hidden->mixbuf = NULL; |
| |
| SDL_DestroySemaphore(hidden->semaphore); |
| hidden->semaphore = NULL; |
| |
| const int prev_sample_frames = device->sample_frames; |
| SDL_AudioSpec prevspec; |
| SDL_copyp(&prevspec, &device->spec); |
| |
| if (!BuildAAudioStream(device)) { |
| return false; // oh well, we tried. |
| } |
| |
| // we don't know the new device spec until we open the new device, so we saved off the old one and force it back |
| // so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec. |
| const int new_sample_frames = device->sample_frames; |
| SDL_AudioSpec newspec; |
| SDL_copyp(&newspec, &device->spec); |
| |
| device->sample_frames = prev_sample_frames; |
| SDL_copyp(&device->spec, &prevspec); |
| if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) { |
| return false; // ugh |
| } |
| return true; |
| } |
| |
| |
| static bool AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) |
| { |
| struct SDL_PrivateAudioData *hidden = device->hidden; |
| |
| // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. |
| const aaudio_result_t err = (aaudio_result_t) SDL_GetAtomicInt(&hidden->error_callback_triggered); |
| if (err) { |
| SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "aaudio: Audio device triggered error %d (%s)", (int) err, ctx.AAudio_convertResultToText(err)); |
| |
| if (!RecoverAAudioDevice(device)) { |
| return false; // oh well, we went down hard. |
| } |
| } else { |
| SDL_MemoryBarrierRelease(); |
| hidden->processed_bytes += buflen; |
| } |
| return true; |
| } |
| |
| static int AAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) |
| { |
| struct SDL_PrivateAudioData *hidden = device->hidden; |
| |
| // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. |
| if (SDL_GetAtomicInt(&hidden->error_callback_triggered)) { |
| SDL_SetAtomicInt(&hidden->error_callback_triggered, 0); |
| return -1; |
| } |
| |
| SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here |
| size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes); |
| SDL_MemoryBarrierAcquire(); |
| SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen); |
| hidden->processed_bytes += buflen; |
| return buflen; |
| } |
| |
| static void AAUDIO_CloseDevice(SDL_AudioDevice *device) |
| { |
| struct SDL_PrivateAudioData *hidden = device->hidden; |
| LOGI(SDL_FUNCTION); |
| |
| 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_aligned_free(hidden->mixbuf); |
| SDL_free(hidden); |
| device->hidden = NULL; |
| } |
| } |
| |
| static void SetOptionalStreamUsage(AAudioStreamBuilder *builder) |
| { |
| if (ctx.AAudioStreamBuilder_setUsage) { // optional API: requires Android 28 |
| const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE); |
| if (hint) { |
| aaudio_usage_t usage = AAUDIO_USAGE_MEDIA; // covers most things, and is the system default. |
| if ((SDL_strcasecmp(hint, "Communications") == 0) || (SDL_strcasecmp(hint, "GameChat") == 0)) { |
| usage = AAUDIO_USAGE_VOICE_COMMUNICATION; |
| } else if (SDL_strcasecmp(hint, "Game") == 0) { |
| usage = AAUDIO_USAGE_GAME; |
| } |
| ctx.AAudioStreamBuilder_setUsage(builder, usage); |
| |
| // !!! FIXME: I _think_ this is okay with the current set of usages we support, but the docs |
| // !!! FIXME: say you need to dip down into Java to call android.app.Activity.setVolumeControlStream(usage) |
| // !!! FIXME: so the physical volume buttons control this stream, but that might be more for special cases |
| // !!! FIXME: like notification sounds, etc, and it's possible you _don't_ want to override this for those |
| // !!! FIXME: special cases, too! We'll revisit if there are bug reports. |
| } |
| } |
| } |
| |
| static bool BuildAAudioStream(SDL_AudioDevice *device) |
| { |
| struct SDL_PrivateAudioData *hidden = device->hidden; |
| const bool recording = device->recording; |
| aaudio_result_t res; |
| |
| SDL_SetAtomicInt(&hidden->error_callback_triggered, 0); |
| |
| AAudioStreamBuilder *builder = NULL; |
| res = ctx.AAudio_createStreamBuilder(&builder); |
| if (res != AAUDIO_OK) { |
| LOGI("SDL Failed AAudio_createStreamBuilder %d", res); |
| return SDL_SetError("SDL Failed AAudio_createStreamBuilder %d", res); |
| } else if (!builder) { |
| LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL"); |
| return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL"); |
| } |
| |
| #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES |
| const int aaudio_device_id = (int) ((size_t) device->handle); |
| LOGI("Opening device id %d", aaudio_device_id); |
| ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id); |
| #endif |
| |
| aaudio_format_t format; |
| if ((device->spec.format == SDL_AUDIO_S32) && (SDL_GetAndroidSDKVersion() >= 31)) { |
| format = AAUDIO_FORMAT_PCM_I32; |
| } else if (device->spec.format == SDL_AUDIO_F32) { |
| format = AAUDIO_FORMAT_PCM_FLOAT; |
| } else { |
| format = AAUDIO_FORMAT_PCM_I16; // sint16 is a safe bet for everything else. |
| } |
| ctx.AAudioStreamBuilder_setFormat(builder, format); |
| ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); |
| ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); |
| |
| int32_t sample_frames; |
| if (SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) { |
| sample_frames = device->sample_frames; |
| } else { |
| // Use 20 ms for the default audio buffer size |
| sample_frames = (device->spec.freq / 50); |
| } |
| ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * sample_frames); // AAudio requires that the buffer capacity is at least |
| ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, sample_frames); // twice the size of the data callback buffer size |
| |
| const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); |
| ctx.AAudioStreamBuilder_setDirection(builder, direction); |
| ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device); |
| ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device); |
| // Some devices have flat sounding audio when low latency mode is enabled, but this is a better experience for most people |
| if (SDL_GetHintBoolean(SDL_HINT_ANDROID_LOW_LATENCY_AUDIO, true)) { |
| SDL_Log("Low latency audio enabled"); |
| ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); |
| } else { |
| SDL_Log("Low latency audio disabled"); |
| } |
| |
| SetOptionalStreamUsage(builder); |
| |
| if (recording && ctx.AAudioStreamBuilder_setInputPreset) { // optional API: requires Android 28 |
| // try to use a microphone that is for recording external audio. Otherwise Android might choose the mic used for talking |
| // on the telephone when held to the user's ear, which is often not useful at any distance from the device. |
| ctx.AAudioStreamBuilder_setInputPreset(builder, AAUDIO_INPUT_PRESET_CAMCORDER); |
| } |
| |
| LOGI("AAudio Try to open %u hz %s %u channels samples %u", |
| device->spec.freq, SDL_GetAudioFormatName(device->spec.format), |
| device->spec.channels, device->sample_frames); |
| |
| res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream); |
| if (res != AAUDIO_OK) { |
| LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); |
| ctx.AAudioStreamBuilder_delete(builder); |
| return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); |
| } |
| ctx.AAudioStreamBuilder_delete(builder); |
| |
| device->sample_frames = (int)ctx.AAudioStream_getFramesPerDataCallback(hidden->stream); |
| if (device->sample_frames == AAUDIO_UNSPECIFIED) { |
| // We'll get variable frames in the callback, make sure we have at least half a buffer available |
| device->sample_frames = (int)ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2; |
| } |
| |
| device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream); |
| device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream); |
| |
| format = ctx.AAudioStream_getFormat(hidden->stream); |
| if (format == AAUDIO_FORMAT_PCM_I16) { |
| device->spec.format = SDL_AUDIO_S16; |
| } else if (format == AAUDIO_FORMAT_PCM_I32) { |
| device->spec.format = SDL_AUDIO_S32; |
| } else if (format == AAUDIO_FORMAT_PCM_FLOAT) { |
| device->spec.format = SDL_AUDIO_F32; |
| } else { |
| return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format); |
| } |
| |
| SDL_UpdatedAudioDeviceFormat(device); |
| |
| // Allocate a triple buffered mixing buffer |
| // Two buffers can be in the process of being filled while the third is being read |
| hidden->num_buffers = 3; |
| hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size); |
| hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), hidden->mixbuf_bytes); |
| if (!hidden->mixbuf) { |
| return false; |
| } |
| hidden->processed_bytes = 0; |
| hidden->callback_bytes = 0; |
| |
| hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers - 1); |
| if (!hidden->semaphore) { |
| LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording); |
| return false; |
| } |
| |
| LOGI("AAudio Actually opened %u hz %s %u channels samples %u, buffers %d", |
| device->spec.freq, SDL_GetAudioFormatName(device->spec.format), |
| device->spec.channels, device->sample_frames, hidden->num_buffers); |
| |
| res = ctx.AAudioStream_requestStart(hidden->stream); |
| if (res != AAUDIO_OK) { |
| LOGI("SDL Failed AAudioStream_requestStart %d recording:%d", res, recording); |
| return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); |
| } |
| |
| LOGI("SDL AAudioStream_requestStart OK"); |
| |
| return true; |
| } |
| |
| // !!! FIXME: make this non-blocking! |
| static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted) |
| { |
| SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1); |
| } |
| |
| static bool AAUDIO_OpenDevice(SDL_AudioDevice *device) |
| { |
| #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES |
| SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero. |
| #endif |
| |
| LOGI(SDL_FUNCTION); |
| |
| if (device->recording) { |
| // !!! FIXME: make this non-blocking! |
| SDL_AtomicInt permission_response; |
| SDL_SetAtomicInt(&permission_response, 0); |
| if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) { |
| return false; |
| } |
| |
| while (SDL_GetAtomicInt(&permission_response) == 0) { |
| SDL_Delay(10); |
| } |
| |
| if (SDL_GetAtomicInt(&permission_response) < 0) { |
| LOGI("This app doesn't have RECORD_AUDIO permission"); |
| return SDL_SetError("This app doesn't have RECORD_AUDIO permission"); |
| } |
| } |
| |
| device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); |
| if (!device->hidden) { |
| return false; |
| } |
| |
| return BuildAAudioStream(device); |
| } |
| |
| static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata) |
| { |
| struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden; |
| if (hidden) { |
| if (hidden->stream) { |
| aaudio_result_t res; |
| |
| if (device->recording) { |
| // Pause() isn't implemented for recording, use Stop() |
| res = ctx.AAudioStream_requestStop(hidden->stream); |
| } else { |
| res = ctx.AAudioStream_requestPause(hidden->stream); |
| } |
| |
| if (res != AAUDIO_OK) { |
| LOGI("SDL Failed AAudioStream_requestPause %d", res); |
| SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); |
| } |
| } |
| } |
| return false; // keep enumerating. |
| } |
| |
| // Pause (block) all non already paused audio devices by taking their mixer lock |
| void AAUDIO_PauseDevices(void) |
| { |
| if (ctx.handle) { // AAUDIO driver is used? |
| (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL); |
| } |
| } |
| |
| // Resume (unblock) all non already paused audio devices by releasing their mixer lock |
| static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata) |
| { |
| struct SDL_PrivateAudioData *hidden = device->hidden; |
| if (hidden) { |
| if (hidden->stream) { |
| aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream); |
| if (res != AAUDIO_OK) { |
| LOGI("SDL Failed AAudioStream_requestStart %d", res); |
| SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res)); |
| } |
| } |
| } |
| return false; // keep enumerating. |
| } |
| |
| void AAUDIO_ResumeDevices(void) |
| { |
| if (ctx.handle) { // AAUDIO driver is used? |
| (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL); |
| } |
| } |
| |
| static void AAUDIO_Deinitialize(void) |
| { |
| Android_StopAudioHotplug(); |
| |
| LOGI(SDL_FUNCTION); |
| if (ctx.handle) { |
| SDL_UnloadObject(ctx.handle); |
| } |
| SDL_zero(ctx); |
| LOGI("End AAUDIO %s", SDL_GetError()); |
| } |
| |
| |
| static bool AAUDIO_Init(SDL_AudioDriverImpl *impl) |
| { |
| LOGI(SDL_FUNCTION); |
| |
| /* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release, |
| * so don't use it until 8.1. |
| * |
| * See https://github.com/google/oboe/issues/40 for more information. |
| */ |
| if (SDL_GetAndroidSDKVersion() < 27) { |
| return false; |
| } |
| |
| SDL_zero(ctx); |
| |
| ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO); |
| if (!ctx.handle) { |
| LOGI("SDL couldn't find " LIB_AAUDIO_SO); |
| return false; |
| } |
| |
| if (!AAUDIO_LoadFunctions(&ctx)) { |
| SDL_UnloadObject(ctx.handle); |
| SDL_zero(ctx); |
| return false; |
| } |
| |
| impl->ThreadInit = Android_AudioThreadInit; |
| impl->Deinitialize = AAUDIO_Deinitialize; |
| impl->OpenDevice = AAUDIO_OpenDevice; |
| impl->CloseDevice = AAUDIO_CloseDevice; |
| impl->WaitDevice = AAUDIO_WaitDevice; |
| impl->PlayDevice = AAUDIO_PlayDevice; |
| impl->GetDeviceBuf = AAUDIO_GetDeviceBuf; |
| impl->WaitRecordingDevice = AAUDIO_WaitDevice; |
| impl->RecordDevice = AAUDIO_RecordDevice; |
| |
| impl->HasRecordingSupport = true; |
| |
| #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES |
| impl->DetectDevices = Android_StartAudioHotplug; |
| #else |
| impl->OnlyHasDefaultPlaybackDevice = true; |
| impl->OnlyHasDefaultRecordingDevice = true; |
| #endif |
| |
| LOGI("SDL AAUDIO_Init OK"); |
| return true; |
| } |
| |
| AudioBootStrap AAUDIO_bootstrap = { |
| "AAudio", "AAudio audio driver", AAUDIO_Init, false, false |
| }; |
| |
| #endif // SDL_AUDIO_DRIVER_AAUDIO |