我的 iOS 音訊處理總結

weixin_34337265發表於2018-07-14

前言

前段時間在閱讀蘋果音訊文件(均列在參考資料一節裡面了),並做了一些音訊相關的開發(主要是帶回音消除的錄音)。這裡做一個總結。

關於 Audio Session

每一個 app 帶有一個 AVAudioSession 的單例(也就是說正常情況下你無法獲得第二個 AVAudioSession 例項)。iOS 系統上每個 app 有各自不同的 AVAudioSession 例項。通過使用這個例項的方法可以告訴系統當前的 app 是怎樣使用手機的音訊服務的,然後系統會根據每個 app 的配置進行相應的協調,儘量滿足所有 app 的請求,當無法滿足的時候,系統儘量滿足前臺 app 的要求或者系統電話服務等。比如說,如果當前另外一個 app 正在播放的話,當前 app 可能希望能將其播放的音訊和其他 app 的音訊一起播放,而不是暫停其他 app 的音訊服務;又比如說當前 app 需要播放音訊或者進行錄音;比如當前 app 只是播放音訊;比如當前 app 只是錄音;比如當前 app 播放音訊時候遮蔽所有其他 app 的音訊等等,總之就是告訴系統當前 app 是如何使用它的音訊服務的。

Audio Session 有三個比較重要的概念:

  1. category
  2. mode
  3. option

通過配置這三個內容,表達了當前 app 的使用音訊服務的具體意圖。

在某些情況下,我們不需要配置 Audio Session 的 category,比如如果使用 AVAudioRecorder 來錄音,並不需要配置 category 為 AVAudioSessionCategoryRecord,因為系統在我們使用 AVAudioRecorder 的錄音服務的時候已經為我們配置了。同時 Audio Session 有預設配置(如果當前 app 不進行配置的話)。當預設配置無法滿足需求的時候,就可以手動配置 Audio Session

Audio Session 另一個重要功能是配置系統音訊服務硬體引數,比如配置輸入的聲道數,取樣率,IO 快取時間等等。其 API 中 setPreferred__ 開頭的方法作用就是這些。

配置完 Audio Session 以後,當我們要求的音訊服務受到打斷(比如,電話來了,則系統要停止錄音和播放;比如,app 退到後臺執行了,如果沒有配置後臺執行的話,系統也會停止當前 app 的音訊服務;),我們可以使用通知中心的方式來監聽,並做一下相應的處理。音訊服務中斷有兩個概念比較重要,就是中斷開始以及中斷結束,我們可以在中斷開始的時候記錄當前播放時間點,中斷結束的時候重新開始播放(當然系統預設行為是會在中斷結束時重新開始播放音訊,但是如果預設行為無法滿足需求時候,就需要自行處理了)。

Audio Session 另一個重要我們需要監聽的變化是路由變化 (Route Change)。比如有新的輸出源來了(比如使用者把耳機插進去或者是使用者開始使用藍芽耳機),或者原來的輸出源不可用了(使用者拔掉耳機等)。

還有一些其他的功能,比如當前其他 app 是否在播放音訊,請求麥克風許可權等,可以檢視具體的 API 文件 AVAudioSession

Audio Queue Service

使用 Audio Queue Service 我們可以做到錄音或者播放音訊。當然我們使用 AVAudioPlayer 也能很簡單的做播放音訊功能,那為什麼要用到 Audio Queue Service 呢?它有幾個優點

  1. 設想你的要嚴格同步不同音訊的播放。 Audio Queue Service 的回撥函式包含相應的時間,來滿足你的需求。
  2. 如果是播放音訊用 Audio Queue Service,在將音訊資料給它的回撥函式之前做一些處理,比如變聲等。
  3. 如果是錄音,可以對回撥函式傳回來的音訊資料做處理,比如寫到檔案或者對這些音訊資料進行其他任何處理

