diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.c')
| -rw-r--r-- | contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.c | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.c b/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.c new file mode 100644 index 0000000..3360bec --- /dev/null +++ b/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.c | |||
| @@ -0,0 +1,551 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifdef SDL_AUDIO_DRIVER_AAUDIO | ||
| 24 | |||
| 25 | #include "../SDL_sysaudio.h" | ||
| 26 | #include "SDL_aaudio.h" | ||
| 27 | |||
| 28 | #include "../../core/android/SDL_android.h" | ||
| 29 | #include <aaudio/AAudio.h> | ||
| 30 | |||
| 31 | #if __ANDROID_API__ < 31 | ||
| 32 | #define AAUDIO_FORMAT_PCM_I32 4 | ||
| 33 | #endif | ||
| 34 | |||
| 35 | struct SDL_PrivateAudioData | ||
| 36 | { | ||
| 37 | AAudioStream *stream; | ||
| 38 | int num_buffers; | ||
| 39 | Uint8 *mixbuf; // Raw mixing buffer | ||
| 40 | size_t mixbuf_bytes; // num_buffers * device->buffer_size | ||
| 41 | size_t callback_bytes; | ||
| 42 | size_t processed_bytes; | ||
| 43 | SDL_Semaphore *semaphore; | ||
| 44 | SDL_AtomicInt error_callback_triggered; | ||
| 45 | }; | ||
| 46 | |||
| 47 | // Debug | ||
| 48 | #if 0 | ||
| 49 | #define LOGI(...) SDL_Log(__VA_ARGS__); | ||
| 50 | #else | ||
| 51 | #define LOGI(...) | ||
| 52 | #endif | ||
| 53 | |||
| 54 | #define LIB_AAUDIO_SO "libaaudio.so" | ||
| 55 | |||
| 56 | typedef struct AAUDIO_Data | ||
| 57 | { | ||
| 58 | SDL_SharedObject *handle; | ||
| 59 | #define SDL_PROC(ret, func, params) ret (*func) params; | ||
| 60 | #include "SDL_aaudiofuncs.h" | ||
| 61 | } AAUDIO_Data; | ||
| 62 | static AAUDIO_Data ctx; | ||
| 63 | |||
| 64 | static bool AAUDIO_LoadFunctions(AAUDIO_Data *data) | ||
| 65 | { | ||
| 66 | #define SDL_PROC(ret, func, params) \ | ||
| 67 | do { \ | ||
| 68 | data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \ | ||
| 69 | if (!data->func) { \ | ||
| 70 | return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \ | ||
| 71 | } \ | ||
| 72 | } while (0); | ||
| 73 | #include "SDL_aaudiofuncs.h" | ||
| 74 | return true; | ||
| 75 | } | ||
| 76 | |||
| 77 | |||
| 78 | static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error) | ||
| 79 | { | ||
| 80 | LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error)); | ||
| 81 | |||
| 82 | // You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here. | ||
| 83 | // Just flag the device so we can kill it in PlayDevice instead. | ||
| 84 | SDL_AudioDevice *device = (SDL_AudioDevice *) userData; | ||
| 85 | SDL_SetAtomicInt(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error. | ||
| 86 | SDL_SignalSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice. | ||
| 87 | } | ||
| 88 | |||
| 89 | static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) | ||
| 90 | { | ||
| 91 | SDL_AudioDevice *device = (SDL_AudioDevice *) userData; | ||
| 92 | struct SDL_PrivateAudioData *hidden = device->hidden; | ||
| 93 | size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec); | ||
| 94 | size_t callback_bytes = numFrames * framesize; | ||
| 95 | size_t old_buffer_index = hidden->callback_bytes / device->buffer_size; | ||
| 96 | |||
| 97 | if (device->recording) { | ||
| 98 | const Uint8 *input = (const Uint8 *)audioData; | ||
| 99 | size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes); | ||
| 100 | size_t size = SDL_min(available_bytes, callback_bytes); | ||
| 101 | size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes; | ||
| 102 | size_t end = (offset + size) % hidden->mixbuf_bytes; | ||
| 103 | SDL_assert(size <= hidden->mixbuf_bytes); | ||
| 104 | |||
| 105 | //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); | ||
| 106 | |||
| 107 | if (offset <= end) { | ||
| 108 | SDL_memcpy(&hidden->mixbuf[offset], input, size); | ||
| 109 | } else { | ||
| 110 | size_t partial = (hidden->mixbuf_bytes - offset); | ||
| 111 | SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial); | ||
| 112 | SDL_memcpy(&hidden->mixbuf[0], &input[partial], end); | ||
| 113 | } | ||
| 114 | |||
| 115 | SDL_MemoryBarrierRelease(); | ||
| 116 | hidden->callback_bytes += size; | ||
| 117 | |||
| 118 | if (size < callback_bytes) { | ||
| 119 | LOGI("Audio recording overflow, dropped %zu frames", (callback_bytes - size) / framesize); | ||
| 120 | } | ||
| 121 | } else { | ||
| 122 | Uint8 *output = (Uint8 *)audioData; | ||
| 123 | size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes); | ||
| 124 | size_t size = SDL_min(available_bytes, callback_bytes); | ||
| 125 | size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes; | ||
| 126 | size_t end = (offset + size) % hidden->mixbuf_bytes; | ||
| 127 | SDL_assert(size <= hidden->mixbuf_bytes); | ||
| 128 | |||
| 129 | //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); | ||
| 130 | |||
| 131 | SDL_MemoryBarrierAcquire(); | ||
| 132 | if (offset <= end) { | ||
| 133 | SDL_memcpy(output, &hidden->mixbuf[offset], size); | ||
| 134 | } else { | ||
| 135 | size_t partial = (hidden->mixbuf_bytes - offset); | ||
| 136 | SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial); | ||
| 137 | SDL_memcpy(&output[partial], &hidden->mixbuf[0], end); | ||
| 138 | } | ||
| 139 | hidden->callback_bytes += size; | ||
| 140 | |||
| 141 | if (size < callback_bytes) { | ||
| 142 | LOGI("Audio playback underflow, missed %zu frames", (callback_bytes - size) / framesize); | ||
| 143 | SDL_memset(&output[size], device->silence_value, (callback_bytes - size)); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | size_t new_buffer_index = hidden->callback_bytes / device->buffer_size; | ||
| 148 | while (old_buffer_index < new_buffer_index) { | ||
| 149 | // Trigger audio processing | ||
| 150 | SDL_SignalSemaphore(hidden->semaphore); | ||
| 151 | ++old_buffer_index; | ||
| 152 | } | ||
| 153 | |||
| 154 | return AAUDIO_CALLBACK_RESULT_CONTINUE; | ||
| 155 | } | ||
| 156 | |||
| 157 | static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) | ||
| 158 | { | ||
| 159 | struct SDL_PrivateAudioData *hidden = device->hidden; | ||
| 160 | size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes); | ||
| 161 | return &hidden->mixbuf[offset]; | ||
| 162 | } | ||
| 163 | |||
| 164 | static bool AAUDIO_WaitDevice(SDL_AudioDevice *device) | ||
| 165 | { | ||
| 166 | while (!SDL_GetAtomicInt(&device->shutdown)) { | ||
| 167 | // this semaphore won't fire when the app is in the background (AAUDIO_PauseDevices was called). | ||
| 168 | if (SDL_WaitSemaphoreTimeout(device->hidden->semaphore, 100)) { | ||
| 169 | return true; // semaphore was signaled, let's go! | ||
| 170 | } | ||
| 171 | // Still waiting on the semaphore (or the system), check other things then wait again. | ||
| 172 | } | ||
| 173 | return true; | ||
| 174 | } | ||
| 175 | |||
| 176 | static bool BuildAAudioStream(SDL_AudioDevice *device); | ||
| 177 | |||
| 178 | static bool RecoverAAudioDevice(SDL_AudioDevice *device) | ||
| 179 | { | ||
| 180 | struct SDL_PrivateAudioData *hidden = device->hidden; | ||
| 181 | |||
| 182 | // attempt to build a new stream, in case there's a new default device. | ||
| 183 | ctx.AAudioStream_requestStop(hidden->stream); | ||
| 184 | ctx.AAudioStream_close(hidden->stream); | ||
| 185 | hidden->stream = NULL; | ||
| 186 | |||
| 187 | SDL_aligned_free(hidden->mixbuf); | ||
| 188 | hidden->mixbuf = NULL; | ||
| 189 | |||
| 190 | SDL_DestroySemaphore(hidden->semaphore); | ||
| 191 | hidden->semaphore = NULL; | ||
| 192 | |||
| 193 | const int prev_sample_frames = device->sample_frames; | ||
| 194 | SDL_AudioSpec prevspec; | ||
| 195 | SDL_copyp(&prevspec, &device->spec); | ||
| 196 | |||
| 197 | if (!BuildAAudioStream(device)) { | ||
| 198 | return false; // oh well, we tried. | ||
| 199 | } | ||
| 200 | |||
| 201 | // 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 | ||
| 202 | // so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec. | ||
| 203 | const int new_sample_frames = device->sample_frames; | ||
| 204 | SDL_AudioSpec newspec; | ||
| 205 | SDL_copyp(&newspec, &device->spec); | ||
| 206 | |||
| 207 | device->sample_frames = prev_sample_frames; | ||
| 208 | SDL_copyp(&device->spec, &prevspec); | ||
| 209 | if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) { | ||
| 210 | return false; // ugh | ||
| 211 | } | ||
| 212 | return true; | ||
| 213 | } | ||
| 214 | |||
| 215 | |||
| 216 | static bool AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) | ||
| 217 | { | ||
| 218 | struct SDL_PrivateAudioData *hidden = device->hidden; | ||
| 219 | |||
| 220 | // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. | ||
| 221 | const aaudio_result_t err = (aaudio_result_t) SDL_GetAtomicInt(&hidden->error_callback_triggered); | ||
| 222 | if (err) { | ||
| 223 | SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "aaudio: Audio device triggered error %d (%s)", (int) err, ctx.AAudio_convertResultToText(err)); | ||
| 224 | |||
| 225 | if (!RecoverAAudioDevice(device)) { | ||
| 226 | return false; // oh well, we went down hard. | ||
| 227 | } | ||
| 228 | } else { | ||
| 229 | SDL_MemoryBarrierRelease(); | ||
| 230 | hidden->processed_bytes += buflen; | ||
| 231 | } | ||
| 232 | return true; | ||
| 233 | } | ||
| 234 | |||
| 235 | static int AAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) | ||
| 236 | { | ||
| 237 | struct SDL_PrivateAudioData *hidden = device->hidden; | ||
| 238 | |||
| 239 | // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. | ||
| 240 | if (SDL_GetAtomicInt(&hidden->error_callback_triggered)) { | ||
| 241 | SDL_SetAtomicInt(&hidden->error_callback_triggered, 0); | ||
| 242 | return -1; | ||
| 243 | } | ||
| 244 | |||
| 245 | SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here | ||
| 246 | size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes); | ||
| 247 | SDL_MemoryBarrierAcquire(); | ||
| 248 | SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen); | ||
| 249 | hidden->processed_bytes += buflen; | ||
| 250 | return buflen; | ||
| 251 | } | ||
| 252 | |||
| 253 | static void AAUDIO_CloseDevice(SDL_AudioDevice *device) | ||
| 254 | { | ||
| 255 | struct SDL_PrivateAudioData *hidden = device->hidden; | ||
| 256 | LOGI(__func__); | ||
| 257 | |||
| 258 | if (hidden) { | ||
| 259 | if (hidden->stream) { | ||
| 260 | ctx.AAudioStream_requestStop(hidden->stream); | ||
| 261 | // !!! 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)? | ||
| 262 | // !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again? | ||
| 263 | ctx.AAudioStream_close(hidden->stream); | ||
| 264 | } | ||
| 265 | |||
| 266 | if (hidden->semaphore) { | ||
| 267 | SDL_DestroySemaphore(hidden->semaphore); | ||
| 268 | } | ||
| 269 | |||
| 270 | SDL_aligned_free(hidden->mixbuf); | ||
| 271 | SDL_free(hidden); | ||
| 272 | device->hidden = NULL; | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | static bool BuildAAudioStream(SDL_AudioDevice *device) | ||
| 277 | { | ||
| 278 | struct SDL_PrivateAudioData *hidden = device->hidden; | ||
| 279 | const bool recording = device->recording; | ||
| 280 | aaudio_result_t res; | ||
| 281 | |||
| 282 | SDL_SetAtomicInt(&hidden->error_callback_triggered, 0); | ||
| 283 | |||
| 284 | AAudioStreamBuilder *builder = NULL; | ||
| 285 | res = ctx.AAudio_createStreamBuilder(&builder); | ||
| 286 | if (res != AAUDIO_OK) { | ||
| 287 | LOGI("SDL Failed AAudio_createStreamBuilder %d", res); | ||
| 288 | return SDL_SetError("SDL Failed AAudio_createStreamBuilder %d", res); | ||
| 289 | } else if (!builder) { | ||
| 290 | LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL"); | ||
| 291 | return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL"); | ||
| 292 | } | ||
| 293 | |||
| 294 | #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES | ||
| 295 | const int aaudio_device_id = (int) ((size_t) device->handle); | ||
| 296 | LOGI("Opening device id %d", aaudio_device_id); | ||
| 297 | ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id); | ||
| 298 | #endif | ||
| 299 | |||
| 300 | aaudio_format_t format; | ||
| 301 | if ((device->spec.format == SDL_AUDIO_S32) && (SDL_GetAndroidSDKVersion() >= 31)) { | ||
| 302 | format = AAUDIO_FORMAT_PCM_I32; | ||
| 303 | } else if (device->spec.format == SDL_AUDIO_F32) { | ||
| 304 | format = AAUDIO_FORMAT_PCM_FLOAT; | ||
| 305 | } else { | ||
| 306 | format = AAUDIO_FORMAT_PCM_I16; // sint16 is a safe bet for everything else. | ||
| 307 | } | ||
| 308 | ctx.AAudioStreamBuilder_setFormat(builder, format); | ||
| 309 | ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); | ||
| 310 | ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); | ||
| 311 | |||
| 312 | const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); | ||
| 313 | ctx.AAudioStreamBuilder_setDirection(builder, direction); | ||
| 314 | ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device); | ||
| 315 | ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device); | ||
| 316 | // Some devices have flat sounding audio when low latency mode is enabled, but this is a better experience for most people | ||
| 317 | if (SDL_GetHintBoolean(SDL_HINT_ANDROID_LOW_LATENCY_AUDIO, true)) { | ||
| 318 | SDL_Log("Low latency audio enabled"); | ||
| 319 | ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); | ||
| 320 | } else { | ||
| 321 | SDL_Log("Low latency audio disabled"); | ||
| 322 | } | ||
| 323 | |||
| 324 | LOGI("AAudio Try to open %u hz %s %u channels samples %u", | ||
| 325 | device->spec.freq, SDL_GetAudioFormatName(device->spec.format), | ||
| 326 | device->spec.channels, device->sample_frames); | ||
| 327 | |||
| 328 | res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream); | ||
| 329 | if (res != AAUDIO_OK) { | ||
| 330 | LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); | ||
| 331 | ctx.AAudioStreamBuilder_delete(builder); | ||
| 332 | return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | ||
| 333 | } | ||
| 334 | ctx.AAudioStreamBuilder_delete(builder); | ||
| 335 | |||
| 336 | device->sample_frames = (int)ctx.AAudioStream_getFramesPerDataCallback(hidden->stream); | ||
| 337 | if (device->sample_frames == AAUDIO_UNSPECIFIED) { | ||
| 338 | // We'll get variable frames in the callback, make sure we have at least half a buffer available | ||
| 339 | device->sample_frames = (int)ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2; | ||
| 340 | } | ||
| 341 | |||
| 342 | device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream); | ||
| 343 | device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream); | ||
| 344 | |||
| 345 | format = ctx.AAudioStream_getFormat(hidden->stream); | ||
| 346 | if (format == AAUDIO_FORMAT_PCM_I16) { | ||
| 347 | device->spec.format = SDL_AUDIO_S16; | ||
| 348 | } else if (format == AAUDIO_FORMAT_PCM_I32) { | ||
| 349 | device->spec.format = SDL_AUDIO_S32; | ||
| 350 | } else if (format == AAUDIO_FORMAT_PCM_FLOAT) { | ||
| 351 | device->spec.format = SDL_AUDIO_F32; | ||
| 352 | } else { | ||
| 353 | return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format); | ||
| 354 | } | ||
| 355 | |||
| 356 | SDL_UpdatedAudioDeviceFormat(device); | ||
| 357 | |||
| 358 | // Allocate a triple buffered mixing buffer | ||
| 359 | // Two buffers can be in the process of being filled while the third is being read | ||
| 360 | hidden->num_buffers = 3; | ||
| 361 | hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size); | ||
| 362 | hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), hidden->mixbuf_bytes); | ||
| 363 | if (!hidden->mixbuf) { | ||
| 364 | return false; | ||
| 365 | } | ||
| 366 | hidden->processed_bytes = 0; | ||
| 367 | hidden->callback_bytes = 0; | ||
| 368 | |||
| 369 | hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers); | ||
| 370 | if (!hidden->semaphore) { | ||
| 371 | LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording); | ||
| 372 | return false; | ||
| 373 | } | ||
| 374 | |||
| 375 | LOGI("AAudio Actually opened %u hz %s %u channels samples %u, buffers %d", | ||
| 376 | device->spec.freq, SDL_GetAudioFormatName(device->spec.format), | ||
| 377 | device->spec.channels, device->sample_frames, hidden->num_buffers); | ||
| 378 | |||
| 379 | res = ctx.AAudioStream_requestStart(hidden->stream); | ||
| 380 | if (res != AAUDIO_OK) { | ||
| 381 | LOGI("SDL Failed AAudioStream_requestStart %d recording:%d", res, recording); | ||
| 382 | return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | ||
| 383 | } | ||
| 384 | |||
| 385 | LOGI("SDL AAudioStream_requestStart OK"); | ||
| 386 | |||
| 387 | return true; | ||
| 388 | } | ||
| 389 | |||
| 390 | // !!! FIXME: make this non-blocking! | ||
| 391 | static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted) | ||
| 392 | { | ||
| 393 | SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1); | ||
| 394 | } | ||
| 395 | |||
| 396 | static bool AAUDIO_OpenDevice(SDL_AudioDevice *device) | ||
| 397 | { | ||
| 398 | #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES | ||
| 399 | SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero. | ||
| 400 | #endif | ||
| 401 | |||
| 402 | LOGI(__func__); | ||
| 403 | |||
| 404 | if (device->recording) { | ||
| 405 | // !!! FIXME: make this non-blocking! | ||
| 406 | SDL_AtomicInt permission_response; | ||
| 407 | SDL_SetAtomicInt(&permission_response, 0); | ||
| 408 | if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) { | ||
| 409 | return false; | ||
| 410 | } | ||
| 411 | |||
| 412 | while (SDL_GetAtomicInt(&permission_response) == 0) { | ||
| 413 | SDL_Delay(10); | ||
| 414 | } | ||
| 415 | |||
| 416 | if (SDL_GetAtomicInt(&permission_response) < 0) { | ||
| 417 | LOGI("This app doesn't have RECORD_AUDIO permission"); | ||
| 418 | return SDL_SetError("This app doesn't have RECORD_AUDIO permission"); | ||
| 419 | } | ||
| 420 | } | ||
| 421 | |||
| 422 | device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); | ||
| 423 | if (!device->hidden) { | ||
| 424 | return false; | ||
| 425 | } | ||
| 426 | |||
| 427 | return BuildAAudioStream(device); | ||
| 428 | } | ||
| 429 | |||
| 430 | static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata) | ||
| 431 | { | ||
| 432 | struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden; | ||
| 433 | if (hidden) { | ||
| 434 | if (hidden->stream) { | ||
| 435 | aaudio_result_t res; | ||
| 436 | |||
| 437 | if (device->recording) { | ||
| 438 | // Pause() isn't implemented for recording, use Stop() | ||
| 439 | res = ctx.AAudioStream_requestStop(hidden->stream); | ||
| 440 | } else { | ||
| 441 | res = ctx.AAudioStream_requestPause(hidden->stream); | ||
| 442 | } | ||
| 443 | |||
| 444 | if (res != AAUDIO_OK) { | ||
| 445 | LOGI("SDL Failed AAudioStream_requestPause %d", res); | ||
| 446 | SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | ||
| 447 | } | ||
| 448 | } | ||
| 449 | } | ||
| 450 | return false; // keep enumerating. | ||
| 451 | } | ||
| 452 | |||
| 453 | // Pause (block) all non already paused audio devices by taking their mixer lock | ||
| 454 | void AAUDIO_PauseDevices(void) | ||
| 455 | { | ||
| 456 | if (ctx.handle) { // AAUDIO driver is used? | ||
| 457 | (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL); | ||
| 458 | } | ||
| 459 | } | ||
| 460 | |||
| 461 | // Resume (unblock) all non already paused audio devices by releasing their mixer lock | ||
| 462 | static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata) | ||
| 463 | { | ||
| 464 | struct SDL_PrivateAudioData *hidden = device->hidden; | ||
| 465 | if (hidden) { | ||
| 466 | if (hidden->stream) { | ||
| 467 | aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream); | ||
| 468 | if (res != AAUDIO_OK) { | ||
| 469 | LOGI("SDL Failed AAudioStream_requestStart %d", res); | ||
| 470 | SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); | ||
| 471 | } | ||
| 472 | } | ||
| 473 | } | ||
| 474 | return false; // keep enumerating. | ||
| 475 | } | ||
| 476 | |||
| 477 | void AAUDIO_ResumeDevices(void) | ||
| 478 | { | ||
| 479 | if (ctx.handle) { // AAUDIO driver is used? | ||
| 480 | (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL); | ||
| 481 | } | ||
| 482 | } | ||
| 483 | |||
| 484 | static void AAUDIO_Deinitialize(void) | ||
| 485 | { | ||
| 486 | Android_StopAudioHotplug(); | ||
| 487 | |||
| 488 | LOGI(__func__); | ||
| 489 | if (ctx.handle) { | ||
| 490 | SDL_UnloadObject(ctx.handle); | ||
| 491 | } | ||
| 492 | SDL_zero(ctx); | ||
| 493 | LOGI("End AAUDIO %s", SDL_GetError()); | ||
| 494 | } | ||
| 495 | |||
| 496 | |||
| 497 | static bool AAUDIO_Init(SDL_AudioDriverImpl *impl) | ||
| 498 | { | ||
| 499 | LOGI(__func__); | ||
| 500 | |||
| 501 | /* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release, | ||
| 502 | * so don't use it until 8.1. | ||
| 503 | * | ||
| 504 | * See https://github.com/google/oboe/issues/40 for more information. | ||
| 505 | */ | ||
| 506 | if (SDL_GetAndroidSDKVersion() < 27) { | ||
| 507 | return false; | ||
| 508 | } | ||
| 509 | |||
| 510 | SDL_zero(ctx); | ||
| 511 | |||
| 512 | ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO); | ||
| 513 | if (!ctx.handle) { | ||
| 514 | LOGI("SDL couldn't find " LIB_AAUDIO_SO); | ||
| 515 | return false; | ||
| 516 | } | ||
| 517 | |||
| 518 | if (!AAUDIO_LoadFunctions(&ctx)) { | ||
| 519 | SDL_UnloadObject(ctx.handle); | ||
| 520 | SDL_zero(ctx); | ||
| 521 | return false; | ||
| 522 | } | ||
| 523 | |||
| 524 | impl->ThreadInit = Android_AudioThreadInit; | ||
| 525 | impl->Deinitialize = AAUDIO_Deinitialize; | ||
| 526 | impl->OpenDevice = AAUDIO_OpenDevice; | ||
| 527 | impl->CloseDevice = AAUDIO_CloseDevice; | ||
| 528 | impl->WaitDevice = AAUDIO_WaitDevice; | ||
| 529 | impl->PlayDevice = AAUDIO_PlayDevice; | ||
| 530 | impl->GetDeviceBuf = AAUDIO_GetDeviceBuf; | ||
| 531 | impl->WaitRecordingDevice = AAUDIO_WaitDevice; | ||
| 532 | impl->RecordDevice = AAUDIO_RecordDevice; | ||
| 533 | |||
| 534 | impl->HasRecordingSupport = true; | ||
| 535 | |||
| 536 | #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES | ||
| 537 | impl->DetectDevices = Android_StartAudioHotplug; | ||
| 538 | #else | ||
| 539 | impl->OnlyHasDefaultPlaybackDevice = true; | ||
| 540 | impl->OnlyHasDefaultRecordingDevice = true; | ||
| 541 | #endif | ||
| 542 | |||
| 543 | LOGI("SDL AAUDIO_Init OK"); | ||
| 544 | return true; | ||
| 545 | } | ||
| 546 | |||
| 547 | AudioBootStrap AAUDIO_bootstrap = { | ||
| 548 | "AAudio", "AAudio audio driver", AAUDIO_Init, false, false | ||
| 549 | }; | ||
| 550 | |||
| 551 | #endif // SDL_AUDIO_DRIVER_AAUDIO | ||
