Android 音訊應用框架

junwua發表於2019-07-04

frameworks\av\media\libmedia
--------AudioRecord.cpp
--------------|
              |_ sp<IAudioRecord> record = audioFlinger->openRecord(input,
              |_  mAudioRecord = record;
              
--------AudioSystem.cpp
--------AudioTrack.cpp

 

frameworks\av\services\audioflinger
--------------AudioFlinger.cpp
              |_ AudioFlinger::openRecord(
              |_ recordTrack = thread->createRecordTrack_l
              |_ recordHandle = new RecordHandle(recordTrack);
              |_ 

              |_ audio_module_handle_t AudioFlinger::loadHwModule_l(const char *name)
              |_ load_audio_interface
              |_ hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name,
              |_ audio_hw_device_open(mod, dev);
              |_

--------------AudioHwDevice.cpp
--------------Threads.cpp
              |_ AudioFlinger::RecordThread::createRecordTrack_l(
              |_ track = new RecordTrack(this, client, sampleRate,
              |_

--------------Tracks.cpp

Audio Application Framework:音訊應用框架

  |---AudioTrack:負責回放資料的輸出,屬 Android 應用框架 API 類

  |---AudioRecord:負責錄音資料的採集,屬 Android 應用框架 API 類

  |---AudioSystem: 負責音訊事務的綜合管理,屬 Android 應用框架 API 類

 

Audio Native Framework:音訊本地框架

  |---AudioTrack:負責回放資料的輸出,屬 Android 本地框架 API 類

  |---AudioRecord:負責錄音資料的採集,屬 Android 本地框架 API 類

  |---AudioSystem: 負責音訊事務的綜合管理,屬 Android 本地框架 API 類

 

Audio Services:音訊服務

   |----AudioPolicyService:音訊策略的制定者,負責音訊裝置切換的策略抉擇、音量調節策略等

   |----AudioFlinger:音訊策略的執行者,負責輸入輸出流裝置的管理及音訊流資料的處理傳輸

 

Audio HAL:音訊硬體抽象層,負責與音訊硬體裝置的互動,由 AudioFlinger 直接呼叫

AudioTrack API 概述
   播放聲音可以使用 MediaPlayer 和 AudioTrack,兩者都提供 Java API 給應用開發者使用。
   兩者的差別在於:
         MediaPlayer 可以播放多種格式的音源,如 mp3、flac、wma、ogg、wav 等,
         而 AudioTrack 只能播放解碼後的 PCM 資料流。

瞭解下音訊重取樣:音訊重取樣是指這樣的一個過程——把一個取樣率的資料轉換為另一個取樣率的資料。
Android 原生系統上,音訊硬體裝置一般都工作在一個固定的取樣率上(如 48 KHz),因此所有音軌資料
都需要重取樣到這個固定的取樣率上,然後再輸出。
  為什麼這麼做?系統中可能存在多個音軌同時播放,而每個音軌的取樣率可能是不一致的;
  比如在播放音樂的過程中,來了一個提示音,這時需要把音樂和提示音混音並輸出到硬體裝置,
  而音樂的取樣率和提示音的取樣率不一致,問題來了,如果硬體裝置工作的取樣率設定為音樂的取樣率的話,
  那麼提示音就會失真;因此最簡單見效的解決方法是:硬體裝置工作的取樣率固定一個值,所有音軌在
   AudioFlinger 都重取樣到這個取樣率上,混音後輸出到硬體裝置,保證所有音軌聽起來都不失真

Google 把多個子類抽取出來獨立成檔案,比如 Threads.cpp、Tracks.cpp、Effects.cpp,
而 AudioFlinger.cpp 只包含對外提供的服務介面了
  |----AudioResampler.cpp:重取樣處理類,可進行取樣率轉換和聲道轉換;由錄製執行緒 
         AudioFlinger::RecordThread 直接使用

  |----AudioMixer.cpp:混音處理類,包括重取樣、音量調節、聲道轉換等,其中的重取樣複用了 AudioResampler;
          由回放執行緒 AudioFlinger::MixerThread 直接使用

  |----Effects.cpp:音效處理類

|----Tracks.cpp:音訊流管理類,可控制音訊流的狀態,如 start、stop、pause

  |-----Threads.cpp:回放執行緒和錄製執行緒類;回放執行緒從 FIFO 讀取回放資料並混音處理,然後寫資料到輸出流裝置;
          錄製執行緒從輸入流裝置讀取錄音資料並重取樣處理,然後寫資料到 FIFO

|-----AudioFlinger.cpp:AudioFlinger 對外提供的服務介面

 

AudioFlinger 服務啟動

從 Android 7.0 開始,AudioFlinger 在系統啟動時由 audioserver 載入(之前版本由 mediaserver 載入),
詳見 frameworks/av/media/audioserver/main_audioserver.cpp:
main_audioserver.cpp 編譯生成的可執行檔案存放在 /system/bin/audioserver,系統啟動時由 init 程式執行,
詳見 frameworks/av/media/audioserver/audioserver.rc:

ThreadBase:PlaybackThread 和 RecordThread 的基類

RecordThread:錄製執行緒類,由 ThreadBase 派生

PlaybackThread:回放執行緒基類,同由 ThreadBase 派生

MixerThread:混音回放執行緒類,由 PlaybackThread 派生,負責處理標識為 AUDIO_OUTPUT_FLAG_PRIMARY、AUDIO_OUTPUT_FLAG_FAST、   AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音訊流,MixerThread 可以把多個音軌的資料混音後再輸出

DirectOutputThread:直輸回放執行緒類,由 PlaybackThread 派生,負責處理標識為 AUDIO_OUTPUT_FLAG_DIRECT 的音訊流,  這種音訊流資料不需要軟體混音,直接輸出到音訊裝置即可

DuplicatingThread:複製回放執行緒類,由 MixerThread 派生,負責複製音訊流資料到其他輸出裝置,使用場景如主音效卡裝置、
   藍芽耳機裝置、USB 音效卡裝置同時輸出

OffloadThread:硬解回放執行緒類,由 DirectOutputThread 派生,負責處理標識為 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音訊流,
   這種音訊流未經軟體解碼的(一般是 MP3、AAC 等格式的資料),需要輸出到硬體解碼器,由硬體解碼器解碼成 PCM 資料  

從 Audio HAL 中,我們通常看到如下 4 種輸出流裝置,分別對應著不同的播放場景:

|----primary_out:主輸出流裝置,用於鈴聲類聲音輸出,對應著標識為 AUDIO_OUTPUT_FLAG_PRIMARY 的音訊流和一個 MixerThread  回放執行緒例項

|----low_latency:低延遲輸出流裝置,用於按鍵音、遊戲背景音等對時延要求高的聲音輸出,對應著標識為 AUDIO_OUTPUT_FLAG_FAST   的音訊流和一個 MixerThread 回放執行緒例項

|----deep_buffer:音樂音軌輸出流裝置,用於音樂等對時延要求不高的聲音輸出,對應著標識為 AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音訊流和一個 MixerThread 回放執行緒例項

|----compress_offload:硬解輸出流裝置,用於需要硬體解碼的資料輸出,對應著標識為 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD  的音訊流和一個 OffloadThread 回放執行緒例項

可能有人產生這樣的疑問:既然 primary_out 裝置一直保持開啟,那麼能耗豈不是很大?這裡闡釋一個概念:輸出流裝置屬於邏輯裝置,  並不是硬體裝置。所以即使輸出流裝置一直保持開啟,只要硬體裝置不工作,那麼就不會影響能耗。那麼硬體裝置什麼時候才會開啟呢?  答案是 PlaybackThread 將音訊資料寫入到輸出流裝置時

系統啟動時,就已經開啟 primary_out、low_latency、deep_buffer 這三種輸出流裝置,並建立對應的 MixerThread 了;
 而此時 DirectOutputThread 與 OffloadThread 不會被建立,直到標識為 AUDIO_OUTPUT_FLAG_DIRECT/AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 
 的音訊流需要輸出時,才開始建立 DirectOutputThread/OffloadThread 和開啟 direct_out/compress_offload 裝置。

NuPlayer DataSource/Parser 解析 mp3、flac 等檔案得到資料編碼資訊,並在構造 AudioTrack 例項時作為引數傳入,AudioFlinger   將基於這些編碼資訊開啟 compress_offload 裝置。到這裡,大家明白了嗎?每個 mp3/flac 檔案的編碼資訊可能是不一樣的,比如 a.mp3 檔案的編碼資訊是 mp3&44.1KHZ&16bit… ,
而 b.flac 檔案的編碼資訊是 flac&48KHz&24bit…; 播放 a.mp3 時,AudioFlinger 開啟一個配置為 mp3&44.1KHz&16bit… 的 
compress_offload 裝置,接著播放 b.flac,就需要關閉之前的 compress_offload 裝置,重新開啟一個配置為 flac&48KHz&24bit… 的 compress_offload 裝置。所以系統不會提前開啟 compress_offload 裝置,只有等到播放 mp3、flac 時取到明確的資料編碼資訊, 才基於這些編碼資訊開啟 compress_offload 裝置。

編碼資訊包含很多條目,切換音源時,是否編碼資訊有一點點不一樣,都需要重新開啟 compress_offload 裝置呢?不能執行時更新資訊到  DSP 嗎?其實 stagefright 和 compress_offload 是支援執行期更新某些資訊的,也就是無縫切換,至於是哪些資訊,依賴於 DSP 演算法  實現;有興趣深入的可以參考 sendMetaDataToHal() 和 compress_set_gapless_metadata() 。

AudioFlinger 音訊流管理
從 AudioTrack、PlaybackThread、輸出流裝置三者的關係圖中,我們看到 AudioTrack 把音訊流資料送入到對應的 PlaybackThread 中,那麼應用程式想控制這些音訊流的話,比如開始播放 start()、停止播放 stop()、暫停播放 pause(),怎麼辦呢?注意應用程式與 AudioFlinger 並不在一個程式上。這就需要 AudioFlinger 提供音訊流管理功能,並提供一套通訊介面可以讓應用程式跨程式控制 AudioFlinger 中的音訊流狀態  

AudioFlinger 音訊流管理由 AudioFlinger::PlaybackThread::Track 實現,Track 與 AudioTrack 是一對一的關係,一個 AudioTrack 建立後,那麼 AudioFlinger 會建立一個 Track 與之對應;PlaybackThread 與 AudioTrack/Track 是一對多的關係,一個 PlaybackThread 可以掛著多個 Track。

音訊流控制最常用的三個介面:
   |----AudioFlinger::PlaybackThread::Track::start:開始播放:把該 Track 置 ACTIVE 狀態,然後新增到 mActiveTracks 向量中,
      最後呼叫 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情況有變

|---AudioFlinger::PlaybackThread::Track::stop:停止播放:把該 Track 置 STOPPED 狀態,最後呼叫 
       AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情況有變

|----AudioFlinger::PlaybackThread::Track::pause:暫停播放:把該 Track 置 PAUSING 狀態,最後呼叫
       AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情況有變

AudioFlinger::PlaybackThread::threadLoop() 得悉情況有變後,呼叫 prepareTracks_l() 重新準備音訊流和混音器:ACTIVE 狀態的 Track 會新增到 mActiveTracks,此外的 Track 會從 mActiveTracks 上移除出來,然後重新準備 AudioMixer。

可見這三個音訊流控制介面是非常簡單的,主要是設定一下 Track 的狀態,然後發個事件通知 PlaybackThread 就行,複雜的處理都在  AudioFlinger::PlaybackThread::threadLoop() 中了。

AudioFlinger::TrackHandle:Track 物件只負責音訊流管理業務,對外並沒有提供跨程式的 Binder 呼叫介面,而應用程式又需要對音訊流進行控制,所以需要一個物件來代理 Track 的跨程式通訊,這個角色就是 TrackHandle,AudioTrack 通過它與 Track 互動

AudioTrack:Android 音訊系統對外提供的一個 API 類,負責音訊流資料輸出;每個音訊流對應著一個 AudioTrack 例項,不同輸出標識的 AudioTrack 會匹配到不同的 AudioFlinger::PlaybackThread;AudioTrack 與 AudioFlinger::PlaybackThread 之間通過 FIFO 來交換音 頻資料,AudioTrack 是 FIFO 生產者,AudioFlinger::PlaybackThread 是 FIFO 消費者

IAudioTrack:IAudioTrack 是鏈結 AudioTrack 與 AudioFlinger 的橋樑;它在 AudioTrack 端的物件是 BpAudioTrack,在 AudioFlinger 端的 物件是 BnAudioTrack,從圖中不難看出,AudioFlinger::TrackHandle 繼承自 BnAudioTrack,而 AudioFlinger::TrackHandle 恰恰是 AudioFlinger::PlaybackThread::Track 的代理物件,所以 AudioTrack 得到 IAudioTrack 例項後,就可以呼叫 IAudioTrack 的介面與 AudioFlinger::PlaybackThread::Track 互動

相關文章