فهرست منبع

Track the formats of data in an SDL_AudioStream

Brick 1 سال پیش
والد
کامیت
43c3c5736d
2فایلهای تغییر یافته به همراه706 افزوده شده و 257 حذف شده
  1. 695 237
      src/audio/SDL_audiocvt.c
  2. 11 20
      src/audio/SDL_sysaudio.h

+ 695 - 237
src/audio/SDL_audiocvt.c

@@ -24,14 +24,431 @@
 
 #include "SDL_audio_c.h"
 
-#include "../SDL_dataqueue.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)
@@ -43,8 +460,9 @@ 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)
+    if (src_rate == dst_rate) {
         return 0;
+    }
 
     Sint64 sample_rate = ((Sint64)src_rate << 32) / (Sint64)dst_rate;
     SDL_assert(sample_rate > 0);
@@ -68,13 +486,13 @@ static int GetResamplerNeededInputFrames(const int output_frames, const Sint64 r
 
 static int GetResamplerPaddingFrames(const Sint64 resample_rate)
 {
+    // This must always be <= GetHistoryBufferSampleFrames()
+
     return resample_rate ? RESAMPLER_MAX_PADDING_FRAMES : 0;
 }
 
-static int GetHistoryBufferSampleFrames(const int required_resampler_frames)
+static int GetHistoryBufferSampleFrames()
 {
-    SDL_assert(required_resampler_frames <= RESAMPLER_MAX_PADDING_FRAMES);
-
     // Even if we aren't currently resampling, make sure to keep enough history in case we need to later.
     return RESAMPLER_MAX_PADDING_FRAMES;
 }
@@ -272,7 +690,7 @@ void SDL_SetupAudioResampler()
 }
 
 static void ResampleAudio(const int chans, const float *inbuf, const int inframes, float *outbuf, const int outframes,
-                         const Sint64 resample_rate, Sint64* resample_offset)
+                          const Sint64 resample_rate, Sint64* resample_offset)
 {
     SDL_assert(resample_rate > 0);
     float *dst = outbuf;
@@ -623,7 +1041,7 @@ void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, i
 }
 
 // Calculate the largest frame size needed to convert between the two formats.
-static int CalculateMaxSampleFrameSize(SDL_AudioFormat src_format, int src_channels, SDL_AudioFormat dst_format, int dst_channels)
+static int CalculateMaxFrameSize(SDL_AudioFormat src_format, int src_channels, SDL_AudioFormat dst_format, int dst_channels)
 {
     const int src_format_size = SDL_AUDIO_BITSIZE(src_format) / 8;
     const int dst_format_size = SDL_AUDIO_BITSIZE(dst_format) / 8;
@@ -633,116 +1051,42 @@ static int CalculateMaxSampleFrameSize(SDL_AudioFormat src_format, int src_chann
     return max_format_size * max_channels;
 }
 
-// this assumes you're holding the stream's lock (or are still creating the stream).
-static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)
+static int GetAudioSpecFrameSize(const SDL_AudioSpec* spec)
 {
-    const SDL_AudioFormat src_format = src_spec->format;
-    const int src_channels = src_spec->channels;
-    const int src_rate = src_spec->freq;
+    return (SDL_AUDIO_BITSIZE(spec->format) / 8) * spec->channels;
+}
 
-    const SDL_AudioFormat dst_format = dst_spec->format;
-    const int dst_channels = dst_spec->channels;
-    const int dst_rate = dst_spec->freq;
+static Sint64 GetStreamResampleRate(SDL_AudioStream* stream, int src_freq)
+{
+    src_freq = (int)((float)src_freq * stream->speed);
 
-    const int src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
-    const int dst_sample_frame_size = (SDL_AUDIO_BITSIZE(dst_format) / 8) * dst_channels;
+    return GetResampleRate(src_freq, stream->dst_spec.freq);
+}
 
