Just can\'t seem to get very far in Core Audio. My goal is to write captured audio data from an instrument unit to a file. I have set up a call to a callback function on an inst
First, your code is still slightly complicated, and including some of "dark corners" of CoreAudio and Obj-C. It is a safer bet first making sure that everything works as intended in plain-C, on the real-time thread. As soon as you have debugged that part of code you can easily add as much Obj-C elegance as needed.
If ignoring possible endianness and file format conversion issues for simplicity, there is still one issue you either have to resolve automatically, using API utilities, or "manually":
AFAIK, data format for ExtAudioFileWriteAsync()
must be interleaved, while the stream format for your AUGraph is not. Assuming we don't deal with endiannes and format conversion here, this is how you can fix it manually (single-channel example). In case your asbd
stream format is non-interleaved stereo, you interleave data in your buffer like this: LRLRLRLRLR...
OSStatus MyRenderProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * ioData)
{
AudioBufferList bufferList;
Float32 samples[inNumberFrames+inNumberFrames];
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mData = samples;
bufferList.mBuffers[0].mNumberChannels = 1;
bufferList.mBuffers[0].mDataByteSize = (inNumberFrames+inNumberFrames)*sizeof(Float32);
Float32 *data = (Float32 *)ioData->mBuffers[0].mData;
if (*ioActionFlags & kAudioUnitRenderAction_PostRender){
static int TEMP_kAudioUnitRenderAction_PostRenderError = (1 << 8);
if (!(*ioActionFlags & TEMP_kAudioUnitRenderAction_PostRenderError) {
for(UInt32 i = 0; i < inNumberFrames; i++)
samples[i+i] = samples [i+i+1] = data[i];//copy buffer[0] to L & R
ExtAudioFileWriteAsync(testRecordFile, inNumberFrames, &bufferList);
}
}
return noErr;
}
This is just one example to show how it works. By studying asbd.mFormatFlags
and setting the proper format in:
ExtAudioFileSetProperty(testRecordFile,
kExtAudioFileProperty_ClientDataFormat,
s,
&asbd);
you can achieve it more elegantly, but this exceeds the scope you this post by far.
Here's the working callback for a 16-bit linear big-endian stereo AIF file:
OSStatus MyRenderProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * ioData)
{
SInt16 samples[inNumberFrames + inNumberFrames];
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mData = samples;
bufferList.mBuffers[0].mNumberChannels = 1;
bufferList.mBuffers[0].mDataByteSize = (inNumberFrames + inNumberFrames) * sizeof(SInt16);
Float32 *leftData = (Float32 *)ioData->mBuffers[0].mData;
Float32 *rightData = (Float32 *)ioData->mBuffers[1].mData;
if (*ioActionFlags & kAudioUnitRenderAction_PostRender){
static int TEMP_kAudioUnitRenderAction_PostRenderError = (1 << 8);
if (!(*ioActionFlags & TEMP_kAudioUnitRenderAction_PostRenderError)){
for (UInt32 i = 0; i < inNumberFrames; i++) {
samples[i + i] = CFSwapInt16HostToBig((SInt16) SHRT_MAX * (leftData)[i]);
samples[i + i + 1] = CFSwapInt16HostToBig((SInt16) SHRT_MAX * (rightData)[i]);
}
CheckError(ExtAudioFileWriteAsync(testRecordFile, inNumberFrames, &bufferList), "ExtAudioFileWriteAsync failed");
}
}
return noErr;
}