title: AVFoundation学习之简介及音频相关
date: 2019-08-20 10:22:52
tags:
一、简介
AVFoundation是可以用它来播放和创建基于时间的视听媒体的几个框架之一
二、音频播放及录制
1.音频播放
(1)初始化AVAudioSession
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playback)
} catch {
print("Category error:\(error)")
}
do {
try session.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
print("Active error: \(error)")
}
*setCategory
Ambient 游戏、效率应用程序 AVAudioSessionCategoryAmbient / kAudioSessionCategory_AmbientSound 使用这个分类应用会随着静音键和屏幕关闭而静音
不会终止其他应用播放的声音,可以和其他自带应用如iPod、Safari同时播放声音。
该类别无法在后台播放声音
不支持录音
Solo Ambient(默认) 游戏、效率应用程序 AVAudioSessionCategorySoloAmbient/kAudioSessionCategory_SoloAmbientSound 类似Ambient
使用这个分类应用会随着静音键和屏幕关闭而静音
会终止其它应用播放声音
该类别无法在后台播放声音
不支持录音
Playback 音频和视频播放 AVAudioSessionCategoryPlayback / kAudioSessionCategory_MediaPlayback 用于以音频为主的应用
不会随着静音键和屏幕关闭而静音
默认会 终止其他应用播放的声音
可在后台播放声音。
不支持录音
Record 录音机、视频捕捉 AVAudioSessionCategoryRecord / kAudioSessionCategory_RecordAudio 录音应用,除了来电铃声、闹钟、日历提醒之外的其他系统声音不会被播放。
不会会随着静音键和屏幕关闭而静音
会终止其他应用播放的声音
只提供单纯录音功能。
Play and Record VoIP、语音聊天 AVAudioSessionCategoryPlayAndRecord / kAudioSessionCategory_PlayAndRecord ,如果应用需要用到iPhone上的听筒,这个类别是你唯一的选择,在这个类别下,声音的默认出口为听筒或者耳机。
不会会随着静音键和屏幕关闭而静音
默认不会 终止其他应用播放的声音
提供录音和播放功能
Audio Processing 离线会话和处理 AVAudioSessionCategoryAudioProcessing / kAudioSessionCategory_AudioProcessing 在不播放或录制音频时使用音频硬件编解码器或信号处理器的类别。例如在执行离线音频格式转换时,此类别禁用播放和禁用录音。应用处于后台时,音频处理通常不会继续,但是可以在应用移至后台时,请求更多时间来完成处理。
Multi-Route 使用外部硬件的高级A/V应用程序 AVAudioSessionCategoryMultiRoute 通过可以用的音频辅助设备和内置音频硬件设备,我们可以自定义使用类型
不会随着静音键和屏幕关闭而静音
会终止其他应用播放的声音
既可以录音也可以播放
除了用以上代码设置category之外,还可以设置一个option属性
session.setCategory(.playback, options: .allowAirPlay)
option有以下类型
1、AVAudioSessionCategoryOptionMixWithOthers : 如果确实用的AVAudioSessionCategoryPlayback实现的一个背景音,但是呢,又想和QQ音乐并存,那么可以在AVAudioSessionCategoryPlayback类别下在设置这个选项,就可以实现共存了。
2、AVAudioSessionCategoryOptionDuckOthers:在实时通话的场景,比如QQ音乐,当进行视频通话的时候,会发现QQ音乐自动声音降低了,此时就是通过设置这个选项来对其他音乐App进行了压制。
3、AVAudioSessionCategoryOptionAllowBluetooth:如果要支持蓝牙耳机电话,则需要设置这个选项。
4、AVAudioSessionCategoryOptionDefaultToSpeaker: 如果在VoIP模式下,希望默认打开免提功能,需要设置这个选项。
5、AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers也是在9.0之后添加的。
6、AVAudioSessionCategoryOptionAllowBluetoothA2DP、AVAudioSessionCategoryOptionAllowAirPlay是10之后新加的,用来支持蓝牙A2DP耳机和AirPlay。
*setActive激活AVAudioSession,当自己App的Session被激活,其他App的就会解除激活,当options传notifyOthersOnDeactivation的时候,自己的Session解除激活后恢复其他App Session的激活状态
print("\(session.isOtherAudioPlaying)")
*可以通过isOtherAudioPlaying这个属性提前判断当前是否有其他App在播放音频
(2)初始化AVAudioPlayer
do {
try avAudioPlayer = AVAudioPlayer(contentsOf: urlPath)
} catch {
print("AVAudioPlayer init error\(error)")
}
if avAudioPlayer != nil {
print("初始化成功")
print("音频时长\(avAudioPlayer.duration)")
avAudioPlayer.delegate = self
// 是否能设置rate属性,只有这个属性设置成YES了才能设置rate属性,并且这些属性都设置在prepareToPlay方法调用之前
avAudioPlayer.enableRate = true
// 允许使用立体声播放声音 如果为-1.0则完全左声道,如果0.0则左右声道平衡,如果为1.0则完全为右声道
avAudioPlayer.pan = 0
avAudioPlayer.isMeteringEnabled = true
// 准备播放,这个方法可以不执行,但执行的话可以降低播放器play方法和你听到声音之间的延时
avAudioPlayer.prepareToPlay()
} else {
print("初始化失败")
}
(3)开始播放语音
@IBAction func playAudio(_ sender: Any) {
isPlay = !isPlay
if isPlay {
avAudioPlayer.play()
if avAudioPlayer.isPlaying {
btnPlayAudio.setTitle("暂停播放", for: .normal)
//开启定时器更新播放进度
self.cadisplayerLinkStartPlayerAudio()
}
} else {
avAudioPlayer.pause()
btnPlayAudio.setTitle("播放本地音频", for: .normal)
}
}
func cadisplayerLinkStartPlayerAudio () {
if cadisplayerlink == nil {
cadisplayerlink = CADisplayLink(target: self, selector: #selector(updateProgress))
cadisplayerlink.preferredFramesPerSecond = 5
cadisplayerlink.add(to: RunLoop.current, forMode: .common)
}
}
@objc func updateProgress() {
print("currentTime: \(avAudioPlayer.currentTime)")
print("deviceCurrentTime: \(avAudioPlayer.deviceCurrentTime)")
progressView.setProgress(Float(avAudioPlayer.currentTime / avAudioPlayer.duration), animated: true)
}
*CADisplayLink 作用相当于NSTimer,可以通过preferredFramesPerSecond
来设置每秒刷新次数.preferredFramesPerSecond
默认值为屏幕最大帧率(maximumFramesPerSecond
),目前是60.
每个计时器对象只能加入到一个runloop
中,但是可以被添加到不同的模式中,如果想从所有的模式中移除计时器,需要执行-invalidate()
方法.
*可以通过avAudioPlayer.peakPower(forChannel: 0)获取某声道的分贝值,avAudioPlayer.averagePower(forChannel: 0)获取某声道的平均分贝值。这个值的范围是从表示最大分贝的0dB(full scale)到最小分贝或静音的-160dB。获取这两个值之前,要先设置属性meteringEnabled
为YES,才能对音频进行测量。另,每当需要读取值时,需要先调用updateMeters
方法才能获取最新的值。
2.音频录制
*录音要在infoplist中添加Privacy - Microphone Usage Description
(1)初始化AVAudioSession
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playAndRecord)
} catch {
print("session init error:\(error.localizedDescription)")
}
do {
try session.setActive(true, options: .notifyOthersOnDeactivation)
} catch {
print("session active error:\(error.localizedDescription)")
}
(2)初始化AVAudioRecorder
labelState.text = "准备录制"
filePath = NSTemporaryDirectory() + "memo.caf"
do {
self.avAudioRecoder = try AVAudioRecorder(url: URL(fileURLWithPath: filePath), settings: [AVFormatIDKey:kAudioFormatAppleIMA4,AVSampleRateKey: 44100.0, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: kRenderQuality_Medium])
} catch {
print("audioRecoder init error:\(error.localizedDescription)")
}
if self.avAudioRecoder != nil {
self.avAudioRecoder.delegate = self
self.avAudioRecoder.isMeteringEnabled = true
self.avAudioRecoder.prepareToRecord()
}
/* AVAudioRecorder 录音类这个后面说
可以设置的一些属性 :
<1>AVNumberOfChannelsKey 通道数
<2>AVSampleRateKey 采样率 一般用44100
<3>AVLinearPCMBitDepthKey 比特率 一般设16 32
<4>AVEncoderAudioQualityKey 质量
<5>AVEncoderBitRateKey 比特采样率 一般是128000
<6>AVChannelLayoutKey 通道布局值是一个包含AudioChannelLayout的NSData对象
*/
*AVFormatIDKey写入内容的音频格式,常用的音频格式支持的值
kAudioFormatLinearPCM
kAudioFormatMPEG4AAC
kAudioFormatAppleLossless
kAudioFormatAppleIMA4
kAudioFormatiLBC
kAudioFormatULaw
kAudioFormatLinearPCM -会将未压缩的音频流写入文件中, 这种格式的保真度最高,相应的文件也最大。
AAC或Apple IMA4的压缩格式会显著缩小文件,还能保证高质量的音频内容。
*AVSampleRateKey --定义录音器采样率。采样率定义了对输入的模拟音频信号每一秒内的采样数。采样率决定音频的质量及最终文件大小。一般标准的采样率:8k、16k、22.5k、44.1k
*AVNumbeOfChannelsKey – 定义记录音频通道数。默认值1,单声道录制。设置2-立体声录制。除非使用外部硬件进行录制,一般应该创建单声道录音
(3)开始录音
if !self.avAudioRecoder.isRecording {
//开启定时器
timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
timer.schedule(deadline: .now(), repeating: 1)
var time = 0
timer.setEventHandler {
time += 1
if time <= 10 {
//小于10s更新录制时间和分贝
DispatchQueue.main.async {
self.updateRecordTimeAndPower(time: time)
}
} else {
//停止录音
self.stopRecord()
}
}
self.timer.resume()
self.avAudioRecoder.record()
}
*更新录制时间和分贝
labelRecordTime.text = "\(time)秒"
self.avAudioRecoder.updateMeters()
let peakPower = self.avAudioRecoder.peakPower(forChannel: 0)
let averPower = self.avAudioRecoder.averagePower(forChannel: 0)
labelPeakPower.text = "分贝值\(peakPower)"
labelAveragePower.text = "平均分贝值\(averPower)"
(4)停止录音,保存视频
func stopRecord() {
self.timer.cancel()
self.avAudioRecoder.stop()
self.labelState.text = "录制结束"
self.saveRecordingName(name: "TestAudio") { (save, error) in
if save {
print("保存视频成功")
} else {
print("保存视频失败 error: \(error)")
}
}
}
func saveRecordingName(name: String, block:(_ save: Bool, _ error: String)->()) {
let timesTamp = NSDate.timeIntervalSinceReferenceDate
let fileName = "\(name)-\(timesTamp).m4a"
let docDir = self.documentsDirectory()
let destPath = docDir + "/\(fileName)"
print(destPath)
print(self.filePath)
let destUrl = URL(fileURLWithPath: destPath)
var errorStr = ""
do {
try FileManager.default.moveItem(at: URL(fileURLWithPath: self.filePath), to: destUrl)
} catch {
errorStr = error.localizedDescription
}
if errorStr.isEmpty {
self.destPath = destUrl
block(true, errorStr)
self.avAudioRecoder.prepareToRecord()
} else {
block(false, errorStr)
}
}
三、文字转语音
let synthesizer = AVSpeechSynthesizer()
let voice = AVSpeechSynthesisVoice(language: "zh-CN")
let utterance = AVSpeechUtterance(string: "哈哈哈哈哈哈哈")
utterance.rate = 1//播放速率
utterance.voice = voice
// utterance.pitchMultiplier = 0.8// 可在播放待定语句时候改变声调
utterance.postUtteranceDelay = 0.1// 语音合成器在播放下一条语句的时候有短暂的停顿 这个属性指定停顿的时间
synthesizer.speak(utterance)
print("目前支持的语音列表:\(AVSpeechSynthesisVoice.speechVoices())")
四、参考链接
https://www.cnblogs.com/zhangxiaoxu/p/8022957.html
https://www.jianshu.com/p/bd333acf62e2 包含播放中断情况的处理