浏览代码

Refactored SDL_audiocvt.c

Brick 1 年之前
父节点
当前提交
a62e62f97a

+ 4 - 0
VisualC-GDK/SDL/SDL.vcxproj

@@ -370,6 +370,8 @@
     <ClInclude Include="..\..\src\audio\SDL_audio_c.h" />
     <ClInclude Include="..\..\src\audio\SDL_audiodev_c.h" />
     <ClInclude Include="..\..\src\audio\SDL_sysaudio.h" />
+    <ClInclude Include="..\..\src\audio\SDL_audioqueue.h" />
+    <ClInclude Include="..\..\src\audio\SDL_audioresample.h" />
     <ClInclude Include="..\..\src\audio\SDL_wave.h" />
     <ClInclude Include="..\..\src\audio\wasapi\SDL_wasapi.h" />
     <ClInclude Include="..\..\src\core\gdk\SDL_gdk.h" />
@@ -549,6 +551,8 @@
     <ClCompile Include="..\..\src\audio\SDL_audiocvt.c" />
     <ClCompile Include="..\..\src\audio\SDL_audiodev.c" />
     <ClCompile Include="..\..\src\audio\SDL_audiotypecvt.c" />
+    <ClCompile Include="..\..\src\audio\SDL_audioqueue.c" />
+    <ClCompile Include="..\..\src\audio\SDL_audioresample.c" />
     <ClCompile Include="..\..\src\audio\SDL_mixer.c" />
     <ClCompile Include="..\..\src\audio\SDL_wave.c" />
     <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />

+ 12 - 0
VisualC-GDK/SDL/SDL.vcxproj.filters

@@ -419,6 +419,12 @@
     <ClInclude Include="..\..\src\audio\SDL_sysaudio.h">
       <Filter>audio</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\audio\SDL_audioqueue.h">
+      <Filter>audio</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\audio\SDL_audioresample.h">
+      <Filter>audio</Filter>
+    </ClInclude>    
     <ClInclude Include="..\..\src\core\windows\SDL_hid.h">
       <Filter>core\windows</Filter>
     </ClInclude>
@@ -854,6 +860,12 @@
     <ClCompile Include="..\..\src\audio\SDL_audiotypecvt.c">
       <Filter>audio</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\audio\SDL_audioqueue.c">
+      <Filter>audio</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\audio\SDL_audioresample.c">
+      <Filter>audio</Filter>
+    </ClCompile>    
     <ClCompile Include="..\..\src\audio\SDL_wave.c">
       <Filter>audio</Filter>
     </ClCompile>

+ 4 - 0
VisualC-WinRT/SDL-UWP.vcxproj

@@ -94,6 +94,8 @@
     <ClInclude Include="..\src\audio\SDL_audiodev_c.h" />
     <ClInclude Include="..\src\audio\SDL_audio_c.h" />
     <ClInclude Include="..\src\audio\SDL_sysaudio.h" />
+    <ClInclude Include="..\src\audio\SDL_audioqueue.h" />
+    <ClInclude Include="..\src\audio\SDL_audioresample.h" />
     <ClInclude Include="..\src\audio\SDL_wave.h" />
     <ClInclude Include="..\src\audio\wasapi\SDL_wasapi.h" />
     <ClInclude Include="..\src\core\windows\SDL_directx.h" />
@@ -193,6 +195,8 @@
     <ClCompile Include="..\src\audio\SDL_audiocvt.c" />
     <ClCompile Include="..\src\audio\SDL_audiodev.c" />
     <ClCompile Include="..\src\audio\SDL_audiotypecvt.c" />
+    <ClCompile Include="..\src\audio\SDL_audioqueue.c" />
+    <ClCompile Include="..\src\audio\SDL_audioresample.c" />
     <ClCompile Include="..\src\audio\SDL_mixer.c" />
     <ClCompile Include="..\src\audio\SDL_wave.c" />
     <ClCompile Include="..\src\audio\wasapi\SDL_wasapi.c" />

+ 12 - 0
VisualC-WinRT/SDL-UWP.vcxproj.filters

@@ -183,6 +183,12 @@
     <ClInclude Include="..\src\audio\SDL_sysaudio.h">
       <Filter>Source Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\src\audio\SDL_audioqueue.h">
+      <Filter>Source Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\src\audio\SDL_audioresample.h">
+      <Filter>Source Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\src\audio\SDL_wave.h">
       <Filter>Source Files</Filter>
     </ClInclude>
@@ -471,6 +477,12 @@
     <ClCompile Include="..\src\audio\SDL_audiotypecvt.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\audio\SDL_audioqueue.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\src\audio\SDL_audioresample.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>        
     <ClCompile Include="..\src\audio\SDL_mixer.c">
       <Filter>Source Files</Filter>
     </ClCompile>

+ 4 - 0
VisualC/SDL/SDL.vcxproj

@@ -319,6 +319,8 @@
     <ClInclude Include="..\..\src\audio\SDL_audio_c.h" />
     <ClInclude Include="..\..\src\audio\SDL_audiodev_c.h" />
     <ClInclude Include="..\..\src\audio\SDL_sysaudio.h" />
+    <ClInclude Include="..\..\src\audio\SDL_audioqueue.h" />
+    <ClInclude Include="..\..\src\audio\SDL_audioresample.h" />
     <ClInclude Include="..\..\src\audio\SDL_wave.h" />
     <ClInclude Include="..\..\src\audio\wasapi\SDL_wasapi.h" />
     <ClInclude Include="..\..\src\core\windows\SDL_directx.h" />
@@ -475,6 +477,8 @@
     <ClCompile Include="..\..\src\audio\SDL_audiocvt.c" />
     <ClCompile Include="..\..\src\audio\SDL_audiodev.c" />
     <ClCompile Include="..\..\src\audio\SDL_audiotypecvt.c" />
+    <ClCompile Include="..\..\src\audio\SDL_audioqueue.c" />
+    <ClCompile Include="..\..\src\audio\SDL_audioresample.c" />
     <ClCompile Include="..\..\src\audio\SDL_mixer.c" />
     <ClCompile Include="..\..\src\audio\SDL_wave.c" />
     <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />

+ 12 - 0
VisualC/SDL/SDL.vcxproj.filters

@@ -410,6 +410,12 @@
     <ClInclude Include="..\..\src\audio\SDL_sysaudio.h">
       <Filter>audio</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\audio\SDL_audioqueue.h">
+      <Filter>audio</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\audio\SDL_audioresample.h">
+      <Filter>audio</Filter>
+    </ClInclude>    
     <ClInclude Include="..\..\src\core\windows\SDL_hid.h">
       <Filter>core\windows</Filter>
     </ClInclude>
@@ -833,6 +839,12 @@
     <ClCompile Include="..\..\src\audio\SDL_audiotypecvt.c">
       <Filter>audio</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\audio\SDL_audioqueue.c">
+      <Filter>audio</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\audio\SDL_audioresample.c">
+      <Filter>audio</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\audio\SDL_wave.c">
       <Filter>audio</Filter>
     </ClCompile>

+ 169 - 809
src/audio/SDL_audiocvt.c

@@ -20,707 +20,16 @@
 */
 #include "SDL_internal.h"
 
-// Functions for audio drivers to perform runtime conversion of audio format
-
 #include "SDL_audio_c.h"
 
