Friday, September 28, 2012

Recording a voice audio in iPhone


Recording a voice audio in iPhone

This tutorial explains how to record a voice in iPhone.

There are multiple ways to record audio on the iPhone, but some are suited to tasks better than others. I'm going to show you a simple way to record audio into a file and get its power level (volume if you will). This tutorial makes use of the AudioToolbox framework which you must add to your project to get this code working. Firstly I highly recommend you make your own AudioRecorder class to deal with the low level recording, that way you can just port your class from project to project with little hassle, and save time writing your own code. Firstly create a header file like so:

#import 
#import 
#import 

@interface AudioRecorder : NSObject
{
AudioStreamBasicDescription dataFormat;
}

-(id) init;

@end

This adds a description of your audio stream to your class, now you will want to write your initialisation code in your main file, this is where we will fill the dataFormat instance variable.

-(id) init
{
dataFormat.mSampleRate = 44100.0f;
dataFormat.mFormatID = kAudioFormatLinearPCM;
dataFormat.mFramesPerPacket = 1;
dataFormat.mChannelsPerFrame = 1;
dataFormat.mBytesPerFrame = 2;
dataFormat.mBytesPerPacket = 2;
dataFormat.mBitsPerChannel = 16;
dataFormat.mReserved = 0;
dataFormat.mFormatFlags = kLinearPCMFormatFlagIsBigEndian | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
return self;
}

Basically what this code is saying is that we want a mono recording (mChannelsPerFrame) at a sample rate of 44.1kHz (mSampleRate) using SInt16's (mBytesPerFrame and kLinearPCMFormatFlagIsSignedInteger) and that we are recording in Linear PCM (pretty standard). So now that we have sorted those parameters out, we need to add the Audio Queue and the Input Callback, this means not only another instance variable in our header, but also a function that is outside the scope of the class, however by cleverly inputting the right data we can make it work for multiple instances of the class.

@interface AudioRecorder : NSObject
{
AudioStreamBasicDescription dataFormat;
AudioQueueRef queue;
}

@property AudioQueueRef queue;

// Our new C callback
void AudioInputCallback(void* inUserData,AudioQueueRef inAQ,AudioQueueBufferRef inBuffer,const AudioTimeStamp *inStartTime,UInt32 inNumberPacketDescriptions,const AudioStreamPacketDescription *inPacketDescs)
{
}
// Declaring the get/set of our new public variable
@synthesize queue;
// Add this code to the init function
if(AudioQueueNewInput(&dataFormat,AudioInputCallback,self,CFRunLoopGetCurrent(),kCFRunLoopCommonModes,0,&queue) == noErr)
{
}

So this declares the queue variable public, adds our AudioInputCallback C function, and inputs data into our audio queue. Next we have to deal with our audio buffers:

// Add this variable to our interface
AudioQueueBufferRef buffers[3];

