#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; size_t mixbuf_bytes; size_t callback_bytes;
size_t processed_bytes;
SDL_Semaphore *semaphore;
SDL_AtomicInt error_callback_triggered;
};
#if 0#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); \
} 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));
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
SDL_SetAtomicInt(&device->hidden->error_callback_triggered, (int) error); SDL_SignalSemaphore(device->hidden->semaphore); }
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);
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);
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) {
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)) {
if (SDL_WaitSemaphoreTimeout(device->hidden->semaphore, 100)) {
return true; }
}
return true;
}
static bool BuildAAudioStream(SDL_AudioDevice *device);
static bool RecoverAAudioDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
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; }
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; }
return true;
}
static bool AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
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; }
} 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;
if (SDL_GetAtomicInt(&hidden->error_callback_triggered)) {
SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
return -1;
}
SDL_assert(buflen == device->buffer_size); 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);
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) { const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE);
if (hint) {
aaudio_usage_t usage = AAUDIO_USAGE_MEDIA; 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);
}
}
}
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; }
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 {
sample_frames = (device->spec.freq / 50);
}
ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * sample_frames); ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, sample_frames);
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);
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) { 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) {
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);
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;
}
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); #endif
LOGI(SDL_FUNCTION);
if (device->recording) {
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) {
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; }
void AAUDIO_PauseDevices(void)
{
if (ctx.handle) { (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL);
}
}
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; }
void AAUDIO_ResumeDevices(void)
{
if (ctx.handle) { (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);
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