Recorder Challenge


#1

Am I on the right track here?

audioRecorder is declared in MediaPlayerViewController.h:

	AVAudioRecorder *audioRecorder;
	IBOutlet UIButton *recordButton;     // Button for recording audio

and declared an IBAction:

- (IBAction)recordSound:(id)sender;

In MediaPlayerViewController.m:

in -(void)viewDidLoad:

	// Set up the audio recorder
	
	NSString *filenameWithPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:@"recorded.mp3"];
	NSURL *recordFilenameWithURL = [NSURL fileURLWithPath:filenameWithPath];
	
	NSMutableDictionary *recorderSettings = [[NSMutableDictionary alloc] init];
	
	NSNumber *sampleRate = [[NSNumber alloc] initWithFloat:441000.0];
	NSNumber *channels = [[NSNumber alloc] initWithInt:1];
	NSNumber *audioFormat = [[NSNumber alloc] initWithInt:kAudioFormatMPEGLayer3];
	NSNumber *audioQuality = [[NSNumber alloc] initWithInt:AVAudioQualityMax];
	NSNumber *audioBitRate = [[NSNumber alloc] initWithInt:128];
	
	
	[recorderSettings setObject:audioFormat forKey:AVFormatIDKey];
	[recorderSettings setObject:sampleRate forKey:AVSampleRateKey];
	[recorderSettings setObject:channels forKey:AVNumberOfChannelsKey];
	[recorderSettings setObject:audioQuality forKey:AVEncoderAudioQualityKey];
	[recorderSettings setObject:audioBitRate forKey:AVEncoderBitRateKey];
	
	audioRecorder = [[AVAudioRecorder alloc] initWithURL:recordFilenameWithURL settings:recorderSettings error:NULL];  // This line currently returns NIL

I’ve added a button with Interface Builder, and linked it to recordSound, which is below:


-(IBAction)recordSound:(id)sender
{
	BOOL error;
	
	if ([audioRecorder isRecording]) {
		[audioRecorder stop];
		[recordButton setTitle:@"Record Sound" forState:UIControlStateNormal];
		[recordButton setTitleColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1] forState:UIControlStateNormal];
		
	} else {
		error = [audioRecorder prepareToRecord];
		
		if (error=[audioRecorder record]) {
			[recordButton setTitle:@"Recording.  Tap to stop" forState:UIControlStateNormal];
			[recordButton setTitleColor:[UIColor colorWithRed:1 green:0 blue:0 alpha:1] forState:UIControlStateNormal];
		}
	}
}

I’ve traced my problem to where I initialize audioRecorder - the line returns NIL, so clearly there’s a problem with it.

Is my problem
(a) with how and/or where my path to the datafile is? (Documents)
(b) with the recorderSettings dictionary?
© Somewhere else?


#2

Hi,

On the right track but unfortunately iOS doesn’t have a built in mp3 recording codec only a playback one.

See the Codecs chapter in http://developer.apple.com/library/ios/#documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html%23//apple_ref/doc/uid/TP40003577-CH10-SW5

Try it with kAudioFormatAppleIMA4 and a .caf extension.

You probably meant 44100 for the sample rate and use stringByAppendingPathComponent instead of stringByAppendingString:@“recorded.mp3” as you’ll get “Documentsrecorded.mp3” otherwise.

Gareth


#3

Yep, Gareth, that was it. All I needed to do after making that change was initialize a local audio player and play the sound back after stopping recording.


#4

I’m puzzled on this one. For some reason, I cannot get the audio recorder to work - even after pattering it after the changes mentioned in the above posts.

Here’s what happens. If I tap the “Start recording” button, it correctly attempts to prepare the audio recorder…and that’s it. There must be something failing in the record piece, because I cannot tap the button again to stop recording. According to the code, the only thing I can figure out is that it must be due to recording not actually being invoked - which results in a loop where the user will never be presented with a “Stop recording” option.

Any suggestions as to what I’m missing? I’m wondering if maybe the problem is that I need to scrap this and try to use Audio Queue Services instead?

