SDL: Added SDL_PauseAudioStreamDevice() and SDL_ResumeAudioStreamDevice()

From 534768c7c53f44695cde49ca91fcaa4692db4322 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 26 May 2024 08:10:51 -0700
Subject: [PATCH] Added SDL_PauseAudioStreamDevice() and
 SDL_ResumeAudioStreamDevice()

---
 include/SDL3/SDL_audio.h          | 63 +++++++++++++++++++++++++++++++
 src/audio/SDL_audio.c             | 39 ++++++++++++++++---
 src/dynapi/SDL_dynapi.sym         |  2 +
 src/dynapi/SDL_dynapi_overrides.h |  2 +
 src/dynapi/SDL_dynapi_procs.h     |  2 +
 5 files changed, 102 insertions(+), 6 deletions(-)

diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h
index 6f77585cdf617..c15b5a31dcbe8 100644
--- a/include/SDL3/SDL_audio.h
+++ b/include/SDL3/SDL_audio.h
@@ -1097,6 +1097,69 @@ extern SDL_DECLSPEC int SDLCALL SDL_FlushAudioStream(SDL_AudioStream *stream);
  */
 extern SDL_DECLSPEC int SDLCALL SDL_ClearAudioStream(SDL_AudioStream *stream);
 
+/**
+ * Use this function to pause audio playback on the audio device associated with an audio stream.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * \param stream The audio stream associated with the audio device to pause
+ * \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_ResumeAudioStreamDevice
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_PauseAudioStreamDevice(SDL_AudioStream *stream);
+
+/**
+ * Use this function to unpause audio playback on the audio device associated with an audio stream.
+ *
+ * This function unpauses audio processing for a given device that has
+ * previously been paused. Once unpaused, any bound audio streams will begin to progress again, and audio can be generated.
+ *
+ * \param stream The audio stream associated with the audio device to resume
+ * \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_PauseAudioStreamDevice
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_ResumeAudioStreamDevice(SDL_AudioStream *stream);
+
+/**
+ * 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_ResumeAudioDevice
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_AudioDevicePaused(SDL_AudioDeviceID dev);
+
 /**
  * Lock an audio stream for serialized access.
  *
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index b3b1bd80c1f00..98d6712c39d16 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -1947,13 +1947,20 @@ void SDL_UnbindAudioStream(SDL_AudioStream *stream)
 SDL_AudioDeviceID SDL_GetAudioStreamDevice(SDL_AudioStream *stream)
 {
     SDL_AudioDeviceID retval = 0;
-    if (stream) {
-        SDL_LockMutex(stream->lock);
-        if (stream->bound_device) {
-            retval = stream->bound_device->instance_id;
-        }
-        SDL_UnlockMutex(stream->lock);
+
+    if (!stream) {
+        SDL_InvalidParamError("stream");
+        return 0;
     }
+
+    SDL_LockMutex(stream->lock);
+    if (stream->bound_device) {
+        retval = stream->bound_device->instance_id;
+    } else {
+        SDL_SetError("Audio stream not bound to an audio device");
+    }
+    SDL_UnlockMutex(stream->lock);
+
     return retval;
 }
 
@@ -2024,6 +2031,26 @@ SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_Au
     return stream;
 }
 
+int SDL_PauseAudioStreamDevice(SDL_AudioStream *stream)
+{
+    SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream);
+    if (!devid) {
+        return -1;
+    }
+
+    return SDL_PauseAudioDevice(devid);
+}
+
+int SDL_ResumeAudioStreamDevice(SDL_AudioStream *stream)
+{
+    SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream);
+    if (!devid) {
+        return -1;
+    }
+
+    return SDL_ResumeAudioDevice(devid);
+}
+
 #define NUM_FORMATS 8
 static const SDL_AudioFormat format_list[NUM_FORMATS][NUM_FORMATS + 1] = {
     { SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_S16LE, SDL_AUDIO_S16BE, SDL_AUDIO_S32LE, SDL_AUDIO_S32BE, SDL_AUDIO_F32LE, SDL_AUDIO_F32BE, 0 },
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 4b5758984e38b..744cd0eb7ee9b 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -589,6 +589,7 @@ SDL3_0.0.0 {
     SDL_OpenUserStorage;
     SDL_OutOfMemory;
     SDL_PauseAudioDevice;
+    SDL_PauseAudioStreamDevice;
     SDL_PauseHaptic;
     SDL_PeepEvents;
     SDL_PenConnected;
@@ -656,6 +657,7 @@ SDL3_0.0.0 {
     SDL_ResetLogPriorities;
     SDL_RestoreWindow;
     SDL_ResumeAudioDevice;
+    SDL_ResumeAudioStreamDevice;
     SDL_ResumeHaptic;
     SDL_RumbleGamepad;
     SDL_RumbleGamepadTriggers;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index d4301d0df8857..2e5558159c268 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -614,6 +614,7 @@
 #define SDL_OpenUserStorage SDL_OpenUserStorage_REAL
 #define SDL_OutOfMemory SDL_OutOfMemory_REAL
 #define SDL_PauseAudioDevice SDL_PauseAudioDevice_REAL
+#define SDL_PauseAudioStreamDevice SDL_PauseAudioStreamDevice_REAL
 #define SDL_PauseHaptic SDL_PauseHaptic_REAL
 #define SDL_PeepEvents SDL_PeepEvents_REAL
 #define SDL_PenConnected SDL_PenConnected_REAL
@@ -681,6 +682,7 @@
 #define SDL_ResetLogPriorities SDL_ResetLogPriorities_REAL
 #define SDL_RestoreWindow SDL_RestoreWindow_REAL
 #define SDL_ResumeAudioDevice SDL_ResumeAudioDevice_REAL
+#define SDL_ResumeAudioStreamDevice SDL_ResumeAudioStreamDevice_REAL
 #define SDL_ResumeHaptic SDL_ResumeHaptic_REAL
 #define SDL_RumbleGamepad SDL_RumbleGamepad_REAL
 #define SDL_RumbleGamepadTriggers SDL_RumbleGamepadTriggers_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 1b069ff39981b..c78e76d58cd91 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -625,6 +625,7 @@ SDL_DYNAPI_PROC(int,SDL_OpenURL,(const char *a),(a),return)
 SDL_DYNAPI_PROC(SDL_Storage*,SDL_OpenUserStorage,(const char *a, const char *b, SDL_PropertiesID c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_OutOfMemory,(void),(),return)
 SDL_DYNAPI_PROC(int,SDL_PauseAudioDevice,(SDL_AudioDeviceID a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_PauseAudioStreamDevice,(SDL_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_PauseHaptic,(SDL_Haptic *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_PeepEvents,(SDL_Event *a, int b, SDL_EventAction c, Uint32 d, Uint32 e),(a,b,c,d,e),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_PenConnected,(SDL_PenID a),(a),return)
@@ -692,6 +693,7 @@ SDL_DYNAPI_PROC(void,SDL_ResetKeyboard,(void),(),)
 SDL_DYNAPI_PROC(void,SDL_ResetLogPriorities,(void),(),)
 SDL_DYNAPI_PROC(int,SDL_RestoreWindow,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_ResumeAudioDevice,(SDL_AudioDeviceID a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_ResumeAudioStreamDevice,(SDL_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_ResumeHaptic,(SDL_Haptic *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_RumbleGamepad,(SDL_Gamepad *a, Uint16 b, Uint16 c, Uint32 d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_RumbleGamepadTriggers,(SDL_Gamepad *a, Uint16 b, Uint16 c, Uint32 d),(a,b,c,d),return)