diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 05556a850f..0545d3c422 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -370,6 +370,8 @@
+
+
@@ -549,6 +551,8 @@
+
+
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 863cb5f6e6..62f79c6b1f 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -419,6 +419,12 @@
audio
+
+ audio
+
+
+ audio
+
core\windows
@@ -854,6 +860,12 @@
audio
+
+ audio
+
+
+ audio
+
audio
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj
index c6b9b05d43..93e3b3ccca 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj
+++ b/VisualC-WinRT/SDL-UWP.vcxproj
@@ -94,6 +94,8 @@
+
+
@@ -193,6 +195,8 @@
+
+
diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters
index 9e44ed1a2b..2ba518c9dd 100644
--- a/VisualC-WinRT/SDL-UWP.vcxproj.filters
+++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters
@@ -183,6 +183,12 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files
@@ -471,6 +477,12 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 2eae831c56..e355404082 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -319,6 +319,8 @@
+
+
@@ -475,6 +477,8 @@
+
+
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 0be6f82e2f..dbf4e543ee 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -410,6 +410,12 @@
audio
+
+ audio
+
+
+ audio
+
core\windows
@@ -833,6 +839,12 @@
audio
+
+ audio
+
+
+ audio
+
audio
diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c
index dc521f1349..5cad0d0ac0 100644
--- a/src/audio/SDL_audiocvt.c
+++ b/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_audioqueue.h"
+#include "SDL_audioresample.h"
-#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;
-}
-
-// !!! FIXME: This will blow up on weird processors.
#ifndef SDL_INT_MAX
-#define SDL_INT_MAX 0x7FFFFFFF
+#define SDL_INT_MAX ((int)(~0u>>1))
#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;
- }
-#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 (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);
+ 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;
}
- output_frames = GetResamplerAvailableOutputFrames(input_frames, resample_rate, resample_offset);
+ 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) {
+ // 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 = 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;
- Sint64 resample_offset = stream->resample_offset;
- SDL_AudioTrack* track;
+ void* iter = SDL_BeginAudioQueueIter(stream->queue);
- for (track = GetCurrentAudioTrack(stream->queue); track; track = track->next) {
- total += GetAudioStreamTrackAvailableFrames(stream, track, resample_offset);
- resample_offset = 0;
+ Sint64 resample_offset = stream->resample_offset;
+ Sint64 output_frames = 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);
+ UpdateAudioStreamHistoryBuffer(stream, 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);
-
- 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;
+ int total = 0;
- while (len > 0) {
- SDL_AudioTrack* track = GetCurrentAudioTrack(stream->queue);
+ 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 (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;
+ 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);
diff --git a/src/audio/SDL_audioqueue.c b/src/audio/SDL_audioqueue.c
new file mode 100644
index 0000000000..c27bb4094a
--- /dev/null
+++ b/src/audio/SDL_audioqueue.c
@@ -0,0 +1,516 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ 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;
+ }
+}
diff --git a/src/audio/SDL_audioqueue.h b/src/audio/SDL_audioqueue.h
new file mode 100644
index 0000000000..76012e91db
--- /dev/null
+++ b/src/audio/SDL_audioqueue.h
@@ -0,0 +1,77 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ 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_
diff --git a/src/audio/SDL_audioresample.c b/src/audio/SDL_audioresample.c
new file mode 100644
index 0000000000..b4531ecb5c
--- /dev/null
+++ b/src/audio/SDL_audioresample.c
@@ -0,0 +1,332 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ 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);
+}
diff --git a/src/audio/SDL_audioresample.h b/src/audio/SDL_audioresample.h
new file mode 100644
index 0000000000..dc781f18e0
--- /dev/null
+++ b/src/audio/SDL_audioresample.h
@@ -0,0 +1,43 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2023 Sam Lantinga
+
+ 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_
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 6649000ec0..52236dfd05 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/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.
diff --git a/test/testaudiostreamdynamicresample.c b/test/testaudiostreamdynamicresample.c
index 7e9e09211b..8acd02e0b5 100644
--- a/test/testaudiostreamdynamicresample.c
+++ b/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");
diff --git a/test/testautomation_audio.c b/test/testautomation_audio.c
index fe1a93ab04..829962fe87 100644
--- a/test/testautomation_audio.c
+++ b/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 };