Here is what I am working with thus far:

MediaPlayerAppDelegate.h

#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>

@interface MediaPlayerAppDelegate : NSObject <UIApplicationDelegate, AVAudioPlayerDelegate> {
    UIWindow *window;
	IBOutlet UIButton *audioButton;	// We want our "Play audio file" button to change its title dynamically, so it needs to be an IBOutlet
	SystemSoundID shortSound;	// From the AudioToolbox framework
	
	AVAudioPlayer *audioPlayer;
	MPMoviePlayerController *moviePlayer;
	
	// Challenge: Audio Recording
	IBOutlet UIButton *recordButton;
	AVAudioPlayer *recordedAudioPlayer;
	AVAudioRecorder *audioRecorder;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

- (IBAction)playAudioFile:(id)sender;
- (IBAction)playVideoFile:(id)sender;
- (IBAction)playShortSound:(id)sender;
- (IBAction)recordSound:(id)sender;

@end

MediaPlayerAppDelegate.m - application:didFinishLaunchingWithOptions:launchOptions

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
        : : : : :
	// Challenge: Audio recording
	NSString *recordedAudioPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) 
									objectAtIndex:0] 
								   stringByAppendingString:@"recorded.caf"];
	NSURL *recordURL = [NSURL fileURLWithPath:recordedAudioPath];
	// Define audio player with URL for playback later on
	recordedAudioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:recordURL error:nil];
	// Define dictionary for audio recorder settings
	NSMutableDictionary *recorderSettings = [[NSMutableDictionary alloc] init];
	// Set recording format/quality
	NSNumber *sampleRate = [[NSNumber alloc] initWithFloat:44100.0];
	NSNumber *channels = [[NSNumber alloc] initWithInt:1];
	NSNumber *audioFormat = [[NSNumber alloc] initWithInt:kAudioFormatAppleIMA4];
	NSNumber *audioQuality = [[NSNumber alloc] initWithInt:AVAudioQualityMax];
	NSNumber *audioBitRate = [[NSNumber alloc] initWithInt:128];
	// Define settings in audio recorder dictionary
	[recorderSettings setObject:audioFormat forKey:AVFormatIDKey];
	[recorderSettings setObject:sampleRate forKey:AVSampleRateKey];
	[recorderSettings setObject:channels forKey:AVNumberOfChannelsKey];
	[recorderSettings setObject:audioQuality forKey:AVEncoderAudioQualityKey];
	[recorderSettings setObject:audioBitRate forKey:AVEncoderBitRateKey];
	// Initialize audio recorder
	audioRecorder = [[AVAudioRecorder alloc] initWithURL:recordURL settings:recorderSettings error:nil];
	
	// Default
    [self.window makeKeyAndVisible];    
    return YES;
}

MediaPlayerAppDelegate.m - recordSound

#pragma mark - Challenge: Audio recording and playback
- (IBAction)recordSound:(id)sender{	
	if ([audioRecorder isRecording]) {
		NSLog(@"Stop recording session and playback file");
		[audioRecorder stop];
		[recordButton setTitle:@"Record Sound" forState:UIControlStateNormal];
		[recordButton setTitleColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1] forState:UIControlStateNormal];	
		[recordedAudioPlayer play];
	} else {
		// Prepare to record
		NSLog(@"Prepare audio recorder");
		[audioRecorder prepareToRecord];
		[audioRecorder record];
		NSLog(@"Recording underway!");
		[recordButton setTitle:@"Recording...Tap to stop" forState:UIControlStateNormal];
		[recordButton setTitleColor:[UIColor colorWithRed:1 green:0 blue:0 alpha:1] forState:UIControlStateNormal];						
	}	
}

#5

Have you tried stepping through the program using the debugger? That should help you find and identify any loop you have in there.

(I know, it’s an obvious question, but…)


#6

The baffling thing is that I do not receive a nil object/error when initializing the AVRecorder, but why on earth would it fail on the prepare to record call? According to the debugger, the “rror = [audioRecorder prepareToRecord]” evaluates to NO; consequently an [audioRecorder record] method fails, too. This is so weird!

