Refactored audio conversion to reduce copying

More of the logic has been moved into SDL_AudioQueue,
allowing data to be converted directly from the input buffer.
main
Brick 2024-04-04 19:22:29 +01:00 committed by Sam Lantinga
parent ae57b0c9d8
commit edaab8ad9f
7 changed files with 478 additions and 409 deletions

View File

@ -789,13 +789,13 @@ extern DECLSPEC float SDLCALL SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *
extern DECLSPEC int SDLCALL SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float ratio); extern DECLSPEC int SDLCALL SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float ratio);
/** /**
* Add data to be converted/resampled to the stream. * Add data to the stream.
* *
* This data must match the format/channels/samplerate specified in the latest * This data must match the format/channels/samplerate specified in the latest
* call to SDL_SetAudioStreamFormat, or the format specified when creating the * call to SDL_SetAudioStreamFormat, or the format specified when creating the
* stream if it hasn't been changed. * stream if it hasn't been changed.
* *
* Note that this call simply queues unconverted data for later. This is * Note that this call simply copies the unconverted data for later. This is
* different than SDL2, where data was converted during the Put call and the * different than SDL2, where data was converted during the Put call and the
* Get call would just dequeue the previously-converted data. * Get call would just dequeue the previously-converted data.
* *

View File

@ -308,20 +308,10 @@ static int UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSp
return 0; return 0;
} }
const size_t history_buffer_allocation = SDL_GetResamplerHistoryFrames() * SDL_AUDIO_FRAMESIZE(*spec); if (SDL_ResetAudioQueueHistory(stream->queue, SDL_GetResamplerHistoryFrames()) != 0) {
Uint8 *history_buffer = stream->history_buffer; return -1;
if (stream->history_buffer_allocation < history_buffer_allocation) {
history_buffer = (Uint8 *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), history_buffer_allocation);
if (!history_buffer) {
return -1;
}
SDL_aligned_free(stream->history_buffer);
stream->history_buffer = history_buffer;
stream->history_buffer_allocation = history_buffer_allocation;
} }
SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(spec->format), history_buffer_allocation);
SDL_copyp(&stream->input_spec, spec); SDL_copyp(&stream->input_spec, spec);
return 0; return 0;
@ -338,7 +328,7 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_
} }
retval->freq_ratio = 1.0f; retval->freq_ratio = 1.0f;
retval->queue = SDL_CreateAudioQueue(4096); retval->queue = SDL_CreateAudioQueue(8192);
if (!retval->queue) { if (!retval->queue) {
SDL_free(retval); SDL_free(retval);
@ -550,22 +540,12 @@ static int CheckAudioStreamIsFullySetup(SDL_AudioStream *stream)
return 0; return 0;
} }
int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) static int PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata)
{ {
#if DEBUG_AUDIOSTREAM #if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: wants to put %d bytes", len); SDL_Log("AUDIOSTREAM: wants to put %d bytes", len);
#endif #endif
if (!stream) {
return SDL_InvalidParamError("stream");
} else if (!buf) {
return SDL_InvalidParamError("buf");
} else if (len < 0) {
return SDL_InvalidParamError("len");
} else if (len == 0) {
return 0; // nothing to do.
}
SDL_LockMutex(stream->lock); SDL_LockMutex(stream->lock);
if (CheckAudioStreamIsFullySetup(stream) != 0) { if (CheckAudioStreamIsFullySetup(stream) != 0) {
@ -580,24 +560,13 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
SDL_AudioTrack* track = NULL; SDL_AudioTrack* track = NULL;
// When copying in large amounts of data, try and do as much work as possible if (callback) {
// outside of the stream lock, otherwise the output device is likely to be starved. track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, (Uint8 *)buf, len, len, callback, userdata);
const int large_input_thresh = 1024 * 1024;
if (len >= large_input_thresh) {
SDL_AudioSpec src_spec;
SDL_copyp(&src_spec, &stream->src_spec);
SDL_UnlockMutex(stream->lock);
size_t chunk_size = SDL_GetAudioQueueChunkSize(stream->queue);
track = SDL_CreateChunkedAudioTrack(&src_spec, (const Uint8 *)buf, len, chunk_size);
if (!track) { if (!track) {
SDL_UnlockMutex(stream->lock);
return -1; return -1;
} }
SDL_LockMutex(stream->lock);
} }
const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0; const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0;
@ -611,7 +580,6 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
} }
if (retval == 0) { if (retval == 0) {
stream->total_bytes_queued += len;
if (stream->put_callback) { if (stream->put_callback) {
const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available; const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available;
stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail); stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail);
@ -623,6 +591,49 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
return retval; return retval;
} }
static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, int len)
{
SDL_free((void*) buf);
}
int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
{
if (!stream) {
return SDL_InvalidParamError("stream");
} else if (!buf) {
return SDL_InvalidParamError("buf");
} else if (len < 0) {
return SDL_InvalidParamError("len");
} else if (len == 0) {
return 0; // nothing to do.
}
// When copying in large amounts of data, try and do as much work as possible
// outside of the stream lock, otherwise the output device is likely to be starved.
const int large_input_thresh = 64 * 1024;
if (len >= large_input_thresh) {
void* data = SDL_malloc(len);
if (!data) {
return -1;
}
SDL_memcpy(data, buf, len);
buf = data;
int ret = PutAudioStreamBuffer(stream, buf, len, FreeAllocatedAudioBuffer, NULL);
if (ret < 0) {
SDL_free(data);
}
return ret;
}
return PutAudioStreamBuffer(stream, buf, len, NULL, NULL);
}
int SDL_FlushAudioStream(SDL_AudioStream *stream) int SDL_FlushAudioStream(SDL_AudioStream *stream)
{ {
if (!stream) { if (!stream) {
@ -655,31 +666,6 @@ static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t ne
return ptr; return ptr;
} }
static void UpdateAudioStreamHistoryBuffer(SDL_AudioStream* stream,
Uint8* input_buffer, int input_bytes, Uint8* left_padding, int padding_bytes)
{
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(stream->input_spec);
if (left_padding) {
// Fill in the left padding using the history buffer
SDL_assert(padding_bytes <= history_bytes);
SDL_memcpy(left_padding, history_buffer + history_bytes - padding_bytes, padding_bytes);
}
// Update the history buffer using the new input data
if (input_bytes >= history_bytes) {
SDL_memcpy(history_buffer, input_buffer + (input_bytes - history_bytes), history_bytes);
} else {
int preserve_bytes = history_bytes - input_bytes;
SDL_memmove(history_buffer, history_buffer + input_bytes, preserve_bytes);
SDL_memcpy(history_buffer + preserve_bytes, input_buffer, input_bytes);
}
}
static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter, static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter,
Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, SDL_bool* out_flushed) Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, SDL_bool* out_flushed)
{ {
@ -777,7 +763,6 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
const SDL_AudioFormat src_format = src_spec->format; const SDL_AudioFormat src_format = src_spec->format;
const int src_channels = src_spec->channels; const int src_channels = src_spec->channels;
const int src_frame_size = SDL_AUDIO_FRAMESIZE(*src_spec);
const SDL_AudioFormat dst_format = dst_spec->format; const SDL_AudioFormat dst_format = dst_spec->format;
const int dst_channels = dst_spec->channels; const int dst_channels = dst_spec->channels;
@ -793,34 +778,19 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
// Not resampling? It's an easy conversion (and maybe not even that!) // Not resampling? It's an easy conversion (and maybe not even that!)
if (resample_rate == 0) { if (resample_rate == 0) {
Uint8* input_buffer = NULL; Uint8* work_buffer = NULL;
// If no conversion is happening, read straight into the output buffer. // Ensure we have enough scratch space for any conversions
// Note, this is just to avoid extra copies. if ((src_format != dst_format) || (src_channels != dst_channels)) {
// Some other formats may fit directly into the output buffer, but i'd rather process data in a SIMD-aligned buffer. work_buffer = EnsureAudioStreamWorkBufferSize(stream, output_frames * max_frame_size);
if ((src_format == dst_format) && (src_channels == dst_channels)) {
input_buffer = (Uint8 *)buf;
} else {
input_buffer = EnsureAudioStreamWorkBufferSize(stream, output_frames * max_frame_size);
if (!input_buffer) { if (!work_buffer) {
return -1; return -1;
} }
} }
const int input_bytes = output_frames * src_frame_size; if (SDL_ReadFromAudioQueue(stream->queue, buf, dst_format, dst_channels, 0, output_frames, 0, work_buffer) != buf) {
if (SDL_ReadFromAudioQueue(stream->queue, input_buffer, input_bytes) != 0) { return SDL_SetError("Not enough data in queue");
SDL_assert(!"Not enough data in queue (read)");
}
stream->total_bytes_queued -= input_bytes;
// Even if we aren't currently resampling, we always need to update the history buffer
UpdateAudioStreamHistoryBuffer(stream, input_buffer, input_bytes, NULL, 0);
// Convert the data, if necessary
if (buf != input_buffer) {
ConvertAudio(output_frames, input_buffer, src_format, src_channels, buf, dst_format, dst_channels, input_buffer);
} }
return 0; return 0;
@ -832,9 +802,10 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
// can require a different number of input_frames, depending on the resample_offset. // can require a different number of input_frames, depending on the resample_offset.
// Infact, input_frames can sometimes even be zero when upsampling. // Infact, input_frames can sometimes even be zero when upsampling.
const int input_frames = (int) SDL_GetResamplerInputFrames(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 = SDL_GetResamplerPaddingFrames(resample_rate); const int padding_frames = SDL_GetResamplerPaddingFrames(resample_rate);
const SDL_AudioFormat resample_format = SDL_AUDIO_F32;
// If increasing channels, do it after resampling, since we'd just // If increasing channels, do it after resampling, since we'd just
// do more work to resample duplicate channels. If we're decreasing, do // do more work to resample duplicate channels. If we're decreasing, do
@ -843,7 +814,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
const int resample_channels = SDL_min(src_channels, dst_channels); const int resample_channels = SDL_min(src_channels, dst_channels);
// The size of the frame used when resampling // The size of the frame used when resampling
const int resample_frame_size = resample_channels * sizeof(float); const int resample_frame_size = SDL_AUDIO_BYTESIZE(resample_format) * resample_channels;
// The main portion of the work_buffer can be used to store 3 things: // The main portion of the work_buffer can be used to store 3 things:
// src_sample_frame_size * (left_padding+input_buffer+right_padding) // src_sample_frame_size * (left_padding+input_buffer+right_padding)
@ -854,14 +825,14 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
// resample_frame_size * output_frames // resample_frame_size * output_frames
// //
// Note, ConvertAudio requires (num_frames * max_sample_frame_size) of scratch space // Note, ConvertAudio requires (num_frames * max_sample_frame_size) of scratch space
const int work_buffer_frames = input_frames + (resampler_padding_frames * 2); const int work_buffer_frames = input_frames + (padding_frames * 2);
int work_buffer_capacity = work_buffer_frames * max_frame_size; int work_buffer_capacity = work_buffer_frames * max_frame_size;
int resample_buffer_offset = -1; int resample_buffer_offset = -1;
// Check if we can resample directly into the output buffer. // Check if we can resample directly into the output buffer.
// Note, this is just to avoid extra copies. // Note, this is just to avoid extra copies.
// Some other formats may fit directly into the output buffer, but i'd rather process data in a SIMD-aligned buffer. // Some other formats may fit directly into the output buffer, but i'd rather process data in a SIMD-aligned buffer.
if ((dst_format != SDL_AUDIO_F32) || (dst_channels != resample_channels)) { if ((dst_format != resample_format) || (dst_channels != resample_channels)) {
// Allocate space for converting the resampled output to the destination format // Allocate space for converting the resampled output to the destination format
int resample_convert_bytes = output_frames * max_frame_size; int resample_convert_bytes = output_frames * max_frame_size;
work_buffer_capacity = SDL_max(work_buffer_capacity, resample_convert_bytes); work_buffer_capacity = SDL_max(work_buffer_capacity, resample_convert_bytes);
@ -883,45 +854,15 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
return -1; return -1;
} }
const int padding_bytes = resampler_padding_frames * src_frame_size; const Uint8* input_buffer = SDL_ReadFromAudioQueue(stream->queue,
NULL, resample_format, resample_channels,
padding_frames, input_frames, padding_frames, work_buffer);
Uint8* work_buffer_tail = work_buffer; if (!input_buffer) {
return SDL_SetError("Not enough data in queue (resample)");
// Split the work_buffer into [left_padding][input_buffer][right_padding]
Uint8* left_padding = work_buffer_tail;
work_buffer_tail += padding_bytes;
Uint8* input_buffer = work_buffer_tail;
work_buffer_tail += input_bytes;
Uint8* right_padding = work_buffer_tail;
work_buffer_tail += padding_bytes;
SDL_assert((work_buffer_tail - work_buffer) <= work_buffer_capacity);
// Now read unconverted data from the queue into the work buffer to fulfill the request.
if (SDL_ReadFromAudioQueue(stream->queue, input_buffer, input_bytes) != 0) {
SDL_assert(!"Not enough data in queue (resample read)");
}
stream->total_bytes_queued -= input_bytes;
// Update the history buffer and fill in the left padding
UpdateAudioStreamHistoryBuffer(stream, input_buffer, input_bytes, left_padding, 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)); input_buffer += padding_frames * resample_frame_size;
// Resampling! get the work buffer to float32 format, etc, in-place.
ConvertAudio(work_buffer_frames, work_buffer, src_format, src_channels, work_buffer, SDL_AUDIO_F32, resample_channels, NULL);
// Update the work_buffer pointers based on the new frame size
input_buffer = work_buffer + ((input_buffer - work_buffer) / src_frame_size * resample_frame_size);
work_buffer_tail = work_buffer + ((work_buffer_tail - work_buffer) / src_frame_size * resample_frame_size);
SDL_assert((work_buffer_tail - work_buffer) <= work_buffer_capacity);
// Decide where the resampled output goes // Decide where the resampled output goes
void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf; void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf;
@ -932,9 +873,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
resample_rate, &stream->resample_offset); resample_rate, &stream->resample_offset);
// Convert to the final format, if necessary // Convert to the final format, if necessary
if (buf != resample_buffer) { ConvertAudio(output_frames, resample_buffer, resample_format, resample_channels, buf, dst_format, dst_channels, work_buffer);
ConvertAudio(output_frames, resample_buffer, SDL_AUDIO_F32, resample_channels, buf, dst_format, dst_channels, work_buffer);
}
return 0; return 0;
} }
@ -1074,7 +1013,9 @@ int SDL_GetAudioStreamQueued(SDL_AudioStream *stream)
} }
SDL_LockMutex(stream->lock); SDL_LockMutex(stream->lock);
const Uint64 total = stream->total_bytes_queued;
size_t total = SDL_GetAudioQueueQueued(stream->queue);
SDL_UnlockMutex(stream->lock); SDL_UnlockMutex(stream->lock);
// if this overflows an int, just clamp it to a maximum. // if this overflows an int, just clamp it to a maximum.
@ -1092,7 +1033,6 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream)
SDL_ClearAudioQueue(stream->queue); SDL_ClearAudioQueue(stream->queue);
SDL_zero(stream->input_spec); SDL_zero(stream->input_spec);
stream->resample_offset = 0; stream->resample_offset = 0;
stream->total_bytes_queued = 0;
SDL_UnlockMutex(stream->lock); SDL_UnlockMutex(stream->lock);
return 0; return 0;
@ -1118,7 +1058,6 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream)
SDL_UnbindAudioStream(stream); SDL_UnbindAudioStream(stream);
} }
SDL_aligned_free(stream->history_buffer);
SDL_aligned_free(stream->work_buffer); SDL_aligned_free(stream->work_buffer);
SDL_DestroyAudioQueue(stream->queue); SDL_DestroyAudioQueue(stream->queue);
SDL_DestroyMutex(stream->lock); SDL_DestroyMutex(stream->lock);
@ -1126,6 +1065,11 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream)
SDL_free(stream); SDL_free(stream);
} }
static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len)
{
// We don't own the buffer, but know it will outlive the stream
}
int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len,
const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len)
{ {
@ -1153,7 +1097,7 @@ int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data
SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec); SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec);
if (stream) { if (stream) {
if ((SDL_PutAudioStreamData(stream, src_data, src_len) == 0) && (SDL_FlushAudioStream(stream) == 0)) { if ((PutAudioStreamBuffer(stream, src_data, src_len, DontFreeThisAudioBuffer, NULL) == 0) && (SDL_FlushAudioStream(stream) == 0)) {
dstlen = SDL_GetAudioStreamAvailable(stream); dstlen = SDL_GetAudioStreamAvailable(stream);
if (dstlen >= 0) { if (dstlen >= 0) {
dst = (Uint8 *)SDL_malloc(dstlen); dst = (Uint8 *)SDL_malloc(dstlen);

View File

@ -21,253 +21,131 @@
#include "SDL_internal.h" #include "SDL_internal.h"
#include "SDL_audioqueue.h" #include "SDL_audioqueue.h"
#include "SDL_sysaudio.h"
#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq)) #define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq))
typedef struct SDL_MemoryPool SDL_MemoryPool;
struct SDL_MemoryPool
{
void *free_blocks;
size_t block_size;
size_t num_free;
size_t max_free;
};
struct SDL_AudioTrack struct SDL_AudioTrack
{ {
SDL_AudioSpec spec; SDL_AudioSpec spec;
SDL_bool flushed; SDL_bool flushed;
SDL_AudioTrack *next; SDL_AudioTrack *next;
size_t (*avail)(void *ctx); void *userdata;
int (*write)(void *ctx, const Uint8 *buf, size_t len); SDL_ReleaseAudioBufferCallback callback;
size_t (*read)(void *ctx, Uint8 *buf, size_t len, SDL_bool advance);
void (*destroy)(void *ctx); Uint8 *data;
size_t head;
size_t tail;
size_t capacity;
}; };
struct SDL_AudioQueue struct SDL_AudioQueue
{ {
SDL_AudioTrack *head; SDL_AudioTrack *head;
SDL_AudioTrack *tail; SDL_AudioTrack *tail;
size_t chunk_size;
Uint8 *history_buffer;
size_t history_length;
size_t history_capacity;
SDL_MemoryPool track_pool;
SDL_MemoryPool chunk_pool;
}; };
typedef struct SDL_AudioChunk SDL_AudioChunk; // Allocate a new block, avoiding checking for ones already in the pool
static void *AllocNewMemoryPoolBlock(const SDL_MemoryPool *pool)
struct SDL_AudioChunk
{ {
SDL_AudioChunk *next; return SDL_malloc(pool->block_size);
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) // Allocate a new block, first checking if there are any in the pool
static void *AllocMemoryPoolBlock(SDL_MemoryPool *pool)
{ {
while (chunk) { if (pool->num_free == 0) {
SDL_AudioChunk *next = chunk->next; return AllocNewMemoryPoolBlock(pool);
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) {
return NULL;
} }
ResetAudioChunk(chunk); void *block = pool->free_blocks;
pool->free_blocks = *(void **)block;
return chunk; --pool->num_free;
return block;
} }
static void DestroyAudioTrackChunk(SDL_ChunkedAudioTrack *track, SDL_AudioChunk *chunk) // Free a block, or add it to the pool if there's room
static void FreeMemoryPoolBlock(SDL_MemoryPool *pool, void *block)
{ {
// Keeping a list of free chunks reduces memory allocations, if (pool->num_free < pool->max_free) {
// But also increases the amount of work to perform when freeing the track. *(void **)block = pool->free_blocks;
const size_t max_free_bytes = 64 * 1024; pool->free_blocks = block;
++pool->num_free;
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 { } else {
DestroyAudioChunk(chunk); SDL_free(block);
} }
} }
static SDL_AudioChunk *CreateAudioTrackChunk(SDL_ChunkedAudioTrack *track) // Destroy a pool and all of its blocks
static void DestroyMemoryPool(SDL_MemoryPool *pool)
{ {
if (track->num_free_chunks > 0) { void *block = pool->free_blocks;
SDL_AudioChunk *chunk = track->free_chunks; pool->free_blocks = NULL;
pool->num_free = 0;
track->free_chunks = chunk->next; while (block) {
--track->num_free_chunks; void *next = *(void **)block;
SDL_free(block);
ResetAudioChunk(chunk); block = next;
return chunk;
} }
return CreateAudioChunk(track->chunk_size);
} }
static size_t AvailChunkedAudioTrack(void *ctx) // Keeping a list of free chunks reduces memory allocations,
// But also increases the amount of work to perform when freeing the track.
static void InitMemoryPool(SDL_MemoryPool *pool, size_t block_size, size_t max_free)
{ {
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx; SDL_zerop(pool);
return track->queued_bytes; SDL_assert(block_size >= sizeof(void *));
pool->block_size = block_size;
pool->max_free = max_free;
} }
static int WriteToChunkedAudioTrack(void *ctx, const Uint8 *data, size_t len) // Allocates a number of blocks and adds them to the pool
static int ReserveMemoryPoolBlocks(SDL_MemoryPool *pool, size_t num_blocks)
{ {
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx; for (; num_blocks; --num_blocks) {
void *block = AllocNewMemoryPoolBlock(pool);
SDL_AudioChunk *chunk = track->tail; if (block == NULL) {
return SDL_OutOfMemory();
// Handle the first chunk
if (!chunk) {
chunk = CreateAudioTrackChunk(track);
if (!chunk) {
return -1;
} }
SDL_assert((track->head == NULL) && (track->tail == NULL) && (track->queued_bytes == 0)); *(void **)block = pool->free_blocks;
track->head = chunk; pool->free_blocks = block;
track->tail = chunk; ++pool->num_free;
} }
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) {
chunk = track->tail;
SDL_AudioChunk *next = chunk->next;
chunk->next = NULL;
chunk->tail = old_tail;
DestroyAudioChunks(next);
return -1;
}
track->tail = chunk;
track->queued_bytes += total;
return 0; return 0;
} }
static size_t ReadFromChunkedAudioTrack(void *ctx, Uint8 *data, size_t len, SDL_bool advance) void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
{ {
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx; SDL_ClearAudioQueue(queue);
SDL_AudioChunk *chunk = track->head;
size_t total = 0; DestroyMemoryPool(&queue->track_pool);
size_t head = 0; DestroyMemoryPool(&queue->chunk_pool);
SDL_aligned_free(queue->history_buffer);
while (chunk) { SDL_free(queue);
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 = (SDL_ChunkedAudioTrack *)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) {
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 *SDL_CreateAudioQueue(size_t chunk_size)
@ -278,35 +156,42 @@ SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
return NULL; return NULL;
} }
queue->chunk_size = chunk_size; InitMemoryPool(&queue->track_pool, sizeof(SDL_AudioTrack), 8);
InitMemoryPool(&queue->chunk_pool, chunk_size, 4);
if (ReserveMemoryPoolBlocks(&queue->track_pool, 2) != 0) {
SDL_DestroyAudioQueue(queue);
return NULL;
}
return queue; return queue;
} }
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue) static void DestroyAudioTrack(SDL_AudioQueue *queue, SDL_AudioTrack *track)
{ {
SDL_ClearAudioQueue(queue); track->callback(track->userdata, track->data, (int)track->capacity);
SDL_free(queue); FreeMemoryPoolBlock(&queue->track_pool, track);
} }
void SDL_ClearAudioQueue(SDL_AudioQueue *queue) void SDL_ClearAudioQueue(SDL_AudioQueue *queue)
{ {
SDL_AudioTrack *track = queue->head; SDL_AudioTrack *track = queue->head;
queue->head = NULL; queue->head = NULL;
queue->tail = NULL; queue->tail = NULL;
queue->history_length = 0;
while (track) { while (track) {
SDL_AudioTrack *next = track->next; SDL_AudioTrack *next = track->next;
track->destroy(track); DestroyAudioTrack(queue, track);
track = next; track = next;
} }
} }
static void SDL_FlushAudioTrack(SDL_AudioTrack *track) static void FlushAudioTrack(SDL_AudioTrack *track)
{ {
track->flushed = SDL_TRUE; track->flushed = SDL_TRUE;
track->write = NULL;
} }
void SDL_FlushAudioQueue(SDL_AudioQueue *queue) void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
@ -314,7 +199,7 @@ void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
SDL_AudioTrack *track = queue->tail; SDL_AudioTrack *track = queue->tail;
if (track) { if (track) {
SDL_FlushAudioTrack(track); FlushAudioTrack(track);
} }
} }
@ -326,7 +211,7 @@ void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
SDL_bool flushed = track->flushed; SDL_bool flushed = track->flushed;
SDL_AudioTrack *next = track->next; SDL_AudioTrack *next = track->next;
track->destroy(track); DestroyAudioTrack(queue, track);
track = next; track = next;
if (flushed) { if (flushed) {
@ -335,27 +220,59 @@ void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
} }
queue->head = track; queue->head = track;
queue->history_length = 0;
if (!track) { if (!track) {
queue->tail = NULL; queue->tail = NULL;
} }
} }
size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue) SDL_AudioTrack *SDL_CreateAudioTrack(
SDL_AudioQueue *queue, const SDL_AudioSpec *spec,
Uint8 *data, size_t len, size_t capacity,
SDL_ReleaseAudioBufferCallback callback, void *userdata)
{ {
return queue->chunk_size; SDL_AudioTrack *track = AllocMemoryPoolBlock(&queue->track_pool);
}
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) { if (!track) {
return NULL; return NULL;
} }
if (track->write(track, data, len) != 0) { SDL_zerop(track);
track->destroy(track); SDL_copyp(&track->spec, spec);
track->userdata = userdata;
track->callback = callback;
track->data = data;
track->head = 0;
track->tail = len;
track->capacity = capacity;
return track;
}
static void SDLCALL FreeChunkedAudioBuffer(void *userdata, const void *buf, int len)
{
SDL_AudioQueue *queue = userdata;
FreeMemoryPoolBlock(&queue->chunk_pool, (void *)buf);
}
static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec)
{
void *chunk = AllocMemoryPoolBlock(&queue->chunk_pool);
if (!chunk) {
return NULL;
}
size_t capacity = queue->chunk_pool.block_size;
capacity -= capacity % SDL_AUDIO_FRAMESIZE(*spec);
SDL_AudioTrack *track = SDL_CreateAudioTrack(queue, spec, chunk, 0, capacity, FreeChunkedAudioBuffer, queue);
if (!track) {
FreeMemoryPoolBlock(&queue->chunk_pool, chunk);
return NULL; return NULL;
} }
@ -369,7 +286,7 @@ void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
if (tail) { if (tail) {
// If the spec has changed, make sure to flush the previous track // If the spec has changed, make sure to flush the previous track
if (!AUDIO_SPECS_EQUAL(tail->spec, track->spec)) { if (!AUDIO_SPECS_EQUAL(tail->spec, track->spec)) {
SDL_FlushAudioTrack(tail); FlushAudioTrack(tail);
} }
tail->next = track; tail->next = track;
@ -380,6 +297,19 @@ void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
queue->tail = track; queue->tail = track;
} }
static size_t WriteToAudioTrack(SDL_AudioTrack *track, const Uint8 *data, size_t len)
{
if (track->flushed || track->tail >= track->capacity) {
return 0;
}
len = SDL_min(len, track->capacity - track->tail);
SDL_memcpy(&track->data[track->tail], data, len);
track->tail += len;
return len;
}
int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len) int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len)
{ {
if (len == 0) { if (len == 0) {
@ -388,29 +318,43 @@ int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, cons
SDL_AudioTrack *track = queue->tail; SDL_AudioTrack *track = queue->tail;
if ((track) && !AUDIO_SPECS_EQUAL(track->spec, *spec)) { if (track) {
SDL_FlushAudioTrack(track); if (!AUDIO_SPECS_EQUAL(track->spec, *spec)) {
FlushAudioTrack(track);
}
} else {
SDL_assert(!queue->head);
track = CreateChunkedAudioTrack(queue, spec);
if (!track) {
return -1;
}
queue->head = track;
queue->tail = track;
} }
if ((!track) || (!track->write)) { for (;;) {
SDL_AudioTrack *new_track = CreateChunkedAudioTrack(spec, queue->chunk_size); size_t written = WriteToAudioTrack(track, data, len);
data += written;
len -= written;
if (len == 0) {
break;
}
SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec);
if (!new_track) { if (!new_track) {
return -1; return -1;
} }
if (track) { track->next = new_track;
track->next = new_track;
} else {
queue->head = new_track;
}
queue->tail = new_track; queue->tail = new_track;
track = new_track; track = new_track;
} }
return track->write(track, data, len); return 0;
} }
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue) void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
@ -432,7 +376,7 @@ size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_Audi
SDL_AudioTrack *track = iter; SDL_AudioTrack *track = iter;
iter = iter->next; iter = iter->next;
size_t avail = track->avail(track); size_t avail = track->tail - track->head;
if (avail >= SDL_SIZE_MAX - queued_bytes) { if (avail >= SDL_SIZE_MAX - queued_bytes) {
queued_bytes = SDL_SIZE_MAX; queued_bytes = SDL_SIZE_MAX;
@ -454,61 +398,244 @@ size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_Audi
return queued_bytes; return queued_bytes;
} }
int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len) static const Uint8 *PeekIntoAudioQueuePast(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{ {
size_t total = 0;
SDL_AudioTrack *track = queue->head; SDL_AudioTrack *track = queue->head;
for (;;) { if (track->head >= len) {
if (!track) { return &track->data[track->head - len];
return SDL_SetError("Reading past end of queue"); }
}
total += track->read(track, &data[total], len - total, SDL_TRUE); size_t past = len - track->head;
if (past > queue->history_length) {
return NULL;
}
SDL_memcpy(data, &queue->history_buffer[queue->history_length - past], past);
SDL_memcpy(&data[past], track->data, track->head);
return data;
}
static void UpdateAudioQueueHistory(SDL_AudioQueue *queue,
const Uint8 *data, size_t len)
{
Uint8 *history_buffer = queue->history_buffer;
size_t history_bytes = queue->history_length;
if (len >= history_bytes) {
SDL_memcpy(history_buffer, &data[len - history_bytes], history_bytes);
} else {
size_t preserve = history_bytes - len;
SDL_memmove(history_buffer, &history_buffer[len], preserve);
SDL_memcpy(&history_buffer[preserve], data, len);
}
}
static const Uint8 *ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{
SDL_AudioTrack *track = queue->head;
if (track->tail - track->head >= len) {
const Uint8 *ptr = &track->data[track->head];
track->head += len;
return ptr;
}
size_t total = 0;
for (;;) {
size_t avail = SDL_min(len - total, track->tail - track->head);
SDL_memcpy(&data[total], &track->data[track->head], avail);
track->head += avail;
total += avail;
if (total == len) { if (total == len) {
return 0; break;
} }
if (track->flushed) { if (track->flushed) {
return SDL_SetError("Reading past end of flushed track"); SDL_SetError("Reading past end of flushed track");
return NULL;
} }
SDL_AudioTrack *next = track->next; SDL_AudioTrack *next = track->next;
if (!next) { if (!next) {
return SDL_SetError("Reading past end of incomplete track"); SDL_SetError("Reading past end of incomplete track");
return NULL;
} }
UpdateAudioQueueHistory(queue, track->data, track->tail);
queue->head = next; queue->head = next;
DestroyAudioTrack(queue, track);
track->destroy(track);
track = next; track = next;
} }
return data;
} }
int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len) static const Uint8 *PeekIntoAudioQueueFuture(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{ {
size_t total = 0;
SDL_AudioTrack *track = queue->head; SDL_AudioTrack *track = queue->head;
for (;;) { if (track->tail - track->head >= len) {
if (!track) { return &track->data[track->head];
return SDL_SetError("Peeking past end of queue"); }
}
total += track->read(track, &data[total], len - total, SDL_FALSE); size_t total = 0;
for (;;) {
size_t avail = SDL_min(len - total, track->tail - track->head);
SDL_memcpy(&data[total], &track->data[track->head], avail);
total += avail;
if (total == len) { if (total == len) {
return 0; break;
} }
if (track->flushed) { if (track->flushed) {
// If we have run out of data, fill the rest with silence. // If we have run out of data, fill the rest with silence.
SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total); SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total);
return 0; break;
} }
track = track->next; track = track->next;
if (!track) {
SDL_SetError("Peeking past end of incomplete track");
return NULL;
}
} }
return data;
}
const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels,
int past_frames, int present_frames, int future_frames,
Uint8 *scratch)
{
SDL_AudioTrack *track = queue->head;
if (!track) {
return NULL;
}
SDL_AudioFormat src_format = track->spec.format;
SDL_AudioFormat src_channels = track->spec.channels;
size_t src_frame_size = SDL_AUDIO_BYTESIZE(src_format) * src_channels;
size_t dst_frame_size = SDL_AUDIO_BYTESIZE(dst_format) * dst_channels;
size_t src_past_bytes = past_frames * src_frame_size;
size_t src_present_bytes = present_frames * src_frame_size;
size_t src_future_bytes = future_frames * src_frame_size;
size_t dst_past_bytes = past_frames * dst_frame_size;
size_t dst_present_bytes = present_frames * dst_frame_size;
size_t dst_future_bytes = future_frames * dst_frame_size;
SDL_bool convert = (src_format != dst_format) || (src_channels != dst_channels);
if (convert && !dst) {
// The user didn't ask for the data to be copied, but we need to convert it, so store it in the scratch buffer
dst = scratch;
}
// Can we get all of the data straight from this track?
if ((track->head >= src_past_bytes) && ((track->tail - track->head) >= (src_present_bytes + src_future_bytes))) {
const Uint8 *ptr = &track->data[track->head - src_past_bytes];
track->head += src_present_bytes;
// Do we still need to copy/convert the data?
if (dst) {
ConvertAudio(past_frames + present_frames + future_frames, ptr,
src_format, src_channels, dst, dst_format, dst_channels, scratch);
ptr = dst;
}
return ptr;
}
if (!dst) {
// The user didn't ask for the data to be copied, but we need to, so store it in the scratch buffer
dst = scratch;
} else if (!convert) {
// We are only copying, not converting, so copy straight into the dst buffer
scratch = dst;
}
Uint8 *ptr = dst;
if (src_past_bytes) {
ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, dst, dst_format, dst_channels, scratch);
dst += dst_past_bytes;
scratch += dst_past_bytes;
}
if (src_present_bytes) {
ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, dst, dst_format, dst_channels, scratch);
dst += dst_present_bytes;
scratch += dst_present_bytes;
}
if (src_future_bytes) {
ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, dst, dst_format, dst_channels, scratch);
dst += dst_future_bytes;
scratch += dst_future_bytes;
}
return ptr;
}
size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue)
{
size_t total = 0;
void *iter = SDL_BeginAudioQueueIter(queue);
while (iter) {
SDL_AudioSpec src_spec;
SDL_bool flushed;
size_t avail = SDL_NextAudioQueueIter(queue, &iter, &src_spec, &flushed);
if (avail >= SDL_SIZE_MAX - total) {
total = SDL_SIZE_MAX;
break;
}
total += avail;
}
return total;
}
int SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames)
{
SDL_AudioTrack *track = queue->head;
if (!track) {
return -1;
}
size_t length = num_frames * SDL_AUDIO_FRAMESIZE(track->spec);
Uint8 *history_buffer = queue->history_buffer;
if (queue->history_capacity < length) {
history_buffer = SDL_aligned_alloc(SDL_SIMDGetAlignment(), length);
if (!history_buffer) {
return -1;
}
SDL_aligned_free(queue->history_buffer);
queue->history_buffer = history_buffer;
queue->history_capacity = length;
}
queue->history_length = length;
SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(track->spec.format), length);
return 0;
} }

View File

@ -25,6 +25,8 @@
// Internal functions used by SDL_AudioStream for queueing audio. // Internal functions used by SDL_AudioStream for queueing audio.
typedef void(SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen);
typedef struct SDL_AudioQueue SDL_AudioQueue; typedef struct SDL_AudioQueue SDL_AudioQueue;
typedef struct SDL_AudioTrack SDL_AudioTrack; typedef struct SDL_AudioTrack SDL_AudioTrack;
@ -44,16 +46,14 @@ void SDL_FlushAudioQueue(SDL_AudioQueue *queue);
// REQUIRES: The head track must exist, and must have been flushed // REQUIRES: The head track must exist, and must have been flushed
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue); 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 // Write data to the end of queue
// REQUIRES: If the spec has changed, the last track must have been flushed // 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); 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 // Create a track where the input data is owned by the caller
SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size); SDL_AudioTrack *SDL_CreateAudioTrack(SDL_AudioQueue *queue,
const SDL_AudioSpec *spec, Uint8 *data, size_t len, size_t capacity,
SDL_ReleaseAudioBufferCallback callback, void *userdata);
// Add a track to the end of the queue // Add a track to the end of the queue
// REQUIRES: `track != NULL` // REQUIRES: `track != NULL`
@ -66,12 +66,14 @@ void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
// REQUIRES: `*inout_iter != NULL` (a valid 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); 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 const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
// REQUIRES: There must be enough data in the queue Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels,
int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len); int past_frames, int present_frames, int future_frames,
Uint8 *scratch);
// Peek into the start of the queue // Get the total number of bytes currently queued
// REQUIRES: There must be enough data in the queue, unless it has been flushed, in which case missing data is filled with silence. size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue);
int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len);
int SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames);
#endif // SDL_audioqueue_h_ #endif // SDL_audioqueue_h_

View File

@ -190,7 +190,6 @@ struct SDL_AudioStream
float freq_ratio; float freq_ratio;
struct SDL_AudioQueue* queue; struct SDL_AudioQueue* queue;
Uint64 total_bytes_queued;
SDL_AudioSpec input_spec; // The spec of input data currently being processed SDL_AudioSpec input_spec; // The spec of input data currently being processed
Sint64 resample_offset; Sint64 resample_offset;
@ -198,9 +197,6 @@ struct SDL_AudioStream
Uint8 *work_buffer; // used for scratch space during data conversion/resampling. Uint8 *work_buffer; // used for scratch space during data conversion/resampling.
size_t work_buffer_allocation; size_t work_buffer_allocation;
Uint8 *history_buffer; // history for left padding and future sample rate changes.
size_t history_buffer_allocation;
SDL_bool simplified; // SDL_TRUE if created via SDL_OpenAudioDeviceStream SDL_bool simplified; // SDL_TRUE if created via SDL_OpenAudioDeviceStream
SDL_LogicalAudioDevice *bound_device; SDL_LogicalAudioDevice *bound_device;

View File

@ -10,8 +10,6 @@
freely. freely.
*/ */
/* !!! FIXME: this code is not up to standards for SDL3 test apps. Someone should improve this. */
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <SDL3/SDL_main.h> #include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h> #include <SDL3/SDL_test.h>
@ -117,6 +115,7 @@ static void queue_audio()
SDL_Log("Converting audio from %i to %i", spec.freq, new_spec.freq); SDL_Log("Converting audio from %i to %i", spec.freq, new_spec.freq);
/* You shouldn't actually use SDL_ConvertAudioSamples like this (just put the data straight into the stream and let it handle conversion) */
retval = retval ? retval : SDL_ConvertAudioSamples(&spec, audio_buf, audio_len, &new_spec, &new_data, &new_len); retval = retval ? retval : SDL_ConvertAudioSamples(&spec, audio_buf, audio_len, &new_spec, &new_data, &new_len);
retval = retval ? retval : SDL_SetAudioStreamFormat(stream, &new_spec, NULL); retval = retval ? retval : SDL_SetAudioStreamFormat(stream, &new_spec, NULL);
retval = retval ? retval : SDL_PutAudioStreamData(stream, new_data, new_len); retval = retval ? retval : SDL_PutAudioStreamData(stream, new_data, new_len);
@ -207,6 +206,7 @@ static void loop(void)
SDL_Event e; SDL_Event e;
SDL_FPoint p; SDL_FPoint p;
SDL_AudioSpec src_spec, dst_spec; SDL_AudioSpec src_spec, dst_spec;
int queued_bytes = 0;
int available_bytes = 0; int available_bytes = 0;
float available_seconds = 0; float available_seconds = 0;
@ -294,6 +294,8 @@ static void loop(void)
} }
} }
queued_bytes = SDL_GetAudioStreamQueued(stream);
for (i = 0; i < state->num_windows; i++) { for (i = 0; i < state->num_windows; i++) {
int draw_y = 0; int draw_y = 0;
SDL_Renderer* rend = state->renderers[i]; SDL_Renderer* rend = state->renderers[i];
@ -326,6 +328,9 @@ static void loop(void)
draw_textf(rend, 0, draw_y, "Available: %4.2f (%i bytes)", available_seconds, available_bytes); draw_textf(rend, 0, draw_y, "Available: %4.2f (%i bytes)", available_seconds, available_bytes);
draw_y += FONT_LINE_HEIGHT; draw_y += FONT_LINE_HEIGHT;
draw_textf(rend, 0, draw_y, "Queued: %i bytes", queued_bytes);
draw_y += FONT_LINE_HEIGHT;
SDL_LockAudioStream(stream); SDL_LockAudioStream(stream);
draw_textf(rend, 0, draw_y, "Get Callback: %i/%i bytes, %2i ms ago", draw_textf(rend, 0, draw_y, "Get Callback: %i/%i bytes, %2i ms ago",

View File

@ -821,11 +821,6 @@ static double sine_wave_sample(const Sint64 idx, const Sint64 rate, const Sint64
return SDL_sin(((double)(idx * freq % rate)) / ((double)rate) * (SDL_PI_D * 2) + phase); return SDL_sin(((double)(idx * freq % rate)) / ((double)rate) * (SDL_PI_D * 2) + phase);
} }
static void free_audio_buffer(void* userdata, const void* buf, int len)
{
SDL_free((void*) buf);
}
/* Split the data into randomly sized chunks */ /* Split the data into randomly sized chunks */
static int put_audio_data_split(SDL_AudioStream* stream, const void* buf, int len) static int put_audio_data_split(SDL_AudioStream* stream, const void* buf, int len)
{ {