iOS - AVAudioRecorder录制音频

AVFoundation中使用AVAudioRecorder类实现音频录制功能。AVAudioRecorder同用于播放音频的兄弟类一样,构建于Audio Queue Service之上,是一个功能强大且代码简单易用的API。我们可以在Mac和iOS设备上使用这个类来从内置的麦克风录制音频,也可以从外部音频设备进行录制,比如数字音频接口或USB麦克风等

使用AVAudioRecorder能够做以下几件事情:

  • 可以进行音频录制,一直到用户停止录制(停止录制表示本次录音结束)
  • 录制具体的时间段
  • 暂停和恢复录制
  • 我们能够使用等级测量获取音频的输入等级数据

常用属性和方法

  • 属性
 // 是否正在录音
 open var isRecording: Bool { get }

 // 录制文件的URL
 open var url: URL { get } 

 // 录音配置,当prepareToRecord方法被调用,设置才有效
 open var settings: [String : Any] { get }

 // 当prepareToRecord方法被调用后可以获取音频格式
 @available(iOS 10.0, *)
 open var format: AVAudioFormat { get }
 
 // 录音代理
 unowned(unsafe) open var delegate: AVAudioRecorderDelegate?
   
 // 在录制期间获得当前的录制时间
 open var currentTime: TimeInterval { get }

 // 设备当前时间
 open var deviceCurrentTime: TimeInterval { get }

 // 是否启用录音测量,默认是关闭的,如果启用录音测量可以获得录音分贝等数据信息
 open var isMeteringEnabled: Bool 

 // 当前录音的通道
 open var channelAssignments: [AVAudioSessionChannelDescription]?
  • 方法
 // 初始化方法,注意其中的url必须是本地文件url,settings是录音格式、编码等设置
 public init(url: URL, settings: [String : Any]) throws
    
 // 根据URL和音频格式初始化录音对象
 @available(iOS 10.0, *)
 public init(url: URL, format: AVAudioFormat) throws

 // 为录音准备缓冲区,如果不手动调用,在调用record录音时也会自动调用
 open func prepareToRecord() -> Bool

 // 录音开始,暂停后调用会恢复录音
 open func record() -> Bool 

 // 基于deviceCurrentTime,在指定时间后开始录音
 open func record(atTime time: TimeInterval) -> Bool 

 // 按指定时长进行录音
 open func record(forDuration duration: TimeInterval) -> Bool

 // 在指定的时间点开始录制需要的时常
 open func record(atTime time: TimeInterval, forDuration duration: TimeInterval) -> Bool 

 // 暂停录制
 open func pause() 

 // 停止录制
 open func stop()

 // 删除录音,注意要删除录音此时录音机必须处于停止状态
 open func deleteRecording() -> Bool 

 // 更新测量数据,注意只有meteringEnabled为YES此方法才可用
 open func updateMeters()

 // 指定通道的测量峰值,注意只有调用完updateMeters才有值
 open func peakPower(forChannel channelNumber: Int) -> Float 

 // 指定通道的测量平均值,注意只有调用完updateMeters才有值
 open func averagePower(forChannel channelNumber: Int) -> Float
  • 代理(AVAudioRecorderDelegate)
public protocol AVAudioRecorderDelegate : NSObjectProtocol {

    // 录音完成或者停止后调用,如果由于中断停止不会触发
    optional public func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool)

    // 录音编码发送错误时调用
    optional public func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?)
    
    // 下面中断事件方法在iOS8之后被废弃,iOS之后使用AVAudioSession的通知进行监听
    @available(iOS, introduced: 2.2, deprecated: 8.0)
    optional public func audioRecorderBeginInterruption(_ recorder: AVAudioRecorder)

    @available(iOS, introduced: 6.0, deprecated: 8.0)
    optional public func audioRecorderEndInterruption(_ recorder: AVAudioRecorder, withOptions flags: Int)
}

录音实现思路

1、导入AVFoundation框架,因为我们需要AVAudioSessionAVAudioRecorderAVAudioPlayer等类

2、创建音频会话session,调用AVAudioSession的类方法open class func sharedInstance() -> AVAudioSession;返回一个单例实例

3、请求用户授权进行录音,如果用户同意那么调用setCategory设置音频会话分类,为了录制和播放音频,会话类型选择AVAudioSessionCategoryPlayAndRecord,否则提示用户进行设置