-/* SDL's resampler uses a "bandlimited interpolation" algorithm:
-     https://ccrma.stanford.edu/~jos/resample/ */
-
-#include "SDL_audio_resampler_filter.h"
-
-typedef struct SDL_AudioChunk SDL_AudioChunk;
-typedef struct SDL_AudioTrack SDL_AudioTrack;
-typedef struct SDL_AudioQueue SDL_AudioQueue;
-
-struct SDL_AudioChunk
-{
-    SDL_AudioChunk *next;
-    size_t head;
-    size_t tail;
-    Uint8 data[SDL_VARIABLE_LENGTH_ARRAY];
-};
-
-struct SDL_AudioTrack
-{
-    SDL_AudioSpec spec;
-    SDL_AudioTrack *next;
-
-    SDL_AudioChunk *head;
-    SDL_AudioChunk *tail;
-
-    size_t queued_bytes;
-    SDL_bool flushed;
-};
-
-struct SDL_AudioQueue
-{
-    SDL_AudioTrack *head;
-    SDL_AudioTrack *tail;
-    size_t chunk_size;
-
-    SDL_AudioChunk *free_chunks;
-    size_t num_free_chunks;
-};
-
-static void DestroyAudioChunk(SDL_AudioChunk *chunk)
-{
-    SDL_free(chunk);
-}
-
-static void DestroyAudioChunks(SDL_AudioChunk *chunk)
-{
-    while (chunk) {
-        SDL_AudioChunk *next = chunk->next;
-        DestroyAudioChunk(chunk);
-        chunk = next;
-    }
-}
-
-static void DestroyAudioTrack(SDL_AudioTrack *track)
-{
-    DestroyAudioChunks(track->head);
-
-    SDL_free(track);
-}
-
-static SDL_AudioQueue *CreateAudioQueue(size_t chunk_size)
-{
-    SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue));
-
-    if (queue == NULL) {
-        SDL_OutOfMemory();
-        return NULL;
-    }
-
-    queue->chunk_size = chunk_size;
-    return queue;
-}
-
-static void ClearAudioQueue(SDL_AudioQueue *queue)
-{
-    SDL_AudioTrack *track = queue->head;
-
-    while (track) {
-        SDL_AudioTrack *next = track->next;
-        DestroyAudioTrack(track);
-        track = next;
-    }
-
-    queue->head = NULL;
-    queue->tail = NULL;
-
-    DestroyAudioChunks(queue->free_chunks);
-    queue->free_chunks = NULL;
-    queue->num_free_chunks = 0;
-}
-
-static void DestroyAudioQueue(SDL_AudioQueue *queue)
-{
-    ClearAudioQueue(queue);
-
-    SDL_free(queue);
-}
-
-static void ResetAudioChunk(SDL_AudioChunk* chunk)
-{
-    chunk->next = NULL;
-    chunk->head = 0;
-    chunk->tail = 0;
-}
-
-static SDL_AudioChunk *CreateAudioChunk(size_t chunk_size)
-{
-    SDL_AudioChunk *chunk = (SDL_AudioChunk *)SDL_malloc(sizeof(*chunk) + chunk_size);
-
-    if (chunk == NULL) {
-        return NULL;
-    }
-
-    ResetAudioChunk(chunk);
-
-    return chunk;
-}
-
-static SDL_AudioTrack *CreateAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec)
-{
-    SDL_AudioTrack *track = (SDL_AudioTrack *)SDL_calloc(1, sizeof(*track));
-
-    if (track == NULL) {
-        return NULL;
-    }
-
-    SDL_copyp(&track->spec, spec);
-
-    return track;
-}
-
-static SDL_AudioTrack *GetAudioQueueTrackForWriting(SDL_AudioQueue *queue, const SDL_AudioSpec *spec)
-{
-    SDL_AudioTrack *track = queue->tail;
-
-    if ((track == NULL) || track->flushed) {
-        SDL_AudioTrack *new_track = CreateAudioTrack(queue, spec);
-
-        if (new_track == NULL) {
-            SDL_OutOfMemory();
-            return NULL;
-        }
-
-        if (track) {
-            track->next = new_track;
-        } else {
-            queue->head = new_track;
-        }
-
-        queue->tail = new_track;
-
-        track = new_track;
-    } else {
-        SDL_assert((track->spec.format == spec->format) &&
-                   (track->spec.channels == spec->channels) &&
-                   (track->spec.freq == spec->freq));
-    }
-
-    return track;
-}
-
-static SDL_AudioChunk* CreateAudioChunkFromQueue(SDL_AudioQueue *queue)
-{
-    if (queue->num_free_chunks > 0) {
-        SDL_AudioChunk* chunk = queue->free_chunks;
-
-        queue->free_chunks = chunk->next;
-        --queue->num_free_chunks;
-
-        ResetAudioChunk(chunk);
-
-        return chunk;
-    }
-
-    return CreateAudioChunk(queue->chunk_size);
-}
-
-static int WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len)
-{
-    if (len == 0) {
-        return 0;
-    }
-
-    SDL_AudioTrack *track = GetAudioQueueTrackForWriting(queue, spec);
-
-    if (track == NULL) {
-        return -1;
-    }
-
-    SDL_AudioChunk *chunk = track->tail;
-    const size_t chunk_size = queue->chunk_size;
-
-    // Allocate the first chunk here to simplify the logic later on
-    if (chunk == NULL) {
-        chunk = CreateAudioChunkFromQueue(queue);
-
-        if (chunk == NULL) {
-            return SDL_OutOfMemory();
-        }
-
-        SDL_assert((track->head == NULL) && (track->queued_bytes == 0));
-        track->head = chunk;
-        track->tail = chunk;
-    }
-
-    size_t total = 0;
-
-    while (total < len) {
-        if (chunk->tail >= chunk_size) {
-            SDL_AudioChunk *next = CreateAudioChunkFromQueue(queue);
-
-            if (next == NULL) {
-                break;
-            }
-
-            chunk->next = next;
-            chunk = next;
-        }
-
-        size_t to_write = chunk_size - chunk->tail;
-        to_write = SDL_min(to_write, len - total);
-
-        SDL_memcpy(&chunk->data[chunk->tail], &data[total], to_write);
-        chunk->tail += to_write;
-
-        total += to_write;
-    }
-
-    // Roll back the changes if we couldn't write all the data
-    if (total < len) {
-        chunk = track->tail;
-
-        SDL_AudioChunk *next = chunk->next;
-        chunk->next = NULL;
-
-        while (next) {
-            chunk = next;
-            next = chunk->next;
-
-            SDL_assert(chunk->head == 0);
-            SDL_assert(total >= chunk->tail);
-            total -= chunk->tail;
-
-            DestroyAudioChunk(chunk);
-        }
-
-        chunk = track->tail;
-
-        SDL_assert(chunk->tail >= total);
-        chunk->tail -= total;
-        SDL_assert(chunk->head <= chunk->tail);
-
-        return SDL_OutOfMemory();
-    }
-
-    track->tail = chunk;
-    track->queued_bytes += total;
-
-    return 0;
-}
-
-static void ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
-{
-    if (len == 0) {
-        return;
-    }
-
-    SDL_AudioTrack *track = queue->head;
-    SDL_AudioChunk *chunk = track->head;
-    size_t total = 0;
-
-    SDL_assert(len <= track->queued_bytes);
-
-    for (;;) {
-        SDL_assert(chunk != NULL);
-
-        size_t to_read = chunk->tail - chunk->head;
-        to_read = SDL_min(to_read, len - total);
-        SDL_memcpy(&data[total], &chunk->data[chunk->head], to_read);
-        total += to_read;
-
-        if (total == len) {
-            chunk->head += to_read;
-            break;
-        }
-
-        SDL_AudioChunk *next = chunk->next;
-
-        const size_t max_free_chunks = 4;
-
-        if (queue->num_free_chunks < max_free_chunks) {
-            chunk->next = queue->free_chunks;
-            queue->free_chunks = chunk;
-            ++queue->num_free_chunks;
-        } else {
-            DestroyAudioChunk(chunk);
-        }
-
-        chunk = next;
-    }
-
-    SDL_assert(total == len);
-    SDL_assert(chunk != NULL);
-    track->head = chunk;
-
-    SDL_assert(track->queued_bytes >= total);
-    track->queued_bytes -= total;
-}
-
-static size_t PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
-{
-    SDL_AudioTrack *track = queue->head;
-    SDL_AudioChunk *chunk = track->head;
-    size_t total = 0;
-
-    for (; chunk; chunk = chunk->next) {
-        size_t to_read = chunk->tail - chunk->head;
-        to_read = SDL_min(to_read, len - total);
-        SDL_memcpy(&data[total], &chunk->data[chunk->head], to_read);
-        total += to_read;
-
-        if (total == len) {
-            break;
-        }
-    }
-
-    return total;
-}
-
-static void FlushAudioQueue(SDL_AudioQueue *queue)
-{
-    SDL_AudioTrack *track = queue->tail;
-
-    if (track) {
-        track->flushed = SDL_TRUE;
-    }
-}
-
-static SDL_AudioTrack* GetCurrentAudioTrack(SDL_AudioQueue *queue)
-{
-    return queue->head;
-}
-
-static void PopCurrentAudioTrack(SDL_AudioQueue *queue)
-{
-    SDL_AudioTrack *track = queue->head;
-
-    SDL_assert(track->flushed);
-
-    SDL_AudioTrack *next = track->next;
-    DestroyAudioTrack(track);
-
-    queue->head = next;
-
-    if (next == NULL)
-        queue->tail = NULL;
-}
-
-static SDL_AudioChunk *CreateAudioChunks(size_t chunk_size, const Uint8 *data, size_t len)
-{
-    SDL_assert(len != 0);
-
-    SDL_AudioChunk *head = NULL;
-    SDL_AudioChunk *tail = NULL;
-
-    while (len > 0) {
-        SDL_AudioChunk *chunk = CreateAudioChunk(chunk_size);
-
-        if (chunk == NULL) {
-            break;
-        }
-
-        size_t to_write = SDL_min(len, chunk_size);
-
-        SDL_memcpy(chunk->data, data, to_write);
-        chunk->tail = to_write;
-
-        data += to_write;
-        len -= to_write;
-
-        if (tail) {
-            tail->next = chunk;
-        } else {
-            head = chunk;
-        }
-
-        tail = chunk;
-    }
-
-    if (len > 0) {
-        DestroyAudioChunks(head);
-        SDL_OutOfMemory();
-        return NULL;
-    }
-
-    tail->next = head;
-
-    return tail;
-}
-
-static int WriteChunksToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, SDL_AudioChunk *chunks, size_t len)
-{
-    SDL_AudioChunk *tail = chunks;
-    SDL_AudioChunk *head = tail->next;
-    tail->next = NULL;
-
-    SDL_AudioTrack *track = GetAudioQueueTrackForWriting(queue, spec);
-
-    if (track == NULL) {
-        DestroyAudioChunks(head);
-        return -1;
-    }
-
-    if (track->tail) {
-        track->tail->next = head;
-    } else {
-        track->head = head;
-    }
-
-    track->tail = tail;
-    track->queued_bytes += len;
-
-    return 0;
-}
-
-/* For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`.
- * Note, when upsampling, it is also possible to start sampling from `srcpos = -1`. */
-#define RESAMPLER_MAX_PADDING_FRAMES (RESAMPLER_ZERO_CROSSINGS + 1)
-
-/* The source position is tracked using 32:32 fixed-point arithmetic.
- * This gives high precision and avoids lots of divides in ResampleAudio. */
-static Sint64 GetResampleRate(const int src_rate, const int dst_rate)
-{
-    SDL_assert(src_rate > 0);
-    SDL_assert(dst_rate > 0);
-
-    if (src_rate == dst_rate) {
-        return 0;
-    }
-
-    Sint64 sample_rate = ((Sint64)src_rate << 32) / (Sint64)dst_rate;
-    SDL_assert(sample_rate > 0);
-
-    return sample_rate;
-}
+#include "SDL_audioqueue.h"
+#include "SDL_audioresample.h"
 
-// !!! FIXME: This will blow up on weird processors.
 #ifndef SDL_INT_MAX