理解 Audio Queue Service 比較重要的是它的 buffer queue。拿錄音來說,一般設定的快取是3個。首先通過 AudioQueueEnqueueBuffer 將可用快取提供給相應的 queue。然後系統開始將記錄下的音訊資料放到第一個快取,當快取滿的時候,回撥函式會將該 buffer 返回給你並將該快取出列,在回撥函式中我們可以對這些資料進行處理,與此同時系統開始將資料寫到第二個快取,當我們的回撥函式處理完第一個返回的快取時候,我們需要重新使用 AudioQueueEnqueueBuffer 將該快取入列,以便系統再次使用。當第二個快取返回的時候,系統開始往第三個快取寫資料,寫完之後返回第三個快取,並開始往之前返回的第一個快取寫資料。這就是一個典型的佇列結構(先進先出,後進後出)。

Audio Unit

Audio Unit 是所有 iOS 以及 macOS 上音訊框架的最底層,無論使用的是 AVAudioRecorder、AVAudioPlayer、或者 Audio Queue Service、OpenAL 等,最終底層實現都是通過 Audio Unit 來完成的。

在 iOS 上可用的 audio unit 是有限的,macOS 上面可以自定義一個 audio unit 但是 iOS 上不行,只能使用系統提供的 audio unit。

什麼時候使用 Audio Unit ?官方的說法是,當你需要高度可控的、高效能、高靈活性或者需要某種特別的功能(比如迴音消除,只在 audio unit 提供支援,所有高層 API 均不支援迴音消除)的時候,才需要使用 audio unit。

有4類 audio unit(具體用途看名字就能理解):

  1. Effect
  2. Mixing
  3. I/O
  4. Format convert

使用 audio unit 有兩種方式:

  1. 直接使用
  2. 混合構建使用,AUGraph

第一種方式是對於比較簡單的結構。
第二種方式是用於構建複雜的音訊處理流程。配置具體的 audio unit 的屬性的時候還是會用到直接使用種的方法。

Audio Unit 重要概念

audio unit 重要的概念是 scope 和 element。scope 包含 element。

scope 分三種:

  1. Input scope
  2. Output scope
  3. global scope

scope 概念有一點抽象,可以這樣理解 scope,比如 input scope 表示裡面所有的 element 都需要一個輸入。output scope 表示裡面所有的 element 都會輸出到某個地方。至於 global scope,應該是用來配置一些和輸入輸出概念無關的屬性。

element 官方的解釋是可以理解成 bus,就是將資料從 element 的一頭傳到另一頭。

其他

  1. 音訊格式轉換
  2. 音訊(流)讀寫

iOS 錄音的幾種方式

  1. 使用 AVAudioRecorder
  2. 使用 Audio Queue Service
  3. 使用 Audio Unit
  4. 使用 OpenAL

iOS 播放音訊的方式

  1. 使用 AVAudioPlayer
  2. 使用 AVPlayer
  3. 使用 System Sound Services
  4. 使用 Audio Queue Service
  5. 使用 Audio Unit
  6. 使用 OpenAL

一些需要思考的問題

  1. 如何獲取當前揚聲器播放的音訊資料?(包括其他 app)
  2. 如何實時錄音,同時當前手機正在播放音訊
  3. 如何做迴音消除,或者不借助系統提供的迴音消除功能來完成迴音消除的需求?

這裡有些問題我也不知道如何解答,若有了解的,請多多指教一下。

一些有用的開原始碼

  1. aurioTouch 官方的關於 audio unit 使用 demo 程式碼。注意其中關於 AVAudioSession 配置的順序是錯的,你可以看它的程式碼和 AVAudioSession Api 的說明來知道錯誤的地方
  2. XBEchoCancellation 可以學習其中關於迴音消除的使用。官方的 aurioTouch demo 簡單的改 audio unit 型別為 voiceprocess 也可以做迴音消除,但是當涉及到同時播放音訊時,官方 demo 在某些 iPhone 上會失敗,所以建議參考這個原始碼

參考資料

  1. Core Audio Overview
  2. Audio Session Programming Guide
  3. Audio Queue Services Programming Guide
  4. Audio Unit Programming Guide
  5. Audio Unit Hosting Guide for iOS
  6. Multimedia Programming Guide

相關文章