I have already explained how to record audio using AudioQueue's, however that method is really only practical for recording audio into a specific file, it doesn't let us drill down to sound samples and sound analysis. However RemoteIO is a lot more fragile than AudioQueue, and a lot less refined. Apple also provides limited documentation on its usage, it is however a good idea to look up Apples aurioTouch example code. This is however a bit involved, especially if you just want to master the basics first, although I will recommend it's fourier transform library. Anyway onto business, first thing we will need to do is define our variables in our interface:
@interface AudioRIO : NSObject
{
AURenderCallbackStruct inputProc;
AudioUnit rioUnit;
AudioStreamBasicDescription audioFormat;
AudioBufferList* bufferList;
BOOL startedCallback;
}
OK now I'll explain what we've done here. The inputProc variable holds the information about the callback function we give to the RemoteIO system. The rioUnit is simply an identifier for the recording unit we have started. The audioFormat is much like the audioFormat in the AudioQueue tutorial, it defines information like the sample rate (but we'll come to that in a minute). The bufferList is a bufferList we shall allocate at the start to preventing allocation from happening in the audio callback, the reason for this is because RemoteIO does not take kindly to people using allocation methods during its callback (I have no idea why, but it slows down the entire program to a crawl for 9 seconds if you). startedCallback simply logs if our Audio Unit has started, and once it has we can begin audio analysis. Next we will define our callback method:
static OSStatus AudioRemoteIOInput(void* inRefCon,AudioUnitRenderActionFlags* ioActionFlags,const AudioTimeStamp* inTimeStamp,UInt32 inBusNumber,UInt32 inNumberFrames,AudioBufferList* ioData)
{
AudioRIO* audioRIO = (AudioRIO*) inRefCon;
ioData = audioRIO.bufferList;
OSStatus result = AudioUnitRender(audioRIO.rioUnit,ioActionFlags,inTimeStamp,inBusNumber,inNumberFrames,ioData);
if(audioRIO.startedCallback)
{
switch(result)
{
case noErr:
{
doSomethingWithAudioBuffer((SInt16*)audioRIO.bufferList->mBuffers[0].mData,inNumberFrames);
break;
}
case kAudioUnitErr_InvalidProperty: NSLog(@"AudioUnitRender Failed: Invalid Property"); break;
case -50: NSLog(@"AudioUnitRender Failed: Invalid Parameter(s)"); break;
default: NSLog(@"AudioUnitRender Failed: Unknown (%d)",result); break;
}
}
return noErr;
}
This method is our callback method, it will be called every time RemoteIO has filled its buffer, and will then pass the buffer along to us for us to do what we want with it. You have to be very careful with the AudioUnitRender, and if your having any problems in your code make sure you check the logs to see if it has returned noErr (no error). Now we have to add our interruption listeners and our property listeners, if we don't at least give them somewhere to point to the RemoteIO becomes very unstable:
void rioInterruptionListener(void* inClientData, UInt32 inInterruption)
{
NSLog(@"rioInterruptionListener");
}
void propListener(void* inClientData,AudioSessionPropertyID inID,UInt32 inDataSize,const void* inData)
{
NSLog(@"propListener");
}
As you can see they do nothing (though I have lately been thinking to reroute audio through them, like the aurioTouch example does), but my applications have worked fine so far using this method. So now we have to initialise our audio units:
-(id) init
{
[super init];
startedCallback = NO;
inputProc.inputProc = AudioRemoteIOInput;
inputProc.inputProcRefCon = self;
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioSessionInitialize(NULL,NULL,rioInterruptionListener,self);
AudioSessionSetActive(true);
UInt32 audioCategory = kAudioSessionCategory_RecordAudio;
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,sizeof(audioCategory),&audioCategory);
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,propListener,self);
AudioComponent inputComponent = AudioComponentFindNext(NULL,&desc);
if(AudioComponentInstanceNew(inputComponent,&rioUnit) == noErr)
{
UInt32 flag = 1;
if(AudioUnitSetProperty(rioUnit,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Input,kInputBus,&flag,sizeof(flag)) == noErr)
{
flag = 0;
AudioUnitSetProperty(rioUnit,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Output,kOutputBus,&flag,sizeof(flag));
audioFormat.mSampleRate = 44100.00;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 1;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = 2;
audioFormat.mBytesPerFrame = 2;
if(AudioUnitSetProperty(rioUnit,kAudioUnitProperty_StreamFormat,kAudioUnitScope_Output,kInputBus,&audioFormat,sizeof(audioFormat)) == noErr)
{
if(AudioUnitSetProperty(rioUnit,kAudioOutputUnitProperty_SetInputCallback,kAudioUnitScope_Global,kInputBus,&inputProc,sizeof(inputProc)) == noErr)
{
UInt32 allocFlag = 1;
if(AudioUnitSetProperty(rioUnit,kAudioUnitProperty_ShouldAllocateBuffer,kAudioUnitScope_Input,1,&allocFlag,sizeof(allocFlag)) == noErr)
{
if(AudioUnitInitialize(rioUnit) == noErr)
{
bufferList = (AudioBufferList*) malloc(sizeof(AudioBufferList));
bufferList->mNumberBuffers = audioFormat.mChannelsPerFrame;
for(UInt32 i=0;i<bufferList->mNumberBuffers;i++)
{
bufferList->mBuffers[i].mNumberChannels = 1;
bufferList->mBuffers[i].mDataByteSize = (1024*2) * audioFormat.mBytesPerFrame;
bufferList->mBuffers[i].mData = malloc(bufferList->mBuffers[i].mDataByteSize);
}
if(AudioOutputUnitStart(rioUnit) == noErr) // Takes a long time
{
NSLog(@"AudioRemoteIO Started");
startedCallback = YES;
}
else NSLog(@"Failed AudioOutputUnitStart");
}
else NSLog(@"Failed AudioUnitInitialize");
}
else NSLog(@"Failed AudioUnitSetProperty ShouldAllocateBuffer");
}
else NSLog(@"Failed AudioUnitSetProperty inputProc");
}
else NSLog(@"Failed AudioUnitSetProperty");
}
else NSLog(@"Failed AudioUnitSetProperty");
}
else NSLog(@"Failed AudioComponentInstanceNew");
return self;
}
So here is where we do all the allocating and initialising for our RemoteIO process. Once startedCallback is equal to YES then we have a functioning RemoteIO that is calling our callback method regularly with buffers full of sound samples. So only 1 more thing to do now, and that's learn how to shut down the RemoteIO without causing any unstability in the program. I generally add a terminate method to my class, but it doesn't matter what you name it as long as you simply call these 2 functions in it:
AudioOutputUnitStop(rioUnit);
AudioUnitUninitialize(rioUnit);
Now we have a nice stable RemoteIO unit giving us updates on recorded samples whenever the buffers fill up. This enables us to do processing on the samples, which can give us a range of rich applications.