mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-10-04 17:06:25 +00:00
Refactored SDL_audiocvt.c
This commit is contained in:
516
src/audio/SDL_audioqueue.c
Normal file
516
src/audio/SDL_audioqueue.c
Normal 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user