-#define SDL_INT_MAX 0x7FFFFFFF
-#endif
-
-static int GetResamplerAvailableOutputFrames(const size_t input_frames, const Sint64 resample_rate, const Sint64 resample_offset)
-{
-    const Sint64 output_frames = (((Sint64)input_frames << 32) - resample_offset + resample_rate - 1) / resample_rate;
-
-    return (int) SDL_clamp(output_frames, 0, SDL_INT_MAX);
-}
-
-static int GetResamplerNeededInputFrames(const int output_frames, const Sint64 resample_rate, const Sint64 resample_offset)
-{
-    const Sint32 input_frames = (Sint32)((((output_frames - 1) * resample_rate) + resample_offset) >> 32) + 1;
-
-    return (int) SDL_clamp(input_frames, 0, SDL_INT_MAX);
-}
-
-static int GetResamplerPaddingFrames(const Sint64 resample_rate)
-{
-    // This must always be <= GetHistoryBufferSampleFrames()
-
-    return resample_rate ? RESAMPLER_MAX_PADDING_FRAMES : 0;
-}
-
-static int GetHistoryBufferSampleFrames()
-{
-    // Even if we aren't currently resampling, make sure to keep enough history in case we need to later.
-    return RESAMPLER_MAX_PADDING_FRAMES;
-}
-
-#define RESAMPLER_FILTER_INTERP_BITS (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)
-#define RESAMPLER_FILTER_INTERP_RANGE (1 << RESAMPLER_FILTER_INTERP_BITS)
-
-#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
-
-#define RESAMPLER_FULL_FILTER_SIZE (RESAMPLER_SAMPLES_PER_FRAME * (RESAMPLER_SAMPLES_PER_ZERO_CROSSING + 1))
-
-static void ResampleFrame_Scalar(const float* src, float* dst, const float* raw_filter, const float interp, const int chans)
-{
-    int i, chan;
-
-    float filter[RESAMPLER_SAMPLES_PER_FRAME];
-
-    // Interpolate between the nearest two filters
-    for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
-        filter[i] = (raw_filter[i] * (1.0f - interp)) + (raw_filter[i + RESAMPLER_SAMPLES_PER_FRAME] * interp);
-    }
-
-    if (chans == 2) {
-        float v0 = 0.0f;
-        float v1 = 0.0f;
-
-        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
-            const float scale = filter[i];
-            v0 += src[i * 2 + 0] * scale;
-            v1 += src[i * 2 + 1] * scale;
-        }
-
-        dst[0] = v0;
-        dst[1] = v1;
-        return;
-    }
-
-    if (chans == 1) {
-        float v0 = 0.0f;
-
-        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
-            v0 += src[i] * filter[i];
-        }
-
-        dst[0] = v0;
-        return;
-    }
-
-    for (chan = 0; chan < chans; chan++) {
-        float f = 0.0f;
-
-        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
-            f += src[i * chans + chan] * filter[i];
-        }
-
-        dst[chan] = f;
-    }
-}
-
-#ifdef SDL_SSE_INTRINSICS
-static void SDL_TARGETING("sse") ResampleFrame_SSE(const float* src, float* dst, const float* raw_filter, const float interp, const int chans)
-{
-#if RESAMPLER_SAMPLES_PER_FRAME != 10
-#error Invalid samples per frame
-#endif
-
-    // Load the filter
-    __m128 f0 = _mm_loadu_ps(raw_filter + 0);
-    __m128 f1 = _mm_loadu_ps(raw_filter + 4);
-    __m128 f2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64*)(raw_filter + 8));
-
-    __m128 g0 = _mm_loadu_ps(raw_filter + 10);
-    __m128 g1 = _mm_loadu_ps(raw_filter + 14);
-    __m128 g2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64*)(raw_filter + 18));
-
-    __m128 interp1 = _mm_set1_ps(interp);
-    __m128 interp2 = _mm_sub_ps(_mm_set1_ps(1.0f), _mm_set1_ps(interp));
-
-    // Linear interpolate the filter
-    f0 = _mm_add_ps(_mm_mul_ps(f0, interp2), _mm_mul_ps(g0, interp1));
-    f1 = _mm_add_ps(_mm_mul_ps(f1, interp2), _mm_mul_ps(g1, interp1));
-    f2 = _mm_add_ps(_mm_mul_ps(f2, interp2), _mm_mul_ps(g2, interp1));
-
-    if (chans == 2) {
-        // Duplicate each of the filter elements
-        g0 = _mm_unpackhi_ps(f0, f0);
-        f0 = _mm_unpacklo_ps(f0, f0);
-        g1 = _mm_unpackhi_ps(f1, f1);
-        f1 = _mm_unpacklo_ps(f1, f1);
-        f2 = _mm_unpacklo_ps(f2, f2);
-
-        // Multiply the filter by the input
-        f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
-        g0 = _mm_mul_ps(g0, _mm_loadu_ps(src + 4));
-        f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 8));
-        g1 = _mm_mul_ps(g1, _mm_loadu_ps(src + 12));
-        f2 = _mm_mul_ps(f2, _mm_loadu_ps(src + 16));
-
-        // Calculate the sum
-        f0 = _mm_add_ps(_mm_add_ps(_mm_add_ps(f0, g0), _mm_add_ps(f1, g1)), f2);
-        f0 = _mm_add_ps(f0, _mm_movehl_ps(f0, f0));
-
-        // Store the result
-        _mm_storel_pi((__m64*) dst, f0);
-        return;
-    }
-
-    if (chans == 1) {
-        // Multiply the filter by the input
-        f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
-        f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 4));
-        f2 = _mm_mul_ps(f2, _mm_loadl_pi(_mm_setzero_ps(), (const __m64*)(src + 8)));
-
-        // Calculate the sum
-        f0 = _mm_add_ps(f0, f1);
-        f0 = _mm_add_ps(_mm_add_ps(f0, f2), _mm_movehl_ps(f0, f0));
-        f0 = _mm_add_ss(f0, _mm_shuffle_ps(f0, f0, _MM_SHUFFLE(1, 1, 1, 1)));
-
-        // Store the result
-        _mm_store_ss(dst, f0);
-        return;
-    }
-
-    float filter[RESAMPLER_SAMPLES_PER_FRAME];
-    _mm_storeu_ps(filter + 0, f0);
-    _mm_storeu_ps(filter + 4, f1);
-    _mm_storel_pi((__m64*)(filter + 8), f2);
-
-    int i, chan = 0;
-
-    for (; chan + 4 <= chans; chan += 4) {
-        f0 = _mm_setzero_ps();
-
-        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
-            f0 = _mm_add_ps(f0, _mm_mul_ps(_mm_loadu_ps(&src[i * chans + chan]), _mm_load1_ps(&filter[i])));
-        }
-
-        _mm_storeu_ps(&dst[chan], f0);
-    }
-
-    for (; chan < chans; chan++) {
-        f0 = _mm_setzero_ps();
-
-        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
-            f0 = _mm_add_ss(f0, _mm_mul_ss(_mm_load_ss(&src[i * chans + chan]), _mm_load_ss(&filter[i])));
-        }
-
-        _mm_store_ss(&dst[chan], f0);
-    }
-}
-#endif
-
-static void (*ResampleFrame)(const float* src, float* dst, const float* raw_filter, const float interp, const int chans);
-
-static float FullResamplerFilter[RESAMPLER_FULL_FILTER_SIZE];
-
-void SDL_SetupAudioResampler()
-{
-    static SDL_bool setup = SDL_FALSE;
-    if (setup) {
-        return;
-    }
-
-    // Build a table combining the left and right wings, for faster access
-    int i, j;
-
-    for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
-        for (j = 0; j < RESAMPLER_ZERO_CROSSINGS; j++) {
-            int lwing = (i * RESAMPLER_SAMPLES_PER_FRAME) + (RESAMPLER_ZERO_CROSSINGS - 1) - j;
-            int rwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - lwing;
-
-            float value = ResamplerFilter[(i * RESAMPLER_ZERO_CROSSINGS) + j];
-            FullResamplerFilter[lwing] = value;
-            FullResamplerFilter[rwing] = value;
-        }
-    }
-
-    for (i = 0; i < RESAMPLER_ZERO_CROSSINGS; ++i) {
-        int rwing = i + RESAMPLER_ZERO_CROSSINGS;
-        int lwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - rwing;
-
-        FullResamplerFilter[lwing] = 0.0f;
-        FullResamplerFilter[rwing] = 0.0f;
-    }
-
-    ResampleFrame = ResampleFrame_Scalar;
-
-#ifdef SDL_SSE_INTRINSICS
-    if (SDL_HasSSE()) {
-        ResampleFrame = ResampleFrame_SSE;
-    }
+#define SDL_INT_MAX ((int)(~0u>>1))
 #endif
 
-    setup = SDL_TRUE;
-}
-
-static void ResampleAudio(const int chans, const float *inbuf, const int inframes, float *outbuf, const int outframes,
-                          const Sint64 resample_rate, Sint64* resample_offset)
-{
-    SDL_assert(resample_rate > 0);
-    float *dst = outbuf;
-    int i;
-
-    Sint64 srcpos = *resample_offset;
-
-    for (i = 0; i < outframes; i++) {
-        int srcindex = (int)(Sint32)(srcpos >> 32);
-        Uint32 srcfraction = (Uint32)(srcpos & 0xFFFFFFFF);
-        srcpos += resample_rate;
-
-        SDL_assert(srcindex >= -1 && srcindex < inframes);
-
-        const float* filter = &FullResamplerFilter[(srcfraction >> RESAMPLER_FILTER_INTERP_BITS) * RESAMPLER_SAMPLES_PER_FRAME];
-        const float interp = (float)(srcfraction & (RESAMPLER_FILTER_INTERP_RANGE - 1)) * (1.0f / RESAMPLER_FILTER_INTERP_RANGE);
-
-        const float* src = &inbuf[(srcindex - (RESAMPLER_ZERO_CROSSINGS - 1)) * chans];
-        ResampleFrame(src, dst, filter, interp, chans);
-
-        dst += chans;
-    }
-
-    *resample_offset = srcpos - ((Sint64)inframes << 32);
-}
+#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq))
 
 /*
  * CHANNEL LAYOUTS AS SDL EXPECTS THEM:
@@ -1056,16 +365,27 @@ static int CalculateMaxFrameSize(SDL_AudioFormat src_format, int src_channels, S
     return max_format_size * max_channels;
 }
 
-static Sint64 GetStreamResampleRate(SDL_AudioStream* stream, int src_freq)
+static Sint64 GetAudioStreamResampleRate(SDL_AudioStream* stream, int src_freq, Sint64 resample_offset)
 {
     src_freq = (int)((float)src_freq * stream->freq_ratio);
 
-    return GetResampleRate(src_freq, stream->dst_spec.freq);
+    Sint64 resample_rate = SDL_GetResampleRate(src_freq, stream->dst_spec.freq);
+
+    // If src_freq == dst_freq, and we aren't between frames, don't resample
+    if ((resample_rate == 0x100000000) && (resample_offset == 0)) {
+        resample_rate = 0;
+    }
+
+    return resample_rate;
 }
 
-static int ResetHistoryBuffer(SDL_AudioStream *stream, const SDL_AudioSpec *spec)
+static int UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSpec *spec)
 {
-    const size_t history_buffer_allocation = GetHistoryBufferSampleFrames() * SDL_AUDIO_FRAMESIZE(*spec);
+    if (AUDIO_SPECS_EQUAL(stream->input_spec, *spec)) {
+        return 0;
+    }
+
+    const size_t history_buffer_allocation = SDL_GetResamplerHistoryFrames() * SDL_AUDIO_FRAMESIZE(*spec);
     Uint8 *history_buffer = stream->history_buffer;
 
     if (stream->history_buffer_allocation < history_buffer_allocation) {
@@ -1079,6 +399,7 @@ static int ResetHistoryBuffer(SDL_AudioStream *stream, const SDL_AudioSpec *spec
     }
 
     SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(spec->format), history_buffer_allocation);
+    SDL_copyp(&stream->input_spec, spec);
 
     return 0;
 }
@@ -1097,8 +418,7 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_
     }
 
     retval->freq_ratio = 1.0f;
-    retval->queue = CreateAudioQueue(4096);
-    retval->track_changed = SDL_TRUE;
+    retval->queue = SDL_CreateAudioQueue(4096);
 
     if (retval->queue == NULL) {
         SDL_free(retval);
@@ -1219,13 +539,7 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s
     SDL_LockMutex(stream->lock);
 
     if (src_spec) {
-        // If the format hasn't changed, don't try and flush the stream.
-        if ((stream->src_spec.format != src_spec->format) ||
-            (stream->src_spec.channels != src_spec->channels) ||
-            (stream->src_spec.freq != src_spec->freq)) {
-            SDL_FlushAudioStream(stream);
-            SDL_copyp(&stream->src_spec, src_spec);
-        }
+        SDL_copyp(&stream->src_spec, src_spec);
     }
 
     if (dst_spec) {
@@ -1313,20 +627,22 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
         return SDL_SetError("Can't add partial sample frames");
     }
 
-    SDL_AudioChunk* chunks = NULL;
+    SDL_AudioTrack* track = NULL;
 
     // When copying in large amounts of data, try and do as much work as possible
     // outside of the stream lock, otherwise the output device is likely to be starved.
-    const int large_input_thresh = 64 * 1024;
+    const int large_input_thresh = 1024 * 1024;
 
     if (len >= large_input_thresh) {
-        size_t chunk_size = stream->queue->chunk_size;
+        SDL_AudioSpec src_spec;
+        SDL_copyp(&src_spec, &stream->src_spec);
 
         SDL_UnlockMutex(stream->lock);
 
-        chunks = CreateAudioChunks(chunk_size, (const Uint8*) buf, len);
+        size_t chunk_size = SDL_GetAudioQueueChunkSize(stream->queue);
+        track = SDL_CreateChunkedAudioTrack(&src_spec, buf, len, chunk_size);
 
-        if (chunks == NULL) {
+        if (track == NULL) {
             return -1;
         }
 
@@ -1335,10 +651,13 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
 
     const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0;
 
-    // just queue the data, we convert/resample when dequeueing.
-    const int retval = chunks
-        ? WriteChunksToAudioQueue(stream->queue, &stream->src_spec, chunks, len)
-        : WriteToAudioQueue(stream->queue, &stream->src_spec, (const Uint8*) buf, len);
+    int retval = 0;
+
+    if (track != NULL) {
+        SDL_AddTrackToAudioQueue(stream->queue, track);
+    } else {
+        retval = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, buf, len);
+    }
 
     if ((retval == 0) && stream->put_callback) {
         const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available;
@@ -1350,7 +669,6 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
     return retval;
 }
 
-
 int SDL_FlushAudioStream(SDL_AudioStream *stream)
 {
     if (stream == NULL) {
@@ -1358,7 +676,7 @@ int SDL_FlushAudioStream(SDL_AudioStream *stream)
     }
 
     SDL_LockMutex(stream->lock);
-    FlushAudioQueue(stream->queue);
+    SDL_FlushAudioQueue(stream->queue);
     SDL_UnlockMutex(stream->lock);
 
     return 0;
@@ -1366,7 +684,7 @@ int SDL_FlushAudioStream(SDL_AudioStream *stream)
 
 /* this does not save the previous contents of stream->work_buffer. It's a work buffer!!
    The returned buffer is aligned/padded for use with SIMD instructions. */
