aaudio: Deal with device recovery.
Android claims to work with multiple devices, but doesn't actually appear to (at least, afaict), and it will report tons of devices that all just seem to play to the current default output, so for now, turn this off and only expose a default device. And then, with that default output, attempt to recover on errors by throwing away the current AAudioStream and building a new one. This let me plug/unplug a set of headphones from the headphone jack and audio would switch correctly to the new output.main
parent
a8813b58a6
commit
482c238953
|
@ -85,7 +85,7 @@ static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_re
|
||||||
// You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here.
|
// 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 WaitDevice/PlayDevice instead.
|
// Just flag the device so we can kill it in WaitDevice/PlayDevice instead.
|
||||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
|
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
|
||||||
SDL_AtomicSet(&device->hidden->error_callback_triggered, 1);
|
SDL_AtomicSet(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error.
|
||||||
SDL_PostSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice.
|
SDL_PostSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,14 +169,61 @@ static void AAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||||
SDL_WaitSemaphore(device->hidden->semaphore);
|
SDL_WaitSemaphore(device->hidden->semaphore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int BuildAAudioStream(SDL_AudioDevice *device);
|
||||||
|
|
||||||
|
static int RecoverAAudioDeviceIfFailed(SDL_AudioDevice *device)
|
||||||
|
{
|
||||||
|
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||||
|
const aaudio_result_t err = (aaudio_result_t) SDL_AtomicGet(&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));
|
||||||
|
|
||||||
|
// 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) == -1) {
|
||||||
|
return -1; // 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) == -1) {
|
||||||
|
return -1; // ugh
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're recovering from PlayDevice, so wait until the data callback fires so we know we fed the pending buffer to the device.
|
||||||
|
SDL_WaitSemaphore(device->hidden->semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
static int AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||||
{
|
{
|
||||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||||
|
|
||||||
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
|
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
|
||||||
if (SDL_AtomicGet(&hidden->error_callback_triggered)) {
|
if (RecoverAAudioDeviceIfFailed(device) == -1) {
|
||||||
SDL_AtomicSet(&hidden->error_callback_triggered, 0);
|
return -1; // oh well, we went down hard.
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_MemoryBarrierRelease();
|
SDL_MemoryBarrierRelease();
|
||||||
|
@ -219,34 +266,18 @@ static void AAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||||
SDL_DestroySemaphore(hidden->semaphore);
|
SDL_DestroySemaphore(hidden->semaphore);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_free(hidden->mixbuf);
|
SDL_aligned_free(hidden->mixbuf);
|
||||||
SDL_free(hidden);
|
SDL_free(hidden);
|
||||||
device->hidden = NULL;
|
device->hidden = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
|
static int BuildAAudioStream(SDL_AudioDevice *device)
|
||||||
{
|
{
|
||||||
struct SDL_PrivateAudioData *hidden;
|
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||||
const SDL_bool iscapture = device->iscapture;
|
const SDL_bool iscapture = device->iscapture;
|
||||||
aaudio_result_t res;
|
aaudio_result_t res;
|
||||||
|
|
||||||
SDL_assert(device->handle != NULL); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero.
|
|
||||||
|
|
||||||
LOGI(__func__);
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hidden = device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
|
||||||
if (hidden == NULL) {
|
|
||||||
return SDL_OutOfMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_AtomicSet(&hidden->error_callback_triggered, 0);
|
SDL_AtomicSet(&hidden->error_callback_triggered, 0);
|
||||||
|
|
||||||
AAudioStreamBuilder *builder = NULL;
|
AAudioStreamBuilder *builder = NULL;
|
||||||
|
@ -262,9 +293,11 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||||
ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq);
|
ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq);
|
||||||
ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels);
|
ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels);
|
||||||
|
|
||||||
|
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||||
const int aaudio_device_id = (int) ((size_t) device->handle);
|
const int aaudio_device_id = (int) ((size_t) device->handle);
|
||||||
LOGI("Opening device id %d", aaudio_device_id);
|
LOGI("Opening device id %d", aaudio_device_id);
|
||||||
ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id);
|
ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id);
|
||||||
|
#endif
|
||||||
|
|
||||||
const aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
|
const aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
|
||||||
ctx.AAudioStreamBuilder_setDirection(builder, direction);
|
ctx.AAudioStreamBuilder_setDirection(builder, direction);
|
||||||
|
@ -321,7 +354,7 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||||
// Allocate a double buffered mixing buffer
|
// Allocate a double buffered mixing buffer
|
||||||
hidden->num_buffers = 2;
|
hidden->num_buffers = 2;
|
||||||
hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
|
hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
|
||||||
hidden->mixbuf = (Uint8 *)SDL_malloc(hidden->mixbuf_bytes);
|
hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), hidden->mixbuf_bytes);
|
||||||
if (hidden->mixbuf == NULL) {
|
if (hidden->mixbuf == NULL) {
|
||||||
return SDL_OutOfMemory();
|
return SDL_OutOfMemory();
|
||||||
}
|
}
|
||||||
|
@ -343,9 +376,33 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("SDL AAudioStream_requestStart OK");
|
LOGI("SDL AAudioStream_requestStart OK");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||||
|
{
|
||||||
|
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||||
|
SDL_assert(device->handle != NULL); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOGI(__func__);
|
||||||
|
|
||||||
|
if (device->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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||||
|
if (device->hidden == NULL) {
|
||||||
|
return SDL_OutOfMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return BuildAAudioStream(device);
|
||||||
|
}
|
||||||
|
|
||||||
static SDL_bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
|
static SDL_bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
|
||||||
{
|
{
|
||||||
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden;
|
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden;
|
||||||
|
@ -449,7 +506,6 @@ static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl->ThreadInit = Android_AudioThreadInit;
|
impl->ThreadInit = Android_AudioThreadInit;
|
||||||
impl->DetectDevices = Android_StartAudioHotplug;
|
|
||||||
impl->Deinitialize = AAUDIO_Deinitialize;
|
impl->Deinitialize = AAUDIO_Deinitialize;
|
||||||
impl->OpenDevice = AAUDIO_OpenDevice;
|
impl->OpenDevice = AAUDIO_OpenDevice;
|
||||||
impl->CloseDevice = AAUDIO_CloseDevice;
|
impl->CloseDevice = AAUDIO_CloseDevice;
|
||||||
|
@ -461,6 +517,13 @@ static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||||
|
|
||||||
impl->HasCaptureSupport = SDL_TRUE;
|
impl->HasCaptureSupport = SDL_TRUE;
|
||||||
|
|
||||||
|
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||||
|
impl->DetectDevices = Android_StartAudioHotplug;
|
||||||
|
#else
|
||||||
|
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||||
|
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
|
||||||
|
#endif
|
||||||
|
|
||||||
LOGI("SDL AAUDIO_Init OK");
|
LOGI("SDL AAUDIO_Init OK");
|
||||||
return SDL_TRUE;
|
return SDL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1006,6 +1006,7 @@ JNIEXPORT void JNICALL
|
||||||
SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture,
|
SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture,
|
||||||
jstring name, jint device_id)
|
jstring name, jint device_id)
|
||||||
{
|
{
|
||||||
|
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||||
if (SDL_GetCurrentAudioDriver() != NULL) {
|
if (SDL_GetCurrentAudioDriver() != NULL) {
|
||||||
void *handle = (void *)((size_t)device_id);
|
void *handle = (void *)((size_t)device_id);
|
||||||
if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) {
|
if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) {
|
||||||
|
@ -1014,16 +1015,19 @@ SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_c
|
||||||
(*env)->ReleaseStringUTFChars(env, name, utf8name);
|
(*env)->ReleaseStringUTFChars(env, name, utf8name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture,
|
SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture,
|
||||||
jint device_id)
|
jint device_id)
|
||||||
{
|
{
|
||||||
|
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||||
if (SDL_GetCurrentAudioDriver() != NULL) {
|
if (SDL_GetCurrentAudioDriver() != NULL) {
|
||||||
SDL_Log("Removing device with handle %d, capture %d", device_id, is_capture);
|
SDL_Log("Removing device with handle %d, capture %d", device_id, is_capture);
|
||||||
SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id)));
|
SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id)));
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Paddown */
|
/* Paddown */
|
||||||
|
|
|
@ -32,6 +32,9 @@ extern "C" {
|
||||||
|
|
||||||
#include "../../audio/SDL_sysaudio.h"
|
#include "../../audio/SDL_sysaudio.h"
|
||||||
|
|
||||||
|
// this appears to be broken right now (on Android, not SDL, I think...?).
|
||||||
|
#define ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES 0
|
||||||
|
|
||||||
/* Interface from the SDL library into the Android Java activity */
|
/* Interface from the SDL library into the Android Java activity */
|
||||||
extern void Android_JNI_SetActivityTitle(const char *title);
|
extern void Android_JNI_SetActivityTitle(const char *title);
|
||||||
extern void Android_JNI_SetWindowStyle(SDL_bool fullscreen);
|
extern void Android_JNI_SetWindowStyle(SDL_bool fullscreen);
|
||||||
|
|
Loading…
Reference in New Issue