From 01f7b53865eb59c4e80c4f1cbd6e83743f127b85 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 22 Jun 2023 00:59:26 -0400
Subject: [PATCH] audio: Readded (logical) device pausing.
---
docs/README-migration.md | 7 +--
include/SDL3/SDL_audio.h | 81 +++++++++++++++++++++++++++++++
src/audio/SDL_audio.c | 40 +++++++++++++++
src/dynapi/SDL_dynapi.sym | 3 ++
src/dynapi/SDL_dynapi_overrides.h | 3 ++
src/dynapi/SDL_dynapi_procs.h | 3 ++
6 files changed, 134 insertions(+), 3 deletions(-)
diff --git a/docs/README-migration.md b/docs/README-migration.md
index ffa2a385475b..c5d6131d74f0 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 7b5239643baa..6962b8f3de41 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 f4033c0fd0e7..74a36d5f7b51 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 0745fd025f14..7346c2e57ddf 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 4964e776291e..28066c561b82 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 b51ac769c174..211dedbf5b5c 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)