|
@@ -21,253 +21,131 @@
|
|
|
#include "SDL_internal.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))
|
|
|
|
|
|
+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
|
|
|
{
|
|
|
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);
|
|
|
+ void *userdata;
|
|
|
+ SDL_ReleaseAudioBufferCallback callback;
|
|
|
+
|
|
|
+ Uint8 *data;
|
|
|
+ size_t head;
|
|
|
+ size_t tail;
|
|
|
+ size_t capacity;
|
|
|
};
|
|
|
|
|
|
struct SDL_AudioQueue
|
|
|
{
|
|
|
SDL_AudioTrack *head;
|
|
|
SDL_AudioTrack *tail;
|
|
|
- size_t chunk_size;
|
|
|
-};
|
|
|
|
|
|
-typedef struct SDL_AudioChunk SDL_AudioChunk;
|
|
|
+ Uint8 *history_buffer;
|
|
|
+ size_t history_length;
|
|
|
+ size_t history_capacity;
|
|
|
|
|
|
-struct SDL_AudioChunk
|
|
|
-{
|
|
|
- SDL_AudioChunk *next;
|
|
|
- size_t head;
|
|
|
- size_t tail;
|
|
|
- Uint8 data[SDL_VARIABLE_LENGTH_ARRAY];
|
|
|
+ SDL_MemoryPool track_pool;
|
|
|
+ SDL_MemoryPool chunk_pool;
|
|
|
};
|
|
|
|
|
|
-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)
|
|
|
+// Allocate a new block, avoiding checking for ones already in the pool
|
|
|
+static void *AllocNewMemoryPoolBlock(const SDL_MemoryPool *pool)
|
|
|
{
|
|
|
- SDL_free(chunk);
|
|
|
+ return SDL_malloc(pool->block_size);
|
|
|
}
|
|
|
|
|
|
-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) {
|
|
|
- SDL_AudioChunk *next = chunk->next;
|
|
|
- DestroyAudioChunk(chunk);
|
|
|
- chunk = next;
|
|
|
+ if (pool->num_free == 0) {
|
|
|
+ return AllocNewMemoryPoolBlock(pool);
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-static void ResetAudioChunk(SDL_AudioChunk *chunk)
|
|
|
-{
|
|
|
- chunk->next = NULL;
|
|
|
- chunk->head = 0;
|
|
|
- chunk->tail = 0;
|
|
|
+ void *block = pool->free_blocks;
|
|
|
+ pool->free_blocks = *(void **)block;
|
|
|
+ --pool->num_free;
|
|
|
+ return block;
|
|
|
}
|
|
|
|
|
|
-static SDL_AudioChunk *CreateAudioChunk(size_t chunk_size)
|
|
|
+// Free a block, or add it to the pool if there's room
|
|
|
+static void FreeMemoryPoolBlock(SDL_MemoryPool *pool, void *block)
|
|
|
{
|
|
|
- SDL_AudioChunk *chunk = (SDL_AudioChunk *)SDL_malloc(sizeof(*chunk) + chunk_size);
|
|
|
-
|
|
|
- if (!chunk) {
|
|
|
- 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;
|
|
|
+ if (pool->num_free < pool->max_free) {
|
|
|
+ *(void **)block = pool->free_blocks;
|
|
|
+ pool->free_blocks = block;
|
|
|
+ ++pool->num_free;
|
|
|
} 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) {
|
|
|
- SDL_AudioChunk *chunk = track->free_chunks;
|
|
|
-
|
|
|
- track->free_chunks = chunk->next;
|
|
|
- --track->num_free_chunks;
|
|
|
-
|
|
|
- ResetAudioChunk(chunk);
|
|
|
-
|
|
|
- return chunk;
|
|
|
+ void *block = pool->free_blocks;
|
|
|
+ pool->free_blocks = NULL;
|
|
|
+ pool->num_free = 0;
|
|
|
+
|
|
|
+ while (block) {
|
|
|
+ void *next = *(void **)block;
|
|
|
+ SDL_free(block);
|
|
|
+ block = next;
|
|
|
}
|
|
|
-
|
|
|
- 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;
|
|
|
-
|
|
|
- SDL_AudioChunk *chunk = track->tail;
|
|
|
-
|
|
|
- // Handle the first chunk
|
|
|
- if (!chunk) {
|
|
|
- chunk = CreateAudioTrackChunk(track);
|
|
|
+ for (; num_blocks; --num_blocks) {
|
|
|
+ void *block = AllocNewMemoryPoolBlock(pool);
|
|
|
|
|
|
- if (!chunk) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
-
|
|
|
- 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;
|
|
|
+ if (block == NULL) {
|
|
|
+ return SDL_OutOfMemory();
|
|
|
}
|
|
|
|
|
|
- SDL_AudioChunk *next = CreateAudioTrackChunk(track);
|
|
|
- chunk->next = next;
|
|
|
- chunk = next;
|
|
|
+ *(void **)block = pool->free_blocks;
|
|
|
+ pool->free_blocks = block;
|
|
|
+ ++pool->num_free;
|
|
|
}
|
|
|
|
|
|
- // 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;
|
|
|
}
|
|
|
|
|
|
-static size_t ReadFromChunkedAudioTrack(void *ctx, Uint8 *data, size_t len, SDL_bool advance)
|
|
|
-{
|
|
|
- SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)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 = (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)
|
|
|
+void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
|
|
|
{
|
|
|
- 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;
|
|
|
+ SDL_ClearAudioQueue(queue);
|
|
|
|
|
|
- track->chunk_size = chunk_size;
|
|
|
+ DestroyMemoryPool(&queue->track_pool);
|
|
|
+ DestroyMemoryPool(&queue->chunk_pool);
|
|
|
+ SDL_aligned_free(queue->history_buffer);
|
|
|
|
|
|
- return &track->track;
|
|
|
+ SDL_free(queue);
|
|
|
}
|
|
|
|
|
|
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
|
|
@@ -278,35 +156,42 @@ SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
-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)
|
|
|
{
|
|
|
SDL_AudioTrack *track = queue->head;
|
|
|
+
|
|
|
queue->head = NULL;
|
|
|
queue->tail = NULL;
|
|
|
+ queue->history_length = 0;
|
|
|
|
|
|
while (track) {
|
|
|
SDL_AudioTrack *next = track->next;
|
|
|
- track->destroy(track);
|
|
|
+ DestroyAudioTrack(queue, track);
|
|
|
track = next;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static void SDL_FlushAudioTrack(SDL_AudioTrack *track)
|
|
|
+static void FlushAudioTrack(SDL_AudioTrack *track)
|
|
|
{
|
|
|
track->flushed = SDL_TRUE;
|
|
|
- track->write = NULL;
|
|
|
}
|
|
|
|
|
|
void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
|
|
@@ -314,7 +199,7 @@ void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
|
|
|
SDL_AudioTrack *track = queue->tail;
|
|
|
|
|
|
if (track) {
|
|
|
- SDL_FlushAudioTrack(track);
|
|
|
+ FlushAudioTrack(track);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -326,7 +211,7 @@ void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
|
|
|
SDL_bool flushed = track->flushed;
|
|
|
|
|
|
SDL_AudioTrack *next = track->next;
|
|
|
- track->destroy(track);
|
|
|
+ DestroyAudioTrack(queue, track);
|
|
|
track = next;
|
|
|
|
|
|
if (flushed) {
|
|
@@ -335,27 +220,59 @@ void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
|
|
|
}
|
|
|
|
|
|
queue->head = track;
|
|
|
+ queue->history_length = 0;
|
|
|
|
|
|
if (!track) {
|
|
|
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);
|
|
|
+
|
|
|
+ if (!track) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ SDL_zerop(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;
|
|
|
}
|
|
|
|
|
|
-SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size)
|
|
|
+static void SDLCALL FreeChunkedAudioBuffer(void *userdata, const void *buf, int len)
|
|
|
{
|
|
|
- SDL_AudioTrack *track = CreateChunkedAudioTrack(spec, chunk_size);
|
|
|
+ SDL_AudioQueue *queue = userdata;
|
|
|
|
|
|
- if (!track) {
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
- if (track->write(track, data, len) != 0) {
|
|
|
- track->destroy(track);
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
@@ -369,7 +286,7 @@ void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
|
|
|
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);
|
|
|
+ FlushAudioTrack(tail);
|
|
|
}
|
|
|
|
|
|
tail->next = track;
|
|
@@ -380,6 +297,19 @@ void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *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)
|
|
|
{
|
|
|
if (len == 0) {
|
|
@@ -388,29 +318,43 @@ int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, cons
|
|
|
|
|
|
SDL_AudioTrack *track = queue->tail;
|
|
|
|
|
|
- if ((track) && !AUDIO_SPECS_EQUAL(track->spec, *spec)) {
|
|
|
- SDL_FlushAudioTrack(track);
|
|
|
+ if (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)) {
|
|
|
- SDL_AudioTrack *new_track = CreateChunkedAudioTrack(spec, queue->chunk_size);
|
|
|
+ for (;;) {
|
|
|
+ size_t written = WriteToAudioTrack(track, data, len);
|
|
|
+ data += written;
|
|
|
+ len -= written;
|
|
|
|
|
|
- if (!new_track) {
|
|
|
- return -1;
|
|
|
+ if (len == 0) {
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
- if (track) {
|
|
|
- track->next = new_track;
|
|
|
- } else {
|
|
|
- queue->head = new_track;
|
|
|
+ SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec);
|
|
|
+
|
|
|
+ if (!new_track) {
|
|
|
+ return -1;
|
|
|
}
|
|
|
|
|
|
+ track->next = new_track;
|
|
|
queue->tail = new_track;
|
|
|
-
|
|
|
track = new_track;
|
|
|
}
|
|
|
|
|
|
- return track->write(track, data, len);
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
iter = iter->next;
|
|
|
|
|
|
- size_t avail = track->avail(track);
|
|
|
+ size_t avail = track->tail - track->head;
|
|
|
|
|
|
if (avail >= SDL_SIZE_MAX - queued_bytes) {
|
|
|
queued_bytes = SDL_SIZE_MAX;
|
|
@@ -454,61 +398,244 @@ size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_Audi
|
|
|
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;
|
|
|
|
|
|
- for (;;) {
|
|
|
- if (!track) {
|
|
|
- return SDL_SetError("Reading past end of queue");
|
|
|
- }
|
|
|
+ if (track->head >= len) {
|
|
|
+ return &track->data[track->head - len];
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
|
|
|
- total += track->read(track, &data[total], len - total, SDL_TRUE);
|
|
|
+ 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) {
|
|
|
- return 0;
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
|
if (!next) {
|
|
|
- return SDL_SetError("Reading past end of incomplete track");
|
|
|
+ SDL_SetError("Reading past end of incomplete track");
|
|
|
+ return NULL;
|
|
|
}
|
|
|
|
|
|
- queue->head = next;
|
|
|
+ UpdateAudioQueueHistory(queue, track->data, track->tail);
|
|
|
|
|
|
- track->destroy(track);
|
|
|
+ queue->head = next;
|
|
|
+ DestroyAudioTrack(queue, track);
|
|
|
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;
|
|
|
|
|
|
- for (;;) {
|
|
|
- if (!track) {
|
|
|
- return SDL_SetError("Peeking past end of queue");
|
|
|
- }
|
|
|
+ if (track->tail - track->head >= len) {
|
|
|
+ return &track->data[track->head];
|
|
|
+ }
|
|
|
+
|
|
|
+ size_t total = 0;
|
|
|
|
|
|
- total += track->read(track, &data[total], len - total, SDL_FALSE);
|
|
|
+ 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) {
|
|
|
- return 0;
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
}
|