From f1b109005ca89729ec801cfd097ffe5102a31523 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sat, 27 Jan 2024 18:13:01 -0500
Subject: [PATCH] aaudio: Backport headphone hotplugging support from SDL3.
Fixes #4985.
(cherry picked from commit ec25d6b1e860e1689044c1d145cbbcbe1aa5011f)
---
src/audio/aaudio/SDL_aaudio.c | 118 ++++++++++++++++++++++++++++++----
src/audio/aaudio/SDL_aaudio.h | 1 +
2 files changed, 107 insertions(+), 12 deletions(-)
diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c
index 057d7930b03d..401fef6a8bc2 100644
--- a/src/audio/aaudio/SDL_aaudio.c
+++ b/src/audio/aaudio/SDL_aaudio.c
@@ -102,21 +102,16 @@ static int aaudio_OpenDevice(_THIS, const char *devname)
ctx.AAudioStreamBuilder_setSampleRate(ctx.builder, this->spec.freq);
ctx.AAudioStreamBuilder_setChannelCount(ctx.builder, this->spec.channels);
if(devname) {
- int aaudio_device_id = SDL_atoi(devname);
- LOGI("Opening device id %d", aaudio_device_id);
- ctx.AAudioStreamBuilder_setDeviceId(ctx.builder, aaudio_device_id);
+ private->devid = SDL_atoi(devname);
+ LOGI("Opening device id %d", private->devid);
+ ctx.AAudioStreamBuilder_setDeviceId(ctx.builder, private->devid);
}
{
- 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(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;
- }
+ const aaudio_format_t format = (this->spec.format == AUDIO_S16SYS) ? AAUDIO_FORMAT_PCM_I16 : AAUDIO_FORMAT_PCM_FLOAT;
ctx.AAudioStreamBuilder_setFormat(ctx.builder, format);
}
@@ -212,6 +207,97 @@ static Uint8 *aaudio_GetDeviceBuf(_THIS)
return private->mixbuf;
}
+/* Try to reestablish an AAudioStream.
+
+ This needs to get a stream with the same format as the previous one,
+ even if this means AAudio needs to handle a conversion it didn't when
+ we initially opened the device. If we can't get that, we are forced
+ to give up here.
+
+ (This is more robust in SDL3, which is designed to handle
+ abrupt format changes.)
+*/
+static int RebuildAAudioStream(SDL_AudioDevice *device)
+{
+ struct SDL_PrivateAudioData *hidden = device->hidden;
+ const SDL_bool iscapture = device->iscapture;
+ aaudio_result_t res;
+
+ ctx.AAudioStreamBuilder_setSampleRate(ctx.builder, device->spec.freq);
+ ctx.AAudioStreamBuilder_setChannelCount(ctx.builder, device->spec.channels);
+ if(hidden->devid) {
+ LOGI("Reopening device id %d", hidden->devid);
+ ctx.AAudioStreamBuilder_setDeviceId(ctx.builder, hidden->devid);
+ }
+ {
+ const aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
+ ctx.AAudioStreamBuilder_setDirection(ctx.builder, direction);
+ }
+ {
+ const aaudio_format_t format = (device->spec.format == AUDIO_S16SYS) ? AAUDIO_FORMAT_PCM_I16 : AAUDIO_FORMAT_PCM_FLOAT;
+ ctx.AAudioStreamBuilder_setFormat(ctx.builder, format);
+ }
+
+ ctx.AAudioStreamBuilder_setErrorCallback(ctx.builder, aaudio_errorCallback, hidden);
+ ctx.AAudioStreamBuilder_setPerformanceMode(ctx.builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+
+ LOGI("AAudio Try to reopen %u hz %u bit chan %u %s samples %u",
+ device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
+ device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->spec.samples);
+
+ res = ctx.AAudioStreamBuilder_openStream(ctx.builder, &hidden->stream);
+ if (res != AAUDIO_OK) {
+ LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res);
+ return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
+ }
+
+ {
+ const aaudio_format_t fmt = ctx.AAudioStream_getFormat(hidden->stream);
+ SDL_AudioFormat sdlfmt = (SDL_AudioFormat) 0;
+ if (fmt == AAUDIO_FORMAT_PCM_I16) {
+ sdlfmt = AUDIO_S16SYS;
+ } else if (fmt == AAUDIO_FORMAT_PCM_FLOAT) {
+ sdlfmt = AUDIO_F32SYS;
+ }
+
+ /* We handle this better in SDL3, but this _needs_ to match the previous stream for SDL2. */
+ if ( (device->spec.freq != ctx.AAudioStream_getSampleRate(hidden->stream)) ||
+ (device->spec.channels != ctx.AAudioStream_getChannelCount(hidden->stream)) ||
+ (device->spec.format != sdlfmt) ) {
+ LOGI("Didn't get an identical spec from AAudioStream during reopen!");
+ ctx.AAudioStream_close(hidden->stream);
+ hidden->stream = NULL;
+ return SDL_SetError("Didn't get an identical spec from AAudioStream during reopen!");
+ }
+ }
+
+ res = ctx.AAudioStream_requestStart(hidden->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));
+ }
+
+ return 0;
+}
+
+static int RecoverAAudioDevice(SDL_AudioDevice *device)
+{
+ struct SDL_PrivateAudioData *hidden = device->hidden;
+ AAudioStream *stream = hidden->stream;
+
+ /* attempt to build a new stream, in case there's a new default device. */
+ hidden->stream = NULL;
+ ctx.AAudioStream_requestStop(stream);
+ ctx.AAudioStream_close(stream);
+
+ if (RebuildAAudioStream(device) < 0) {
+ return -1; // oh well, we tried.
+ }
+
+ return 0;
+}
+
+
static void aaudio_PlayDevice(_THIS)
{
struct SDL_PrivateAudioData *private = this->hidden;
@@ -220,6 +306,9 @@ static void aaudio_PlayDevice(_THIS)
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));
+ if (RecoverAAudioDevice(this) < 0) {
+ return; /* oh well, we went down hard. */
+ }
} else {
LOGI("SDL AAudio play: %d frames, wanted:%d frames", (int)res, private->mixlen / private->frame_size);
}
@@ -408,6 +497,7 @@ void aaudio_ResumeDevices(void)
*/
SDL_bool aaudio_DetectBrokenPlayState(void)
{
+ AAudioStream *stream;
struct SDL_PrivateAudioData *private;
int64_t framePosition, timeNanoseconds;
aaudio_result_t res;
@@ -417,10 +507,14 @@ SDL_bool aaudio_DetectBrokenPlayState(void)
}
private = audioDevice->hidden;
+ stream = private->stream;
+ if (!stream) {
+ return SDL_FALSE;
+ }
- res = ctx.AAudioStream_getTimestamp(private->stream, CLOCK_MONOTONIC, &framePosition, &timeNanoseconds);
+ res = ctx.AAudioStream_getTimestamp(stream, CLOCK_MONOTONIC, &framePosition, &timeNanoseconds);
if (res == AAUDIO_ERROR_INVALID_STATE) {
- aaudio_stream_state_t currentState = ctx.AAudioStream_getState(private->stream);
+ aaudio_stream_state_t currentState = ctx.AAudioStream_getState(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);
diff --git a/src/audio/aaudio/SDL_aaudio.h b/src/audio/aaudio/SDL_aaudio.h
index 86870b7ea3a6..d61d1b02b6de 100644
--- a/src/audio/aaudio/SDL_aaudio.h
+++ b/src/audio/aaudio/SDL_aaudio.h
@@ -38,6 +38,7 @@ struct SDL_PrivateAudioData
Uint8 *mixbuf;
int mixlen;
int frame_size;
+ int devid;
};
void aaudio_ResumeDevices(void);