-    const int prev_history_buffer_frames = stream->history_buffer_frames;
-    const Sint64 resample_rate = GetResampleRate(src_rate, dst_rate);
-    const int resampler_padding_frames = GetResamplerPaddingFrames(resample_rate);
-    const int history_buffer_frames = GetHistoryBufferSampleFrames(resampler_padding_frames);
-    const int history_buffer_frame_size = CalculateMaxSampleFrameSize(stream->src_spec.format, stream->src_spec.channels, src_format, src_channels);
-    const size_t history_buffer_allocation = history_buffer_frames * history_buffer_frame_size;
+static int ResetHistoryBuffer(SDL_AudioStream *stream, const SDL_AudioSpec *spec)
+{
+    const size_t history_buffer_allocation = GetHistoryBufferSampleFrames() * GetAudioSpecFrameSize(spec);
     Uint8 *history_buffer = stream->history_buffer;
 
-    // do all the things that can fail upfront, so we can just return an error without changing the stream if anything goes wrong.
-
-    // set up for (possibly new) conversions
-
-    // grow the history buffer if necessary; often times this won't be, as it already buffers more than immediately necessary in case of a dramatic downsample.
     if (stream->history_buffer_allocation < history_buffer_allocation) {
         history_buffer = (Uint8 *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), history_buffer_allocation);
         if (!history_buffer) {
             return SDL_OutOfMemory();
         }
-    }
-
-    // okay, we've done all the things that can fail, now we can change stream state.
-
-    if (stream->history_buffer) {
-        if (history_buffer_frames <= prev_history_buffer_frames) {
-            ConvertAudio(history_buffer_frames, stream->history_buffer,
-                stream->src_spec.format, stream->src_spec.channels,
-                history_buffer,
-                src_format, src_channels, NULL);
-        } else {
-            ConvertAudio(prev_history_buffer_frames, stream->history_buffer,
-                stream->src_spec.format, stream->src_spec.channels,
-                history_buffer + ((history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size),
-                src_format, src_channels, NULL);
-            SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(src_format), (history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size);  // silence oldest history samples.
-        }
-    } else if (history_buffer != NULL) {
-        SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(src_format), history_buffer_allocation);
-    }
-
-    if (history_buffer != stream->history_buffer) {
         SDL_aligned_free(stream->history_buffer);
         stream->history_buffer = history_buffer;
         stream->history_buffer_allocation = history_buffer_allocation;
     }
 
-    stream->resampler_padding_frames = resampler_padding_frames;
-    stream->history_buffer_frames = history_buffer_frames;
-    stream->src_sample_frame_size = src_sample_frame_size;
-    stream->dst_sample_frame_size = dst_sample_frame_size;
-    stream->max_sample_frame_size = CalculateMaxSampleFrameSize(src_format, src_channels, dst_format, dst_channels);
-    stream->resample_rate = resample_rate;
-
-    if (src_spec != &stream->src_spec) {
-        SDL_memcpy(&stream->src_spec, src_spec, sizeof (SDL_AudioSpec));
-    }
-
-    if (dst_spec != &stream->dst_spec) {
-        SDL_memcpy(&stream->dst_spec, dst_spec, sizeof (SDL_AudioSpec));
-    }
+    SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(spec->format), history_buffer_allocation);
 
     return 0;
 }
 
 SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)
 {
-    // !!! FIXME: fail if audio isn't initialized
-
-    if (!src_spec) {
-        SDL_InvalidParamError("src_spec");
-        return NULL;
-    } else if (!dst_spec) {
-        SDL_InvalidParamError("dst_spec");
-        return NULL;
-    } else if (!SDL_IsSupportedChannelCount(src_spec->channels)) {
-        SDL_InvalidParamError("src_spec->channels");
-        return NULL;
-    } else if (!SDL_IsSupportedChannelCount(dst_spec->channels)) {
-        SDL_InvalidParamError("dst_spec->channels");
-        return NULL;
-    } else if (src_spec->freq <= 0) {
-        SDL_InvalidParamError("src_spec->freq");
-        return NULL;
-    } else if (dst_spec->freq <= 0) {
-        SDL_InvalidParamError("dst_spec->freq");
-        return NULL;
-    } else if (src_spec->freq >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) {
-        SDL_SetError("Source rate is too high");
-        return NULL;
-    } else if (dst_spec->freq >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) {
-        SDL_SetError("Destination rate is too high");
-        return NULL;
-    } else if (!SDL_IsSupportedAudioFormat(src_spec->format)) {
-        SDL_InvalidParamError("src_spec->format");
-        return NULL;
-    } else if (!SDL_IsSupportedAudioFormat(dst_spec->format)) {
-        SDL_InvalidParamError("dst_spec->format");
+    if (!SDL_WasInit(SDL_INIT_AUDIO)) {
+        SDL_SetError("Audio subsystem is not initialized");
         return NULL;
     }
 
@@ -752,24 +1096,23 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_
         return NULL;
     }
 
-    const int packetlen = 4096; // !!! FIXME: good enough for now.
-    retval->queue = SDL_CreateDataQueue(packetlen, (size_t)packetlen * 2);
-    if (!retval->queue) {
-        SDL_DestroyAudioStream(retval);
-        return NULL; // SDL_CreateDataQueue should have called SDL_SetError.
-    }
-
-    retval->lock = SDL_GetDataQueueMutex(retval->queue);
-    SDL_assert(retval->lock != NULL);
+    retval->speed = 1.0f;
+    retval->queue = CreateAudioQueue(4096);
+    retval->track_changed = SDL_TRUE;
 
-    // Make sure we've chosen audio conversion functions (SIMD, scalar, etc.)
-    SDL_ChooseAudioConverters();  // !!! FIXME: let's do this during SDL_Init
-    SDL_SetupAudioResampler();
+    if (retval->queue == NULL) {
+        SDL_free(retval);
+        return NULL;
+    }
 
-    retval->packetlen = packetlen;
-    SDL_memcpy(&retval->src_spec, src_spec, sizeof (SDL_AudioSpec));
+    retval->lock = SDL_CreateMutex();
+    if (retval->lock == NULL) {
+        SDL_free(retval->queue);
+        SDL_free(retval);
+        return NULL;
+    }
 
-    if (SetAudioStreamFormat(retval, src_spec, dst_spec) == -1) {
+    if (SDL_SetAudioStreamFormat(retval, src_spec, dst_spec) == -1) {
         SDL_DestroyAudioStream(retval);
         return NULL;
     }
@@ -816,14 +1159,22 @@ int SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, S
     if (!stream) {
         return SDL_InvalidParamError("stream");
     }
+
     SDL_LockMutex(stream->lock);
     if (src_spec) {
-        SDL_memcpy(src_spec, &stream->src_spec, sizeof (SDL_AudioSpec));
+        SDL_copyp(src_spec, &stream->src_spec);
     }
     if (dst_spec) {
-        SDL_memcpy(dst_spec, &stream->dst_spec, sizeof (SDL_AudioSpec));
+        SDL_copyp(dst_spec, &stream->dst_spec);
     }
     SDL_UnlockMutex(stream->lock);
+
+    if (src_spec && src_spec->format == 0) {
+        return SDL_SetError("Stream has no source format");
+    } else if (dst_spec && dst_spec->format == 0) {
+        return SDL_SetError("Stream has no destination format");
+    }
+
     return 0;
 }
 
@@ -833,6 +1184,10 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s
         return SDL_InvalidParamError("stream");
     }
 
