前言
前段時(shí)間在閱讀蘋果音頻文檔(均列在參考資料一節(jié)里面了),并做了一些音頻相關(guān)的開發(fā)(主要是帶回音消除的錄音)。這里做一個(gè)總結(jié)。
關(guān)于 Audio Session
每一個(gè) app 帶有一個(gè) AVAudioSession 的單例(也就是說正常情況下你無法獲得第二個(gè) AVAudioSession 實(shí)例)。iOS 系統(tǒng)上每個(gè) app 有各自不同的 AVAudioSession 實(shí)例。通過使用這個(gè)實(shí)例的方法可以告訴系統(tǒng)當(dāng)前的 app 是怎樣使用手機(jī)的音頻服務(wù)的,然后系統(tǒng)會(huì)根據(jù)每個(gè) app 的配置進(jìn)行相應(yīng)的協(xié)調(diào),盡量滿足所有 app 的請(qǐng)求,當(dāng)無法滿足的時(shí)候,系統(tǒng)盡量滿足前臺(tái) app 的要求或者系統(tǒng)電話服務(wù)等。比如說,如果當(dāng)前另外一個(gè) app 正在播放的話,當(dāng)前 app 可能希望能將其播放的音頻和其他 app 的音頻一起播放,而不是暫停其他 app 的音頻服務(wù);又比如說當(dāng)前 app 需要播放音頻或者進(jìn)行錄音;比如當(dāng)前 app 只是播放音頻;比如當(dāng)前 app 只是錄音;比如當(dāng)前 app 播放音頻時(shí)候屏蔽所有其他 app 的音頻等等,總之就是告訴系統(tǒng)當(dāng)前 app 是如何使用它的音頻服務(wù)的。
Audio Session 有三個(gè)比較重要的概念:
- category
- mode
- option
通過配置這三個(gè)內(nèi)容,表達(dá)了當(dāng)前 app 的使用音頻服務(wù)的具體意圖。
在某些情況下,我們不需要配置 Audio Session 的 category,比如如果使用 AVAudioRecorder 來錄音,并不需要配置 category 為 AVAudioSessionCategoryRecord,因?yàn)橄到y(tǒng)在我們使用 AVAudioRecorder 的錄音服務(wù)的時(shí)候已經(jīng)為我們配置了。同時(shí) Audio Session 有默認(rèn)配置(如果當(dāng)前 app 不進(jìn)行配置的話)。當(dāng)默認(rèn)配置無法滿足需求的時(shí)候,就可以手動(dòng)配置 Audio Session
Audio Session 另一個(gè)重要功能是配置系統(tǒng)音頻服務(wù)硬件參數(shù),比如配置輸入的聲道數(shù),采樣率,IO 緩存時(shí)間等等。其 API 中 setPreferred__ 開頭的方法作用就是這些。
配置完 Audio Session 以后,當(dāng)我們要求的音頻服務(wù)受到打斷(比如,電話來了,則系統(tǒng)要停止錄音和播放;比如,app 退到后臺(tái)運(yùn)行了,如果沒有配置后臺(tái)運(yùn)行的話,系統(tǒng)也會(huì)停止當(dāng)前 app 的音頻服務(wù);),我們可以使用通知中心的方式來監(jiān)聽,并做一下相應(yīng)的處理。音頻服務(wù)中斷有兩個(gè)概念比較重要,就是中斷開始以及中斷結(jié)束,我們可以在中斷開始的時(shí)候記錄當(dāng)前播放時(shí)間點(diǎn),中斷結(jié)束的時(shí)候重新開始播放(當(dāng)然系統(tǒng)默認(rèn)行為是會(huì)在中斷結(jié)束時(shí)重新開始播放音頻,但是如果默認(rèn)行為無法滿足需求時(shí)候,就需要自行處理了)。
Audio Session 另一個(gè)重要我們需要監(jiān)聽的變化是路由變化 (Route Change)。比如有新的輸出源來了(比如用戶把耳機(jī)插進(jìn)去或者是用戶開始使用藍(lán)牙耳機(jī)),或者原來的輸出源不可用了(用戶拔掉耳機(jī)等)。
還有一些其他的功能,比如當(dāng)前其他 app 是否在播放音頻,請(qǐng)求麥克風(fēng)權(quán)限等,可以查看具體的 API 文檔 AVAudioSession。
Audio Queue Service
使用 Audio Queue Service 我們可以做到錄音或者播放音頻。當(dāng)然我們使用 AVAudioPlayer 也能很簡(jiǎn)單的做播放音頻功能,那為什么要用到 Audio Queue Service 呢?它有幾個(gè)優(yōu)點(diǎn)
- 設(shè)想你的要嚴(yán)格同步不同音頻的播放。 Audio Queue Service 的回調(diào)函數(shù)包含相應(yīng)的時(shí)間,來滿足你的需求。
- 如果是播放音頻用 Audio Queue Service,在將音頻數(shù)據(jù)給它的回調(diào)函數(shù)之前做一些處理,比如變聲等。
- 如果是錄音,可以對(duì)回調(diào)函數(shù)傳回來的音頻數(shù)據(jù)做處理,比如寫到文件或者對(duì)這些音頻數(shù)據(jù)進(jìn)行其他任何處理
理解 Audio Queue Service 比較重要的是它的 buffer queue。拿錄音來說,一般設(shè)置的緩存是3個(gè)。首先通過 AudioQueueEnqueueBuffer 將可用緩存提供給相應(yīng)的 queue。然后系統(tǒng)開始將記錄下的音頻數(shù)據(jù)放到第一個(gè)緩存,當(dāng)緩存滿的時(shí)候,回調(diào)函數(shù)會(huì)將該 buffer 返回給你并將該緩存出列,在回調(diào)函數(shù)中我們可以對(duì)這些數(shù)據(jù)進(jìn)行處理,與此同時(shí)系統(tǒng)開始將數(shù)據(jù)寫到第二個(gè)緩存,當(dāng)我們的回調(diào)函數(shù)處理完第一個(gè)返回的緩存時(shí)候,我們需要重新使用 AudioQueueEnqueueBuffer 將該緩存入列,以便系統(tǒng)再次使用。當(dāng)?shù)诙€(gè)緩存返回的時(shí)候,系統(tǒng)開始往第三個(gè)緩存寫數(shù)據(jù),寫完之后返回第三個(gè)緩存,并開始往之前返回的第一個(gè)緩存寫數(shù)據(jù)。這就是一個(gè)典型的隊(duì)列結(jié)構(gòu)(先進(jìn)先出,后進(jìn)后出)。
Audio Unit
Audio Unit 是所有 iOS 以及 macOS 上音頻框架的最底層,無論使用的是 AVAudioRecorder、AVAudioPlayer、或者 Audio Queue Service、OpenAL 等,最終底層實(shí)現(xiàn)都是通過 Audio Unit 來完成的。
在 iOS 上可用的 audio unit 是有限的,macOS 上面可以自定義一個(gè) audio unit 但是 iOS 上不行,只能使用系統(tǒng)提供的 audio unit。
什么時(shí)候使用 Audio Unit ?官方的說法是,當(dāng)你需要高度可控的、高性能、高靈活性或者需要某種特別的功能(比如回音消除,只在 audio unit 提供支持,所有高層 API 均不支持回音消除)的時(shí)候,才需要使用 audio unit。
有4類 audio unit(具體用途看名字就能理解):
- Effect
- Mixing
- I/O
- Format convert
使用 audio unit 有兩種方式:
- 直接使用
- 混合構(gòu)建使用,AUGraph
第一種方式是對(duì)于比較簡(jiǎn)單的結(jié)構(gòu)。
第二種方式是用于構(gòu)建復(fù)雜的音頻處理流程。配置具體的 audio unit 的屬性的時(shí)候還是會(huì)用到直接使用種的方法。
Audio Unit 重要概念
audio unit 重要的概念是 scope 和 element。scope 包含 element。
scope 分三種:
- Input scope
- Output scope
- global scope
scope 概念有一點(diǎn)抽象,可以這樣理解 scope,比如 input scope 表示里面所有的 element 都需要一個(gè)輸入。output scope 表示里面所有的 element 都會(huì)輸出到某個(gè)地方。至于 global scope,應(yīng)該是用來配置一些和輸入輸出概念無關(guān)的屬性。
element 官方的解釋是可以理解成 bus,就是將數(shù)據(jù)從 element 的一頭傳到另一頭。
其他
- 音頻格式轉(zhuǎn)換
- 音頻(流)讀寫
iOS 錄音的幾種方式
- 使用 AVAudioRecorder
- 使用 Audio Queue Service
- 使用 Audio Unit
- 使用 OpenAL
iOS 播放音頻的方式
- 使用 AVAudioPlayer
- 使用 AVPlayer
- 使用 System Sound Services
- 使用 Audio Queue Service
- 使用 Audio Unit
- 使用 OpenAL
一些需要思考的問題
- 如何獲取當(dāng)前揚(yáng)聲器播放的音頻數(shù)據(jù)?(包括其他 app)
- 如何實(shí)時(shí)錄音,同時(shí)當(dāng)前手機(jī)正在播放音頻
- 如何做回音消除,或者不借助系統(tǒng)提供的回音消除功能來完成回音消除的需求?
這里有些問題我也不知道如何解答,若有了解的,請(qǐng)多多指教一下。
一些有用的開源代碼
- aurioTouch 官方的關(guān)于 audio unit 使用 demo 代碼。注意其中關(guān)于 AVAudioSession 配置的順序是錯(cuò)的,你可以看它的代碼和 AVAudioSession Api 的說明來知道錯(cuò)誤的地方
- XBEchoCancellation 可以學(xué)習(xí)其中關(guān)于回音消除的使用。官方的 aurioTouch demo 簡(jiǎn)單的改 audio unit 類型為 voiceprocess 也可以做回音消除,但是當(dāng)涉及到同時(shí)播放音頻時(shí),官方 demo 在某些 iPhone 上會(huì)失敗,所以建議參考這個(gè)源碼