// Add this code into our init function
for(int i=0;i<3 br="br" i="i">{
AudioQueueAllocateBuffer(queue,(dataFormat.mSampleRate/10.0f)*dataFormat.mBytesPerFrame,&buffers[i]);
AudioQueueEnqueueBuffer(queue,buffers[i],0,nil);
}

This allocates our buffers with the right amount of space, and then Enqueue's them to await our audio recording input. Next we will kick start the process by using the AudioQueueStart function.

AudioQueueStart(queue, NULL);

Now we have a functioning audio recording code, you will notice if you put logs into AudioInputCallback, you will see it gets called every so often (ie when the buffers will up), however in the callback we will need to handle the continued Enqueue of our buffers defined in the interface, so we will need to add this into the AudioInputCallback function:

AudioRecorder* recorder = (AudioRecorder*) inUserData;
AudioQueueEnqueueBuffer(recorder.queue,inBuffer,0,nil);

Now what this does is grabs our AudioRecorder class from the inUserData, then uses the queue defined in that class to enqueue the next buffer for getting more audio. Now we are functioning, but what about writing the recorded audio data to a file or getting the power of an audio recording? Well that's coming here, first I'll focus on the power level (because it's much easier than writing file data):

// Define these functions in the interface
-(float) getPeakPower;
-(float) getAveragePower;

// At the end of init
UInt32 enabledLevelMeter = true;
AudioQueueSetProperty(queue,kAudioQueueProperty_EnableLevelMetering,&enabledLevelMeter,sizeof(UInt32));
// New functions
-(float) getPeakPower
{
AudioQueueLevelMeterState levelMeter;
UInt32 levelMeterSize = sizeof(AudioQueueLevelMeterState);
AudioQueueGetProperty(queue,kAudioQueueProperty_CurrentLevelMeterDB,&levelMeter,&levelMeterSize);
return levelMeter.mPeakPower;
}

-(float) getAveragePower
{
AudioQueueLevelMeterState levelMeter;
UInt32 levelMeterSize = sizeof(AudioQueueLevelMeterState);
AudioQueueGetProperty(queue,kAudioQueueProperty_CurrentLevelMeterDB,&levelMeter,&levelMeterSize);
return levelMeter.mAveragePower;
}

Now you can simply call getPeakPower or getAveragePower to obtain the average or peak power of the audio being recorded in decibels. Now I usually gauge power by dividing the Average Power by -80.0, because I use that as a ceiling, and yes I am well aware volume can get up to 120dB in power, but for most purposes a ceiling of 80dB is fine. Now for the complex stuff, file writing. What we are going to do is add a specific function to our class that allows us to define a file to write to, and some functions that tell us whether we are recording to a file or not as well as allowing us to stop the file recording.

// New interface variables
AudioFileID audioFile;
bool fileRecording;
SInt64 currentPacket;
NSString* recordFile;
// Functions to add to our class
-(bool) isRecording;
-(bool) record:(NSString*)filename;
-(void) stopRecording;

-(bool) isRecording
{
return fileRecording;
}

-(bool) record:(NSString*)file
{
char cfile[256];
[file getCString:cfile maxLength:sizeof(cfile) encoding:NSUTF8StringEncoding];
CFURLRef fileURL = CFURLCreateFromFileSystemRepresentation(NULL,(UInt8*)cfile,strlen(cfile),false);
OSStatus result = AudioFileCreateWithURL(fileURL,kAudioFileAIFFType,&dataFormat,kAudioFileFlags_EraseFile,&audioFile);
if(result != noErr)
fileRecording = false;
else fileRecording = true;
if(fileRecording)
recordFile = file;
return fileRecording;
}

-(void) stopRecording
{
fileRecording = false;
AudioFileClose(audioFile);
}

Now there's a lot of code here, so I'll try to explain it. The isRecording function basically informs other classes on whether we are currently recording, the record function is where our imagine happens. It uses AudioFileCreateWithURL to create an audio file header based on our format definition in the earlier part of this tutorial, the rest of that function is basically setting the recording fileRecording variable and changing the NSString to a CFURLRef type. The stop recording function is also very self explanatory, it just closes the audio file and shutdowns the fileRecording variable. But we still aren't using the AudioInputCallback to record! Well that's the last piece of the puzzle:

// Add these properties to the interface
@property AudioFileID audioFile;
@property bool fileRecording;
@property SInt64 currentPacket;

// Add this code to AudioInputCallback
if(recorder.fileRecording)
{
AudioFileWritePackets(recorder.audioFile,false,inBuffer->mAudioDataByteSize,inPacketDescs,recorder.currentPacket,&inNumberPacketDescriptions,inBuffer->mAudioData);
recorder.currentPacket += inNumberPacketDescriptions;
}
// Synthesize these variables
@synthesize audioFile,fileRecording,currentPacket;

Now here we basically make the fileRecording, audioFile and currentPacket variable public, so we can use them in the AudioInputCallback, then we write the audio packets to our file. It's as simple as that.

1 comment:

  1. Hey, thank you very much for the tutorial.
    Could you please put a file that summarize the whole thing?
    Because there are some parts that I don't really understand, for example when you give some line of codes to add to the init function, I'm not sure where in the code already in place I should put it.
    There are a few other parts that I don't get very well.
    As this tutorial would be very usefull to me, it would be very kind of you to summarize the two files content, AudioRecorder.h and AudioRecorder.m

    Thanks again, good work.

    L.W.

    ReplyDelete