Refactored SDL_audiocvt.c

This commit is contained in:
Brick 2023-09-06 18:38:01 +01:00 committed by Sam Lantinga
parent 31229fd47f
commit a62e62f97a
14 changed files with 1212 additions and 832 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

516
src/audio/SDL_audioqueue.c Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -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.

View File

@ -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");

View File

@ -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 };