Question

I'm writing a VOIP based app for the iPhone. I'm having a strange issue where when the user presses the screen there is a glitch in the audio, this happens also when you press the volume up / down buttons on the phone itself. After days of debugging I've found its something to do with my circular buffer. I swapped mine for the one here:

http://atastypixel.com/blog/a-simple-fast-circular-buffer-implementation-for-audio-processing/

this one doesn't cause a glitch but the latency is nearly 4 times longer than mine, I have to have minimal latency and can't figure out whats going on with my app.

Setup:

I followed: http://www.stefanpopp.de/2011/capture-iphone-microphone/ somewhat to make the basic app but I have different settings / functions etc. I have a view controller that has a property of this audioProcessor class, this class has a variable for the circular buffer. In the recording callback I send the data, this is all fine. In a CFSocket callback I add data from the network to this buffer and then the playback callback pulls data from this buffer and passes it to the system.

At some point during the playback if the user presses triggers a UI event it all goes to hell and this strange data shows up. I'm guessing its some sort of threading issue but I have little or no experience in this area. I'd appreciate any help. Here is the relative code:

Network Callback - adding data to buffer:

static void addDataToBuffer(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
    AudioUnitCBufferProduce(&audioProcessor->auCBuffer, (uint8_t*)[(__bridge NSData *)data bytes], [(__bridge NSData *)data length]);
}

Audio unit playback - copying data form the buffer and placing into "targetBuffer" which is pointing to ioData:

static OSStatus playbackCallback(void *inRefCon,
                             AudioUnitRenderActionFlags *ioActionFlags,
                             const AudioTimeStamp *inTimeStamp,
                             UInt32 inBusNumber,
                             UInt32 inNumberFrames,
                             AudioBufferList *ioData)
{

    uint8_t *targetBuffer = (uint8_t*)ioData->mBuffers[0].mData;
    AudioUnitCBufferConsume(&audioProcessor->auCBuffer, targetBuffer, inNumberFrames);

    return noErr;
}

Buffer init:

void AudioUnitCBufferInit(AudioUnitCBuffer *b)
{
  // create array of bytes of length specified, fill with silence
  uint8_t buffer[2048];

  for(int i = 0; i < 2048; i++)
  {
      buffer[i] = 0xd5;
  }

  // init buffer elements
  b->buffer = buffer;
  b->consumer = buffer;
  b->producer = buffer;
  b->length = 2048;
}

Buffer producer / consumer:

This is written so that you pass in a pointer to the function and this pointer is then filled with the data, should there be no data the pointer will be filled with the ALAW hex value for silence. This keeps the audio unit code small as the buffer ensures that it will always supply it with data. This is also mush faster than copying to somewhere temporary and then memcpy 'ing it into the buffer which the link above uses and is far to slow for my needs.

inline static void AudioUnitCBufferProduce(AudioUnitCBuffer *b, uint8_t *bytes, int32_t len)
{   
//printf("\n\ninside producer: len %i \n\n", len);
while(len--)
{
    // if producer catches up with consumer, skip a byte
    if (b->producer+1 == b->consumer)
    {
        //printf("b->producer+1 == b->consumer == continue \n");
        continue;
    }
    else
    {
        //printf("b->producer+1 != b->consumer == add byte \n");
        *b->producer = *bytes++;
        b->producer++;

        if(b->producer == &b->buffer[b->length-1])
        {
            //printf("\n\nproducer == end, skipping \n\n");
            b->producer = b->buffer;
        }
    }
}
}

inline static void AudioUnitCBufferConsume(AudioUnitCBuffer *b, uint8_t *bytes, int32_t len)
{
while(len--)
{
    // if producer catches up with consumer, skip a byte
    if (b->consumer == b->producer)
    {
        *bytes++ = 0xd5;
    }
    else
    {
        *bytes++ = *b->consumer;
        b->consumer++;

        if(b->consumer == &b->buffer[b->length-1])
        {
            b->consumer = b->buffer;
        }
    }
}
}
Was it helpful?

Solution

Ok wrote a different style of Circular buffer that seems to have done the trick, very similar latency and no glitching. I still don't fully understand why this is better, any one with experience in this please share.

Due to very little of this stuff being posted on by apple, below is my Circular buffer implementation that works well with my VOIP setup, feel free to use it, any suggestions are welcome, just don't come after me if it doesn't work for you. This time its an objective-c class.

Please note that this was designed to use with ALAW format not linearPCM, "0xd5" is a byte of silence in ALAW, unsure what this will be in PCM but would expect it to be noise.

CircularBuffer.h:

//
//  CircularBuffer.h
//  clevercall
//
//  Created by Simon Mcloughlin on 10/1/2013.
//
//

#import <Foundation/Foundation.h>

@interface CircularBuffer : NSObject

-(int) availableBytes;
-(id) initWithLength:(int)length;
-(void) produceToBuffer:(const void*)data ofLength:(int)length;
-(void) consumeBytesTo:(void *)buf OfLength:(int)length;

@end

CircularBuffer.m:

//
//  CircularBuffer.m
//  clevercall
//
//  Created by Simon Mcloughlin on 10/1/2013.
//
//

#import "CircularBuffer.h"

@implementation CircularBuffer
{
    unsigned int gBufferLength;
    unsigned int gAvailableBytes;
    unsigned int gHead;
    unsigned int gTail;
    void *gBuffer;
}

// Init instance with a certain length and alloc the space
-(id)initWithLength:(int)length
{
    self = [super init];

    if (self != nil)
    {
        gBufferLength = length;
        gBuffer = malloc(length);
        memset(gBuffer, 0xd5, length);

        gAvailableBytes = 0;
        gHead = 0;
        gTail = 0;
    }

    return self;
}

// return the number of bytes stored in the buffer
-(int) availableBytes
{
    return gAvailableBytes;
}

-(void) produceToBuffer:(const void*)data ofLength:(int)length
{
    // if the number of bytes to add to the buffer will go past the end.
    // copy enough to fill to the end
    // go back to the start
    // fill the remaining
    if((gHead + length) > gBufferLength-1)
    {
        int remainder = ((gBufferLength-1) - gHead);
        memcpy(gBuffer + gHead, data, remainder);
        gHead = 0;
        memcpy(gBuffer + gHead, data + remainder, (length - remainder));
        gHead += (length - remainder);
        gAvailableBytes += length;
    }
    // if there is room in the buffer for these bytes add them
    else if((gAvailableBytes + length) <= gBufferLength-1)
    {
        memcpy(gBuffer + gHead, data, length);
        gAvailableBytes += length;
        gHead += length;
    }
    else
    {
        //NSLog(@"--- Discarded ---");
    }
}

-(void) consumeBytesTo:(void *)buf OfLength:(int)length
{
    // if the tail is at a point where there is not enough between it and the end to fill the buffer.
    // copy out whats left
    // move back to the start
    // copy out the rest
    if((gTail + length) > gBufferLength-1 && length <= gAvailableBytes)
    {
        int remainder = ((gBufferLength-1) - gTail);
        memcpy(buf, gBuffer + gTail, remainder);
        gTail = 0;
        memcpy(buf + remainder, gBuffer, (length -remainder));
        gAvailableBytes-=length;
        gTail += (length -remainder);
    }
    // if there is enough bytes in the buffer
    else if(length <= gAvailableBytes)
    {
        memcpy(buf, gBuffer + gTail, length);
        gAvailableBytes-=length;
        gTail+=length;
    }
    // else play silence
    else
    {
        memset(buf, 0xd5, length);
    }
}

@end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top