Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVAudioRecorder won't record IF movie was previously recorded & played

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:

  1. Record a movie using UIImagePickerController
  2. Play back the recorded movie using MPMoviePlayerController
  3. Try to make a voice recording using AVAudioRecorder

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?

like image 592
Roberto Avatar asked Nov 10 '10 19:11

Roberto


3 Answers

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
}
like image 189
rmomin Avatar answered Nov 17 '22 11:11

rmomin


Had the same problem. My Solution was to STOP the movie before starting the recording session. My code was similar to rmomin's code.

like image 27
Ed Liss Avatar answered Nov 17 '22 12:11

Ed Liss


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:

  1. Load up AVAsset (for example, if you're loading from a file, use AVURLAsset directly or add it to a AVMutableComposition and use that) and keep a reference to it. Don't let it go until you're done with it. Loading it is what takes all the time.
  2. Once you're ready to play: instantiate AVPlayerItem with your asset, then AVPlayer with the AVPlayerItem and play it. Don't keep a reference to AVPlayerItem, AVPlayer will keep a reference to it and you can't reuse it with another player anyway.
  3. Once it's done playing, immediately destroy AVPlayer...release it, set its var to nil, whatever you need to do. **
  4. Now you can record. AVPlayer doesn't exist, so AVAudioRecorder is free to do its thing.
  5. When you're ready to play again, re-instantiate AVPlayerItem with the asset you've already loaded & AVPlayer. Again, this is trivial. The asset has already been loaded so there shouldn't be a delay.

** 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.

like image 1
Aaron Hayman Avatar answered Nov 17 '22 11:11

Aaron Hayman