-static Uint8 *EnsureStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen)
+static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen)
 {
     if (stream->work_buffer_allocation >= newlen) {
         return stream->work_buffer;
@@ -1384,14 +702,14 @@ static Uint8 *EnsureStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen)
     return ptr;
 }
 
-static void UpdateStreamHistoryBuffer(SDL_AudioStream* stream, const SDL_AudioSpec* spec,
+static void UpdateAudioStreamHistoryBuffer(SDL_AudioStream* stream,
     Uint8* input_buffer, int input_bytes, Uint8* left_padding, int padding_bytes)
 {
-    const int history_buffer_frames = GetHistoryBufferSampleFrames();
+    const int history_buffer_frames = SDL_GetResamplerHistoryFrames();
 
     // Even if we aren't currently resampling, we always need to update the history buffer
     Uint8 *history_buffer = stream->history_buffer;
-    int history_bytes = history_buffer_frames * SDL_AUDIO_FRAMESIZE(*spec);
+    int history_bytes = history_buffer_frames * SDL_AUDIO_FRAMESIZE(stream->input_spec);
 
     if (left_padding != NULL) {
         // Fill in the left padding using the history buffer
@@ -1409,46 +727,99 @@ static void UpdateStreamHistoryBuffer(SDL_AudioStream* stream, const SDL_AudioSp
     }
 }
 
-static Sint64 GetAudioStreamTrackAvailableFrames(SDL_AudioStream* stream, SDL_AudioTrack* track, Sint64 resample_offset)
+static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter,
+    Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, SDL_bool* out_flushed)
 {
-    size_t input_frames = track->queued_bytes / SDL_AUDIO_FRAMESIZE(track->spec);
-    Sint64 resample_rate = GetStreamResampleRate(stream, track->spec.freq);
-    Sint64 output_frames = (Sint64) input_frames;
+    SDL_AudioSpec spec;
+    SDL_bool flushed;
+    size_t queued_bytes = SDL_NextAudioQueueIter(stream->queue, inout_iter, &spec, &flushed);
+
+    if (out_spec) {
+        SDL_copyp(out_spec, &spec);
+    }
+
+    // There is infinite audio available, whether or not we are resampling
+    if (queued_bytes == SDL_SIZE_MAX) {
+        *inout_resample_offset = 0;
+
+        if (out_flushed) {
+            *out_flushed = SDL_FALSE;
+        }
+
+        return SDL_MAX_SINT32;
+    }
+
+    Sint64 resample_offset = *inout_resample_offset;
+    Sint64 resample_rate = GetAudioStreamResampleRate(stream, spec.freq, resample_offset);
+    Sint64 output_frames = (Sint64)(queued_bytes / SDL_AUDIO_FRAMESIZE(spec));
 
     if (resample_rate) {
-        if (!track->flushed) {
-            SDL_assert(track->next == NULL);
-            const int history_buffer_frames = GetHistoryBufferSampleFrames();
-            input_frames -= SDL_min(input_frames, (size_t) history_buffer_frames);
+        // Resampling requires padding frames to the left and right of the current position.
+        // Past the end of the track, the right padding is filled with silence.
+        // But we only want to do that if the track is actually finished (flushed).
+        if (!flushed) {
+            output_frames -= SDL_GetResamplerPaddingFrames(resample_rate);
         }
 
-        output_frames = GetResamplerAvailableOutputFrames(input_frames, resample_rate, resample_offset);
+        output_frames = SDL_GetResamplerOutputFrames(output_frames, resample_rate, &resample_offset);
+    }
+
+    if (flushed) {
+        resample_offset = 0;
+    }
+
+    *inout_resample_offset = resample_offset;
+
+    if (out_flushed) {
+        *out_flushed = flushed;
     }
 
     return output_frames;
 }
 
-static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream *stream)
+static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out_resample_offset)
 {
-    Sint64 total = 0;
+    void* iter = SDL_BeginAudioQueueIter(stream->queue);
+
     Sint64 resample_offset = stream->resample_offset;
-    SDL_AudioTrack* track;
+    Sint64 output_frames = 0;
 
-    for (track = GetCurrentAudioTrack(stream->queue); track; track = track->next) {
-        total += GetAudioStreamTrackAvailableFrames(stream, track, resample_offset);
-        resample_offset = 0;
+    while (iter) {
+        output_frames += NextAudioStreamIter(stream, &iter, &resample_offset, NULL, NULL);
+
+        // Already got loads of frames. Just clamp it to something reasonable
+        if (output_frames >= SDL_MAX_SINT32) {
+            output_frames = SDL_MAX_SINT32;
+            break;
+        }
     }
 
-    return total;
+    if (out_resample_offset) {
+        *out_resample_offset = resample_offset;
+    }
+
+    return output_frames;
+}
+
+static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spec, SDL_bool* out_flushed)
+{
+    void* iter = SDL_BeginAudioQueueIter(stream->queue);
+
+    if (iter == NULL) {
+        SDL_zerop(out_spec);
+        *out_flushed = SDL_FALSE;
+        return 0;
+    }
+
+    Sint64 resample_offset = stream->resample_offset;
+    return NextAudioStreamIter(stream, &iter, &resample_offset, out_spec, out_flushed);
 }
 
 // You must hold stream->lock and validate your parameters before calling this!
 // Enough input data MUST be available!
 static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int output_frames)
 {
-    SDL_AudioTrack* track = GetCurrentAudioTrack(stream->queue);
-
-    const SDL_AudioSpec* src_spec = &track->spec;
+    const SDL_AudioSpec* src_spec = &stream->input_spec;
     const SDL_AudioSpec* dst_spec = &stream->dst_spec;
 
     const SDL_AudioFormat src_format = src_spec->format;
@@ -1459,7 +830,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
     const int dst_channels = dst_spec->channels;
 
     const int max_frame_size = CalculateMaxFrameSize(src_format, src_channels, dst_format, dst_channels);
-    const Sint64 resample_rate = GetStreamResampleRate(stream, src_spec->freq);
+    const Sint64 resample_rate = GetAudioStreamResampleRate(stream, src_spec->freq, stream->resample_offset);
 
 #if DEBUG_AUDIOSTREAM
     SDL_Log("AUDIOSTREAM: asking for %d frames.", output_frames);
@@ -1477,7 +848,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
         if ((src_format == dst_format) && (src_channels == dst_channels)) {
             input_buffer = buf;
         } else {
-            input_buffer = EnsureStreamWorkBufferSize(stream, output_frames * max_frame_size);
+            input_buffer = EnsureAudioStreamWorkBufferSize(stream, output_frames * max_frame_size);
 
             if (!input_buffer) {
                 return -1;
@@ -1485,10 +856,12 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
         }
 
         const int input_bytes = output_frames * src_frame_size;
-        ReadFromAudioQueue(stream->queue, input_buffer, input_bytes);
+        if (SDL_ReadFromAudioQueue(stream->queue, input_buffer, input_bytes) != 0) {
+            SDL_assert(!"Not enough data in queue (read)");
+        }
 
         // Even if we aren't currently resampling, we always need to update the history buffer
-        UpdateStreamHistoryBuffer(stream, src_spec, input_buffer, input_bytes, NULL, 0);
+        UpdateAudioStreamHistoryBuffer(stream, input_buffer, input_bytes, NULL, 0);
 
         // Convert the data, if necessary
         if (buf != input_buffer) {
@@ -1503,10 +876,10 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
     // Because resampling happens "between" frames, The same number of output_frames
     // can require a different number of input_frames, depending on the resample_offset.
     // Infact, input_frames can sometimes even be zero when upsampling.
-    const int input_frames = GetResamplerNeededInputFrames(output_frames, resample_rate, stream->resample_offset);
+    const int input_frames = (int) SDL_GetResamplerInputFrames(output_frames, resample_rate, stream->resample_offset);
     const int input_bytes = input_frames * src_frame_size;
 
-    const int resampler_padding_frames = GetResamplerPaddingFrames(resample_rate);
+    const int resampler_padding_frames = SDL_GetResamplerPaddingFrames(resample_rate);
 
     // If increasing channels, do it after resampling, since we'd just
     // do more work to resample duplicate channels. If we're decreasing, do
@@ -1549,7 +922,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
         work_buffer_capacity += resample_bytes;
     }
 
-    Uint8* work_buffer = EnsureStreamWorkBufferSize(stream, work_buffer_capacity);
+    Uint8* work_buffer = EnsureAudioStreamWorkBufferSize(stream, work_buffer_capacity);
 
     if (!work_buffer) {
         return -1;
@@ -1572,19 +945,16 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
     SDL_assert((work_buffer_tail - work_buffer) <= work_buffer_capacity);
 
     // Now read unconverted data from the queue into the work buffer to fulfill the request.
-    ReadFromAudioQueue(stream->queue, input_buffer, (size_t) input_bytes);
+    if (SDL_ReadFromAudioQueue(stream->queue, input_buffer, input_bytes) != 0) {
+        SDL_assert(!"Not enough data in queue (resample read)");
+    }
 
     // Update the history buffer and fill in the left padding
-    UpdateStreamHistoryBuffer(stream, src_spec, input_buffer, input_bytes, left_padding, padding_bytes);
-
-    // Fill in the right padding by peeking into the input queue
-    const int right_padding_bytes = (int) PeekIntoAudioQueue(stream->queue, right_padding, padding_bytes);
+    UpdateAudioStreamHistoryBuffer(stream, input_buffer, input_bytes, left_padding, padding_bytes);
 
-    if (right_padding_bytes < padding_bytes) {
-        // If we have run out of data, fill the rest with silence.
-        // This should only happen if the stream has been flushed.
-        SDL_assert(track->flushed);
-        SDL_memset(right_padding + right_padding_bytes, SDL_GetSilenceValueForFormat(src_format), padding_bytes - right_padding_bytes);
+    // Fill in the right padding by peeking into the input queue (missing data is filled with silence)
+    if (SDL_PeekIntoAudioQueue(stream->queue, right_padding, padding_bytes) != 0) {
+        SDL_assert(!"Not enough data in queue (resample peek)");
     }
 
     SDL_assert(work_buffer_frames == input_frames + (resampler_padding_frames * 2));
@@ -1600,7 +970,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
     // Decide where the resampled output goes
     void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf;
 
-    ResampleAudio(resample_channels,
+    SDL_ResampleAudio(resample_channels,
                   (const float *) input_buffer, input_frames,
                   (float*) resample_buffer, output_frames,
                   resample_rate, &stream->resample_offset);
@@ -1646,83 +1016,73 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len)
     // give the callback a chance to fill in more stream data if it wants.
     if (stream->get_callback) {
         Sint64 total_request = len / dst_frame_size;  // start with sample frames desired
-        Sint64 approx_request = total_request;
+        Sint64 additional_request = total_request;
 
-        const Sint64 available_frames = GetAudioStreamAvailableFrames(stream);
-        approx_request -= SDL_min(available_frames, approx_request);
+        Sint64 resample_offset = 0;
+        Sint64 available_frames = GetAudioStreamAvailableFrames(stream, &resample_offset);
 
-        const Sint64 resample_rate = GetStreamResampleRate(stream, stream->src_spec.freq);
+        additional_request -= SDL_min(additional_request, available_frames);
+
+        Sint64 resample_rate = GetAudioStreamResampleRate(stream, stream->src_spec.freq, resample_offset);
 
         if (resample_rate) {
-            total_request = GetResamplerNeededInputFrames((int) total_request, resample_rate, 0);
-            approx_request = GetResamplerNeededInputFrames((int) approx_request, resample_rate, 0);
+            total_request = SDL_GetResamplerInputFrames(total_request, resample_rate, resample_offset);
+            additional_request = SDL_GetResamplerInputFrames(additional_request, resample_rate, resample_offset);
         }
 
         total_request *= SDL_AUDIO_FRAMESIZE(stream->src_spec);  // convert sample frames to bytes.
-        approx_request *= SDL_AUDIO_FRAMESIZE(stream->src_spec);  // convert sample frames to bytes.
-        stream->get_callback(stream->get_callback_userdata, stream, (int) SDL_min(approx_request, SDL_INT_MAX), (int) SDL_min(total_request, SDL_INT_MAX));
+        additional_request *= SDL_AUDIO_FRAMESIZE(stream->src_spec);  // convert sample frames to bytes.
+        stream->get_callback(stream->get_callback_userdata, stream, (int) SDL_min(additional_request, SDL_INT_MAX), (int) SDL_min(total_request, SDL_INT_MAX));
     }
 
+    // Process the data in chunks to avoid allocating too much memory (and potential integer overflows)
     const int chunk_size = 4096;
 
-    int retval = 0;
-
-    while (len > 0) {
-        SDL_AudioTrack* track = GetCurrentAudioTrack(stream->queue);
+    int total = 0;
 
-        if (track == NULL) {
-            break;
-        }
-
-        const Sint64 max_frames = GetAudioStreamTrackAvailableFrames(stream, track, stream->resample_offset);
-
-        if (max_frames == 0) {
-            if (track->flushed) {
-                PopCurrentAudioTrack(stream->queue);
-                stream->track_changed = SDL_TRUE;
+    while (total < len) {
+        // Audio is processed a track at a time.
+        SDL_AudioSpec input_spec;
+        SDL_bool flushed;
+        const Sint64 available_frames = GetAudioStreamHead(stream, &input_spec, &flushed);
+
+        if (available_frames == 0) {
+            if (flushed) {
+                SDL_PopAudioQueueHead(stream->queue);
+                SDL_zero(stream->input_spec);
                 stream->resample_offset = 0;
                 continue;
             }
-
+            // There are no frames available, but the track hasn't been flushed, so more might be added later.
             break;
         }
 
-        if (stream->track_changed) {
-            if (ResetHistoryBuffer(stream, &track->spec) != 0) {
-                retval = -1;
-                break;
-            }
-
-            stream->track_changed = SDL_FALSE;
+        if (UpdateAudioStreamInputSpec(stream, &input_spec) != 0) {
+            total = total ? total : -1;
+            break;
         }
 
         // Clamp the output length to the maximum currently available.
-        // GetAudioStreamDataInternal assumes enough input data is available.
-        int output_frames = len / dst_frame_size;
+        // GetAudioStreamDataInternal requires enough input data is available.
+        int output_frames = (len - total) / dst_frame_size;
         output_frames = SDL_min(output_frames, chunk_size);
-        output_frames = (int) SDL_min(output_frames, max_frames);
+        output_frames = (int) SDL_min(output_frames, available_frames);
 
-        if (GetAudioStreamDataInternal(stream, buf, output_frames) != 0) {
-            if (retval == 0) {
-                retval = -1;
-            }
+        if (GetAudioStreamDataInternal(stream, &buf[total], output_frames) != 0) {
+            total = total ? total : -1;
             break;
         }
 
-        const int output_bytes = output_frames * dst_frame_size;
-
-        buf += output_bytes;
-        len -= output_bytes;
-        retval += output_bytes;
+        total += output_frames * dst_frame_size;
     }
 
     SDL_UnlockMutex(stream->lock);
 
 #if DEBUG_AUDIOSTREAM
-    SDL_Log("AUDIOSTREAM: Final result was %d", retval);
+    SDL_Log("AUDIOSTREAM: Final result was %d", total);
 #endif
 
-    return retval;
+    return total;
 }
 
 // number of converted/resampled bytes available
@@ -1739,7 +1099,7 @@ int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)
         return 0;
     }
 
-    Sint64 count = GetAudioStreamAvailableFrames(stream);
+    Sint64 count = GetAudioStreamAvailableFrames(stream, NULL);
 
     // convert from sample frames to bytes in destination format.
     count *= SDL_AUDIO_FRAMESIZE(stream->dst_spec);
@@ -1747,7 +1107,7 @@ int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)
     SDL_UnlockMutex(stream->lock);
 
     // if this overflows an int, just clamp it to a maximum.
-    return (int) SDL_min(count, 0x7FFFFFFF);
+    return (int) SDL_min(count, SDL_INT_MAX);
 }
 
 int SDL_ClearAudioStream(SDL_AudioStream *stream)
@@ -1758,8 +1118,8 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream)
 
     SDL_LockMutex(stream->lock);
 
-    ClearAudioQueue(stream->queue);
-    stream->track_changed = SDL_TRUE;
+    SDL_ClearAudioQueue(stream->queue);
+    SDL_zero(stream->input_spec);
     stream->resample_offset = 0;
 
     SDL_UnlockMutex(stream->lock);
@@ -1782,7 +1142,7 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream)
 
     SDL_aligned_free(stream->history_buffer);
     SDL_aligned_free(stream->work_buffer);
-    DestroyAudioQueue(stream->queue);
+    SDL_DestroyAudioQueue(stream->queue);
     SDL_DestroyMutex(stream->lock);
 
     SDL_free(stream);

+ 516 - 0
src/audio/SDL_audioqueue.c

@@ -0,0 +1,516 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#include "SDL_audioqueue.h"
+
+#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq))
+
+struct SDL_AudioTrack
+{
+    SDL_AudioSpec spec;
+    SDL_bool flushed;
+    SDL_AudioTrack *next;
+
+    size_t (*avail)(void *ctx);
+    int (*write)(void *ctx, const Uint8 *buf, size_t len);
+    size_t (*read)(void *ctx, Uint8 *buf, size_t len, SDL_bool advance);
+    void (*destroy)(void *ctx);
+};
+
+struct SDL_AudioQueue
+{
+    SDL_AudioTrack *head;
+    SDL_AudioTrack *tail;
+    size_t chunk_size;
+};
+
+typedef struct SDL_AudioChunk SDL_AudioChunk;
+
+struct SDL_AudioChunk
+{
+    SDL_AudioChunk *next;
+    size_t head;
+    size_t tail;
+    Uint8 data[SDL_VARIABLE_LENGTH_ARRAY];
+};
+
+typedef struct SDL_ChunkedAudioTrack
+{
+    SDL_AudioTrack track;
+
+    size_t chunk_size;
+
+    SDL_AudioChunk *head;
+    SDL_AudioChunk *tail;
+    size_t queued_bytes;
+
+    SDL_AudioChunk *free_chunks;
+    size_t num_free_chunks;
+} SDL_ChunkedAudioTrack;
+
+static void DestroyAudioChunk(SDL_AudioChunk *chunk)
+{
+    SDL_free(chunk);
+}
+
+static void DestroyAudioChunks(SDL_AudioChunk *chunk)
+{
+    while (chunk) {
+        SDL_AudioChunk *next = chunk->next;
+        DestroyAudioChunk(chunk);
+        chunk = next;
+    }
+}
+
+static void ResetAudioChunk(SDL_AudioChunk *chunk)
+{
+    chunk->next = NULL;
+    chunk->head = 0;
+    chunk->tail = 0;
+}
+
+static SDL_AudioChunk *CreateAudioChunk(size_t chunk_size)
+{
+    SDL_AudioChunk *chunk = (SDL_AudioChunk *)SDL_malloc(sizeof(*chunk) + chunk_size);
+
+    if (chunk == NULL) {
+        return NULL;
+    }
+
+    ResetAudioChunk(chunk);
+
+    return chunk;
+}
+
+static void DestroyAudioTrackChunk(SDL_ChunkedAudioTrack *track, SDL_AudioChunk *chunk)
+{
+    // Keeping a list of free chunks reduces memory allocations,
+    // But also increases the amount of work to perform when freeing the track.
+    const size_t max_free_bytes = 64 * 1024;
+
+    if (track->chunk_size * track->num_free_chunks < max_free_bytes) {
+        chunk->next = track->free_chunks;
+        track->free_chunks = chunk;
+        ++track->num_free_chunks;
+    } else {
+        DestroyAudioChunk(chunk);
+    }
+}
+
+static SDL_AudioChunk *CreateAudioTrackChunk(SDL_ChunkedAudioTrack *track)
+{
+    if (track->num_free_chunks > 0) {
+        SDL_AudioChunk *chunk = track->free_chunks;
+
+        track->free_chunks = chunk->next;
+        --track->num_free_chunks;
+
+        ResetAudioChunk(chunk);
+
+        return chunk;
+    }
+
+    return CreateAudioChunk(track->chunk_size);
+}
+
+static size_t AvailChunkedAudioTrack(void *ctx)
+{
+    SDL_ChunkedAudioTrack *track = ctx;
+
+    return track->queued_bytes;
+}
+
+static int WriteToChunkedAudioTrack(void *ctx, const Uint8 *data, size_t len)
+{
+    SDL_ChunkedAudioTrack *track = ctx;
+
+    SDL_AudioChunk *chunk = track->tail;
+
+    // Handle the first chunk
+    if (chunk == NULL) {
+        chunk = CreateAudioTrackChunk(track);
+
+        if (chunk == NULL) {
+            return SDL_OutOfMemory();
+        }
+
+        SDL_assert((track->head == NULL) && (track->tail == NULL) && (track->queued_bytes == 0));
+        track->head = chunk;
+        track->tail = chunk;
+    }
+
+    size_t total = 0;
+    size_t old_tail = chunk->tail;
+    size_t chunk_size = track->chunk_size;
+
+    while (chunk) {
+        size_t to_write = chunk_size - chunk->tail;
+        to_write = SDL_min(to_write, len - total);
+        SDL_memcpy(&chunk->data[chunk->tail], &data[total], to_write);
+        total += to_write;
+
+        chunk->tail += to_write;
+
+        if (total == len) {
+            break;
+        }
+
+        SDL_AudioChunk *next = CreateAudioTrackChunk(track);
+        chunk->next = next;
+        chunk = next;
+    }
+
+    // Roll back the changes if we couldn't write all the data
+    if (chunk == NULL) {
+        chunk = track->tail;
+
+        SDL_AudioChunk *next = chunk->next;
+        chunk->next = NULL;
+        chunk->tail = old_tail;
+
+        DestroyAudioChunks(next);
+
+        return SDL_OutOfMemory();
+    }
+
+    track->tail = chunk;
+    track->queued_bytes += total;
+
+    return 0;
+}
+
+static size_t ReadFromChunkedAudioTrack(void *ctx, Uint8 *data, size_t len, SDL_bool advance)
+{
+    SDL_ChunkedAudioTrack *track = ctx;
+    SDL_AudioChunk *chunk = track->head;
+
+    size_t total = 0;
+    size_t head = 0;
+
+    while (chunk) {
+        head = chunk->head;
+
+        size_t to_read = chunk->tail - head;
+        to_read = SDL_min(to_read, len - total);
+        SDL_memcpy(&data[total], &chunk->data[head], to_read);
+        total += to_read;
+
+        SDL_AudioChunk *next = chunk->next;
+
+        if (total == len) {
+            head += to_read;
+            break;
+        }
+
+        if (advance) {
+            DestroyAudioTrackChunk(track, chunk);
+        }
+
+        chunk = next;
+    }
+
+    if (advance) {
+        if (chunk) {
+            chunk->head = head;
+            track->head = chunk;
+        } else {
+            track->head = NULL;
+            track->tail = NULL;
+        }
+
+        track->queued_bytes -= total;
+    }
+
+    return total;
+}
+
+static void DestroyChunkedAudioTrack(void *ctx)
+{
+    SDL_ChunkedAudioTrack *track = ctx;
+    DestroyAudioChunks(track->head);
+    DestroyAudioChunks(track->free_chunks);
+    SDL_free(track);
+}
+
+static SDL_AudioTrack *CreateChunkedAudioTrack(const SDL_AudioSpec *spec, size_t chunk_size)
+{
+    SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)SDL_calloc(1, sizeof(*track));
+
+    if (track == NULL) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+
+    SDL_copyp(&track->track.spec, spec);
+    track->track.avail = AvailChunkedAudioTrack;
+    track->track.write = WriteToChunkedAudioTrack;
+    track->track.read = ReadFromChunkedAudioTrack;
+    track->track.destroy = DestroyChunkedAudioTrack;
+
+    track->chunk_size = chunk_size;
+
+    return &track->track;
+}
+
+SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
+{
+    SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue));
+
+    if (queue == NULL) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+
+    queue->chunk_size = chunk_size;
+
+    return queue;
+}
+
+void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
+{
+    SDL_ClearAudioQueue(queue);
+
+    SDL_free(queue);
+}
+
+void SDL_ClearAudioQueue(SDL_AudioQueue *queue)
+{
+    SDL_AudioTrack *track = queue->head;
+    queue->head = NULL;
+    queue->tail = NULL;
+
+    while (track) {
+        SDL_AudioTrack *next = track->next;
+        track->destroy(track);
+        track = next;
+    }
+}
+
+static void SDL_FlushAudioTrack(SDL_AudioTrack *track)
+{
+    track->flushed = SDL_TRUE;
+    track->write = NULL;
+}
+
+void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
+{
+    SDL_AudioTrack *track = queue->tail;
+
+    if (track) {
+        SDL_FlushAudioTrack(track);
+    }
+}
+
+void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
+{
+    SDL_AudioTrack *track = queue->head;
+
+    for (;;) {
+        SDL_bool flushed = track->flushed;
+
+        SDL_AudioTrack *next = track->next;
+        track->destroy(track);
+        track = next;
+
+        if (flushed) {
+            break;
+        }
+    }
+
+    queue->head = track;
+
+    if (track == NULL) {
+        queue->tail = NULL;
+    }
+}
+
+size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue)
+{
+    return queue->chunk_size;
+}
+
+SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size)
+{
+    SDL_AudioTrack *track = CreateChunkedAudioTrack(spec, chunk_size);
+
+    if (track == NULL) {
+        return NULL;
+    }
+
+    if (track->write(track, data, len) != 0) {
+        track->destroy(track);
+        return NULL;
+    }
+
+    return track;
+}
+
+void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
+{
+    SDL_AudioTrack *tail = queue->tail;
+
+    if (tail) {
+        // If the spec has changed, make sure to flush the previous track
+        if (!AUDIO_SPECS_EQUAL(tail->spec, track->spec)) {
+            SDL_FlushAudioTrack(tail);
+        }
+
+        tail->next = track;
+    } else {
+        queue->head = track;
+    }
+
+    queue->tail = track;
+}
+
+int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len)
+{
+    if (len == 0) {
+        return 0;
+    }
+
+    SDL_AudioTrack *track = queue->tail;
+
+    if ((track != NULL) && !AUDIO_SPECS_EQUAL(track->spec, *spec)) {
+        SDL_FlushAudioTrack(track);
+    }
+
+    if ((track == NULL) || (track->write == NULL)) {
+        SDL_AudioTrack *new_track = CreateChunkedAudioTrack(spec, queue->chunk_size);
+
+        if (new_track == NULL) {
+            return SDL_OutOfMemory();
+        }
+
+        if (track) {
+            track->next = new_track;
+        } else {
+            queue->head = new_track;
+        }
+
+        queue->tail = new_track;
+
+        track = new_track;
+    }
+
+    return track->write(track, data, len);
+}
+
+void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
+{
+    return queue->head;
+}
+
+size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed)
+{
+    SDL_AudioTrack *iter = *inout_iter;
+    SDL_assert(iter != NULL);
+
+    SDL_copyp(out_spec, &iter->spec);
+
+    SDL_bool flushed = SDL_FALSE;
+    size_t queued_bytes = 0;
+
+    while (iter) {
+        SDL_AudioTrack *track = iter;
+        iter = iter->next;
+
+        size_t avail = track->avail(track);
+
+        if (avail >= SDL_SIZE_MAX - queued_bytes) {
+            queued_bytes = SDL_SIZE_MAX;
+            flushed = SDL_FALSE;
+            break;
+        }
+
+        queued_bytes += avail;
+        flushed = track->flushed;
+
+        if (flushed) {
+            break;
+        }
+    }
+
+    *inout_iter = iter;
+    *out_flushed = flushed;
+
+    return queued_bytes;
+}
+
+int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
+{
+    size_t total = 0;
+    SDL_AudioTrack *track = queue->head;
+
+    for (;;) {
+        if (track == NULL) {
+            return SDL_SetError("Reading past end of queue");
+        }
+
+        total += track->read(track, &data[total], len - total, SDL_TRUE);
+
+        if (total == len) {
+            return 0;
+        }
+
+        if (track->flushed) {
+            return SDL_SetError("Reading past end of flushed track");
+        }
+
+        SDL_AudioTrack *next = track->next;
+
+        if (next == NULL) {
+            return SDL_SetError("Reading past end of incomplete track");
+        }
+
+        queue->head = next;
+
+        track->destroy(track);
+        track = next;
+    }
+}
+
+int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
+{
+    size_t total = 0;
+    SDL_AudioTrack *track = queue->head;
+
+    for (;;) {
+        if (track == NULL) {
+            return SDL_SetError("Peeking past end of queue");
+        }
+
+        total += track->read(track, &data[total], len - total, SDL_FALSE);
+
+        if (total == len) {
+            return 0;
+        }
+
+        if (track->flushed) {
+            // If we have run out of data, fill the rest with silence.
+            SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total);
+            return 0;
+        }
+
+        track = track->next;
+    }
+}

