My iPhone app uses "AVAudioRecorder" to make voice recordings. It also uses "UIImagePickerController" to record movies and "MPMoviePlayerController" to play movies.
Everything works fine until I do all three things in a row:
When I call AVAudioRecorder's "record" method in step 3, it returns NO indicating failure, but giving no hints as to why (come on Apple!) AVAudioRecorder's audioRecorderEncodeErrorDidOccur delegate method is never called and I receive no other errors when setting up the recorder.
My first guess was that the movie recording/playing was modifying the shared instance of "AVAudioSession" in such a way that it prevented the audio recorder from working. However, I'm manually setting AVAudioSession's category property to "AVAudioSessionCategoryRecord" and I make the audio session active before trying to record.
Here's my method for creating the recorder:
- (void)createAudioRecorder
{
NSError *error = nil;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryRecord error:&error];
if (error)
...
[audioSession setActive:YES error:&error];
if (error)
...
NSMutableDictionary *settings = [[NSMutableDictionary alloc] init];
// General Audio Format Settings
[settings setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[settings setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[settings setValue:[NSNumber numberWithInt:1] forKey:AVNumberOfChannelsKey];
// Encoder Settings
[settings setValue:[NSNumber numberWithInt:AVAudioQualityMin] forKey:AVEncoderAudioQualityKey];
[settings setValue:[NSNumber numberWithInt:96] forKey:AVEncoderBitRateKey];
[settings setValue:[NSNumber numberWithInt:16] forKey:AVEncoderBitDepthHintKey];
// Write the audio to a temporary file
NSURL *tempURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"Recording.m4a"]];
audioRecorder = [[AVAudioRecorder alloc] initWithURL:tempURL settings:settings error:&error];
if (error)
...
audioRecorder.delegate = self;
if ([audioRecorder prepareToRecord] == NO)
NSLog(@"Recorder fails to prepare!");
[settings release];
}
And here's my method to start recording:
- (void)startRecording
{
if (!audioRecorder)
[self createAudioRecorder];
NSError *error = nil;
[[AVAudioSession sharedInstance] setActive:YES error:&error];
if (error)
...
BOOL recording = [audioRecorder record];
if (!recording)
NSLog(@"Recording won't start!");
}
Has anyone run into this problem before?
I was having the same issue. Before I fixed the issue, my recording/playback code was like this:
Start Recording Function
- (BOOL) startRecording {
@try {
NSDictionary *recordSetting = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey, [NSNumber numberWithFloat: 44100.0], AVSampleRateKey, [NSNumber numberWithInt: 1], AVNumberOfChannelsKey, [NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey, nil];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *soundFilePath = [documentsPath stringByAppendingPathComponent:@"recording.caf"];
if(audioRecorder != nil) {
[audioRecorder stop];
[audioRecorder release];
audioRecorder = nil;
}
NSError *err = nil;
audioRecorder = [[AVAudioRecorder alloc] initWithURL:soundFileURL settings:recordSetting error:&err];
[soundFileURL release];
[recordSetting release];
if(!audioRecorder || err){
NSLog(@"recorder initWithURL: %@ %d %@", [err domain], [err code], [[err userInfo] description]);
return NO;
}
[audioRecorder peakPowerForChannel:8];
[audioRecorder updateMeters];
audioRecorder.meteringEnabled = YES;
[audioRecorder record];
}
@catch (NSException * e) {
return NO;
}
recording = YES;
return YES;
}
Stop Recording Function
- (BOOL) stopRecording {
@try {
[audioRecorder stop];
[audioRecorder release];
audioRecorder = nil;
recording = NO;
}
@catch (NSException * e) {
return NO;
}
return YES;
}
Start Playing Function
- (BOOL) startPlaying {
@try {
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *soundFilePath = [documentsPath stringByAppendingPathComponent:@"recording.caf"]; NSURL * soundFileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
NSError *err = nil;
if (audioPlayer) {
[audioPlayer release];
audioPlayer = nil;
}
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error: &err];
[soundFileURL release];
if (!audioPlayer || err) {
NSLog(@"recorder: %@ %d %@", [err domain], [err code], [[err userInfo] description]);
return NO;
}
[audioPlayer prepareToPlay];
[audioPlayer setDelegate: self];
[audioPlayer play];
playing = YES;
}
@catch (NSException * e) {
return NO;
}
return YES;
}
Stop Playing Function
- (BOOL) stopPlaying {
@try {
[audioPlayer stop];
[audioPlayer release];
audioPlayer = nil;
playing = NO;
}
@catch (NSException * e) {
return NO;
}
return YES;
}
I fixed the recording issue after playing a captured video, the code is as follows:
- (BOOL) startRecording {
@try {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryRecord error:nil];
// rest of the recording code is the same .
}
- (BOOL) stopRecording {
@try {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
// rest of the code is the same
}
- (BOOL) startPlaying {
@try {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
// rest of the code is the same.
}
- (BOOL) stopPlaying {
// There is no change in this function
}
Had the same problem. My Solution was to STOP the movie before starting the recording session. My code was similar to rmomin's code.
I've been having the same problem. I use AVPlayer to play compositions (previous recordings I've used AVAudioRecord for). However, I found that once I've used AVPlayer I could no longer use AVAudioRecorder. After some searching, I discovered that so long as AVPlayer is instantiated in memory and has been played at least once (which is usually what you do immediately after instantiating it) AVAudioRecorder will not record. However, once AVPlayer is dealloc'd, AVAudioRecorder is then free to record again. It appears that AVPlayer holds on to some kind of connection that AVAudioRecorder needs, and it's greedy...it won't let it go until you pry it from it's cold dead hands.
This is the solution I've found. Some people claim that instantiating AVPlayer takes too much time to keep breaking down and setting back up. However, this is not true. Instantiating AVPlayer is actually quite trivial. So also is instantiating AVPlayerItem. What isn't trivial is loading up AVAsset (or any of it's subclasses). You really only want to do that once. They key is to use this sequence:
** Note that destroying AVPlayer may take more than just releasing it and setting its var to nil. Most likely, you've also added a periodic time observer to keep track of the play progress. When you do this, you receive back an opaque object you're supposed to hold on to. If you don't remove this item from the player AND release it/set it to nil, AVPlayer will not dealloc. It appears that Apple creates an intentional retain cycle you must break manually. So before you destroy AVPlayer you need to (example):
[_player removeTimeObserver:_playerObserver];
[_playerObserver release]; //Only if you're not using ARC
_playerObserver = nil;
As a side note, you may also have set up NSNotifications (I use one to determine when the player has completed playing)...don't forget to remove those as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With