Initial work on the audio queue data source.

Public issue https://github.com/mackron/miniaudio/issues/744
This commit is contained in:
David Reid
2026-01-22 11:29:53 +10:00
parent ba963e46b5
commit 8a43271555
2 changed files with 327 additions and 3 deletions
+6 -3
View File
@@ -15,7 +15,7 @@ effect.
#define DEVICE_CHANNELS 1 /* For this example, always set to 1. */
static ma_waveform g_sourceData; /* The underlying data source of the source node. */
static ma_audio_buffer_ref g_exciteData; /* The underlying data source of the excite node. */
static ma_audio_queue g_exciteData; /* The underlying data source of the excite node. */
static ma_data_source_node g_sourceNode; /* A data source node containing the source data we'll be sending through to the vocoder. This will be routed into the first bus of the vocoder node. */
static ma_data_source_node g_exciteNode; /* A data source node containing the excite data we'll be sending through to the vocoder. This will be routed into the second bus of the vocoder node. */
static ma_vocoder_node g_vocoderNode; /* The vocoder node. */
@@ -37,7 +37,7 @@ void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uin
the data source is our `pInput` buffer. We need to update the underlying data source so that it
read data from `pInput`.
*/
ma_audio_buffer_ref_set_data(&g_exciteData, pInput, frameCount);
ma_audio_queue_push_pcm_frames(&g_exciteData, pInput, frameCount);
/* With the source buffer configured we can now read directly from the node graph. */
ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL);
@@ -53,6 +53,7 @@ int main(int argc, char** argv)
ma_data_source_node_config sourceNodeConfig;
ma_data_source_node_config exciteNodeConfig;
ma_waveform_config waveformConfig;
ma_audio_queue_config audioQueueConfig;
deviceConfig = ma_device_config_init(ma_device_type_duplex);
deviceConfig.capture.pDeviceID = NULL;
@@ -115,7 +116,9 @@ int main(int argc, char** argv)
/* Excite/modulator. Attached to input bus 1 of the vocoder node. */
result = ma_audio_buffer_ref_init(device.capture.format, device.capture.channels, device.sampleRate, NULL, 0, &g_exciteData);
audioQueueConfig = ma_audio_queue_config_init(device.capture.format, device.capture.channels, device.sampleRate, 0);
result = ma_audio_queue_init(&audioQueueConfig, &g_exciteData);
if (result != MA_SUCCESS) {
printf("Failed to initialize audio buffer for source.");
goto done2;
+321
View File
@@ -6189,6 +6189,60 @@ MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_b
/*
Audio Queue
===========
The audio queue is a data source that allows you to push data from one thread and then read it from
another. The audio queue can be fixed sized or dynamic. Dynamic is the default. To use a static
queue set the the size to non-0.
With a static queue, if you attempt to push data but there is no room, the entire data will be rejected
and MA_NO_SPACE will be returned. For a dynamic queue it is restricted by memory capacity.
There is no seeking and no notion of a cursor. The length will be how many PCM frames are currently
sitting in the queue.
The total capacity of the queue in PCM frames is restricted to 32-bits.
This is thread safe, but not currently 100% lock-free. This currently uses a dumb spinlock for thread
safety, but there are plans to improve on this later.
*/
typedef struct
{
ma_format format;
ma_uint32 channels;
ma_uint32 sampleRate;
ma_uint32 sizeInFrames; /* When set to 0 (the default), the queue will be dynamically expanding. When set to non-0, the audio queue will be static. */
ma_allocation_callbacks allocationCallbacks;
} ma_audio_queue_config;
MA_API ma_audio_queue_config ma_audio_queue_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames);
typedef struct
{
ma_data_source_base ds;
ma_format format;
ma_uint32 channels;
ma_uint32 sampleRate;
ma_bool32 isStatic;
ma_uint32 capacityInFrames;
ma_uint64 cursorWrite;
ma_uint64 cursorRead;
void* pBuffer;
ma_allocation_callbacks allocationCallbacks;
ma_spinlock lock; /* Temporary until we get a good lock-free solution working. */
} ma_audio_queue;
MA_API ma_result ma_audio_queue_init(const ma_audio_queue_config* pConfig, ma_audio_queue* pAudioQueue);
MA_API void ma_audio_queue_uninit(ma_audio_queue* pAudioQueue);
MA_API ma_result ma_audio_queue_push_pcm_frames(ma_audio_queue* pAudioQueue, const void* pFrames, ma_uint64 frameCount);
MA_API ma_result ma_audio_queue_read_pcm_frames(ma_audio_queue* pAudioQueue, void* pFrames, ma_uint64 frameCount, ma_uint64* pFramesRead);
MA_API ma_result ma_audio_queue_get_length_in_pcm_frames(ma_audio_queue* pAudioQueue, ma_uint64* pLength);
/************************************************************************************************************************************************************
Ring Buffer
@@ -66125,6 +66179,273 @@ MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_b
MA_API ma_audio_queue_config ma_audio_queue_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames)
{
ma_audio_queue_config config;
MA_ZERO_OBJECT(&config);
config.format = format;
config.channels = channels;
config.sampleRate = sampleRate;
config.sizeInFrames = sizeInFrames;
return config;
}
static ma_result ma_audio_queue__data_source_on_read(ma_data_source* pDataSource, void* pFrames, ma_uint64 frameCount, ma_uint64* pFramesRead)
{
return ma_audio_queue_read_pcm_frames((ma_audio_queue*)pDataSource, pFrames, frameCount, pFramesRead);
}
static ma_result ma_audio_queue__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
{
ma_audio_queue* pAudioQueue = (ma_audio_queue*)pDataSource;
*pFormat = pAudioQueue->format;
*pChannels = pAudioQueue->channels;
*pSampleRate = pAudioQueue->sampleRate;
ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pAudioQueue->channels);
return MA_SUCCESS;
}
static ma_result ma_audio_queue__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength)
{
return ma_audio_queue_get_length_in_pcm_frames((ma_audio_queue*)pDataSource, pLength);
}
static ma_data_source_vtable ma_gDataSourceVTable_AudioQueue =
{
ma_audio_queue__data_source_on_read,
NULL, /* No seeking in audio queues. */
ma_audio_queue__data_source_on_get_data_format,
NULL, /* No notion of a cursor. */
ma_audio_queue__data_source_on_get_length,
NULL, /* onSetLooping */
0
};
MA_API ma_result ma_audio_queue_init(const ma_audio_queue_config* pConfig, ma_audio_queue* pAudioQueue)
{
ma_result result;
ma_data_source_config dataSourceConfig;
if (pAudioQueue == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pAudioQueue);
if (pConfig == NULL) {
return MA_INVALID_ARGS;
}
/* Initialize the data source first. */
dataSourceConfig = ma_data_source_config_init();
dataSourceConfig.vtable = &ma_gDataSourceVTable_AudioQueue;
result = ma_data_source_init(&dataSourceConfig, &pAudioQueue->ds);
if (result != MA_SUCCESS) {
return result;
}
pAudioQueue->format = pConfig->format;
pAudioQueue->channels = pConfig->channels;
pAudioQueue->sampleRate = pConfig->sampleRate;
pAudioQueue->capacityInFrames = pConfig->sizeInFrames;
ma_allocation_callbacks_init_copy(&pAudioQueue->allocationCallbacks, &pConfig->allocationCallbacks);
if (pAudioQueue->capacityInFrames > 0) {
pAudioQueue->pBuffer = ma_malloc(pAudioQueue->capacityInFrames * ma_get_bytes_per_frame(pAudioQueue->format, pAudioQueue->channels), &pAudioQueue->allocationCallbacks);
if (pAudioQueue->pBuffer == NULL) {
return MA_OUT_OF_MEMORY;
}
pAudioQueue->isStatic = MA_TRUE;
}
return MA_SUCCESS;
}
MA_API void ma_audio_queue_uninit(ma_audio_queue* pAudioQueue)
{
if (pAudioQueue == NULL) {
return;
}
ma_free(pAudioQueue->pBuffer, &pAudioQueue->allocationCallbacks);
}
static void ma_audio_queue_write_pcm_frames_nolock(ma_audio_queue* pAudioQueue, const void* pFrames, ma_uint64 frameCount, ma_uint32 bpf)
{
ma_uint64 framesWritten = 0;
while (framesWritten < frameCount) {
ma_uint64 framesRemaining = (frameCount - framesWritten);
ma_uint64 framesToWriteThisIteration = framesRemaining;
ma_uint64 writeOffset;
writeOffset = pAudioQueue->cursorWrite % pAudioQueue->capacityInFrames;
if (framesToWriteThisIteration + writeOffset > pAudioQueue->capacityInFrames) {
framesToWriteThisIteration = pAudioQueue->capacityInFrames - writeOffset;
}
MA_COPY_MEMORY(ma_offset_ptr(pAudioQueue->pBuffer, writeOffset * bpf), ma_offset_ptr(pFrames, framesWritten * bpf), (size_t)(framesToWriteThisIteration * bpf));
pAudioQueue->cursorWrite += framesToWriteThisIteration;
framesWritten += framesToWriteThisIteration;
}
}
static void ma_audio_queue_read_pcm_frames_nolock(ma_audio_queue* pAudioQueue, void* pFrames, ma_uint64 frameCount, ma_uint32 bpf)
{
ma_uint64 framesRead = 0;
while (framesRead < frameCount) {
ma_uint64 framesRemaining = (frameCount - framesRead);
ma_uint64 framesToReadThisIteration = framesRemaining;
ma_uint64 readOffset;
readOffset = pAudioQueue->cursorRead % pAudioQueue->capacityInFrames;
if (framesToReadThisIteration + readOffset > pAudioQueue->capacityInFrames) {
framesToReadThisIteration = pAudioQueue->capacityInFrames - readOffset;
}
MA_COPY_MEMORY(ma_offset_ptr(pFrames, framesRead * bpf), ma_offset_ptr(pAudioQueue->pBuffer, readOffset * bpf), (size_t)(framesToReadThisIteration * bpf));
pAudioQueue->cursorRead += framesToReadThisIteration;
framesRead += framesToReadThisIteration;
}
}
MA_API ma_result ma_audio_queue_push_pcm_frames(ma_audio_queue* pAudioQueue, const void* pFrames, ma_uint64 frameCount)
{
ma_result result;
ma_uint64 length;
ma_uint32 bpf;
ma_uint32 newCapacity;
void* pNewBuffer = NULL;
void* pOldBuffer = NULL;
if (pAudioQueue == NULL) {
return MA_INVALID_ARGS;
}
if (pFrames == NULL) {
return MA_INVALID_ARGS; /* Push silence instead? */
}
result = ma_audio_queue_get_length_in_pcm_frames(pAudioQueue, &length);
if (result != MA_SUCCESS) {
return result;
}
bpf = ma_get_bytes_per_frame(pAudioQueue->format, pAudioQueue->channels);
if (frameCount > (pAudioQueue->capacityInFrames - length)) {
if (pAudioQueue->isStatic) {
return MA_NO_SPACE;
} else {
newCapacity = pAudioQueue->capacityInFrames * 2;
if (newCapacity < length + frameCount) {
newCapacity = length + frameCount;
}
pNewBuffer = ma_malloc(newCapacity * bpf, &pAudioQueue->allocationCallbacks);
if (pNewBuffer == NULL) {
return MA_OUT_OF_MEMORY;
}
}
}
ma_spinlock_lock(&pAudioQueue->lock);
{
length = pAudioQueue->cursorWrite - pAudioQueue->cursorRead;
/* If we have a new buffer we need to swap out the old one with the new. */
if (pNewBuffer != NULL) {
pOldBuffer = pAudioQueue->pBuffer;
/* Fill the new buffer. */
ma_audio_queue_read_pcm_frames_nolock(pAudioQueue, pNewBuffer, length, bpf);
/* Normalize our cursors. */
pAudioQueue->cursorRead = 0;
pAudioQueue->cursorWrite = length;
/* Swap out the buffer. */
pAudioQueue->pBuffer = pNewBuffer;
pAudioQueue->capacityInFrames = newCapacity;
}
ma_audio_queue_write_pcm_frames_nolock(pAudioQueue, pFrames, frameCount, bpf);
}
ma_spinlock_unlock(&pAudioQueue->lock);
if (pOldBuffer != NULL) {
ma_free(pOldBuffer, &pAudioQueue->allocationCallbacks);
}
return MA_SUCCESS;
}
MA_API ma_result ma_audio_queue_read_pcm_frames(ma_audio_queue* pAudioQueue, void* pFrames, ma_uint64 frameCount, ma_uint64* pFramesRead)
{
ma_result result;
ma_uint64 length;
ma_uint32 bpf;
/* Clamp the frame count to the length. */
result = ma_audio_queue_get_length_in_pcm_frames(pAudioQueue, &length);
if (result != MA_SUCCESS) {
return result;
}
if (frameCount > length) {
frameCount = length;
}
bpf = ma_get_bytes_per_frame(pAudioQueue->format, pAudioQueue->channels);
ma_spinlock_lock(&pAudioQueue->lock);
{
ma_audio_queue_read_pcm_frames_nolock(pAudioQueue, pFrames, frameCount, bpf);
}
ma_spinlock_unlock(&pAudioQueue->lock);
*pFramesRead = frameCount;
/* Note that we never returning MA_AT_END for a queue because there isn't really a notion of it. */
return MA_SUCCESS;
}
MA_API ma_result ma_audio_queue_get_length_in_pcm_frames(ma_audio_queue* pAudioQueue, ma_uint64* pLength)
{
if (pLength == NULL) {
return MA_INVALID_ARGS;
}
*pLength = 0;
if (pAudioQueue == NULL) {
return MA_INVALID_ARGS;
}
ma_spinlock_lock(&pAudioQueue->lock);
{
*pLength = (pAudioQueue->cursorWrite - pAudioQueue->cursorRead);
}
ma_spinlock_unlock(&pAudioQueue->lock);
return MA_SUCCESS;
}
/**************************************************************************************************************************************************************
VFS