Skip to content

Commit

Permalink
aaudio: Backport headphone hotplugging support from SDL3.
Browse files Browse the repository at this point in the history
  • Loading branch information
icculus committed Jan 27, 2024
1 parent 230ae79 commit 2f06280
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 10 deletions.
108 changes: 98 additions & 10 deletions src/audio/aaudio/SDL_aaudio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -212,6 +207,96 @@ 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;

/* 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;

if (RebuildAAudioStream(device) < 0) {
return -1; // oh well, we tried.
}

return 0;
}


static void aaudio_PlayDevice(_THIS)
{
struct SDL_PrivateAudioData *private = this->hidden;
Expand All @@ -220,6 +305,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);
}
Expand Down
1 change: 1 addition & 0 deletions src/audio/aaudio/SDL_aaudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ struct SDL_PrivateAudioData
Uint8 *mixbuf;
int mixlen;
int frame_size;
int devid;
};

void aaudio_ResumeDevices(void);
Expand Down

0 comments on commit 2f06280

Please sign in to comment.