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框架,因为我们需要AVAudioSession
、AVAudioRecorder
、AVAudioPlayer
等类
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
字段并写明使用原因
当应用程序试图访问麦克风时,操作系统会为用户弹出询问对话框
如果用户选择了Don't Allow按钮,那么所有试图录制的内容都是没有声音的。如果用户改变了想法,允许应用程序录制,需要改变一些设置,具体方法为在setting应用程序中设置Privacy|Microphone setting
实战
实现一个简单的录制播放功能,界面如下,包括开始/暂停录制、停止录制、播放录制音频等按钮
基本代码
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开发秘籍》