+ 77 - 0
src/audio/SDL_audioqueue.h

@@ -0,0 +1,77 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifndef SDL_audioqueue_h_
+#define SDL_audioqueue_h_
+
+// Internal functions used by SDL_AudioStream for queueing audio.
+
+typedef struct SDL_AudioQueue SDL_AudioQueue;
+typedef struct SDL_AudioTrack SDL_AudioTrack;
+
+// Create a new audio queue
+SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size);
+
+// Destroy an audio queue
+void SDL_DestroyAudioQueue(SDL_AudioQueue *queue);
+
+// Completely clear the queue
+void SDL_ClearAudioQueue(SDL_AudioQueue *queue);
+
+// Mark the last track as flushed
+void SDL_FlushAudioQueue(SDL_AudioQueue *queue);
+
+// Pop the current head track
+// REQUIRES: The head track must exist, and must have been flushed
+void SDL_PopAudioQueueHead(SDL_AudioQueue *queue);
+
+// Get the chunk size, mostly for use with SDL_CreateChunkedAudioTrack
+// This can be called from any thread
+size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue);
+
+// Write data to the end of queue
+// REQUIRES: If the spec has changed, the last track must have been flushed
+int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len);
+
+// Create a track without needing to hold any locks
+SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size);
+
+// Add a track to the end of the queue
+// REQUIRES: `track != NULL`
+void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track);
+
+// Iterate over the tracks in the queue
+void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
+
+// Query and update the track iterator
+// REQUIRES: `*inout_iter != NULL` (a valid iterator)
+size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed);
+
+// Read data from the start of the queue
+// REQUIRES: There must be enough data in the queue
+int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len);
+
+// Peek into the start of the queue
+// REQUIRES: There must be enough data in the queue, unless it has been flushed, in which case missing data is filled with silence.
+int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len);
+
+#endif // SDL_audioqueue_h_

