SDL: audio: Added SDL_GetAudioStreamQueued

From 34b931f7eb29d717a8625f7f889ba33864c0231d Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Tue, 19 Sep 2023 12:37:21 -0400
Subject: [PATCH] audio: Added SDL_GetAudioStreamQueued

---
 include/SDL3/SDL_audio.h          | 33 +++++++++++++++++++++++++++++++
 src/audio/SDL_audiocvt.c          | 30 ++++++++++++++++++++++++----
 src/audio/SDL_sysaudio.h          |  1 +
 src/dynapi/SDL_dynapi.sym         |  1 +
 src/dynapi/SDL_dynapi_overrides.h |  1 +
 src/dynapi/SDL_dynapi_procs.h     |  1 +
 6 files changed, 63 insertions(+), 4 deletions(-)

diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h
index c1eeda2cdec3..7b76a85de1b6 100644
--- a/include/SDL3/SDL_audio.h
+++ b/include/SDL3/SDL_audio.h
@@ -858,6 +858,39 @@ extern DECLSPEC int SDLCALL SDL_GetAudioStreamData(SDL_AudioStream *stream, void
  */
 extern DECLSPEC int SDLCALL SDL_GetAudioStreamAvailable(SDL_AudioStream *stream);
 
+
+/**
+ * Get the number of sample frames currently queued.
+ *
+ * Since audio streams can change their input format at any time, even if there
+ * is still data queued in a different format, this reports the queued _sample
+ * frames_, so if you queue two stereo samples in float32 format and then
+ * queue five mono samples in Sint16 format, this will return 6.
+ *
+ * Queued data is not converted until it is consumed by SDL_GetAudioStreamData,
+ * so this value should be representative of the exact data that was put into
+ * the stream.
+ *
+ * If the stream has so much data that it would overflow an int, the return
+ * value is clamped to a maximum value, but no queued data is lost; if there
+ * are gigabytes of data queued, the app might need to read some of it with
+ * SDL_GetAudioStreamData before this function's return value is no longer
+ * clamped.
+ *
+ * \param stream The audio stream to query
+ * \returns the number of sample frames queued.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_PutAudioStreamData
+ * \sa SDL_GetAudioStreamData
+ * \sa SDL_ClearAudioStream
+ */
+extern DECLSPEC int SDLCALL SDL_GetAudioStreamQueued(SDL_AudioStream *stream);
+
+
 /**
  * Tell the stream that you're done sending data, and anything being buffered
  * should be converted/resampled and made available immediately.
diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c
index eefc61101a47..018701d3e891 100644
--- a/src/audio/SDL_audiocvt.c
+++ b/src/audio/SDL_audiocvt.c
@@ -657,9 +657,12 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
         retval = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, buf, len);
     }
 
-    if ((retval == 0) && stream->put_callback) {
-        const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available;
-        stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail);
+    if (retval == 0) {
+        stream->total_frames_queued += len / SDL_AUDIO_FRAMESIZE(stream->src_spec);
+        if (stream->put_callback) {
+            const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available;
+            stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail);
+        }
     }
 
     SDL_UnlockMutex(stream->lock);
@@ -858,6 +861,8 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
             SDL_assert(!"Not enough data in queue (read)");
         }
 
+        stream->total_frames_queued -= output_frames;
+
         // Even if we aren't currently resampling, we always need to update the history buffer
         UpdateAudioStreamHistoryBuffer(stream, input_buffer, input_bytes, NULL, 0);
 
@@ -946,6 +951,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
     if (SDL_ReadFromAudioQueue(stream->queue, input_buffer, input_bytes) != 0) {
         SDL_assert(!"Not enough data in queue (resample read)");
     }
+    stream->total_frames_queued -= input_frames;
 
     // Update the history buffer and fill in the left padding
     UpdateAudioStreamHistoryBuffer(stream, input_buffer, input_bytes, left_padding, padding_bytes);
@@ -1083,7 +1089,7 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len)
     return total;
 }
 
-// number of converted/resampled bytes available
+// number of converted/resampled bytes available for output
 int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)
 {
     if (!stream) {
@@ -1108,6 +1114,21 @@ int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)
     return (int) SDL_min(count, SDL_INT_MAX);
 }
 
+// number of sample frames that are currently queued as input.
+int SDL_GetAudioStreamQueued(SDL_AudioStream *stream)
+{
+    if (!stream) {
+        return SDL_InvalidParamError("stream");
+    }
+
+    SDL_LockMutex(stream->lock);
+    const Uint64 total = stream->total_frames_queued;
+    SDL_UnlockMutex(stream->lock);
+
+    // if this overflows an int, just clamp it to a maximum.
+    return (int) SDL_min(total, SDL_INT_MAX);
+}
+
 int SDL_ClearAudioStream(SDL_AudioStream *stream)
 {
     if (stream == NULL) {
@@ -1119,6 +1140,7 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream)
     SDL_ClearAudioQueue(stream->queue);
     SDL_zero(stream->input_spec);
     stream->resample_offset = 0;
+    stream->total_frames_queued = 0;
 
     SDL_UnlockMutex(stream->lock);
     return 0;
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 656397831429..abd16e800b06 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -175,6 +175,7 @@ struct SDL_AudioStream
     float freq_ratio;
 
     struct SDL_AudioQueue* queue;
+    Uint64 total_frames_queued;
 
     SDL_AudioSpec input_spec; // The spec of input data currently being processed
     Sint64 resample_offset;
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index f1d418eb6579..54467c5f5161 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -906,6 +906,7 @@ SDL3_0.0.0 {
     SDL_GetAudioStreamFrequencyRatio;
     SDL_SetAudioStreamFrequencyRatio;
     SDL_SetAudioPostmixCallback;
+    SDL_GetAudioStreamQueued;
     # 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 ffb08c079d45..da300e291329 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -931,3 +931,4 @@
 #define SDL_GetAudioStreamFrequencyRatio SDL_GetAudioStreamFrequencyRatio_REAL
 #define SDL_SetAudioStreamFrequencyRatio SDL_SetAudioStreamFrequencyRatio_REAL
 #define SDL_SetAudioPostmixCallback SDL_SetAudioPostmixCallback_REAL
+#define SDL_GetAudioStreamQueued SDL_GetAudioStreamQueued_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 71bda6eff3fa..a4ffe8d12c67 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -977,3 +977,4 @@ SDL_DYNAPI_PROC(int,SDL_SetWindowFocusable,(SDL_Window *a, SDL_bool b),(a,b),ret
 SDL_DYNAPI_PROC(float,SDL_GetAudioStreamFrequencyRatio,(SDL_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFrequencyRatio,(SDL_AudioStream *a, float b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetAudioPostmixCallback,(SDL_AudioDeviceID a, SDL_AudioPostmixCallback b, void *c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_GetAudioStreamQueued,(SDL_AudioStream *a),(a),return)