| /* | 
 |   Simple DirectMedia Layer | 
 |   Copyright (C) 1997-2023 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" | 
 |  | 
 | #if SDL_AUDIO_DRIVER_AAUDIO | 
 |  | 
 | #include "SDL_audio.h" | 
 | #include "SDL_loadso.h" | 
 | #include "../SDL_audio_c.h" | 
 | #include "../../core/android/SDL_android.h" | 
 | #include "SDL_aaudio.h" | 
 |  | 
 | /* Debug */ | 
 | #if 0 | 
 | #define LOGI(...) SDL_Log(__VA_ARGS__); | 
 | #else | 
 | #define LOGI(...) | 
 | #endif | 
 |  | 
 | typedef struct AAUDIO_Data | 
 | { | 
 |     AAudioStreamBuilder *builder; | 
 |     void *handle; | 
 | #define SDL_PROC(ret, func, params) ret (*func) params; | 
 | #include "SDL_aaudiofuncs.h" | 
 | #undef SDL_PROC | 
 | } AAUDIO_Data; | 
 | static AAUDIO_Data ctx; | 
 |  | 
 | static SDL_AudioDevice *audioDevice = NULL; | 
 | static SDL_AudioDevice *captureDevice = NULL; | 
 |  | 
 | static int aaudio_LoadFunctions(AAUDIO_Data *data) | 
 | { | 
 | #define SDL_PROC(ret, func, params)                                                             \ | 
 |     do {                                                                                        \ | 
 |         data->func = SDL_LoadFunction(data->handle, #func);                                     \ | 
 |         if (!data->func) {                                                                      \ | 
 |             return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \ | 
 |         }                                                                                       \ | 
 |     } while (0); | 
 | #include "SDL_aaudiofuncs.h" | 
 | #undef SDL_PROC | 
 |     return 0; | 
 | } | 
 |  | 
 | void aaudio_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error); | 
 | void aaudio_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error) | 
 | { | 
 |     LOGI("SDL aaudio_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error)); | 
 | } | 
 |  | 
 | #define LIB_AAUDIO_SO "libaaudio.so" | 
 |  | 
 | static int aaudio_OpenDevice(_THIS, const char *devname) | 
 | { | 
 |     struct SDL_PrivateAudioData *private; | 
 |     SDL_bool iscapture = this->iscapture; | 
 |     aaudio_result_t res; | 
 |     LOGI(__func__); | 
 |  | 
 |     SDL_assert((captureDevice == NULL) || !iscapture); | 
 |     SDL_assert((audioDevice == NULL) || iscapture); | 
 |  | 
 |     if (iscapture) { | 
 |         if (!Android_JNI_RequestPermission("android.permission.RECORD_AUDIO")) { | 
 |             LOGI("This app doesn't have RECORD_AUDIO permission"); | 
 |             return SDL_SetError("This app doesn't have RECORD_AUDIO permission"); | 
 |         } | 
 |     } | 
 |  | 
 |     if (iscapture) { | 
 |         captureDevice = this; | 
 |     } else { | 
 |         audioDevice = this; | 
 |     } | 
 |  | 
 |     this->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*this->hidden)); | 
 |     if (this->hidden == NULL) { | 
 |         return SDL_OutOfMemory(); | 
 |     } | 
 |     private = this->hidden; | 
 |  | 
 |     ctx.AAudioStreamBuilder_setSampleRate(ctx.builder, this->spec.freq); | 
 |     ctx.AAudioStreamBuilder_setChannelCount(ctx.builder, this->spec.channels); | 
 |     if(devname != NULL) { | 
 |         int aaudio_device_id = SDL_atoi(devname); | 
 |         LOGI("Opening device id %d", aaudio_device_id); | 
 |         ctx.AAudioStreamBuilder_setDeviceId(ctx.builder, aaudio_device_id); | 
 |     } | 
 |     { | 
 |         aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); | 
 |         ctx.AAudioStreamBuilder_setDirection(ctx.builder, direction); | 
 |     } | 
 |     { | 
 |         aaudio_format_t format = AAUDIO_FORMAT_PCM_FLOAT; | 
 |         if (this->spec.format == AUDIO_S16SYS) { | 
 |             format = AAUDIO_FORMAT_PCM_I16; | 
 |         } else if (this->spec.format == AUDIO_S16SYS) { | 
 |             format = AAUDIO_FORMAT_PCM_FLOAT; | 
 |         } | 
 |         ctx.AAudioStreamBuilder_setFormat(ctx.builder, format); | 
 |     } | 
 |  | 
 |     ctx.AAudioStreamBuilder_setErrorCallback(ctx.builder, aaudio_errorCallback, private); | 
 |  | 
 |     LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u", | 
 |          this->spec.freq, SDL_AUDIO_BITSIZE(this->spec.format), | 
 |          this->spec.channels, (this->spec.format & 0x1000) ? "BE" : "LE", this->spec.samples); | 
 |  | 
 |     res = ctx.AAudioStreamBuilder_openStream(ctx.builder, &private->stream); | 
 |     if (res != AAUDIO_OK) { | 
 |         LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); | 
 |         return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | 
 |     } | 
 |  | 
 |     this->spec.freq = ctx.AAudioStream_getSampleRate(private->stream); | 
 |     this->spec.channels = ctx.AAudioStream_getChannelCount(private->stream); | 
 |     { | 
 |         aaudio_format_t fmt = ctx.AAudioStream_getFormat(private->stream); | 
 |         if (fmt == AAUDIO_FORMAT_PCM_I16) { | 
 |             this->spec.format = AUDIO_S16SYS; | 
 |         } else if (fmt == AAUDIO_FORMAT_PCM_FLOAT) { | 
 |             this->spec.format = AUDIO_F32SYS; | 
 |         } | 
 |     } | 
 |  | 
 |     LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u", | 
 |          this->spec.freq, SDL_AUDIO_BITSIZE(this->spec.format), | 
 |          this->spec.channels, (this->spec.format & 0x1000) ? "BE" : "LE", this->spec.samples); | 
 |  | 
 |     SDL_CalculateAudioSpec(&this->spec); | 
 |  | 
 |     /* Allocate mixing buffer */ | 
 |     if (!iscapture) { | 
 |         private->mixlen = this->spec.size; | 
 |         private->mixbuf = (Uint8 *)SDL_malloc(private->mixlen); | 
 |         if (private->mixbuf == NULL) { | 
 |             return SDL_OutOfMemory(); | 
 |         } | 
 |         SDL_memset(private->mixbuf, this->spec.silence, this->spec.size); | 
 |     } | 
 |  | 
 |     private->frame_size = this->spec.channels * (SDL_AUDIO_BITSIZE(this->spec.format) / 8); | 
 |  | 
 |     res = ctx.AAudioStream_requestStart(private->stream); | 
 |     if (res != AAUDIO_OK) { | 
 |         LOGI("SDL Failed AAudioStream_requestStart %d iscapture:%d", res, iscapture); | 
 |         return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | 
 |     } | 
 |  | 
 |     LOGI("SDL AAudioStream_requestStart OK"); | 
 |     return 0; | 
 | } | 
 |  | 
 | static void aaudio_CloseDevice(_THIS) | 
 | { | 
 |     struct SDL_PrivateAudioData *private = this->hidden; | 
 |     aaudio_result_t res; | 
 |     LOGI(__func__); | 
 |  | 
 |     if (private->stream) { | 
 |         res = ctx.AAudioStream_requestStop(private->stream); | 
 |         if (res != AAUDIO_OK) { | 
 |             LOGI("SDL Failed AAudioStream_requestStop %d", res); | 
 |             SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | 
 |             return; | 
 |         } | 
 |  | 
 |         res = ctx.AAudioStream_close(private->stream); | 
 |         if (res != AAUDIO_OK) { | 
 |             LOGI("SDL Failed AAudioStreamBuilder_delete %d", res); | 
 |             SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | 
 |             return; | 
 |         } | 
 |     } | 
 |  | 
 |     if (this->iscapture) { | 
 |         SDL_assert(captureDevice == this); | 
 |         captureDevice = NULL; | 
 |     } else { | 
 |         SDL_assert(audioDevice == this); | 
 |         audioDevice = NULL; | 
 |     } | 
 |  | 
 |     SDL_free(this->hidden->mixbuf); | 
 |     SDL_free(this->hidden); | 
 | } | 
 |  | 
 | static Uint8 *aaudio_GetDeviceBuf(_THIS) | 
 | { | 
 |     struct SDL_PrivateAudioData *private = this->hidden; | 
 |     return private->mixbuf; | 
 | } | 
 |  | 
 | static void aaudio_PlayDevice(_THIS) | 
 | { | 
 |     struct SDL_PrivateAudioData *private = this->hidden; | 
 |     aaudio_result_t res; | 
 |     int64_t timeoutNanoseconds = 1 * 1000 * 1000; /* 8 ms */ | 
 |     res = ctx.AAudioStream_write(private->stream, private->mixbuf, private->mixlen / private->frame_size, timeoutNanoseconds); | 
 |     if (res < 0) { | 
 |         LOGI("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | 
 |     } else { | 
 |         LOGI("SDL AAudio play: %d frames, wanted:%d frames", (int)res, private->mixlen / private->frame_size); | 
 |     } | 
 |  | 
 | #if 0 | 
 |     /* Log under-run count */ | 
 |     { | 
 |         static int prev = 0; | 
 |         int32_t cnt = ctx.AAudioStream_getXRunCount(private->stream); | 
 |         if (cnt != prev) { | 
 |             SDL_Log("AAudio underrun: %d - total: %d", cnt - prev, cnt); | 
 |             prev = cnt; | 
 |         } | 
 |     } | 
 | #endif | 
 | } | 
 |  | 
 | static int aaudio_CaptureFromDevice(_THIS, void *buffer, int buflen) | 
 | { | 
 |     struct SDL_PrivateAudioData *private = this->hidden; | 
 |     aaudio_result_t res; | 
 |     int64_t timeoutNanoseconds = 8 * 1000 * 1000; /* 8 ms */ | 
 |     res = ctx.AAudioStream_read(private->stream, buffer, buflen / private->frame_size, timeoutNanoseconds); | 
 |     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 / private->frame_size); | 
 |     return res * private->frame_size; | 
 | } | 
 |  | 
 | static void aaudio_Deinitialize(void) | 
 | { | 
 |     LOGI(__func__); | 
 |     if (ctx.handle) { | 
 |         if (ctx.builder) { | 
 |             aaudio_result_t res; | 
 |             res = ctx.AAudioStreamBuilder_delete(ctx.builder); | 
 |             if (res != AAUDIO_OK) { | 
 |                 SDL_SetError("Failed AAudioStreamBuilder_delete %s", ctx.AAudio_convertResultToText(res)); | 
 |             } | 
 |         } | 
 |         SDL_UnloadObject(ctx.handle); | 
 |     } | 
 |     ctx.handle = NULL; | 
 |     ctx.builder = NULL; | 
 |     LOGI("End AAUDIO %s", SDL_GetError()); | 
 | } | 
 |  | 
 | static SDL_bool aaudio_Init(SDL_AudioDriverImpl *impl) | 
 | { | 
 |     aaudio_result_t res; | 
 |     LOGI(__func__); | 
 |  | 
 |     /* 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 SDL_FALSE; | 
 |     } | 
 |  | 
 |     SDL_zero(ctx); | 
 |  | 
 |     ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO); | 
 |     if (ctx.handle == NULL) { | 
 |         LOGI("SDL couldn't find " LIB_AAUDIO_SO); | 
 |         goto failure; | 
 |     } | 
 |  | 
 |     if (aaudio_LoadFunctions(&ctx) < 0) { | 
 |         goto failure; | 
 |     } | 
 |  | 
 |     res = ctx.AAudio_createStreamBuilder(&ctx.builder); | 
 |     if (res != AAUDIO_OK) { | 
 |         LOGI("SDL Failed AAudio_createStreamBuilder %d", res); | 
 |         goto failure; | 
 |     } | 
 |  | 
 |     if (ctx.builder == NULL) { | 
 |         LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL"); | 
 |         goto failure; | 
 |     } | 
 |  | 
 |     impl->DetectDevices = Android_DetectDevices; | 
 |     impl->Deinitialize = aaudio_Deinitialize; | 
 |     impl->OpenDevice = aaudio_OpenDevice; | 
 |     impl->CloseDevice = aaudio_CloseDevice; | 
 |     impl->PlayDevice = aaudio_PlayDevice; | 
 |     impl->GetDeviceBuf = aaudio_GetDeviceBuf; | 
 |     impl->CaptureFromDevice = aaudio_CaptureFromDevice; | 
 |     impl->AllowsArbitraryDeviceNames = SDL_TRUE; | 
 |  | 
 |     /* and the capabilities */ | 
 |     impl->HasCaptureSupport = SDL_TRUE; | 
 |     impl->OnlyHasDefaultOutputDevice = SDL_FALSE; | 
 |     impl->OnlyHasDefaultCaptureDevice = SDL_FALSE; | 
 |  | 
 |     /* this audio target is available. */ | 
 |     LOGI("SDL aaudio_Init OK"); | 
 |     return SDL_TRUE; | 
 |  | 
 | failure: | 
 |     if (ctx.handle) { | 
 |         if (ctx.builder) { | 
 |             ctx.AAudioStreamBuilder_delete(ctx.builder); | 
 |         } | 
 |         SDL_UnloadObject(ctx.handle); | 
 |     } | 
 |     ctx.handle = NULL; | 
 |     ctx.builder = NULL; | 
 |     return SDL_FALSE; | 
 | } | 
 |  | 
 | AudioBootStrap aaudio_bootstrap = { | 
 |     "AAudio", "AAudio audio driver", aaudio_Init, SDL_FALSE | 
 | }; | 
 |  | 
 | /* Pause (block) all non already paused audio devices by taking their mixer lock */ | 
 | void aaudio_PauseDevices(void) | 
 | { | 
 |     /* TODO: Handle multiple devices? */ | 
 |     struct SDL_PrivateAudioData *private; | 
 |     if (audioDevice != NULL && audioDevice->hidden != NULL) { | 
 |         private = (struct SDL_PrivateAudioData *)audioDevice->hidden; | 
 |  | 
 |         if (private->stream) { | 
 |             aaudio_result_t res = ctx.AAudioStream_requestPause(private->stream); | 
 |             if (res != AAUDIO_OK) { | 
 |                 LOGI("SDL Failed AAudioStream_requestPause %d", res); | 
 |                 SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | 
 |             } | 
 |         } | 
 |  | 
 |         if (SDL_AtomicGet(&audioDevice->paused)) { | 
 |             /* The device is already paused, leave it alone */ | 
 |             private->resume = SDL_FALSE; | 
 |         } else { | 
 |             SDL_LockMutex(audioDevice->mixer_lock); | 
 |             SDL_AtomicSet(&audioDevice->paused, 1); | 
 |             private->resume = SDL_TRUE; | 
 |         } | 
 |     } | 
 |  | 
 |     if (captureDevice != NULL && captureDevice->hidden != NULL) { | 
 |         private = (struct SDL_PrivateAudioData *)captureDevice->hidden; | 
 |  | 
 |         if (private->stream) { | 
 |             /* Pause() isn't implemented for 'capture', use Stop() */ | 
 |             aaudio_result_t res = ctx.AAudioStream_requestStop(private->stream); | 
 |             if (res != AAUDIO_OK) { | 
 |                 LOGI("SDL Failed AAudioStream_requestStop %d", res); | 
 |                 SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | 
 |             } | 
 |         } | 
 |  | 
 |         if (SDL_AtomicGet(&captureDevice->paused)) { | 
 |             /* The device is already paused, leave it alone */ | 
 |             private->resume = SDL_FALSE; | 
 |         } else { | 
 |             SDL_LockMutex(captureDevice->mixer_lock); | 
 |             SDL_AtomicSet(&captureDevice->paused, 1); | 
 |             private->resume = SDL_TRUE; | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | /* Resume (unblock) all non already paused audio devices by releasing their mixer lock */ | 
 | void aaudio_ResumeDevices(void) | 
 | { | 
 |     /* TODO: Handle multiple devices? */ | 
 |     struct SDL_PrivateAudioData *private; | 
 |     if (audioDevice != NULL && audioDevice->hidden != NULL) { | 
 |         private = (struct SDL_PrivateAudioData *)audioDevice->hidden; | 
 |  | 
 |         if (private->resume) { | 
 |             SDL_AtomicSet(&audioDevice->paused, 0); | 
 |             private->resume = SDL_FALSE; | 
 |             SDL_UnlockMutex(audioDevice->mixer_lock); | 
 |         } | 
 |  | 
 |         if (private->stream) { | 
 |             aaudio_result_t res = ctx.AAudioStream_requestStart(private->stream); | 
 |             if (res != AAUDIO_OK) { | 
 |                 LOGI("SDL Failed AAudioStream_requestStart %d", res); | 
 |                 SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     if (captureDevice != NULL && captureDevice->hidden != NULL) { | 
 |         private = (struct SDL_PrivateAudioData *)captureDevice->hidden; | 
 |  | 
 |         if (private->resume) { | 
 |             SDL_AtomicSet(&captureDevice->paused, 0); | 
 |             private->resume = SDL_FALSE; | 
 |             SDL_UnlockMutex(captureDevice->mixer_lock); | 
 |         } | 
 |  | 
 |         if (private->stream) { | 
 |             aaudio_result_t res = ctx.AAudioStream_requestStart(private->stream); | 
 |             if (res != AAUDIO_OK) { | 
 |                 LOGI("SDL Failed AAudioStream_requestStart %d", res); | 
 |                 SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | /* | 
 |  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. | 
 |  But, AAudioStream_getTimestamp() does return AAUDIO_ERROR_INVALID_STATE | 
 | */ | 
 | SDL_bool aaudio_DetectBrokenPlayState(void) | 
 | { | 
 |     struct SDL_PrivateAudioData *private; | 
 |     int64_t framePosition, timeNanoseconds; | 
 |     aaudio_result_t res; | 
 |  | 
 |     if (audioDevice == NULL || !audioDevice->hidden) { | 
 |         return SDL_FALSE; | 
 |     } | 
 |  | 
 |     private = audioDevice->hidden; | 
 |  | 
 |     res = ctx.AAudioStream_getTimestamp(private->stream, CLOCK_MONOTONIC, &framePosition, &timeNanoseconds); | 
 |     if (res == AAUDIO_ERROR_INVALID_STATE) { | 
 |         aaudio_stream_state_t currentState = ctx.AAudioStream_getState(private->stream); | 
 |         /* AAudioStream_getTimestamp() will also return AAUDIO_ERROR_INVALID_STATE while the stream is still initially starting. But we only care if it silently went invalid while playing. */ | 
 |         if (currentState == AAUDIO_STREAM_STATE_STARTED) { | 
 |             LOGI("SDL aaudio_DetectBrokenPlayState: detected invalid audio device state: AAudioStream_getTimestamp result=%d, framePosition=%lld, timeNanoseconds=%lld, getState=%d", (int)res, (long long)framePosition, (long long)timeNanoseconds, (int)currentState); | 
 |             return SDL_TRUE; | 
 |         } | 
 |     } | 
 |  | 
 |     return SDL_FALSE; | 
 | } | 
 |  | 
 | #endif /* SDL_AUDIO_DRIVER_AAUDIO */ | 
 |  | 
 | /* vi: set ts=4 sw=4 expandtab: */ |