+ 332 - 0
src/audio/SDL_audioresample.c

@@ -0,0 +1,332 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+/* SDL's resampler uses a "bandlimited interpolation" algorithm:
+     https://ccrma.stanford.edu/~jos/resample/ */
+
+#include "SDL_audio_resampler_filter.h"
+
+/* For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`.
+ * Note, when upsampling, it is also possible to start sampling from `srcpos = -1`. */
+#define RESAMPLER_MAX_PADDING_FRAMES (RESAMPLER_ZERO_CROSSINGS + 1)
+
+#define RESAMPLER_FILTER_INTERP_BITS  (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)
+#define RESAMPLER_FILTER_INTERP_RANGE (1 << RESAMPLER_FILTER_INTERP_BITS)
+
+#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
+
+#define RESAMPLER_FULL_FILTER_SIZE (RESAMPLER_SAMPLES_PER_FRAME * (RESAMPLER_SAMPLES_PER_ZERO_CROSSING + 1))
+
+static void ResampleFrame_Scalar(const float *src, float *dst, const float *raw_filter, float interp, int chans)
+{
+    int i, chan;
+
+    float filter[RESAMPLER_SAMPLES_PER_FRAME];
+
+    // Interpolate between the nearest two filters
+    for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
+        filter[i] = (raw_filter[i] * (1.0f - interp)) + (raw_filter[i + RESAMPLER_SAMPLES_PER_FRAME] * interp);
+    }
+
+    if (chans == 2) {
+        float out[2];
+        out[0] = 0.0f;
+        out[1] = 0.0f;
+
+        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
+            const float scale = filter[i];
+            out[0] += src[i * 2 + 0] * scale;
+            out[1] += src[i * 2 + 1] * scale;
+        }
+
+        dst[0] = out[0];
+        dst[1] = out[1];
+        return;
+    }
+
+    if (chans == 1) {
+        float out = 0.0f;
+
+        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
+            out += src[i] * filter[i];
+        }
+
+        dst[0] = out;
+        return;
+    }
+
+    for (chan = 0; chan < chans; chan++) {
+        float f = 0.0f;
+
+        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
+            f += src[i * chans + chan] * filter[i];
+        }
+
+        dst[chan] = f;
+    }
+}
+
+#ifdef SDL_SSE_INTRINSICS
+static void SDL_TARGETING("sse") ResampleFrame_SSE(const float *src, float *dst, const float *raw_filter, float interp, int chans)
+{
+#if RESAMPLER_SAMPLES_PER_FRAME != 10
+#error Invalid samples per frame
+#endif
+
+    // Load the filter
+    __m128 f0 = _mm_loadu_ps(raw_filter + 0);
+    __m128 f1 = _mm_loadu_ps(raw_filter + 4);
+    __m128 f2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(raw_filter + 8));
+
+    __m128 g0 = _mm_loadu_ps(raw_filter + 10);
+    __m128 g1 = _mm_loadu_ps(raw_filter + 14);
+    __m128 g2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(raw_filter + 18));
+
+    __m128 interp1 = _mm_set1_ps(interp);
+    __m128 interp2 = _mm_sub_ps(_mm_set1_ps(1.0f), _mm_set1_ps(interp));
+
+    // Linear interpolate the filter
+    f0 = _mm_add_ps(_mm_mul_ps(f0, interp2), _mm_mul_ps(g0, interp1));
+    f1 = _mm_add_ps(_mm_mul_ps(f1, interp2), _mm_mul_ps(g1, interp1));
+    f2 = _mm_add_ps(_mm_mul_ps(f2, interp2), _mm_mul_ps(g2, interp1));
+
+    if (chans == 2) {
+        // Duplicate each of the filter elements
+        g0 = _mm_unpackhi_ps(f0, f0);
+        f0 = _mm_unpacklo_ps(f0, f0);
+        g1 = _mm_unpackhi_ps(f1, f1);
+        f1 = _mm_unpacklo_ps(f1, f1);
+        f2 = _mm_unpacklo_ps(f2, f2);
+
+        // Multiply the filter by the input
+        f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
+        g0 = _mm_mul_ps(g0, _mm_loadu_ps(src + 4));
+        f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 8));
+        g1 = _mm_mul_ps(g1, _mm_loadu_ps(src + 12));
+        f2 = _mm_mul_ps(f2, _mm_loadu_ps(src + 16));
+
+        // Calculate the sum
+        f0 = _mm_add_ps(_mm_add_ps(_mm_add_ps(f0, g0), _mm_add_ps(f1, g1)), f2);
+        f0 = _mm_add_ps(f0, _mm_movehl_ps(f0, f0));
+
+        // Store the result
+        _mm_storel_pi((__m64 *)dst, f0);
+        return;
+    }
+
+    if (chans == 1) {
+        // Multiply the filter by the input
+        f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
+        f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 4));
+        f2 = _mm_mul_ps(f2, _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(src + 8)));
+
+        // Calculate the sum
+        f0 = _mm_add_ps(f0, f1);
+        f0 = _mm_add_ps(_mm_add_ps(f0, f2), _mm_movehl_ps(f0, f0));
+        f0 = _mm_add_ss(f0, _mm_shuffle_ps(f0, f0, _MM_SHUFFLE(1, 1, 1, 1)));
+
+        // Store the result
+        _mm_store_ss(dst, f0);
+        return;
+    }
+
+    float filter[RESAMPLER_SAMPLES_PER_FRAME];
+    _mm_storeu_ps(filter + 0, f0);
+    _mm_storeu_ps(filter + 4, f1);
+    _mm_storel_pi((__m64 *)(filter + 8), f2);
+
+    int i, chan = 0;
+
+    for (; chan + 4 <= chans; chan += 4) {
+        f0 = _mm_setzero_ps();
+
+        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
+            f0 = _mm_add_ps(f0, _mm_mul_ps(_mm_loadu_ps(&src[i * chans + chan]), _mm_load1_ps(&filter[i])));
+        }
+
+        _mm_storeu_ps(&dst[chan], f0);
+    }
+
+    for (; chan < chans; chan++) {
+        f0 = _mm_setzero_ps();
+
+        for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
+            f0 = _mm_add_ss(f0, _mm_mul_ss(_mm_load_ss(&src[i * chans + chan]), _mm_load_ss(&filter[i])));
+        }
+
+        _mm_store_ss(&dst[chan], f0);
+    }
+}
+#endif
+
+static void (*ResampleFrame)(const float *src, float *dst, const float *raw_filter, float interp, int chans);
+
+static float FullResamplerFilter[RESAMPLER_FULL_FILTER_SIZE];
+
+void SDL_SetupAudioResampler()
+{
+    static SDL_bool setup = SDL_FALSE;
+    if (setup) {
+        return;
+    }
+
+    // Build a table combining the left and right wings, for faster access
+    int i, j;
+
+    for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
+        for (j = 0; j < RESAMPLER_ZERO_CROSSINGS; j++) {
+            int lwing = (i * RESAMPLER_SAMPLES_PER_FRAME) + (RESAMPLER_ZERO_CROSSINGS - 1) - j;
+            int rwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - lwing;
+
+            float value = ResamplerFilter[(i * RESAMPLER_ZERO_CROSSINGS) + j];
+            FullResamplerFilter[lwing] = value;
+            FullResamplerFilter[rwing] = value;
+        }
+    }
+
+    for (i = 0; i < RESAMPLER_ZERO_CROSSINGS; ++i) {
+        int rwing = i + RESAMPLER_ZERO_CROSSINGS;
+        int lwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - rwing;
+
+        FullResamplerFilter[lwing] = 0.0f;
+        FullResamplerFilter[rwing] = 0.0f;
+    }
+
+    ResampleFrame = ResampleFrame_Scalar;
+
+#ifdef SDL_SSE_INTRINSICS
+    if (SDL_HasSSE()) {
+        ResampleFrame = ResampleFrame_SSE;
+    }
+#endif
+
+    setup = SDL_TRUE;
+}
+
+Sint64 SDL_GetResampleRate(int src_rate, int dst_rate)
+{
+    SDL_assert(src_rate > 0);
+    SDL_assert(dst_rate > 0);
+
+    Sint64 sample_rate = ((Sint64)src_rate << 32) / (Sint64)dst_rate;
+    SDL_assert(sample_rate > 0);
+
+    return sample_rate;
+}
+
+int SDL_GetResamplerHistoryFrames()
+{
+    // Even if we aren't currently resampling, make sure to keep enough history in case we need to later.
+
+    return RESAMPLER_MAX_PADDING_FRAMES;
+}
+
+int SDL_GetResamplerPaddingFrames(Sint64 resample_rate)
+{
+    // This must always be <= SDL_GetResamplerHistoryFrames()
+
+    return resample_rate ? RESAMPLER_MAX_PADDING_FRAMES : 0;
+}
+
+// These are not general purpose. They do not check for all possible underflow/overflow
+SDL_FORCE_INLINE Sint64 ResamplerAdd(Sint64 a, Sint64 b, Sint64 *ret)
+{
+    if ((b > 0) && (a > SDL_MAX_SINT64 - b)) {
+        return -1;
+    }
+
+    *ret = a + b;
+    return 0;
+}
+
+SDL_FORCE_INLINE Sint64 ResamplerMul(Sint64 a, Sint64 b, Sint64 *ret)
+{
+    if ((b > 0) && (a > SDL_MAX_SINT64 / b)) {
+        return -1;
+    }
+
+    *ret = a * b;
+    return 0;
+}
+
+Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset)
+{
+    // Calculate the index of the last input frame, then add 1.
+    // ((((output_frames - 1) * resample_rate) + resample_offset) >> 32) + 1
+
+    Sint64 output_offset;
+    if (ResamplerMul(output_frames, resample_rate, &output_offset) ||
+        ResamplerAdd(output_offset, -resample_rate + resample_offset + 0x100000000, &output_offset)) {
+        output_offset = SDL_MAX_SINT64;
+    }
+
+    Sint64 input_frames = (Sint64)(Sint32)(output_offset >> 32);
+    input_frames = SDL_max(input_frames, 0);
+
+    return input_frames;
+}
+
+Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset)
+{
+    Sint64 resample_offset = *inout_resample_offset;
+
+    // input_offset = (input_frames << 32) - resample_offset;
+    Sint64 input_offset;
+    if (ResamplerMul(input_frames, 0x100000000, &input_offset) ||
+        ResamplerAdd(input_offset, -resample_offset, &input_offset)) {
+        input_offset = SDL_MAX_SINT64;
+    }
+
+    // output_frames = div_ceil(input_offset, resample_rate)
+    Sint64 output_frames = (input_offset > 0) ? (((input_offset - 1) / resample_rate) + 1) : 0;
+
+    *inout_resample_offset = (output_frames * resample_rate) - input_offset;
+
+    return output_frames;
+}
+
+void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
+                       Sint64 resample_rate, Sint64 *inout_resample_offset)
+{
+    int i;
+    Sint64 srcpos = *inout_resample_offset;
+
+    SDL_assert(resample_rate > 0);
+
+    for (i = 0; i < outframes; i++) {
+        int srcindex = (int)(Sint32)(srcpos >> 32);
+        Uint32 srcfraction = (Uint32)(srcpos & 0xFFFFFFFF);
+        srcpos += resample_rate;
+
+        SDL_assert(srcindex >= -1 && srcindex < inframes);
+
+        const float *filter = &FullResamplerFilter[(srcfraction >> RESAMPLER_FILTER_INTERP_BITS) * RESAMPLER_SAMPLES_PER_FRAME];
+        const float interp = (float)(srcfraction & (RESAMPLER_FILTER_INTERP_RANGE - 1)) * (1.0f / RESAMPLER_FILTER_INTERP_RANGE);
+
+        const float *frame = &src[(srcindex - (RESAMPLER_ZERO_CROSSINGS - 1)) * chans];
+        ResampleFrame(frame, dst, filter, interp, chans);
+
+        dst += chans;
+    }
+
+    *inout_resample_offset = srcpos - ((Sint64)inframes << 32);
+}

