Circular buffers are pretty much what they sound like – arrays that wrap around. They’re fantastically useful as scratch space for audio processing, and generally passing audio around efficiently.
They’re designed for FIFO (first-in-first-out) use, like storing audio coming in the microphone for later playback or processing.
Consider a naive alternative: You copy the incoming audio into an NSData you allocate, and then pass that NSData off. This means you’re allocating memory each time, and deallocating the memory later once you’re done processing. That allocation incurs a penalty, which can be a show-stopper when part of an audio pipeline – The Core Audio documentation advises against any allocations when within a render callback, for example.
Alternatively, you can allocate space in advance, and write to that, but that has problems too: Either you have a synchronisation nightmare, or you spend lots of time moving bytes around so that the unprocessed audio is always at the beginning of the array.
A better solution is to use a circular buffer, where data goes in at the head, and is read from the tail. When you produce data at the head, the head moves up the array, and wraps around at the end. When you consume at the tail, the tail moves up too, so the tail chases the head around the circle.
Here’s a simple C implementation I recently put together for my app Loopy: TPCircularBuffer
typedef struct { void *buffer; int32_t length; int32_t tail; int32_t head; volatile int32_t fillCount; } TPCircularBuffer; bool TPCircularBufferInit(TPCircularBuffer *buffer, int32_t length); void TPCircularBufferCleanup(TPCircularBuffer *buffer); void TPCircularBufferClear(TPCircularBuffer *buffer); // Reading (consuming) void* TPCircularBufferTail(TPCircularBuffer *buffer, int32_t* availableBytes); void TPCircularBufferConsume(TPCircularBuffer *buffer, int32_t amount); // Writing (producing) void* TPCircularBufferHead(TPCircularBuffer *buffer, int32_t* availableBytes); void TPCircularBufferProduce(TPCircularBuffer *buffer, int32_t amount); int TPCircularBufferProduceBytes(TPCircularBuffer *buffer, const void* src, int32_t len); |
Update: The implementation has been recently amended to make use of a neat virtual memory mapping technique that inserts a virtual copy of the buffer memory directly after the buffer’s end, negating the need for any buffer wrap-around logic. Clients can simply use the returned memory address as if it were contiguous space.
The virtual memory technique was originally proposed by Philip Howard, and adapted to Darwin by Kurt Revis.
Use TPCircularBufferHead to get a pointer to write to the buffer, followed by TPCircularBufferProduce to submit the written data, then TPCircularBufferTail to get a pointer to the next data to read, followed by TPCircularBufferConsume to free up the space once processed.
TPCircularBufferProduceBytes is a convenience routine for writing data straight to the buffer.
The implementation is thread-safe (no need for locks) in the case of a single producer and single consumer.
Here’s an example, using circular buffers to implement a simple playthrough scheme that plays audio coming in the microphone:
@interface MyAudioController : NSObject { TPCircularBuffer buffer; } @end #define kBufferLength 1024 @implementation MyAudioController - (void)setup { // Initialise buffer TPCircularBufferInit(&buffer, kBufferLength); // Setup audio, etc } - (void)dealloc { // Release buffer resources TPCircularBufferCleanup(&buffer); [super dealloc]; } static OSStatus audioInputCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { MyAudioController *THIS = (MyAudioController *)inRefCon; // Render audio into buffer AudioBufferList bufferList; bufferList.mNumberBuffers = 1; bufferList.mBuffers[0].mNumberChannels = 2; bufferList.mBuffers[0].mData = NULL; bufferList.mBuffers[0].mDataByteSize = inNumberFrames * sizeof(SInt16) * 2; OSStatus err = AudioUnitRender(THIS->ioAudioUnit, ioActionFlags, inTimeStamp, kInputBus, inNumberFrames, &bufferList); if ( !checkResultLite(err, "AudioUnitRender") ) { return err; } // Put audio into circular buffer TPCircularBufferProduceBytes(&THIS->buffer, bufferList.mBuffers[0].mData, inNumberFrames * 2 * sizeof(SInt16)); } static OSStatus audioOutputCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { MyAudioController *THIS = (MyAudioController *)inRefCon; int bytesToCopy = ioData->mBuffers[0].mDataByteSize; SInt16 *targetBuffer = (SInt16*)ioData->mBuffers[0].mData; // Pull audio from playthrough buffer int32_t availableBytes; SInt16 *buffer = TPCircularBufferTail(&THIS->buffer, &availableBytes); memcpy(targetBuffer, buffer, MIN(bytesToCopy, availableBytes)); TPCircularBufferConsume(&THIS->buffer, sampleCount); return noErr; } @end |



66 Comments
i’m a chinese girl, and a ios engineer,u blog is useful. thanks
i’m a chinese ios developer too, can we communicate with each other online? what’s your contact?
me too!
I have been looking around for a simple circular buffer implementation for an audio application I am writing, and this seems to fit the bill quite well. What is the license on the code? I need something that is either public domain, released under the Zlib/Libpng license, or under the Boost software license.