Question

I am using the render callback of the ioUnit to store the audio data into a circular buffer:

OSStatus ioUnitRenderCallback(
                          void *inRefCon,
                          AudioUnitRenderActionFlags *ioActionFlags,
                          const AudioTimeStamp *inTimeStamp,
                          UInt32 inBusNumber,
                          UInt32 inNumberFrames,
                          AudioBufferList *ioData)
{
    OSStatus err = noErr;

    AMNAudioController *This = (__bridge AMNAudioController*)inRefCon;

    err = AudioUnitRender(This.encoderMixerNode->unit,
                          ioActionFlags,
                          inTimeStamp,
                          inBusNumber,
                          inNumberFrames,
                          ioData);

    // Copy the audio to the encoder buffer
    TPCircularBufferCopyAudioBufferList(&(This->encoderBuffer), ioData, inTimeStamp, kTPCircularBufferCopyAll, NULL);

    return err;
}

I then want to read the bytes out of the circular buffer, feed them to libLame and then to libShout. I have tried starting a thread and using NSCondition to make it wait until data is available but this causes all sorts of issues due to using locks on the Core Audio callback.

What would be the recommended way to do this?

Thanks in advance.


More detail on how I implemented Adam's answer

I ended up taking Adam's advice and implemented it like so.

Producer

I use TPCircularBufferProduceBytes in the Core Audio Render callback to add the bytes to the circular buffer. In my case I have non-interleaved audio data so I ended up using two circular buffers.

Consumer

  1. I spawn a new thread using pthread_create
  2. Within the new thread create a new CFTimer and add it to the current CFRunLoop (an interval of 0.005 seconds appears to work well)
  3. I tell the current CFRunLoop to run
  4. Within my timer callback I encode the audio and send it to the server (returning quickly if no data is buffered)
  5. I also have a buffer size of 5MB which appears to work well (2MB was giving me overruns). This does seem a bit high :/
Was it helpful?

Solution 2

You're on the right track, but you don't need NSCondition. You definitely don't want to block. The circular buffer implementation you're using is lock free and should do the trick. In the audio render callback, put the data into the buffer by calling TPCircularBufferProduceBytes. Then in the reader context (a timer callback is good, as hotpaw suggests), call TPCircularBufferTail to get the tail pointer (read address) and number of available bytes to read, and then call TPCircularBufferConsume to do the actual reading. Now you've done the transfer without taking any locks. Just make sure the buffer you allocate is large enough to handle the worst-case condition where your reader thread gets held off by the os for whatever reason, otherwise you can hit a buffer overrun condition and will lose data.

OTHER TIPS

Use a repeating timer (NSTimer or CADisplayLink) to poll your lock-free circular buffer or FIFO. Skip doing work if there is not enough data in the buffer, and return (to the run loop). This works because you know the sample rate with high accuracy, and how much data you prefer or need to handle at a time, so can set the polling rate just slightly faster, to be on the safe side, but still be very close to the same efficiency as using conditional locks.

Using semaphores or locks (or anything else with unpredictable latency) in a real-time audio thread callback is not recommended.

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