diff --git a/docs/README-migration.md b/docs/README-migration.md index ffa2a3854..c5d6131d7 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -144,9 +144,11 @@ Rather than iterating over audio devices using a device index, there is a new fu SDL_LockAudioDevice() and SDL_UnlockAudioDevice() have been removed, since there is no callback in another thread to protect. Internally, SDL's audio subsystem and SDL_AudioStream maintain their own locks internally, so audio streams are safe to use from any thread. -SDL_PauseAudioDevice() has been removed; unbinding an audio stream from a device with SDL_UnbindAudioStream() will leave the stream still containing any unconsumed data, effectively pausing it until rebound with SDL_BindAudioStream() again. Devices act like they are "paused" after open, like SDL2, until a stream is bound to it. +SDL_PauseAudioDevice() no longer takes a second argument; it always pauses the device. To unpause, use SDL_UnpauseAudioDevice(). -SDL_GetAudioDeviceStatus() has been removed; there is no more concept of "pausing" a device, just whether streams are bound, so please keep track of your audio streams! +Audio devices, opened by SDL_OpenAudioDevice(), no longer start in a paused state, as they don't begin processing audio until a stream is bound. + +SDL_GetAudioDeviceStatus() has been removed; there is now SDL_IsAudioDevicePaused(). SDL_QueueAudio(), SDL_DequeueAudio, and SDL_ClearQueuedAudio and SDL_GetQueuedAudioSize() have been removed; an SDL_AudioStream bound to a device provides the exact same functionality. @@ -230,7 +232,6 @@ The following functions have been removed: * SDL_OpenAudio() * SDL_CloseAudio() * SDL_PauseAudio() -* SDL_PauseAudioDevice * SDL_GetAudioStatus() * SDL_GetAudioDeviceStatus() * SDL_LockAudio() diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 7b5239643..6962b8f3d 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -407,6 +407,87 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD */ extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec); +/** + * Use this function to pause audio playback on a specified device. + * + * This function pauses audio processing for a given device. Any bound + * audio streams will not progress, and no audio will be generated. + * Pausing one device does not prevent other unpaused devices from running. + * + * Unlike in SDL2, audio devices start in an _unpaused_ state, since an app + * has to bind a stream before any audio will flow. Pausing a paused + * device is a legal no-op. + * + * Pausing a device can be useful to halt all audio without unbinding all + * the audio streams. This might be useful while a game is paused, or + * a level is loading, etc. + * + * Physical devices can not be paused or unpaused, only logical devices + * created through SDL_OpenAudioDevice() can be. + * + * \param dev a device opened by SDL_OpenAudioDevice() + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_UnpauseAudioDevice + * \sa SDL_IsAudioDevicePaused + */ +extern DECLSPEC int SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev); + +/** + * Use this function to unpause audio playback on a specified device. + * + * This function unpauses audio processing for a given device that has + * previously been paused with SDL_PauseAudioDevice(). Once unpaused, any + * bound audio streams will begin to progress again, and audio can be + * generated. + * + * Unlike in SDL2, audio devices start in an _unpaused_ state, since an app + * has to bind a stream before any audio will flow. Unpausing an unpaused + * device is a legal no-op. + * + * Physical devices can not be paused or unpaused, only logical devices + * created through SDL_OpenAudioDevice() can be. + * + * \param dev a device opened by SDL_OpenAudioDevice() + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_UnpauseAudioDevice + * \sa SDL_IsAudioDevicePaused + */ +extern DECLSPEC int SDLCALL SDL_UnpauseAudioDevice(SDL_AudioDeviceID dev); + +/** + * Use this function to query if an audio device is paused. + * + * Unlike in SDL2, audio devices start in an _unpaused_ state, since an app + * has to bind a stream before any audio will flow. + * + * Physical devices can not be paused or unpaused, only logical devices + * created through SDL_OpenAudioDevice() can be. Physical and invalid + * device IDs will report themselves as unpaused here. + * + * \param dev a device opened by SDL_OpenAudioDevice() + * \returns SDL_TRUE if device is valid and paused, SDL_FALSE otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_PauseAudioDevice + * \sa SDL_UnpauseAudioDevice + * \sa SDL_IsAudioDevicePaused + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsAudioDevicePaused(SDL_AudioDeviceID dev); /** * Close a previously-opened audio device. diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index f4033c0fd..74a36d5f7 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -603,7 +603,12 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) } else { SDL_assert(buffer_size <= device->buffer_size); // you can ask for less, but not more. SDL_memset(mix_buffer, device->silence_value, buffer_size); // start with silence. + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + if (SDL_AtomicGet(&logdev->paused)) { + continue; // paused? Skip this logical device. + } + for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. @@ -1165,6 +1170,41 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp return retval; } +static int SetLogicalAudioDevicePauseState(SDL_AudioDeviceID devid, int value) +{ + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); + if (!logdev) { + return -1; // ObtainLogicalAudioDevice will have set an error. + } + SDL_AtomicSet(&logdev->paused, value); + SDL_UnlockMutex(logdev->physical_device->lock); + return 0; +} + +int SDL_PauseAudioDevice(SDL_AudioDeviceID devid) +{ + return SetLogicalAudioDevicePauseState(devid, 1); +} + +int SDLCALL SDL_UnpauseAudioDevice(SDL_AudioDeviceID devid) +{ + return SetLogicalAudioDevicePauseState(devid, 0); +} + +SDL_bool SDL_IsAudioDevicePaused(SDL_AudioDeviceID devid) +{ + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); + SDL_bool retval = SDL_FALSE; + if (logdev) { + if (SDL_AtomicGet(&logdev->paused)) { + retval = SDL_TRUE; + } + SDL_UnlockMutex(logdev->physical_device->lock); + } + return retval; +} + + int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams) { const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 0745fd025..7346c2e57 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -885,6 +885,9 @@ SDL3_0.0.0 { SDL_ConvertAudioSamples; SDL_GetSilenceValueForFormat; SDL_LoadWAV; + SDL_PauseAudioDevice; + SDL_UnpauseAudioDevice; + SDL_IsAudioDevicePaused; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 4964e7762..28066c561 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -911,3 +911,6 @@ #define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL #define SDL_GetSilenceValueForFormat SDL_GetSilenceValueForFormat_REAL #define SDL_LoadWAV SDL_LoadWAV_REAL +#define SDL_PauseAudioDevice SDL_PauseAudioDevice_REAL +#define SDL_UnpauseAudioDevice SDL_UnpauseAudioDevice_REAL +#define SDL_IsAudioDevicePaused SDL_IsAudioDevicePaused_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index b51ac769c..211dedbf5 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -955,3 +955,6 @@ SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioForma SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(const SDL_AudioSpec *a, const Uint8 *b, int c, const SDL_AudioSpec *d, Uint8 **e, int *f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(int,SDL_GetSilenceValueForFormat,(SDL_AudioFormat a),(a),return) SDL_DYNAPI_PROC(int,SDL_LoadWAV,(const char *a, SDL_AudioSpec *b, Uint8 **c, Uint32 *d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_PauseAudioDevice,(SDL_AudioDeviceID a),(a),return) +SDL_DYNAPI_PROC(int,SDL_UnpauseAudioDevice,(SDL_AudioDeviceID a),(a),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_IsAudioDevicePaused,(SDL_AudioDeviceID a),(a),return)