Kadinski
Kadinski

Reputation: 161

Overwriting AudioBufferList.mBuffers[0].mData?

I have a RemoteIO audio unit set up with a render callback. I already have a buffer of rendered audio, and for performance reasons would like to avoid a memcpy.

OSStatus FMixerPlatformAudioUnit::AudioRenderCallback(void* RefCon, AudioUnitRenderActionFlags* ActionFlags,
                                                          const AudioTimeStamp* TimeStamp, UInt32 BusNumber,
                                                          UInt32 NumFrames, AudioBufferList* IOData) 
{
   IOData->mBuffers[0].mData = RenderedBufferPtr;
   RefreshRenderedBufferPtr();
}

This works and sounds fine. However, I am worried that by overwriting IOData->mBuffers[0].mData, I might be leaving the original buffer that mData was pointing to dangling, and thus might be causing a memory leak. Is it an issue to rewrite IOData->mBuffers[0].mData in an InputCallback proc?

Upvotes: 0

Views: 448

Answers (2)

dave234
dave234

Reputation: 4955

I'll start with the annoying answer, just memcpy. This type of optimization will be impossible to measure, I guarantee it.

Now to answer your question.

It's hard to say without measuring, but if I were to expose a bufferlist pointer like the authors of the framework have done, I would keep a separate pointer to the memory to avoid just such a situation.

This is probably nothing like what they're doing, but just to illustrate how you could protect your framework from this type of vulnerability. internalRender is passing clientRender a pointer to a local variable that is created on the stack every render, so you can mangle it as much as you want (as long as you don't try and free it) and you can't make it lose its original pointer (passed in as RefoCon for brevity).

//this is your callback
OSStatus clientRender(void* RefCon, AudioUnitRenderActionFlags* ActionFlags,
                  const AudioTimeStamp* TimeStamp, UInt32 BusNumber,
                  UInt32 NumFrames, AudioBufferList* IOData){

    IOData->mBuffers[0].mData = nil;

    return noErr;
}

//Framework calls this
OSStatus internalRender(void* RefCon, AudioUnitRenderActionFlags* ActionFlags,
                  const AudioTimeStamp* TimeStamp, UInt32 BusNumber,
                  UInt32 NumFrames, AudioBufferList* IOData){

    void *internalMemory = RefCon;
    AudioBufferList clientBufferlist;
    clientBufferlist.mBuffers[0].mData = internalMemory;


    return clientRender(RefCon,ActionFlags,TimeStamp,BusNumber,NumFrames,&clientBufferlist);
}

The only way to tell is to measure. Open the Instruments app and use the Leaks tool and you'll have your answer. But really, the right way to do it is to just memcpy. It's plenty fast unless you can prove otherwise and is the intended use.

Upvotes: 1

hotpaw2
hotpaw2

Reputation: 70733

On any current Apple arm64 CPU, the time to memcpy per sample is on the order of 10,000 times faster than the sample rate period, so is unlikely to be a measurable percentage of the audio unit callback rate. Thus swapping buffers won't perform noticeably faster.

But your bigger problem is that the NumFrames is not guaranteed to remain the same between each successive audio unit callback in iOS, so just swapping the buffer pointer is not the same as copying the exact requested number of samples or audio frames. Your audio will glitch depending on other events on the iOS device (power save modes, notifications, phone calls, key clicks, etc.)

Furthermore, if you didn't allocate it, you don't know the memory allocation size of the IOData buffer you swap out, which might be another possible source of memory corruption if the audio unit assumes a different size.

Upvotes: 2

Related Questions