4、实例化一个音频录制者AVAudioRecorder,指定录音保存的路径并且设置录音相关属性,注意:录音机必须知道录音文件的格式、采样率、通道数、每个采样点的位数等信息,但是也并不是所有的信息都必须设置,通常只需要几个常用设置。AVAudioRecorder为我们提供了settings属性字典用于设置相关信息。

5、根据需求可以设置AVAudioRecorder的代理来监听录制情况。然后调用音频录制者的prepareToRecord(),准备录制音频。该方法执行底层Audio Queue初始化的必要过程,该方法还在URL参数指定的位置创建一个文件,将录制启动时的延时降低到最小

6、最后根据按钮触发录制,停止,播放等基本功能,注意在播放部分我们将使用AVAudioPlayer获取本地存储文件进行播放操作,并且可以设置代理监听播放的情况。

键值信息

在设置字典中指定的键值信息,所需要的完整可用键信息在文件,大部分的键都专门定义来特有的格式,不过下面介绍一些通用的音频格式

  • 音频格式

AVFormatIDKey键定义了写入内容的音频格式,下面的常量都是音频格式所支持的值,全部值在CoreAudio/CoreAudioTypes.h文件中

kAudioFormatLinearPCM
kAudioFormatMPEG4AAC
kAudioFormatAppleLossless
kAudioFormatAppleIMA4
kAudioFormatiLBC
kAudioFormatULaw

选择kAudioFormatLinearPCM会将未压缩的音频流写入到文件中。这种格式的保真度最高,不过相应的文件也最大。选择诸如AAC(kAudioFormatMPEG4AAC)AppleIMA4(kAudioFormatAppleIMA4)的压缩格式会显著缩小文件,还能保证高质量的音频内容

注意:

所指定的音频格式一定要和URL参数定义的文件类型兼容。比如,如果录制一个名为test.wav的文件,隐含的意思就是录制的音频必须满足Waveform Audio File Format(WAVE)的格式要求,即低字节、LinearPCM。为AVFormatIDKey的值指定除kAudioFormatLinearPCM之外的值会导致错误。查询NSError的localizedDescription会返回下面的错误信息描述:

 The operation couldn't be completed.( OSStatus error 1718449215)

 1718449215错误状态是4字节编码的整数值,‘fmt?’意指定义了一种不兼容的音频格式
  • 采用率

AVSampleRateKey用于定义录音器的采用率。采用率定义了对输入的模拟音频信号每一秒的采样数。在录制音频的质量及最终文件大小方面,采样率扮演着至关重要的角色。使用低采用率,比如:8kHz,会导致粗粒度、AM广播类型的录制效果,不过文件较小;使用低于44.1kHz的采用率(CD质量的采用率)会得到非常高质量的内容,不过文件就比较大。对于使用什么采用率最好没有一个明确的定义,不过应该尽量使用标准的采样率,比如8000、16000、22050或44100。最终由我们的耳朵进行判断

  • 通道数

AVNumberOfChannelsKey用于定义记录音频内容的通道数。指定默认值为1意味着使用单声道路径,设置为2意味这立体声录制。除非使用外部硬件进行录制,否则通常应该创建单声道录音

  • 指定格式的键

处理Linear PCM或压缩音频格式时,可以定义一些其他指定格式的键。

请求授权

从iOS10开始,在使用麦克风之前,操作系统要求应用程序必须得到用户的明确许可。需要到Info.plist文件中添加NSMicrophoneUsageDescription字段并写明使用原因

屏幕快照 2018-10-11 上午11.51.10.png

当应用程序试图访问麦克风时,操作系统会为用户弹出询问对话框

如果用户选择了Don't Allow按钮,那么所有试图录制的内容都是没有声音的。如果用户改变了想法,允许应用程序录制,需要改变一些设置,具体方法为在setting应用程序中设置Privacy|Microphone setting

实战

实现一个简单的录制播放功能,界面如下,包括开始/暂停录制、停止录制、播放录制音频等按钮

IMG_0306.PNG

基本代码

class FirstRecoderController: UIViewController {

    //audioRecorder和audioPlayer,一个用于录音,一个用于播放
    var audioRecorder: AVAudioRecorder?
    var audioPlayer: AVAudioPlayer?

