播放暂停的 AVAudioRecorder 文件

Posted

技术标签:

【中文标题】播放暂停的 AVAudioRecorder 文件【英文标题】:Play a paused AVAudioRecorder file 【发布时间】:2012-06-13 07:19:54 【问题描述】:

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

录下他的声音, 暂停录制过程, 听听他的录音 然后继续录制。

我已经达到了可以使用 AVAudioRecorder 和 AVAudioPlayer 录制和播放录音的地步。但是每当我尝试录制、暂停录制然后播放时,播放部分都会失败且没有错误。

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

有什么方法可以播放暂停的录音吗? 如果有请告诉我怎么做

我使用的是 xcode 4.3.2

【问题讨论】:

【参考方案1】:

如果您想播放录音,那么是的,您必须先停止录音,然后才能将文件加载到 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

(我暂时找不到其他链接)

【讨论】:

Bravo 是干净的编码和我需要的 btw ;)【参考方案2】:

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

Swift 中实现 将多个录音合并为一个 不要弄乱曲目

最后一项背后的基本原理是 AVAudioRecorder 的简单录音将只有一个轨道,而整个解决方法的主要原因是将这些单一轨道连接到资产中(参见 附录 3 )。那么为什么不改用AVMutableCompositioninsertTimeRange 方法,它采用AVAsset 而不是AVAssetTrack

相关部分:(full code)

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

这张图帮助我了解了期望什么以及从哪里继承的问题。 (NSObject 隐含为没有继承箭头的超类。)


附录 1: 我对 switch 部分而不是在 AVAssetExportSessionStatus 上使用 KVO 持保留意见,但文档很清楚 exportAsynchronously 的回调块“在编写时被调用已完成或在写入失败的情况下”。

附录2:以防万一有人对AVQueuePlayer有问题:'An AVPlayerItem cannot be associated with more than one instance of AVPlayer'

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

【讨论】:

【参考方案3】:

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 用于开始录制,另一个用于播放录制

【讨论】:

谢谢,但我已经可以进行录音和播放了。我需要暂停正在录制的文件并播放该暂停的文件。所以在你的代码中而不是使用 [recorder stop] 我希望能够使用 [recorder pause] 然后播放到目前为止已经录制的内容 @Fellowsoft 你做到了吗?如果是,请告诉我如何 投反对票,因为这不是所要求的。 @Fellowsoft,你会考虑接受kermitology's answer 吗?在这种情况下,它显然是正确的。

以上是关于播放暂停的 AVAudioRecorder 文件的主要内容,如果未能解决你的问题,请参考以下文章

为啥我无法播放 AVAudioRecorder 录制的声音文件?

从蓝牙命令控制 AVAudioRecorder

AVAudioRecorder 不保存文件

iPhone AVAudioRecorder 进入后台后暂停问题

Swift AVAudioPlayer 不会播放用 AVAudioRecorder 录制的音频

关于使用 AVAudioPlayer 自动录制和自动播放 AVAudioRecorder