+    // Picked mostly arbitrarily.
+    static const int min_freq = 4000;
+    static const int max_freq = 192000;
+
     if (src_spec) {
         if (!SDL_IsSupportedAudioFormat(src_spec->format)) {
             return SDL_InvalidParamError("src_spec->format");
@@ -840,7 +1195,9 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s
             return SDL_InvalidParamError("src_spec->channels");
         } else if (src_spec->freq <= 0) {
             return SDL_InvalidParamError("src_spec->freq");
-        } else if (src_spec->freq >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) {
+        } else if (src_spec->freq < min_freq) {
+            return SDL_SetError("Source rate is too low");
+        } else if (src_spec->freq > max_freq) {
             return SDL_SetError("Source rate is too high");
         }
     }
@@ -852,16 +1209,43 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s
             return SDL_InvalidParamError("dst_spec->channels");
         } else if (dst_spec->freq <= 0) {
             return SDL_InvalidParamError("dst_spec->freq");
-        } else if (dst_spec->freq >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) {
+        } else if (dst_spec->freq < min_freq) {
+            return SDL_SetError("Destination rate is too low");
+        } else if (dst_spec->freq > max_freq) {
             return SDL_SetError("Destination rate is too high");
         }
     }
 
     SDL_LockMutex(stream->lock);
-    const int retval = SetAudioStreamFormat(stream, src_spec ? src_spec : &stream->src_spec, dst_spec ? dst_spec : &stream->dst_spec);
+
+    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);
+        }
+    }
+
+    if (dst_spec) {
+        SDL_copyp(&stream->dst_spec, dst_spec);
+    }
+
     SDL_UnlockMutex(stream->lock);
 
-    return retval;
+    return 0;
+}
+
+static int CheckAudioStreamIsFullySetup(SDL_AudioStream *stream)
+{
+    if (stream->src_spec.format == 0) {
+        return SDL_SetError("Stream has no source format");
+    } else if (stream->dst_spec.format == 0) {
+        return SDL_SetError("Stream has no destination format");
+    }
+
+    return 0;
 }
 
 int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
