AVAudioEngine 比 AVAudioPlayer 更增強大,固然使用上比起 AVAudioPlayer 繁瑣。ios
AVAudioEngine 對於 Core Audio 做了一些使用上的封裝簡化,簡便的作了一些音頻信號的處理。git
使用 AVAudioPlayer ,是音頻文件級別的處理。github
使用 AVAudioEngine,是音頻數據流級別的處理。面試
AVAudioEngine 能夠作到低時延的、實時音頻處理。還能夠作到音頻的多輸入,添加特殊的效果,例如三維空間音效算法
AVAudioEngine 能夠作出強大的音樂處理與混音 app,配合製做複雜的三維空間音效的遊戲,本文來一個簡單的變聲應用swift
通用架構圖,場景是 K 歌bash
首先,簡單理解下微信
來一個 AVAudioEngine 實例,而後添加節點 Node, 有播放器的 Player Node, 音效的 Effect Node.數據結構
將節點連在音頻引擎上,即 AVAudioEngine 實例。而後創建節點間的關聯,組成一條音頻的數據處理鏈。 處理後的音頻數據,流過最後的一個節點,就是音頻引擎的輸出了。架構
須要用到 AVAudioEngine 和 AVAudioPlayerNode
// 音頻引擎是樞紐
var audioAVEngine = AVAudioEngine()
// 播放節點
var enginePlayer = AVAudioPlayerNode()
// 變聲單元:調節音高
let pitchEffect = AVAudioUnitTimePitch()
// 混響單元
let reverbEffect = AVAudioUnitReverb()
// 調節音頻播放速度單元
let rateEffect = AVAudioUnitVarispeed()
// 調節音量單元
let volumeEffect = AVAudioUnitEQ()
// 音頻輸入文件
var engineAudioFile: AVAudioFile!複製代碼
作一些設置
先取得輸入節點的 AVAudioFormat 引用,
這是音頻流數據的默認描述文件,包含通道數、採樣率等信息。
實際上,AVAudioFormat 就是對 Core Audio 的音頻緩衝數據格式文件 AudioStreamBasicDescription, 作了一些封裝。
audioAVEngine 作子節點關聯的時候,能夠用到
// 作一些配置,功能初始化
func setupAudioEngine() {
// 這個例子,是單音
let format = audioAVEngine.inputNode.inputFormat(forBus: 0)
// 添加功能
audioAVEngine.attach(enginePlayer)
audioAVEngine.attach(pitchEffect)
audioAVEngine.attach(reverbEffect)
audioAVEngine.attach(rateEffect)
audioAVEngine.attach(volumeEffect)
// 鏈接功能
audioAVEngine.connect(enginePlayer, to: pitchEffect, format: format)
audioAVEngine.connect(pitchEffect, to: reverbEffect, format: format)
audioAVEngine.connect(reverbEffect, to: rateEffect, format: format)
audioAVEngine.connect(rateEffect, to: volumeEffect, format: format)
audioAVEngine.connect(volumeEffect, to: audioAVEngine.mainMixerNode, format: format)
// 選擇混響效果爲大房間
reverbEffect.loadFactoryPreset(AVAudioUnitReverbPreset.largeChamber)
do {
// 能夠先開啓引擎
try audioAVEngine.start()
} catch {
print("Error starting AVAudioEngine.")
}
}複製代碼
播放
func play(){
let fileURL = getURLforMemo()
var playFlag = true
do {
// 先拿 URL 初始化 AVAudioFile
// AVAudioFile 加載音頻數據,造成數據緩衝區,方便 AVAudioEngine 使用
engineAudioFile = try AVAudioFile(forReading: fileURL)
// 變聲效果,先給一個音高的默認值
// 看效果,來點尖利的
pitchEffect.pitch = 2400
reverbEffect.wetDryMix = UserSetting.shared.reverb
rateEffect.rate = UserSetting.shared.rate
volumeEffect.globalGain = UserSetting.shared.volume
} catch {
engineAudioFile = nil
playFlag = false
print("Error loading AVAudioFile.")
}
// AVAudioPlayer 主要是音量大小的檢測,這裏作了一些取巧
// 就是爲了製做上篇播客介紹的,企鵝張嘴的動畫效果
do {
audioPlayer = try AVAudioPlayer(contentsOf: fileURL)
audioPlayer.delegate = self
if audioPlayer.duration > 0.0 {
// 不靠他播放,要靜音
// audioPlayer 不是用於播放音頻的,因此他的音量設置爲 0
audioPlayer.volume = 0.0
audioPlayer.isMeteringEnabled = true
audioPlayer.prepareToPlay()
} else {
playFlag = false
}
} catch {
audioPlayer = nil
engineAudioFile = nil
playFlag = false
print("Error loading audioPlayer.")
}
// 兩個播放器,要一塊兒播放,前面作了一個 audioPlayer 可用的標記
if playFlag == true {
// enginePlayer,有聲音
// 真正用於播放的 enginePlayer
enginePlayer.scheduleFile(engineAudioFile, at: nil, completionHandler: nil)
enginePlayer.play()
// audioPlayer,沒聲音,用於檢測
audioPlayer.play()
setPlayButtonOn(flag: true)
startUpdateLoop()
audioStatus = .playing
}
}
複製代碼
同時播放 AVAudioPlayerNode (有聲音), AVAudioPlayer (啞吧的,就爲了取下數據與狀態), 經過 AVAudioPlayerNode 添加變聲等音效,經過作音量大小檢測。
看起來有些累贅,蘋果天然是不會推薦這樣作的。
若是是錄音,經過 NodeTapBlock 對音頻輸入流的信息,作實時分析。
播放也相似,處理音頻信號,取出平均音量,就能夠刷新 UI 了。
經過 AVAudioPlayer ,能夠方便拿到當前播放時間,文件播放時長等信息,
經過 AVAudioPlayerDelegate,能夠方便播放結束了,去刷新 UI
固然,使用 AVAudioPlayerNode ,這些都是能夠作到的
結束播放
func stopPlayback() {
setPlayButtonOn(flag: false)
audioStatus = .stopped
// 兩個播放器,一塊兒開始,一塊兒結束
audioPlayer.stop()
enginePlayer.stop()
stopUpdateLoop()
}
複製代碼
音效的 pitch 屬性,取值範圍從 -2400 音分到 2400 音分,包含 4 個八度音階。 默認值爲 0
一個八度音程能夠分爲12個半音。
每個半音的音程至關於相鄰鋼琴鍵間的音程,等於100音分
func setPitch(value: Float) {
pitchEffect.pitch = value
}複製代碼
wetDryMix 的取值範圍是 0 ~ 100,
0 是全乾,幹聲即無音樂的純人聲
100 是全溼潤,空間感很強。
幹聲是原版,溼聲是通過後期處理的。
func toSetReverb(value: Float) {
reverbEffect.wetDryMix = value
}複製代碼
音頻播放速度 rate 的取值範圍是 0.25 ~ 4.0,
默認是 1.0,正常播放。
func toSetRate(value: Float) {
rateEffect.rate = value
}複製代碼
globalGain 的取值範圍是 -96 ~ 24, 單位是分貝
func toSetVolumn(value: Float){
volumeEffect.globalGain = value
}複製代碼
TTS,通常會用到 AVSpeechSynthesizer 和他的代理 AVSpeechSynthesizerDelegate AVSpeechSynthesizer 是 AVFoundation 框架下的一個類,它的功能就是輸入文字,讓你的應用,選擇 iOS 平臺支持的語言和方言,而後合成語音,播放出來。
iOS 平臺,支持三種中文,就是三種口音,有中文簡體 zh-CN,Ting-Ting 朗讀;有 zh-HK,Sin-Ji 朗讀;有 zh-TW,Mei-Jia 朗讀。
可參考 How to get a list of ALL voices on iOS
AVSpeechSynthesizer 須要拿材料 AVSpeechUtterance 去朗讀。
語音文本單元 AVSpeechUtterance 封裝了文字,還有對應的朗讀效果參數。
朗讀效果中,能夠設置口音,本文 Demo 採用 zh-CN。還能夠設置變聲和語速 (發音速度)。
拿到 AVSpeechUtterance ,合成器 AVSpeechSynthesizer 就能夠朗讀了。若是 AVSpeechSynthesizer 正在朗讀,AVSpeechUtterance 就會放在 AVSpeechSynthesizer 的朗讀隊列裏面,按照先進先出的順序等待朗讀。
蘋果框架的粒度都很細,語音合成器 AVSpeechSynthesizer,也有暫定、繼續播放與結束播放功能。
中止了語音合成器 AVSpeechSynthesizer,若是他的朗讀隊列裏面還有語音文本AVSpeechUtterance,剩下的都會直接移除。
使用合成器代理,能夠監聽朗讀時候的事件。例如:開始朗讀,朗讀結束
// 來一個合成器
let synthesizer = AVSpeechSynthesizer()
// ...
// 設置合成器的代理,監聽事件
synthesizer.delegate = self複製代碼
// 朗讀
func play() {
let words = UserSetting.shared.message
// 拿文本,去實例化語音文本單元
let utterance = AVSpeechUtterance(string: words)
// 設置發音爲簡體中文 ( 中國大陸 )
utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN")
// 設置朗讀的語速
utterance.rate = AVSpeechUtteranceMaximumSpeechRate * UserSetting.shared.rate
// 設置音高
utterance.pitchMultiplier = UserSetting.shared.pitch
synthesizer.speak(utterance)
}
// 暫停朗讀,沒有設置當即暫停,是按字暫停
func pausePlayback() {
synthesizer.pauseSpeaking(at: AVSpeechBoundary.word)
}
// 繼續朗讀
func continuePlayback() {
synthesizer.continueSpeaking()
}
// 中止播放
func stopPlayback() {
// 讓合成器立刻中止朗讀
synthesizer.stopSpeaking(at: AVSpeechBoundary.immediate)
// 中止計時器更新狀態,具體見文尾的 github repo
stopUpdateLoop()
setPlayButtonOn(false)
audioStatus = .stopped
}複製代碼
// 開始朗讀。朗讀每個語音文本單元的時候,都會來一下
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) {
setPlayButtonOn(true)
startUpdateLoop()
audioStatus = .playing
}
// 結束朗讀。每個語音文本單元結束朗讀的時候,都會來一下
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
stopUpdateLoop()
setPlayButtonOn(false)
audioStatus = .stopped
}
// 語音文本單元裏面,每個字要朗讀的時候,都會來一下
// 讀書應用,朗讀前,能夠用這個高光正在讀的詞語
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
let speakingString = utterance.speechString as NSString
let word = speakingString.substring(with: characterRange)
print(word)
}
// 暫定朗讀
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didPause utterance: AVSpeechUtterance) {
stopUpdateLoop()
setPlayButtonOn(false)
audioStatus = .paused
}
// 繼續朗讀
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didContinue utterance: AVSpeechUtterance) {
setPlayButtonOn(true)
startUpdateLoop()
audioStatus = .playing
}複製代碼
代碼: