audio 一些基本概念


Audio 是整個 Android 平臺非常重要的一個組成部分,負責音頻數據的採集和輸出、音頻流的控制、音頻設備的管理、音量調節等,主要包括如下部分:

  • 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 直接調用

與 Audio 強相關的有 MultiMedia,MultiMedia 負責音視頻的編解碼,MultiMedia 將解碼後的數據通過 AudioTrack 輸出,而 AudioRecord 採集的錄音數據交由 MultiMedia 進行編碼。


AudioTrack Java API 兩種數據傳輸模式:

Transfer ModeDescription
MODE_STATIC應用進程將回放數據一次性付給 AudioTrack,適用於數據量小、時延要求高的場景
MODE_STREAM用進程需要持續調用 write() 寫數據到 FIFO,寫數據時有可能遭遇阻塞(等待 AudioFlinger::PlaybackThread 消費之前的數據),基本適用所有的音頻場景

audio playback sample

int minBuffSize = AudioTrack.getMinBufferSize(22050, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT);

    AudioTrack track = new AudioTrack(STREAM_MUSIC, 22050, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, minBuffSize, MODE_STREAM);

byte data[] = new byte[minBuffSize/2];

    track.write(data, 0, data.length);

    track.play();

float maxVol = AudioTrack.getMaxVolume();

    track.release();


最小緩衝區的大小 = 最低幀數 * 聲道數 * 採樣深度,(採樣深度以字節爲單位)


2.2. AudioTrack Native API

AudioTrack Native API 四種數據傳輸模式:

Transfer ModeDescription
TRANSFER_CALLBACK在 AudioTrackThread 線程中通過 audioCallback 回調函數主動從應用進程那裏索取數據,ToneGenerator 採用這種模式
TRANSFER_OBTAIN應用進程需要調用 obtainBuffer()/releaseBuffer() 填充數據,目前我還沒有見到實際的使用場景
TRANSFER_SYNC應用進程需要持續調用 write() 寫數據到 FIFO,寫數據時有可能遭遇阻塞(等待 AudioFlinger::PlaybackThread 消費之前的數據),基本適用所有的音頻場景;對應於 AudioTrack Java API 的 MODE_STREAM 模式
TRANSFER_SHARED應用進程將回放數據一次性付給 AudioTrack,適用於數據量小、時延要求高的場景;對應於 AudioTrack Java API 的 MODE_STATIC 模式


AudioTrack Native API 音頻流類型:

Stream TypeDescription
AUDIO_STREAM_VOICE_CALL電話語音
AUDIO_STREAM_SYSTEM系統聲音
AUDIO_STREAM_RING鈴聲聲音,如來電鈴聲、鬧鐘鈴聲等
AUDIO_STREAM_MUSIC音樂聲音
AUDIO_STREAM_ALARM警告音
AUDIO_STREAM_NOTIFICATION通知音
AUDIO_STREAM_DTMFDTMF 音(撥號盤按鍵音)


AudioTrack Native API 輸出標識:

AUDIO_OUTPUT_FLAGDescription
AUDIO_OUTPUT_FLAG_DIRECT表示音頻流直接輸出到音頻設備,不需要軟件混音,一般用於 HDMI 設備聲音輸出
AUDIO_OUTPUT_FLAG_PRIMARY表示音頻流需要輸出到主輸出設備,一般用於鈴聲類聲音
AUDIO_OUTPUT_FLAG_FAST表示音頻流需要快速輸出到音頻設備,一般用於按鍵音、遊戲背景音等對時延要求高的場景
AUDIO_OUTPUT_FLAG_DEEP_BUFFER表示音頻流輸出可以接受較大的時延,一般用於音樂、視頻播放等對時延要求不高的場景
AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD表示音頻流沒有經過軟件解碼,需要輸出到硬件解碼器,由硬件解碼器進行解碼


我們根據不同的播放場景,使用不同的輸出標識,如按鍵音、遊戲背景音對輸出時延要求很高,那麼就需要置

AUDIO_OUTPUT_FLAG_FAST,具體可以參考 ToneGenerator、SoundPool 和 OpenSL ES。


首先要了解音頻領域中,幀(frame)的概念:幀表示一個完整的聲音單元,所謂的聲音單元是指一個採樣樣本;

如果是雙聲道,那麼一個完整的聲音單元就是 2 個樣本,如果是 5.1 聲道,那麼一個完整的聲音單元就是 6 個樣本了。

幀的大小(一個完整的聲音單元的數據量)等於聲道數乘以採樣深度,即 frameSize = channelCount * bytesPerSample

幀的概念非常重要,無論是框架層還是內核層,都是以幀爲單位去管理音頻數據緩衝區的。

其次還得了解音頻領域中,傳輸延遲(latency)的概念:傳輸延遲表示一個週期的音頻數據的傳輸時間。

可能有些讀者一臉懵逼,一個週期的音頻數據,這又是啥?我們再引入週期(period)的概念:

Linux ALSA 把數據緩衝區劃分爲若干個塊,dma 每傳輸完一個塊上的數據即發出一個硬件中斷,cpu 收到中斷信號後,

再配置 dma 去傳輸下一個塊上的數據;一個塊即是一個週期,週期大小(periodSize)即是一個數據塊的幀數。