+ 43 - 0
src/audio/SDL_audioresample.h

@@ -0,0 +1,43 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifndef SDL_audioresample_h_
+#define SDL_audioresample_h_
+
+// Internal functions used by SDL_AudioStream for resampling audio.
+// The resampler uses 32:32 fixed-point arithmetic to track its position.
+
+Sint64 SDL_GetResampleRate(const int src_rate, const int dst_rate);
+
+int SDL_GetResamplerHistoryFrames();
+int SDL_GetResamplerPaddingFrames(Sint64 resample_rate);
+
+Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset);
+Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset);
+
+// Resample some audio.
+// REQUIRES: `inframes >= SDL_GetResamplerInputFrames(outframes)`
+// REQUIRES: At least `SDL_GetResamplerPaddingFrames(...)` extra frames to the left of src, and right of src+inframes
+void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
+                       Sint64 resample_rate, Sint64 *inout_resample_offset);
+
+#endif // SDL_audioresample_h_

+ 2 - 2
src/audio/SDL_sysaudio.h

@@ -68,7 +68,7 @@ extern void SDL_QuitAudio(void);
 // Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results.
 const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format);
 
-// Must be called at least once before using converters (SDL_CreateAudioStream will call it !!! FIXME but probably shouldn't).
+// Must be called at least once before using converters.
 extern void SDL_ChooseAudioConverters(void);
 extern void SDL_SetupAudioResampler(void);
 
