Skip to content

Commit 21f07d4

Browse files
committed
Wire up device disconnect and format change support.
Fixes #34.
1 parent b3d3ae1 commit 21f07d4

File tree

1 file changed

+104
-54
lines changed

1 file changed

+104
-54
lines changed

mojoal.c

Lines changed: 104 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ struct ALCcontext_struct
577577

578578
ALCdevice *device;
579579
SDL_AudioStream *stream;
580+
SDL_AudioDeviceID device_id; // logical device id.
580581
VBAP2D vbap2d;
581582

582583
SDL_AudioSpec spec;
@@ -1843,8 +1844,8 @@ static void mix_disconnected_context(ALCcontext *ctx)
18431844
}
18441845

18451846
i->playlist_next = NULL;
1846-
SDL_SetAtomicInt(&i->mixer_accessible, 0);
18471847
SDL_ClearAudioStream(i->stream); // just in case.
1848+
SDL_SetAtomicInt(&i->mixer_accessible, 0);
18481849
unlock_source(i);
18491850
}
18501851
ctx->playlist = NULL;
@@ -1857,23 +1858,8 @@ static void mix_disconnected_context(ALCcontext *ctx)
18571858
static void SDLCALL context_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)
18581859
{
18591860
ALCcontext *ctx = (ALCcontext *) userdata;
1860-
ALCboolean connected = ALC_FALSE;
1861-
1862-
if (SDL_GetAtomicInt(&ctx->device->connected)) {
1863-
#if 0
1864-
// !!! FIXME: did this ever work? Why would the audio callback fire on a stopped device?
1865-
if (SDL_GetAudioDeviceStatus(device->playback.sdldevice) == SDL_AUDIO_STOPPED) {
1866-
SDL_SetAtomicInt(&device->connected, ALC_FALSE);
1867-
} else {
1868-
connected = ALC_TRUE;
1869-
}
1870-
#else
1871-
connected = ALC_TRUE;
1872-
#endif
1873-
}
1874-
18751861
if (SDL_GetAtomicInt(&ctx->processing)) {
1876-
if (connected) {
1862+
if (SDL_GetAtomicInt(&ctx->device->connected)) {
18771863
mix_context(ctx, stream, additional_amount);
18781864
} else {
18791865
mix_disconnected_context(ctx);
@@ -1912,6 +1898,23 @@ static void set_alc_error(ALCdevice *device, const ALCenum error)
19121898
#define context_needs_recalc(ctx) SDL_MemoryBarrierRelease(); ctx->recalc = AL_TRUE;
19131899
#define source_needs_recalc(src) SDL_MemoryBarrierRelease(); src->recalc = AL_TRUE;
19141900

1901+
// catch events to see if a device has disconnected.
1902+
static bool SDLCALL DeviceDisconnectedEventWatcher(void *userdata, SDL_Event *event)
1903+
{
1904+
ALCdevice *device = (ALCdevice *) userdata;
1905+
if (event->type == SDL_EVENT_AUDIO_DEVICE_REMOVED) {
1906+
if (device->device_id == event->adevice.which) {
1907+
//SDL_Log("MojoAL device=%p device_id=%u is DISCONNECTED", device, (unsigned int) device->device_id);
1908+
if (device->iscapture) {
1909+
SDL_PauseAudioStreamDevice(device->capture.stream); // the app can still read existing data, but don't pull anymore (silence) from the device.
1910+
SDL_FlushAudioStream(device->capture.stream);
1911+
}
1912+
SDL_SetAtomicInt(&device->connected, (int) ALC_FALSE);
1913+
}
1914+
}
1915+
return true;
1916+
}
1917+
19151918
static ALCdevice *prep_alc_device(const char *devicename, const ALCboolean iscapture)
19161919
{
19171920
if (!SDL_Init(SDL_INIT_AUDIO)) {
@@ -1970,25 +1973,27 @@ static ALCdevice *prep_alc_device(const char *devicename, const ALCboolean iscap
19701973
return NULL;
19711974
}
19721975

1973-
ALCdevice *dev = (ALCdevice *) SDL_calloc(1, sizeof (*dev));
1974-
if (!dev) {
1976+
ALCdevice *device = (ALCdevice *) SDL_calloc(1, sizeof (*device));
1977+
if (!device) {
19751978
SDL_QuitSubSystem(SDL_INIT_AUDIO);
19761979
return NULL;
19771980
}
19781981

1979-
dev->device_id = devid;
1982+
device->device_id = devid;
19801983

1981-
dev->name = SDL_strdup(devicename);
1982-
if (!dev->name) {
1983-
SDL_free(dev);
1984+
device->name = SDL_strdup(devicename);
1985+
if (!device->name) {
1986+
SDL_free(device);
19841987
SDL_QuitSubSystem(SDL_INIT_AUDIO);
19851988
return NULL;
19861989
}
19871990

1988-
SDL_SetAtomicInt(&dev->connected, ALC_TRUE);
1989-
dev->iscapture = iscapture;
1991+
SDL_SetAtomicInt(&device->connected, (int) ALC_TRUE);
1992+
device->iscapture = iscapture;
1993+
1994+
SDL_AddEventWatch(DeviceDisconnectedEventWatcher, device);
19901995

1991-
return dev;
1996+
return device;
19921997
}
19931998

19941999
// no api lock; this creates it and otherwise doesn't have any state that can race
@@ -2020,6 +2025,8 @@ ALCboolean alcCloseDevice(ALCdevice *device)
20202025
}
20212026
}
20222027

2028+
SDL_RemoveEventWatch(DeviceDisconnectedEventWatcher, device);
2029+
20232030
for (ALCsizei i = 0; i < device->playback.num_buffer_blocks; i++) {
20242031
SDL_free(device->playback.buffer_blocks[i]);
20252032
}
@@ -2089,6 +2096,57 @@ static ALCboolean alcfmt_to_sdlfmt(const ALCenum alfmt, SDL_AudioFormat *sdlfmt,
20892096
return ALC_TRUE;
20902097
}
20912098

2099+
// catch events to see if output device format has changed. This can let us move to/from surround sound support on the fly, not to mention spend less time doing unnecessary conversions.
2100+
static bool SDLCALL ContextDeviceChangeEventWatcher(void *userdata, SDL_Event *event)
2101+
{
2102+
if (event->type == SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED) {
2103+
ALCcontext *ctx = (ALCcontext *) userdata;
2104+
if (ctx->device_id == event->adevice.which) {
2105+
// stop the whole world while we update everything.
2106+
grab_api_lock();
2107+
SDL_LockAudioStream(ctx->stream);
2108+
2109+
SDL_AudioSpec spec;
2110+
SDL_GetAudioDeviceFormat(ctx->device_id, &spec, NULL);
2111+
spec.format = SDL_AUDIO_F32;
2112+
2113+
//SDL_Log("Changing MojoAL context format for ctx=%p on device_id=%u", ctx, (unsigned int) ctx->device_id);
2114+
//SDL_Log("ctx=%p was { fmt=%s, channels=%d, freq=%d }", ctx, SDL_GetAudioFormatName(ctx->spec.format), ctx->spec.channels, ctx->spec.freq);
2115+
//SDL_Log("ctx=%p now { fmt=%s, channels=%d, freq=%d }", ctx, SDL_GetAudioFormatName(spec.format), spec.channels, spec.freq);
2116+
2117+
SDL_SetAudioStreamFormat(ctx->stream, &spec, NULL); // the output end is connected to the logical device; it would have been updated by SDL.
2118+
2119+
if (ctx->spec.channels != spec.channels) {
2120+
VBAP2D_Init(&ctx->vbap2d, spec.channels); // make sure we have the right speaker layout.
2121+
}
2122+
2123+
for (ALCsizei blocki = 0; blocki < ctx->num_source_blocks; blocki++) {
2124+
SourceBlock *sb = ctx->source_blocks[blocki];
2125+
if (sb->used > 0) {
2126+
for (ALsizei i = 0; i < SDL_arraysize(sb->sources); i++) {
2127+
ALsource *src = &sb->sources[i];
2128+
if (src->allocated) {
2129+
SDL_AudioSpec outspec;
2130+
SDL_GetAudioStreamFormat(src->stream, &outspec, NULL);
2131+
outspec.format = SDL_AUDIO_F32;
2132+
outspec.freq = spec.freq;
2133+
SDL_SetAudioStreamFormat(src->stream, NULL, &outspec);
2134+
}
2135+
}
2136+
}
2137+
}
2138+
2139+
SDL_copyp(&ctx->spec, &spec);
2140+
2141+
SDL_UnlockAudioStream(ctx->stream);
2142+
ungrab_api_lock();
2143+
}
2144+
}
2145+
2146+
return true;
2147+
}
2148+
2149+
20922150
static ALCcontext *_alcCreateContext(ALCdevice *device, const ALCint* attrlist)
20932151
{
20942152
// we don't care about ALC_MONO_SOURCES or ALC_STEREO_SOURCES as we have no hardware limitation.
@@ -2166,6 +2224,7 @@ static ALCcontext *_alcCreateContext(ALCdevice *device, const ALCint* attrlist)
21662224
return NULL;
21672225
}
21682226

2227+
ctx->device_id = SDL_GetAudioStreamDevice(ctx->stream);
21692228
SDL_copyp(&ctx->spec, &spec);
21702229
ctx->framesize = SDL_AUDIO_FRAMESIZE(spec);
21712230
ctx->distance_model = AL_INVERSE_DISTANCE_CLAMPED;
@@ -2189,6 +2248,8 @@ static ALCcontext *_alcCreateContext(ALCdevice *device, const ALCint* attrlist)
21892248

21902249
VBAP2D_Init(&ctx->vbap2d, spec.channels);
21912250

2251+
SDL_AddEventWatch(ContextDeviceChangeEventWatcher, ctx);
2252+
21922253
SDL_ResumeAudioStreamDevice(ctx->stream);
21932254

21942255
return ctx;
@@ -2249,6 +2310,7 @@ static void _alcDestroyContext(ALCcontext *ctx)
22492310
// do this first in case the mixer is running _right now_.
22502311
SDL_SetAtomicInt(&ctx->processing, 0);
22512312

2313+
SDL_RemoveEventWatch(ContextDeviceChangeEventWatcher, ctx);
22522314
SDL_DestroyAudioStream(ctx->stream); // will unbind from the audio device, mixer thread will no longer touch.
22532315

22542316
// these are protected by the api lock; the mixer thread no longer looks at device->playback.contexts as of the migration to SDL3.
@@ -2595,38 +2657,22 @@ ENTRYPOINTVOID(alcGetIntegerv,(ALCdevice *device, ALCenum param, ALCsizei size,
25952657
static void SDLCALL capture_device_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)
25962658
{
25972659
ALCdevice *device = (ALCdevice *) userdata;
2598-
ALCboolean connected = ALC_FALSE;
25992660

26002661
SDL_assert(device->iscapture);
26012662
SDL_assert(stream == device->capture.stream);
26022663

2603-
FIXME("Why would this callback fire if the device wasn't connected?");
2604-
if (SDL_GetAtomicInt(&device->connected)) {
2605-
#if 0
2606-
if (SDL_GetAudioDeviceStatus(device->sdldevice) == SDL_AUDIO_STOPPED) {
2607-
SDL_SetAtomicInt(&device->connected, ALC_FALSE);
2608-
} else {
2609-
connected = ALC_TRUE;
2610-
}
2611-
#else
2612-
connected = ALC_TRUE;
2613-
#endif
2614-
}
2615-
2616-
if (connected) {
2617-
Uint8 bitbucket[512];
2618-
const ALCsizei framesize = device->capture.framesize;
2619-
const ALCsizei maximum = device->capture.max_samples * framesize;
2620-
const ALCsizei maxread = sizeof (bitbucket) / framesize;
2621-
ALCsizei available = SDL_GetAudioStreamAvailable(stream);
2622-
while (available > maximum) {
2623-
const ALCsizei dumpsamps = (ALCsizei) SDL_min(maxread, (available - maximum) / framesize);
2624-
if (!SDL_GetAudioStreamData(stream, bitbucket, dumpsamps * framesize)) {
2625-
SDL_ClearAudioStream(stream);
2626-
return; // oh well.
2627-
}
2628-
available = SDL_GetAudioStreamAvailable(stream);
2664+
Uint8 bitbucket[512];
2665+
const ALCsizei framesize = device->capture.framesize;
2666+
const ALCsizei maximum = device->capture.max_samples * framesize;
2667+
const ALCsizei maxread = sizeof (bitbucket) / framesize;
2668+
ALCsizei available = SDL_GetAudioStreamAvailable(stream);
2669+
while (available > maximum) {
2670+
const ALCsizei dumpsamps = (ALCsizei) SDL_min(maxread, (available - maximum) / framesize);
2671+
if (!SDL_GetAudioStreamData(stream, bitbucket, dumpsamps * framesize)) {
2672+
SDL_ClearAudioStream(stream);
2673+
return; // oh well.
26292674
}
2675+
available = SDL_GetAudioStreamAvailable(stream);
26302676
}
26312677
}
26322678

@@ -2664,6 +2710,9 @@ ALCdevice *alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, AL
26642710
}
26652711

26662712
device->capture.framesize = SDL_AUDIO_FRAMESIZE(spec);
2713+
2714+
SDL_AddEventWatch(DeviceDisconnectedEventWatcher, device);
2715+
26672716
return device;
26682717
}
26692718

@@ -2674,6 +2723,7 @@ ALCboolean alcCaptureCloseDevice(ALCdevice *device)
26742723
return ALC_FALSE;
26752724
}
26762725

2726+
SDL_RemoveEventWatch(DeviceDisconnectedEventWatcher, device);
26772727
SDL_DestroyAudioStream(device->capture.stream);
26782728
SDL_free(device->name);
26792729
SDL_free(device);
@@ -2684,7 +2734,7 @@ ALCboolean alcCaptureCloseDevice(ALCdevice *device)
26842734

26852735
static void _alcCaptureStart(ALCdevice *device)
26862736
{
2687-
if (device && device->iscapture) {
2737+
if (device && device->iscapture && SDL_GetAtomicInt(&device->connected)) {
26882738
// alcCaptureStart() drops any previously-buffered data.
26892739
SDL_ClearAudioStream(device->capture.stream);
26902740
SDL_ResumeAudioStreamDevice(device->capture.stream);

0 commit comments

Comments
 (0)