    //获取音频会话单例
    let audioSession = AVAudioSession.sharedInstance()
    var isAllowed:Bool = false
    @IBOutlet weak var recoderButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.white
        requestRecordPermission()
        setupAudioSession()
    }
}

请求授权访问麦克风

  func requestRecordPermission() {
        //首先要判断是否允许访问麦克风
        audioSession.requestRecordPermission { [weak self] (allowed) in
            if !allowed{
                let alert = UIAlertController(title: "无法访问您的麦克风",
                                              message: "请到设置 -> 隐私 -> 麦克风 ,打开访问权限",
                                              preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "好的", style: .cancel, handler: nil))
                self?.present(alert, animated: true, completion: nil)
                self?.isAllowed = false
            }else{
                self?.isAllowed = true
            }
        }
    }

配置AudioSession,用于录音和播放

 func setupAudioSession() {
      if self.isAllowed{
          do {
              if #available(iOS 10.0, *) {
                  // 设置分类
                  try audioSession.setCategory(.playAndRecord, mode: .default)
              } else {
                  audioSession.perform(NSSelectorFromString("setCategory:error:"), with: AVAudioSession.Category.playAndRecord)
              }
              try audioSession.setActive(true)
              audioRecorder = try AVAudioRecorder(url: self.directoryURL()! as URL,settings: recordSettings())   // 初始化实例
              audioRecorder?.delegate = self // 设置代理
              audioRecorder?.prepareToRecord()  // 准备录音
          } catch let error as NSError{
              print(error)
          }
      }
  }

音频配置和存储目录

  func recordSettings() -> [String : Any] {
       return [AVSampleRateKey : NSNumber(value: Float(44100.0)),//声音采样率
            AVFormatIDKey : NSNumber(value: Int32(kAudioFormatMPEG4AAC)),//编码格式
            AVNumberOfChannelsKey : NSNumber(value: 1),//采集音轨
            AVEncoderAudioQualityKey : NSNumber(value: Int32(AVAudioQuality.medium.rawValue))]//音频质量
  }

 func directoryURL() -> URL? {
      //定义并构建一个url来保存音频,音频文件名为ddMMyyyyHHmmss.caf,根据时间来设置存储文件名
      let currentDateTime = NSDate()
      let formatter = DateFormatter()
      formatter.dateFormat = "ddMMyyyyHHmmss"

      //以下2种格式都可以
      //let recordingName = formatter.stringFromDate(currentDateTime)+".caf"
      let recordingName = formatter.string(from: currentDateTime as Date)+".m4a"

      let fileManager = FileManager.default
      let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
      let documentDirectory = urls[0]
      let soundURL = documentDirectory.appendingPathComponent(recordingName)
      return soundURL
  }

开始/恢复、停止录音

 @IBAction func startOrPauseRecord(sender: AnyObject) {
        if audioRecorder?.isRecording == true { // 是否正在录音,如果没有,开始录音
            audioRecorder?.pause()
            recoderButton.setTitle("Record", for: .normal)
        } else {
            audioRecorder?.record()
            recoderButton.setTitle("Pause", for: .normal)
        }
 }

 @IBAction func stopRecord(sender: AnyObject) {
        if audioRecorder?.isRecording == true {
            audioRecorder?.stop()
        }
  }

播放录音

 @IBAction func startPlaying(sender: AnyObject) {
       guard let audioRecorder = audioRecorder else { return }
       if (!audioRecorder.isRecording){
            do {
                let url = audioRecorder.url
                try audioPlayer = AVAudioPlayer(contentsOf: url)
                audioPlayer!.delegate = self
                audioPlayer!.play()
            } catch let error as NSError{
                print(error)
            }
       }
 }

录音完成和播放音频完成代理

 extension FirstRecoderController: AVAudioRecorderDelegate{
    func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
        if flag{
            let alert = UIAlertController(title: "Recorder",
                                          message: "Finished Recording",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK!", style: .default, handler: {action in
                print("OK was tapped")
            }))
            self.present(alert, animated:true, completion:nil)
        }
    }
 }

 extension FirstRecoderController: AVAudioPlayerDelegate{
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        if flag{ print("播放完成!") }
    }
 }

参考

AVAudioSession
AVAudioRecorder
《AVFoundation开发秘籍》

你可能感兴趣的:(iOS - AVAudioRecorder录制音频)