Blog

A simple, fast circular buffer implementation for audio processing

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
Tagged , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

66 Comments

  1. Posted March 5, 2013 at 8:56 am | Permalink

    i’m a chinese girl, and a ios engineer,u blog is useful. thanks

    • Pete
      Posted April 22, 2013 at 8:26 am | Permalink

      i’m a chinese ios developer too, can we communicate with each other online? what’s your contact?

  2. Posted April 2, 2013 at 9:49 am | Permalink

    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.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use Markdown (surround code in `back-ticks`), or these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Subscribe without commenting