播放暂停的AVAudioRecorder文件

时间:2012-06-13 07:19:54

标签: objective-c xcode avaudioplayer avaudiorecorder

在我的程序中,我希望用户能够:

  • 记录他的声音,
  • 暂停录制过程,
  • 听他录制的内容
  • 然后继续录制。

我已经设法达到了可以用AVAudioRecorder和AVAudioPlayer录制和播放录音的程度。但每当我尝试录制,暂停录制然后播放时,播放部分都会失败而没有错误。

我可以猜测它没有播放的原因是因为音频文件尚未保存并且仍然在内存中等等。

有没有办法播放已暂停的录音? 如果有,请告诉我如何

我正在使用xcode 4.3.2

3 个答案:

答案 0 :(得分:9)

如果你想播放录音,那么你必须先停止录音才能将文件加载到AVAudioPlayer实例中。

如果你想能够播放一些录音,那么在听完录音后再添加更多录音,或者在录音中间录音..那么你就会遇到麻烦。

您必须创建一个新的音频文件,然后将它们组合在一起。

这是我的解决方案:

// Generate a composition of the two audio assets that will be combined into
// a single track
AVMutableComposition* composition = [AVMutableComposition composition];
AVMutableCompositionTrack* audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio
                                                                 preferredTrackID:kCMPersistentTrackID_Invalid];

// grab the two audio assets as AVURLAssets according to the file paths
AVURLAsset* masterAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:self.masterFile] options:nil];
AVURLAsset* activeAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:self.newRecording] options:nil];

NSError* error = nil;

// grab the portion of interest from the master asset
[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, masterAsset.duration)
                    ofTrack:[[masterAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]
                     atTime:kCMTimeZero
                      error:&error];
if (error)
{
    // report the error
    return;
}

// append the entirety of the active recording
[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, activeAsset.duration)
                    ofTrack:[[activeAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]
                     atTime:masterAsset.duration
                      error:&error];

if (error)
{
    // report the error
    return;
}

// now export the two files
// create the export session
// no need for a retain here, the session will be retained by the
// completion handler since it is referenced there

AVAssetExportSession* exportSession = [AVAssetExportSession
                                       exportSessionWithAsset:composition
                                       presetName:AVAssetExportPresetAppleM4A];
if (nil == exportSession)
{
    // report the error
    return;
}


NSString* combined = @"combined file path";// create a new file for the combined file

// configure export session  output with all our parameters
exportSession.outputURL = [NSURL fileURLWithPath:combined]; // output path
exportSession.outputFileType = AVFileTypeAppleM4A; // output file type

[exportSession exportAsynchronouslyWithCompletionHandler:^{

    // export status changed, check to see if it's done, errored, waiting, etc
    switch (exportSession.status)
    {
        case AVAssetExportSessionStatusFailed:
            break;
        case AVAssetExportSessionStatusCompleted:
            break;
        case AVAssetExportSessionStatusWaiting:
            break;
        default:
            break;
    }
    NSError* error = nil;

    // your code for dealing with the now combined file
}];

我不能完全赞同这项工作,但它是根据其他几个人的意见拼凑而成的:

AVAudioRecorder / AVAudioPlayer - append recording to file

(目前我找不到其他链接)

答案 1 :(得分:2)