@@ -874,24 +1258,52 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
         return SDL_InvalidParamError("stream");
     } else if (buf == NULL) {
         return SDL_InvalidParamError("buf");
+    } else if (len < 0) {
+        return SDL_InvalidParamError("len");
     } else if (len == 0) {
         return 0; // nothing to do.
     }
 
     SDL_LockMutex(stream->lock);
 
-    const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0;
+    if (CheckAudioStreamIsFullySetup(stream) != 0) {
+        SDL_UnlockMutex(stream->lock);
+        return -1;
+    }
 
-    if ((len % stream->src_sample_frame_size) != 0) {
+    if ((len % GetAudioSpecFrameSize(&stream->src_spec)) != 0) {
         SDL_UnlockMutex(stream->lock);
         return SDL_SetError("Can't add partial sample frames");
     }
 
+    SDL_AudioChunk* chunks = 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;
+
+    if (len >= large_input_thresh) {
+        size_t chunk_size = stream->queue->chunk_size;
+
+        SDL_UnlockMutex(stream->lock);
+
+        chunks = CreateAudioChunks(chunk_size, (const Uint8*) buf, len);
+
+        if (chunks == NULL) {
+            return -1;
+        }
+
+        SDL_LockMutex(stream->lock);
+    }
+
+    const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0;
+
     // just queue the data, we convert/resample when dequeueing.
-    const int retval = SDL_WriteToDataQueue(stream->queue, buf, len);
-    stream->flushed = SDL_FALSE;
+    const int retval = chunks
+        ? WriteChunksToAudioQueue(stream->queue, &stream->src_spec, chunks, len)
+        : WriteToAudioQueue(stream->queue, &stream->src_spec, (const Uint8*) buf, len);
 
-    if (stream->put_callback) {
+    if ((retval == 0) && stream->put_callback) {
         const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available;
         if (newavail > 0) {   // don't call the callback if we can't actually offer new data (still filling future buffer, only added 1 frame but downsampling needs more to produce new sound, etc).
             stream->put_callback(stream->put_callback_userdata, stream, newavail);
@@ -911,7 +1323,7 @@ int SDL_FlushAudioStream(SDL_AudioStream *stream)
     }
 
     SDL_LockMutex(stream->lock);
-    stream->flushed = SDL_TRUE;
+    FlushAudioQueue(stream->queue);
     SDL_UnlockMutex(stream->lock);
 
     return 0;
@@ -937,11 +1349,14 @@ static Uint8 *EnsureStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen)
     return ptr;
 }
 
-static void UpdateStreamHistoryBuffer(SDL_AudioStream* stream, Uint8* input_buffer, int input_bytes, Uint8* left_padding, int padding_bytes)
+static void UpdateStreamHistoryBuffer(SDL_AudioStream* stream, const SDL_AudioSpec* spec,
+    Uint8* input_buffer, int input_bytes, Uint8* left_padding, int padding_bytes)
 {
+    const int history_buffer_frames = GetHistoryBufferSampleFrames();
+
     // Even if we aren't currently resampling, we always need to update the history buffer
     Uint8 *history_buffer = stream->history_buffer;
-    int history_bytes = stream->history_buffer_frames * stream->src_sample_frame_size;
+    int history_bytes = history_buffer_frames * GetAudioSpecFrameSize(spec);
 
     if (left_padding != NULL) {
         // Fill in the left padding using the history buffer
@@ -959,49 +1374,66 @@ static void UpdateStreamHistoryBuffer(SDL_AudioStream* stream, Uint8* input_buff
     }
 }
 
-// You must hold stream->lock and validate your parameters before calling this!
-static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int len)
+static size_t GetAudioStreamTrackAvailableFrames(SDL_AudioStream* stream, SDL_AudioTrack* track, Sint64 resample_offset)
 {
-    const SDL_AudioFormat src_format = stream->src_spec.format;
-    const int src_channels = stream->src_spec.channels;
-    const int src_sample_frame_size = stream->src_sample_frame_size;
-
-    const SDL_AudioFormat dst_format = stream->dst_spec.format;
-    const int dst_channels = stream->dst_spec.channels;
-    const int dst_sample_frame_size = stream->dst_sample_frame_size;
+    size_t frames = track->queued_bytes / GetAudioSpecFrameSize(&track->spec);
 
-    const int max_sample_frame_size = stream->max_sample_frame_size;
+    Sint64 resample_rate = GetStreamResampleRate(stream, track->spec.freq);
 
-    const int resampler_padding_frames = stream->resampler_padding_frames;
-    const Sint64 resample_rate = stream->resample_rate;
+    if (resample_rate) {
+        if (!track->flushed) {
+            SDL_assert(track->next == NULL);
+            const int history_buffer_frames = GetHistoryBufferSampleFrames();
+            frames -= SDL_min(frames, (size_t)history_buffer_frames);
+        }
 
-#if DEBUG_AUDIOSTREAM
-    SDL_Log("AUDIOSTREAM: asking for an output chunk of %d bytes.", len);
-#endif
+        frames = GetResamplerAvailableOutputFrames(frames, resample_rate, resample_offset);
+    }
 
-    SDL_assert((len % dst_sample_frame_size) == 0);
+    return frames;
+}
 
-    // Clamp the output length to the maximum currently available.
-    // The rest of this function assumes enough input data is available.
-    const int max_available = SDL_GetAudioStreamAvailable(stream);
+static size_t GetAudioStreamAvailableFrames(SDL_AudioStream *stream)
+{
+    size_t total = 0;
+    Sint64 resample_offset = stream->resample_offset;
+    SDL_AudioTrack* track;
 
-    if (len > max_available) {
-        len = max_available;
+    for (track = GetCurrentAudioTrack(stream->queue); track; track = track->next) {
+        total += GetAudioStreamTrackAvailableFrames(stream, track, resample_offset);
+        resample_offset = 0;
     }
 
-    int output_frames = len / dst_sample_frame_size;
+    return total;
+}
 
-    if (output_frames == 0) {
-        return 0;  // nothing to do.
-    }
+// 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* dst_spec = &stream->dst_spec;
+
+    const SDL_AudioFormat src_format = src_spec->format;
+    const int src_channels = src_spec->channels;
+    const int src_frame_size = GetAudioSpecFrameSize(src_spec);
+
+    const SDL_AudioFormat dst_format = dst_spec->format;
+    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);
 
-    int input_frames = output_frames;
-    const int output_bytes = output_frames * dst_sample_frame_size;
+#if DEBUG_AUDIOSTREAM
+    SDL_Log("AUDIOSTREAM: asking for %d frames.", output_frames);
+#endif
+
+    SDL_assert(output_frames > 0);
 
     // Not resampling? It's an easy conversion (and maybe not even that!)
     if (resample_rate == 0) {
-        SDL_assert(input_frames == output_frames);
-
         Uint8* input_buffer = NULL;
 
         // If no conversion is happening, read straight into the output buffer.
@@ -1010,26 +1442,25 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
         if ((src_format == dst_format) && (src_channels == dst_channels)) {
             input_buffer = buf;
         } else {
-            input_buffer = EnsureStreamWorkBufferSize(stream, input_frames * max_sample_frame_size);
+            input_buffer = EnsureStreamWorkBufferSize(stream, output_frames * max_frame_size);
 
             if (!input_buffer) {
                 return -1;
             }
         }
 
-        const int input_bytes = input_frames * src_sample_frame_size;
-        const int bytes_read = (int) SDL_ReadFromDataQueue(stream->queue, input_buffer, input_bytes);
-        SDL_assert(bytes_read == input_bytes);
+        const int input_bytes = output_frames * src_frame_size;
+        ReadFromAudioQueue(stream->queue, input_buffer, input_bytes);
 
         // Even if we aren't currently resampling, we always need to update the history buffer
-        UpdateStreamHistoryBuffer(stream, input_buffer, input_bytes, NULL, 0);
+        UpdateStreamHistoryBuffer(stream, src_spec, input_buffer, input_bytes, NULL, 0);
 
         // Convert the data, if necessary
         if (buf != input_buffer) {
-            ConvertAudio(input_frames, input_buffer, src_format, src_channels, buf, dst_format, dst_channels, input_buffer);
+            ConvertAudio(output_frames, input_buffer, src_format, src_channels, buf, dst_format, dst_channels, input_buffer);
         }
 
-        return output_bytes;
+        return 0;
     }
 
     // Time to do some resampling!
@@ -1037,8 +1468,10 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
     // 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.
-    input_frames = GetResamplerNeededInputFrames(output_frames, resample_rate, stream->resample_offset);
-    const int input_bytes = input_frames * src_sample_frame_size;
+    const int input_frames = GetResamplerNeededInputFrames(output_frames, resample_rate, stream->resample_offset);
+    const int input_bytes = input_frames * src_frame_size;
+
+    const int resampler_padding_frames = 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
@@ -1059,7 +1492,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
     //
     // Note, ConvertAudio requires (num_frames * max_sample_frame_size) of scratch space
     const int work_buffer_frames = input_frames + (resampler_padding_frames * 2);
-    int work_buffer_capacity = work_buffer_frames * max_sample_frame_size;
+    int work_buffer_capacity = work_buffer_frames * max_frame_size;
     int resample_buffer_offset = -1;
 
     // Check if we can resample directly into the output buffer.
@@ -1067,7 +1500,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
     // Some other formats may fit directly into the output buffer, but i'd rather process data in a SIMD-aligned buffer.
     if ((dst_format != SDL_AUDIO_F32SYS) || (dst_channels != resample_channels)) {
         // Allocate space for converting the resampled output to the destination format
-        int resample_convert_bytes = output_frames * max_sample_frame_size;
+        int resample_convert_bytes = output_frames * max_frame_size;
         work_buffer_capacity = SDL_max(work_buffer_capacity, resample_convert_bytes);
 
         // SIMD-align the buffer
@@ -1087,7 +1520,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
         return -1;
     }
 
-    const int padding_bytes = resampler_padding_frames * src_sample_frame_size;
+    const int padding_bytes = resampler_padding_frames * src_frame_size;
 
     Uint8* work_buffer_tail = work_buffer;
 
@@ -1104,21 +1537,18 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
     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.
-    if (input_frames > 0) {
-        int bytes_read = (int) SDL_ReadFromDataQueue(stream->queue, input_buffer, input_bytes);
-        SDL_assert(bytes_read == input_bytes);
-    }
+    ReadFromAudioQueue(stream->queue, input_buffer, (size_t) input_bytes);
 
     // Update the history buffer and fill in the left padding
-    UpdateStreamHistoryBuffer(stream, input_buffer, input_bytes, left_padding, padding_bytes);
+    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) SDL_PeekIntoDataQueue(stream->queue, right_padding, padding_bytes);
+    const int right_padding_bytes = (int) PeekIntoAudioQueue(stream->queue, right_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(stream->flushed);
+        SDL_assert(track->flushed);
         SDL_memset(right_padding + right_padding_bytes, SDL_GetSilenceValueForFormat(src_format), padding_bytes - right_padding_bytes);
     }
 