@@ -174,7 +174,7 @@ struct SDL_AudioStream
 
     struct SDL_AudioQueue* queue;
 
-    SDL_bool track_changed;
+    SDL_AudioSpec input_spec; // The spec of input data currently being processed
     Sint64 resample_offset;
 
     Uint8 *work_buffer;    // used for scratch space during data conversion/resampling.

+ 18 - 14
test/testaudiostreamdynamicresample.c

@@ -35,10 +35,11 @@ static Uint8 *audio_buf = NULL;
 static Uint32 audio_len = 0;
 
 static SDL_bool auto_loop = SDL_TRUE;
-static SDL_bool auto_flush = SDL_TRUE;
+static SDL_bool auto_flush = SDL_FALSE;
 
 static Uint64 last_get_callback = 0;
-static int last_get_amount = 0;
+static int last_get_amount_additional = 0;
+static int last_get_amount_total = 0;
 
 typedef struct Slider
 {
@@ -46,7 +47,7 @@ typedef struct Slider
     SDL_bool changed;
     char fmtlabel[64];
     float pos;
-    int type;
+    int flags;
     float min;
     float mid;
     float max;
@@ -57,7 +58,7 @@ typedef struct Slider
 Slider sliders[NUM_SLIDERS];
 static int active_slider = -1;
 
-static void init_slider(int index, const char* fmtlabel, int type, float value, float min, float max)
+static void init_slider(int index, const char* fmtlabel, int flags, float value, float min, float max)
 {
     Slider* slider = &sliders[index];
 
@@ -67,12 +68,12 @@ static void init_slider(int index, const char* fmtlabel, int type, float value,
     slider->area.h = SLIDER_HEIGHT_PERC * state->window_h;
     slider->changed = SDL_TRUE;
     SDL_strlcpy(slider->fmtlabel, fmtlabel, SDL_arraysize(slider->fmtlabel));
-    slider->type = type;
+    slider->flags = flags;
     slider->min = min;
     slider->max = max;
     slider->value = value;
 
-    if (slider->type == 0) {
+    if (slider->flags & 1) {
         slider->pos = (value - slider->min + 0.5f) / (slider->max - slider->min + 1.0f);
     } else {
         slider->pos = 0.5f;
@@ -269,7 +270,7 @@ static void loop(void)
         value = SDL_clamp(value, 0.0f, 1.0f);
         slider->pos = value;
 
-        if (slider->type == 0) {
+        if (slider->flags & 1) {
             value = slider->min + (value * (slider->max - slider->min + 1.0f));
             value = SDL_clamp(value, slider->min, slider->max);
         } else {
@@ -321,7 +322,8 @@ static void loop(void)
             SDL_SetRenderDrawColor(rend, 0x58, 0x6E, 0x75, 0xFF);
             SDL_RenderFillRect(rend, &area);
 
-            draw_textf(rend, (int)slider->area.x, (int)slider->area.y, slider->fmtlabel, slider->value);
+            draw_textf(rend, (int)slider->area.x, (int)slider->area.y, slider->fmtlabel,
+                (slider->flags & 2) ? ((float)(int)slider->value) : slider->value);
         }
 
         draw_textf(rend, 0, draw_y, "%7s, Loop: %3s, Flush: %3s",
@@ -333,7 +335,8 @@ static void loop(void)
 
         SDL_LockAudioStream(stream);
 
-        draw_textf(rend, 0, draw_y, "Get Callback: %i bytes, %i ms ago", last_get_amount, (int)(SDL_GetTicks() - last_get_callback));
+        draw_textf(rend, 0, draw_y, "Get Callback: %i/%i bytes, %2i ms ago",
+            last_get_amount_additional, last_get_amount_total, (int)(SDL_GetTicks() - last_get_callback));
         draw_y += FONT_LINE_HEIGHT;
 
         SDL_UnlockAudioStream(stream);
@@ -356,10 +359,11 @@ static void loop(void)
     }
 }
 
-static void SDLCALL our_get_callback(void *userdata, SDL_AudioStream *strm, int approx_amount, int total_amount)
+static void SDLCALL our_get_callback(void *userdata, SDL_AudioStream *strm, int additional_amount, int total_amount)
 {
     last_get_callback = SDL_GetTicks();
-    last_get_amount = approx_amount;
+    last_get_amount_additional = additional_amount;
+    last_get_amount_total = total_amount;
 }
 
 int main(int argc, char *argv[])
@@ -415,9 +419,9 @@ int main(int argc, char *argv[])
         return 1;
     }
 
-    init_slider(0, "Speed: %3.2fx", 1, 1.0f, 0.2f, 5.0f);
-    init_slider(1, "Freq: %.0f", 1, (float)spec.freq, 4000.0f, 192000.0f);
-    init_slider(2, "Channels: %.0f", 0, (float)spec.channels, 1.0f, 8.0f);
+    init_slider(0, "Speed: %3.2fx", 0x0, 1.0f, 0.2f, 5.0f);
+    init_slider(1, "Freq: %g", 0x2, (float)spec.freq, 4000.0f, 192000.0f);
+    init_slider(2, "Channels: %g", 0x3, (float)spec.channels, 1.0f, 8.0f);
 
     for (i = 0; i < state->num_windows; i++) {
         SDL_SetWindowTitle(state->windows[i], "Resampler Test");

+ 6 - 6
test/testautomation_audio.c

@@ -443,15 +443,15 @@ static int audio_printCurrentAudioDriver(void *arg)
 /* Definition of all formats, channels, and frequencies used to test audio conversions */
 static SDL_AudioFormat g_audioFormats[] = {
     SDL_AUDIO_S8, SDL_AUDIO_U8,
-    SDL_AUDIO_S16LE, SDL_AUDIO_S16BE, SDL_AUDIO_S16,
-    SDL_AUDIO_S32LE, SDL_AUDIO_S32BE, SDL_AUDIO_S32,
-    SDL_AUDIO_F32LE, SDL_AUDIO_F32BE, SDL_AUDIO_F32
+    SDL_AUDIO_S16LE, SDL_AUDIO_S16BE,
+    SDL_AUDIO_S32LE, SDL_AUDIO_S32BE,
+    SDL_AUDIO_F32LE, SDL_AUDIO_F32BE
 };
 static const char *g_audioFormatsVerbose[] = {
     "SDL_AUDIO_S8", "SDL_AUDIO_U8",
-    "SDL_AUDIO_S16LE", "SDL_AUDIO_S16BE", "SDL_AUDIO_S16",
-    "SDL_AUDIO_S32LE", "SDL_AUDIO_S32BE", "SDL_AUDIO_S32",
-    "SDL_AUDIO_F32LE", "SDL_AUDIO_F32BE", "SDL_AUDIO_F32"
+    "SDL_AUDIO_S16LE", "SDL_AUDIO_S16BE",
+    "SDL_AUDIO_S32LE", "SDL_AUDIO_S32BE",
+    "SDL_AUDIO_F32LE", "SDL_AUDIO_F32BE"
 };
 static const int g_numAudioFormats = SDL_arraysize(g_audioFormats);
 static Uint8 g_audioChannels[] = { 1, 2, 4, 6 };