我们的应用程序与OP描述的要求相同,并遇到了相同的问题(即,如果用户想要听取她记录的内容,则必须停止录制,而不是暂停录制点)。我们的应用(project's Github repo)使用AVQueuePlayer进行播放,使用类似kermitology's answer的方法连接部分录音,但有一些明显的差异:

  • Swift
  • 中实施
  • 多个录音合并为一个
  • 没有混乱曲目

最后一项背后的理由是,AVAudioRecorder的简单录音将有一首曲目,整个解决方法的主要原因是连接资产中的那些单曲(参见附录3 )。那么为什么不使用AVMutableComposition的{​​{1}}方法,而是使用insertTimeRange代替AVAsset

相关部分:(full code

AVAssetTrack

这个图帮助我绕过了什么期望从哪里继承。 (import UIKit import AVFoundation class RecordViewController: UIViewController { /* App allows volunteers to record newspaper articles for the blind and print-impaired, hence the name. */ var articleChunks = [AVURLAsset]() func concatChunks() { let composition = AVMutableComposition() /* `CMTimeRange` to store total duration and know when to insert subsequent assets. */ var insertAt = CMTimeRange(start: kCMTimeZero, end: kCMTimeZero) repeat { let asset = self.articleChunks.removeFirst() let assetTimeRange = CMTimeRange(start: kCMTimeZero, end: asset.duration) do { try composition.insertTimeRange(assetTimeRange, of: asset, at: insertAt.end) } catch { NSLog("Unable to compose asset track.") } let nextDuration = insertAt.duration + assetTimeRange.duration insertAt = CMTimeRange(start: kCMTimeZero, duration: nextDuration) } while self.articleChunks.count != 0 let exportSession = AVAssetExportSession( asset: composition, presetName: AVAssetExportPresetAppleM4A) exportSession?.outputFileType = AVFileType.m4a exportSession?.outputURL = /* create URL for output */ // exportSession?.metadata = ... exportSession?.exportAsynchronously { switch exportSession?.status { case .unknown?: break case .waiting?: break case .exporting?: break case .completed?: break case .failed?: break case .cancelled?: break case .none: break } } /* Clean up (delete partial recordings, etc.) */ } 隐式暗示为没有继承箭头的超类。)

enter image description here

附录1:我对NSObject部分有所保留,而不是在switch上使用KVO,但文档清楚AVAssetExportSessionStatus的回调写入完成或写入失败时调用“”。

附录2:万一有人遇到exportAsynchronously问题:'An AVPlayerItem cannot be associated with more than one instance of AVPlayer'

附录3 :除非您是以立体声录制,但据我所知,移动设备只有一个输入。此外,使用花哨的音频混合还需要使用AVQueuePlayer。一个好的SO线程:正确的AVAudioRecorder Settings for Recording Voice?

答案 2 :(得分:1)

RecordAudioViewController.h

 #import <UIKit/UIKit.h>
 #import <AVFoundation/AVFoundation.h>
 #import <CoreAudio/CoreAudioTypes.h>

   @interface record_audio_testViewController : UIViewController <AVAudioRecorderDelegate> {

IBOutlet UIButton * btnStart;
IBOutlet UIButton * btnPlay;
IBOutlet UIActivityIndicatorView * actSpinner;
BOOL toggle;

//Variables setup for access in the class:
NSURL * recordedTmpFile;
AVAudioRecorder * recorder;
NSError * error;

 }

 @property (nonatomic,retain)IBOutlet UIActivityIndicatorView * actSpinner;
 @property (nonatomic,retain)IBOutlet UIButton * btnStart;
 @property (nonatomic,retain)IBOutlet UIButton * btnPlay;

 - (IBAction) start_button_pressed;
 - (IBAction) play_button_pressed;
 @end

RecordAudioViewController.m

  @synthesize actSpinner, btnStart, btnPlay;
   - (void)viewDidLoad {
    [super viewDidLoad];

//Start the toggle in true mode.
toggle = YES;
btnPlay.hidden = YES;

//Instanciate an instance of the AVAudioSession object.
AVAudioSession * audioSession = [AVAudioSession sharedInstance];
//Setup the audioSession for playback and record. 
//We could just use record and then switch it to playback leter, but
//since we are going to do both lets set it up once.
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error: &error];
//Activate the session
[audioSession setActive:YES error: &error];

  }


 - (IBAction)  start_button_pressed{

if(toggle)
{
    toggle = NO;
    [actSpinner startAnimating];
    [btnStart setTitle:@"Stop Recording" forState: UIControlStateNormal ];  
    btnPlay.enabled = toggle;
    btnPlay.hidden = !toggle;

    //Begin the recording session.
    //Error handling removed.  Please add to your own code.

    //Setup the dictionary object with all the recording settings that this 
    //Recording sessoin will use
    //Its not clear to me which of these are required and which are the bare minimum.
    //This is a good resource: http://www.totodotnet.net/tag/avaudiorecorder/
    NSMutableDictionary* recordSetting = [[NSMutableDictionary alloc] init];
    [recordSetting setValue :[NSNumber numberWithInt:kAudioFormatAppleIMA4] forKey:AVFormatIDKey];
    [recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey]; 
    [recordSetting setValue:[NSNumber numberWithInt: 2] forKey:AVNumberOfChannelsKey];

    //Now that we have our settings we are going to instanciate an instance of our recorder instance.
    //Generate a temp file for use by the recording.
    //This sample was one I found online and seems to be a good choice for making a tmp file that
    //will not overwrite an existing one.
    //I know this is a mess of collapsed things into 1 call.  I can break it out if need be.
    recordedTmpFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent: [NSString stringWithFormat: @"%.0f.%@", [NSDate timeIntervalSinceReferenceDate] * 1000.0, @"caf"]]];
    NSLog(@"Using File called: %@",recordedTmpFile);
    //Setup the recorder to use this file and record to it.
    recorder = [[ AVAudioRecorder alloc] initWithURL:recordedTmpFile settings:recordSetting error:&error];
    //Use the recorder to start the recording.
    //Im not sure why we set the delegate to self yet.  
    //Found this in antother example, but Im fuzzy on this still.
    [recorder setDelegate:self];
    //We call this to start the recording process and initialize 
    //the subsstems so that when we actually say "record" it starts right away.
    [recorder prepareToRecord];
    //Start the actual Recording
    [recorder record];
    //There is an optional method for doing the recording for a limited time see 
    //[recorder recordForDuration:(NSTimeInterval) 10]

}
else
{
    toggle = YES;
    [actSpinner stopAnimating];
    [btnStart setTitle:@"Start Recording" forState:UIControlStateNormal ];
    btnPlay.enabled = toggle;
    btnPlay.hidden = !toggle;

    NSLog(@"Using File called: %@",recordedTmpFile);
    //Stop the recorder.
    [recorder stop];
}
  }

  - (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];

// Release any cached data, images, etc that aren't in use.
  }

  -(IBAction) play_button_pressed{

//The play button was pressed... 
//Setup the AVAudioPlayer to play the file that we just recorded.
AVAudioPlayer * avPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:recordedTmpFile error:&error];
[avPlayer prepareToPlay];
[avPlayer play];

  }

   - (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
//Clean up the temp file.
NSFileManager * fm = [NSFileManager defaultManager];
[fm removeItemAtPath:[recordedTmpFile path] error:&error];
//Call the dealloc on the remaining objects.
[recorder dealloc];
recorder = nil;
recordedTmpFile = nil;
  }


  - (void)dealloc {
[super dealloc];
  }

 @end

RecordAudioViewController.xib

拿2个按钮。 1用于开始录制,另一个用于播放录制