再回到傳輸延遲(latency),傳輸延遲等於週期大小除以採樣率,即 latency = periodSize / sampleRate

最後瞭解下音頻重採樣:音頻重採樣是指這樣的一個過程——把一個採樣率的數據轉換爲另一個採樣率的數據。

Android 原生系統上,音頻硬件設備一般都工作在一個固定的採樣率上(如 48 KHz),因此所有音軌數據都需要重採樣

到這個固定的採樣率上,然後再輸出。爲什麼這麼做?系統中可能存在多個音軌同時播放,而每個音軌的採樣率可能是不一致的;

比如在播放音樂的過程中,來了一個提示音,這時需要把音樂和提示音混音並輸出到硬件設備,而音樂的採樣率和提示音的

採樣率不一致,問題來了,如果硬件設備工作的採樣率設置爲音樂的採樣率的話,那麼提示音就會失真;

因此最簡單見效的解決方法是:硬件設備工作的採樣率固定一個值,所有音軌在 AudioFlinger 都重採樣到這個採樣率上,

混音後輸出到硬件設備,保證所有音軌聽起來都不失真。


重採樣計算的:afFrameCount 是硬件設備處理單個數據塊的幀數,afSampleRate 是硬件設備配置的採樣率,sampleRate 是音軌的採樣率,

如果要把音軌數據重採樣到 afSampleRate 上,那麼反推算出應用程序最少傳入的幀數爲 afFrameCount * sampleRate / afSampleRate,

而爲了播放流暢,實際上還要大一點,所以再乘以一個係數(可參照 framebuffer 雙緩衝,一個緩衝緩存當前的圖像,一個緩衝準備下一幅的圖像,

這樣圖像切換更流暢),然後就得出一個可以保證播放流暢的最低幀數

 minFrameCount = (afFrameCount * sampleRate / afSampleRate) * minBufCount

    

幾個重要的類

  • AudioResampler.cpp:重採樣處理類,可進行採樣率轉換和聲道轉換;由錄製線程 AudioFlinger::RecordThread 直接使用
  • AudioMixer.cpp:混音處理類,包括重採樣、音量調節、聲道轉換等,其中的重採樣複用了 AudioResampler;由回放線程 AudioFlinger::MixerThread 直接使用
  • Effects.cpp:音效處理類
  • Tracks.cpp:音頻流管理類,可控制音頻流的狀態,如 start、stop、pause
  • Threads.cpp:回放線程和錄製線程類;回放線程從 FIFO 讀取回放數據並混音處理,然後寫數據到輸出流設備;錄製線程從輸入流設備讀取錄音數據並重採樣處理,然後寫數據到 FIFO
  • AudioFlinger.cpp:AudioFlinger 對外提供的服務接口


AudioFlinger 對外提供的主要的服務接口如下:

InterfaceDescription
sampleRate獲取硬件設備的採樣率
format獲取硬件設備的音頻格式
frameCount獲取硬件設備的週期幀數
latency獲取硬件設備的傳輸延遲
setMasterVolume調節主輸出設備的音量
setMasterMute靜音主輸出設備
setStreamVolume調節指定類型的音頻流的音量,這種調節不影響其他類型的音頻流的音量
setStreamMute靜音指定類型的音頻流
setVoiceVolume調節通話音量
setMicMute靜音麥克風輸入
setMode切換音頻模式:音頻模式有 4 種,分別是 Normal、Ringtone、Call、Communicatoin
setParameters設置音頻參數:往下調用 HAL 層相應接口,常用於切換音頻通道
getParameters獲取音頻參數:往下調用 HAL 層相應接口
openOutput打開輸出流:打開輸出流設備,並創建 PlaybackThread 對象
closeOutput關閉輸出流:移除並銷燬 PlaybackThread 上面掛着的所有的 Track,退出 PlaybackThread,關閉輸出流設備
openInput打開輸入流:打開輸入流設備,並創建 RecordThread 對象
closeInput關閉輸入流:退出 RecordThread,關閉輸入流設備
createTrack新建輸出流管理對象: 找到對應的 PlaybackThread,創建輸出流管理對象 Track,然後創建並返回該 Track 的代理對象 TrackHandle
openRecord新建輸入流管理對象:找到 RecordThread,創建輸入流管理對象 RecordTrack,然後創建並返回該 RecordTrack 的代理對象 RecordHandle

可以歸納出 AudioFlinger 響應的服務請求主要有:

  • 獲取硬件設備的配置信息
  • 音量調節
  • 靜音操作
  • 音頻模式切換
  • 音頻參數設置
  • 輸入輸出流設備管理
  • 音頻流管理

AudioFlinger 回放錄製線程

  • 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 設備是必須聲明支持的,而且系統啓動時就已經打開 primary_out 設備並創建好對應的 MixerThread 實例。其他類型的輸出流設備並非必須聲明支持的,主要是看硬件上有無這個能力。

可能有人產生這樣的疑問:既然 primary_out 設備一直保持打開,那麼能耗豈不是很大?這裏闡釋一個概念:輸出流設備屬於邏輯設備,並不是硬件設備。所以即使輸出流設備一直保持打開,只要硬件設備不工作,那麼就不會影響能耗。那麼硬件設備什麼時候纔會打開呢?答案是 PlaybackThread 將音頻數據寫入到輸出流設備時。


最後看個金典的架構




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章