I did modify my recordSound method as follows:

[code]#pragma mark - Challenge: Audio recording and playback

  • (IBAction)recordSound:(id)sender{
    BOOL error;

    if ([audioRecorder isRecording]) {
    NSLog(@“Stop recording session and playback file”);
    [audioRecorder stop];
    [recordButton setTitle:@“Record Sound” forState:UIControlStateNormal];
    [recordButton setTitleColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1] forState:UIControlStateNormal];
    [recordedAudioPlayer play];
    } else {
    error = [audioRecorder prepareToRecord];

      if (error=[audioRecorder record]) {
      	[recordButton setTitle:@"Recording. Tap to stop" forState:UIControlStateNormal];
      	[recordButton setTitleColor:[UIColor colorWithRed:1 green:0 blue:0 alpha:1] forState:UIControlStateNormal];
      }
    

    }
    }
    [/code]

::sigh:: It must be something obvious; but one would think this should work? It’s perpetually not recording :cry:


#7

Hi,

First glance:

recordedAudioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:recordURL error:nil];

shouldn’t this be recordedAudioPlayer = [[AVAudioRecorder alloc] initWithContentsOfURL:recordURL error:nil];

HTH
Gareth


#8

I don’t believe so - that was just a local audio player object that would play the recorded file (once the recording process completed). The strange thing is that I do not receive any errors when initializing the audio recorder, but it simply will not budge when prepareToRecord is invoked. It fails there, and I am totally puzzled:

[code]- (IBAction)recordSound:(id)sender{

BOOL isReadyToRecord;	

if ([audioRecorder isRecording]) {
	NSLog(@"Stop recording session and playback file");
	[audioRecorder stop];
	[recordButton setTitle:@"Record Sound" forState:UIControlStateNormal];
	[recordButton setTitleColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1] forState:UIControlStateNormal];	
	
	// Define audio player with URL for playback later on
	NSString *recordedAudioPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) 
									objectAtIndex:0] 
								   stringByAppendingString:@"recorded.caf"];
	NSURL *recordURL = [NSURL fileURLWithPath:recordedAudioPath];
	
	recordedAudioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:recordURL error:nil];
	[recordedAudioPlayer play];
	
} else {		
	NSLog(@"Let's see if our audio recorder is ready for us to record...");
	isReadyToRecord = [audioRecorder prepareToRecord];		
	if (isReadyToRecord) {
		NSLog(@"...yup; our audio recorder is ready. Start recording.");
		[audioRecorder record];
		[recordButton setTitle:@"Recording. Tap to stop" forState:UIControlStateNormal];
		[recordButton setTitleColor:[UIColor colorWithRed:1 green:0 blue:0 alpha:1] forState:UIControlStateNormal];
	} else {
		NSLog(@"...nope. Audio recorder is not ready to record.");
	}
	
}

}
[/code]


#9

OK, this is just too weird. This code works COMPLETELY FINE on the iPhone simulator. It fails when being run in debug mode on an iPhone 4 running iOS 4.2.1. Is that weird, or what?


#10

Problem solved! t worked fine on the simulator because the sim wasn’t restricted by iOS for directories it can write to. Check it out:

/* The following code will fail - why? It fails because it is trying to write some path that is NOT accessible by
		this app.
	NSString *recordedAudioPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) 
									objectAtIndex:0] 
								   stringByAppendingString:@"recorded.caf"];
	 */
	// Correction:
	NSString *recordedAudioPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) 
								   objectAtIndex:0];
	
	recordedAudioPath = [recordedAudioPath stringByAppendingPathComponent:@"recorded.caf"];
	NSURL *recordURL = [NSURL fileURLWithPath:recordedAudioPath];

That was the first time I’ve experienced that =) It makes sense that the simulator would allow you to write all willy nilly; and although I could initialize the AVAudioRecorder it was actually blocked from being used by iOS because it would mean writing outside the allowed directories. Interesting learning point.