@@ -1128,8 +1558,8 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
     ConvertAudio(work_buffer_frames, work_buffer, src_format, src_channels, work_buffer, SDL_AUDIO_F32SYS, resample_channels, NULL);
 
     // Update the work_buffer pointers based on the new frame size
-    input_buffer = work_buffer + ((input_buffer - work_buffer) / src_sample_frame_size * resample_frame_size);
-    work_buffer_tail = work_buffer + ((work_buffer_tail - work_buffer) / src_sample_frame_size * resample_frame_size);
+    input_buffer = work_buffer + ((input_buffer - work_buffer) / src_frame_size * resample_frame_size);
+    work_buffer_tail = work_buffer + ((work_buffer_tail - work_buffer) / src_frame_size * resample_frame_size);
     SDL_assert((work_buffer_tail - work_buffer) <= work_buffer_capacity);
 
     // Decide where the resampled output goes
@@ -1145,7 +1575,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
         ConvertAudio(output_frames, resample_buffer, SDL_AUDIO_F32SYS, resample_channels, buf, dst_format, dst_channels, work_buffer);
     }
 
-    return output_bytes;
+    return 0;
 }
 
 // get converted/resampled data from the stream
@@ -1169,52 +1599,87 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len)
 
     SDL_LockMutex(stream->lock);
 
