diff --git a/examples/duplex_effect.c b/examples/duplex_effect.c index 9adf5d8a..4a200d80 100644 --- a/examples/duplex_effect.c +++ b/examples/duplex_effect.c @@ -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; diff --git a/miniaudio.h b/miniaudio.h index 637f9245..051085db 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -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