From a62e62f97a5595dc27e1eaf69dfa63e2f8855e13 Mon Sep 17 00:00:00 2001 From: Brick <6098371+0x1F9F1@users.noreply.github.com> Date: Wed, 6 Sep 2023 18:38:01 +0100 Subject: [PATCH] Refactored SDL_audiocvt.c --- VisualC-GDK/SDL/SDL.vcxproj | 4 + VisualC-GDK/SDL/SDL.vcxproj.filters | 12 + VisualC-WinRT/SDL-UWP.vcxproj | 4 + VisualC-WinRT/SDL-UWP.vcxproj.filters | 12 + VisualC/SDL/SDL.vcxproj | 4 + VisualC/SDL/SDL.vcxproj.filters | 12 + src/audio/SDL_audiocvt.c | 980 +++++--------------------- src/audio/SDL_audioqueue.c | 516 ++++++++++++++ src/audio/SDL_audioqueue.h | 77 ++ src/audio/SDL_audioresample.c | 332 +++++++++ src/audio/SDL_audioresample.h | 43 ++ src/audio/SDL_sysaudio.h | 4 +- test/testaudiostreamdynamicresample.c | 32 +- test/testautomation_audio.c | 12 +- 14 files changed, 1212 insertions(+), 832 deletions(-) create mode 100644 src/audio/SDL_audioqueue.c create mode 100644 src/audio/SDL_audioqueue.h create mode 100644 src/audio/SDL_audioresample.c create mode 100644 src/audio/SDL_audioresample.h diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 05556a850..0545d3c42 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 863cb5f6e..62f79c6b1 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 c6b9b05d4..93e3b3ccc 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 9e44ed1a2..2ba518c9d 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 2eae831c5..e35540408 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 0be6f82e2..dbf4e543e 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 dc521f134..5cad0d0ac 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 000000000..c27bb4094 --- /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 000000000..76012e91d --- /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 000000000..b4531ecb5 --- /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 000000000..dc781f18e --- /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 6649000ec..52236dfd0 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 7e9e09211..8acd02e0b 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 fe1a93ab0..829962fe8 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 };