-    len -= len % stream->dst_sample_frame_size;  // chop off any fractional sample frame.
+    if (CheckAudioStreamIsFullySetup(stream) != 0) {
+        SDL_UnlockMutex(stream->lock);
+        return -1;
+    }
+
+    const int dst_frame_size = GetAudioSpecFrameSize(&stream->dst_spec);
+
+    len -= len % dst_frame_size;  // chop off any fractional sample frame.
 
     // give the callback a chance to fill in more stream data if it wants.
     if (stream->get_callback) {
-        int approx_request = len / stream->dst_sample_frame_size;  // start with sample frames desired
-        if (stream->resample_rate) {
-            approx_request = GetResamplerNeededInputFrames(approx_request, stream->resample_rate, stream->resample_offset);
+        int approx_request = len / dst_frame_size;  // start with sample frames desired
 
-            if (!stream->flushed) {  // do we need to fill the future buffer to accommodate this, too?
-                approx_request += stream->resampler_padding_frames;
-            }
+        const int available_frames = (int) GetAudioStreamAvailableFrames(stream);
+        approx_request -= SDL_min(available_frames, approx_request);
+
+        const Sint64 resample_rate = GetStreamResampleRate(stream, stream->src_spec.freq);
+
+        // FIXME: Is this correct?
+        if (resample_rate) {
+            approx_request = GetResamplerNeededInputFrames(approx_request, resample_rate, 0);
         }
 
-        approx_request *= stream->src_sample_frame_size;  // convert sample frames to bytes.
-        const int already_have = SDL_GetAudioStreamAvailable(stream);
-        approx_request -= SDL_min(approx_request, already_have);  // we definitely have this much output already packed in.
+        approx_request *= GetAudioSpecFrameSize(&stream->src_spec);  // convert sample frames to bytes.
+
         if (approx_request > 0) {  // don't call the callback if we can satisfy this request with existing data.
             stream->get_callback(stream->get_callback_userdata, stream, approx_request);
         }
     }
 
-    const int chunk_size = stream->dst_sample_frame_size * 4096;
+    const int chunk_size = 4096;
 
     int retval = 0;
-    while (len > 0) { // didn't ask for a whole sample frame, nothing to do
-        const int rc = GetAudioStreamDataInternal(stream, buf, SDL_min(len, chunk_size));
 
-        if (rc == -1) {
-            #if DEBUG_AUDIOSTREAM
-            SDL_Log("AUDIOSTREAM: output chunk ended up producing an error!");
-            #endif
-            if (retval == 0) {
-                retval = -1;
+    while (len > 0) {
+        SDL_AudioTrack* track = GetCurrentAudioTrack(stream->queue);
+
+        if (track == NULL) {
+            break;
+        }
+
+        const int max_frames = (int) GetAudioStreamTrackAvailableFrames(stream, track, stream->resample_offset);
+
+        if (max_frames == 0) {
+            if (track->flushed) {
+                PopCurrentAudioTrack(stream->queue);
+                stream->track_changed = SDL_TRUE;
+                stream->resample_offset = 0;
+                continue;
             }
+
             break;
-        } else {
-            #if DEBUG_AUDIOSTREAM
-            SDL_Log("AUDIOSTREAM: output chunk ended up being %d bytes.", rc);
-            #endif
-            buf += rc;
-            len -= rc;
-            retval += rc;
-            if (rc < chunk_size) {
+        }
+
+        if (stream->track_changed) {
+            if (ResetHistoryBuffer(stream, &track->spec) != 0) {
+                retval = -1;
                 break;
             }
+
+            stream->track_changed = SDL_FALSE;
+        }
+
+        // Clamp the output length to the maximum currently available.
+        // GetAudioStreamDataInternal assumes enough input data is available.
+        int output_frames = len / dst_frame_size;
+        output_frames = SDL_min(output_frames, chunk_size);
+        output_frames = SDL_min(output_frames, max_frames);
+
+        if (GetAudioStreamDataInternal(stream, buf, output_frames) != 0) {
+            if (retval == 0) {
+                retval = -1;
+            }
+            break;
         }
+
+        const int output_bytes = output_frames * dst_frame_size;
+
+        buf += output_bytes;
+        len -= output_bytes;
+        retval += output_bytes;
     }
 
     SDL_UnlockMutex(stream->lock);
@@ -1235,24 +1700,15 @@ int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)
 
     SDL_LockMutex(stream->lock);
 
-    // total bytes available in source format in data queue
-    size_t count = SDL_GetDataQueueSize(stream->queue);
-
-    // total sample frames available in data queue
-    count /= stream->src_sample_frame_size;
-
-    // sample frames after resampling
-    if (stream->resample_rate) {
-        if (!stream->flushed) {
-            // have to save some samples for padding. They aren't available until more data is added or the stream is flushed.
-            count = (count < ((size_t) stream->resampler_padding_frames)) ? 0 : (count - stream->resampler_padding_frames);
-        }
-
-        count = GetResamplerAvailableOutputFrames(count, stream->resample_rate, stream->resample_offset);
+    if (CheckAudioStreamIsFullySetup(stream) != 0) {
+        SDL_UnlockMutex(stream->lock);
+        return 0;
     }
 
+    size_t count = GetAudioStreamAvailableFrames(stream);
+
     // convert from sample frames to bytes in destination format.
-    count *= stream->dst_sample_frame_size;
+    count *= GetAudioSpecFrameSize(&stream->dst_spec);
 
     SDL_UnlockMutex(stream->lock);
 
@@ -1268,33 +1724,35 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream)
     }
 
     SDL_LockMutex(stream->lock);
-    SDL_ClearDataQueue(stream->queue, (size_t)stream->packetlen * 2);
-    if (stream->history_buffer != NULL) {
-        SDL_memset(stream->history_buffer, SDL_GetSilenceValueForFormat(stream->src_spec.format), stream->history_buffer_allocation);
-    }
+
+    ClearAudioQueue(stream->queue);
+    stream->track_changed = SDL_TRUE;
     stream->resample_offset = 0;
-    stream->flushed = SDL_FALSE;
+
     SDL_UnlockMutex(stream->lock);
     return 0;
 }
 
 void SDL_DestroyAudioStream(SDL_AudioStream *stream)
 {
-    if (stream) {
-        const SDL_bool simplified = stream->simplified;
-        if (simplified) {
-            SDL_assert(stream->bound_device->simplified);
-            SDL_CloseAudioDevice(stream->bound_device->instance_id);  // this will unbind the stream.
-        } else {
-            SDL_UnbindAudioStream(stream);
-        }
+    if (stream == NULL) {
+        return;
+    }
 
-        // do not destroy stream->lock! it's a copy of `stream->queue`'s mutex, so destroying the queue will handle it.
-        SDL_DestroyDataQueue(stream->queue);
-        SDL_aligned_free(stream->work_buffer);
-        SDL_aligned_free(stream->history_buffer);
-        SDL_free(stream);
+    const SDL_bool simplified = stream->simplified;
+    if (simplified) {
+        SDL_assert(stream->bound_device->simplified);
+        SDL_CloseAudioDevice(stream->bound_device->instance_id);  // this will unbind the stream.
+    } else {
+        SDL_UnbindAudioStream(stream);
     }
+
+    SDL_aligned_free(stream->history_buffer);
+    SDL_aligned_free(stream->work_buffer);
+    DestroyAudioQueue(stream->queue);
+    SDL_DestroyMutex(stream->lock);
+
+    SDL_free(stream);
 }
 
 int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len,

+ 11 - 20
src/audio/SDL_sysaudio.h

@@ -24,8 +24,6 @@
 #ifndef SDL_sysaudio_h_
 #define SDL_sysaudio_h_
 
-#include "../SDL_dataqueue.h"
-
 #define DEBUG_AUDIOSTREAM 0
 #define DEBUG_AUDIO_CONVERT 0
 
@@ -159,38 +157,31 @@ typedef struct SDL_AudioDriver
     SDL_AtomicInt shutting_down;  // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
 } SDL_AudioDriver;
 
+typedef struct SDL_AudioQueue SDL_AudioQueue;
+
 struct SDL_AudioStream
 {
-    SDL_DataQueue *queue;
-    SDL_Mutex *lock;  // this is just a copy of `queue`'s mutex. We share a lock.
+    SDL_Mutex* lock;
 
     SDL_AudioStreamCallback get_callback;
     void *get_callback_userdata;
     SDL_AudioStreamCallback put_callback;
     void *put_callback_userdata;
 
-    Uint8 *work_buffer;    // used for scratch space during data conversion/resampling.
-    size_t work_buffer_allocation;
-
-    Uint8 *history_buffer;  // history for left padding and future sample rate changes.
-    size_t history_buffer_allocation;
-
-    SDL_bool flushed;
-
-    int resampler_padding_frames;
-    int history_buffer_frames;
-
     SDL_AudioSpec src_spec;
     SDL_AudioSpec dst_spec;
+    float speed;
 
-    Sint64 resample_rate;
+    SDL_AudioQueue* queue;
+
+    SDL_bool track_changed;
     Sint64 resample_offset;
 
-    int src_sample_frame_size;
-    int dst_sample_frame_size;
-    int max_sample_frame_size;
+    Uint8 *work_buffer;    // used for scratch space during data conversion/resampling.
+    size_t work_buffer_allocation;
 
-    int packetlen;
+    Uint8 *history_buffer;  // history for left padding and future sample rate changes.
+    size_t history_buffer_allocation;
 
     SDL_bool simplified;  // SDL_TRUE if created via SDL_OpenAudioDeviceStream