@@ -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)
18571858static 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+
19151918static 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+
20922150static 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,
25952657static 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
26852735static 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