Exploring iPhone Audio Part 5
April 5th, 2008 Posted in Software Development, iPhone
Part 4 of this series talked about writing recorded audio data to a file. In this article we will set create a very simple user interface so we can control the audio functions.
We are going to add a UILabel control that will display text corresponding to the current state of the application. We’ll also add buttons for recording and playback. In future articles we’ll add playback functionality to the application.
Here is what the AppDelegate class looks like currently.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @interface AudioRecorderAppDelegate : NSObject <UIApplicationDelegate> { UIWindow *window; UILabel* labelStatus; UIButton* buttonRecord; UIButton* buttonPlay; RecordState recordState; CFURLRef fileURL; } @property (nonatomic, retain) UIWindow *window; - (BOOL)getFilename:(char*)buffer maxLenth:(int)maxBufferLength; - (void)setupAudioFormat:(AudioStreamBasicDescription*)format; - (void)recordPressed:(id)sender; - (void)playPressed:(id)sender; - (void)startRecording; - (void)stopRecording; @end |
We have added a UILabel and two UIButton member variables for a new user interface controls. We also have a method for setting up the audio format. This can be used for both playback and recording. The recordPressed:id method and playPressed:id method are what will be called whenever the corresponding buttons are pressed. We also now have a method to start recording and a method to stop recording.
Here is the full applicaitonDidFinishLaunching method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | - (void)applicationDidFinishLaunching:(UIApplication *)application { // Create window self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; // Create status label labelStatus = [[UILabel alloc] initWithFrame: CGRectMake(10, 50, 300, 30)]; labelStatus.textAlignment = UITextAlignmentCenter; labelStatus.numberOfLines = 1; labelStatus.text = @"Idle"; // Create Record button buttonRecord = [UIButton buttonWithType:UIButtonTypeNavigation]; [buttonRecord setTitle:@"Record" forStates:UIControlStateNormal]; [buttonRecord addTarget:self action:@selector(recordPressed:) forControlEvents:UIControlEventTouchUpInside]; buttonRecord.center = CGPointMake(window.center.x, 200); // Create Play button buttonPlay = [UIButton buttonWithType:UIButtonTypeNavigation]; [buttonPlay setTitle:@"Play" forStates:UIControlStateNormal]; [buttonPlay addTarget:self action:@selector(playPressed:) forControlEvents:UIControlEventTouchUpInside]; buttonPlay.center = CGPointMake(window.center.x, 150); // Get audio file page char path[256]; [self getFilename:path maxLenth:sizeof path]; fileURL = CFURLCreateFromFileSystemRepresentation(NULL, (UInt8*)path, strlen(path), false); // Init state variables playState.playing = false; recordState.recording = false; // Add the controls to the window [window addSubview:labelStatus]; [window addSubview:buttonRecord]; [window addSubview:buttonPlay]; [window makeKeyAndVisible]; } |
As you can see we are creating the UILabel and the two buttons. We explicitly set a rectangle for the label control. Setting textAlignment to UITextAlignmentCenter will cause the text to be centered horizontally within the control. The label has a single line and will start out set to “Idle”. It’s upper left hand corner will be 10 pixels from the left of the screen and 50 pixels from the top the screen. It will be 300 pixels wide and 30 pixels tall.
The buttons are of type UIButtonTypeNavigation. This is the same same type of button used at the top of may screens for navigation. It will automatically size itself for the button title text. We set the center of the button’s x position to the center of the window so the buttons are centered horizontally on the screen. The addTarget method allows us to wire up a method to be called when the buttons are pressed.
When the application is closed we are freeing all the resources we allocated.
1 2 3 4 5 6 7 8 9 | - (void)dealloc { CFRelease(fileURL); [labelStatus release]; [buttonPlay release]; [buttonRecord release]; [window release]; [super dealloc]; } |
The recordPressed method is called whenever to record button is pressed. The button toggles recording on and off. The recording bool variable in the RecordState struct keeps track of the current recording state.
1 2 3 4 5 6 7 8 9 10 11 12 13 | - (void)recordPressed:(id)sender { if(!recordState.recording) { printf("Starting recording\n"); [self startRecording]; } else { printf("Stopping recording\n"); [self stopRecording]; } } |
Here is the startRecording method. We’ve already discussed how to start up the recording process so I won’t go into much detail for this method. We are calling setupAudioFormat to populate our audio recording data format and setting currentPacket to zero before we start recording. We are also setting the status label to indicate success or failure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | - (void)startRecording { [self setupAudioFormat:&recordState.dataFormat]; recordState.currentPacket = 0; OSStatus status; status = AudioQueueNewInput(&recordState.dataFormat, AudioInputCallback, &recordState, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &recordState.queue); if(status == 0) { for(int i = 0; i < NUM_BUFFERS; i++) { AudioQueueAllocateBuffer(recordState.queue, 16000, &recordState.buffers[i]); AudioQueueEnqueueBuffer(recordState.queue, recordState.buffers[i], 0, NULL); } status = AudioFileCreateWithURL(fileURL, kAudioFileAIFFType, &recordState.dataFormat, kAudioFileFlags_EraseFile, &recordState.audioFile); if(status == 0) { recordState.recording = true; status = AudioQueueStart(recordState.queue, NULL); if(status == 0) { labelStatus.text = @"Recording"; } } } if(status != 0) { labelStatus.text = @"Record Failed"; } } |
The stopRecording method stops recording, frees up the audio buffers, frees the input queue and closes the file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | - (void)stopRecording { recordState.recording = false; AudioQueueStop(recordState.queue, true); for(int i = 0; i < NUM_BUFFERS; i++) { AudioQueueFreeBuffer(recordState.queue, recordState.buffers[i]); } AudioQueueDispose(recordState.queue, true); AudioFileClose(recordState.audioFile); } |
For your reference here are the setupAudioFormat and AudioInputCallback functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | - (void)setupAudioFormat:(AudioStreamBasicDescription*)format { format->mSampleRate = 8000.0; format->mFormatID = kAudioFormatLinearPCM; format->mFramesPerPacket = 1; format->mChannelsPerFrame = 1; format->mBytesPerFrame = 2; format->mBytesPerPacket = 2; format->mBitsPerChannel = 16; format->mReserved = 0; format->mFormatFlags = kLinearPCMFormatFlagIsBigEndian | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; } void AudioInputCallback( void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, const AudioStreamPacketDescription *inPacketDescs) { RecordState* recordState = (RecordState*)inUserData; if(!recordState->recording) { printf("Not recording, returning\n"); } printf("Writing buffer %d\n", recordState->currentPacket); OSStatus status = AudioFileWritePackets(recordState->audioFile, false, inBuffer->mAudioDataByteSize, inPacketDescs, recordState->currentPacket, &inNumberPacketDescriptions, inBuffer->mAudioData); if(status == 0) { recordState->currentPacket += inNumberPacketDescriptions; } AudioQueueEnqueueBuffer(recordState->queue, inBuffer, 0, NULL); } |
As you can see it is very easy to control the audio recording process via user interface controls. The next article in this series will start to get into the process of playing back audio data.












Related Articles:

I am new to cocoa. I tried to work this out in xcode and was a little confused by where the bits and pieces go. Implementation or header files. Can you provide the project file?
If not that’s cool, I totally appreciate these tutorials.
I’ll be posting the source files in the last article which should be up in a day or two. Will try to post source files as I go in my next series of articles.
i’m sorry, I missed the bit about the next article. This is great stuff, I really appreciate it!
Full project? Is the full project somewhere? I see the source files (main.m, AudioRecorderAppDelegate.h,AudioRecorderAppDelegate.m) at the end, but it still seems like something is missing.
This is a GREAT help, but it be even better with a gzip/zip/dmg/whatever of the entire project.
[...] Part 5 of this series we added a simple user interface to control the audio recording process. A UILabel [...]
[...] ?????http://trailsinthesand.com/exploring-iphone-audio-part-5/ [...]