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 Mode | Description |
---|---|
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 Mode | Description |
---|---|
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 Type | Description |
---|---|
AUDIO_STREAM_VOICE_CALL | 電話語音 |
AUDIO_STREAM_SYSTEM | 系統聲音 |
AUDIO_STREAM_RING | 鈴聲聲音,如來電鈴聲、鬧鐘鈴聲等 |
AUDIO_STREAM_MUSIC | 音樂聲音 |
AUDIO_STREAM_ALARM | 警告音 |
AUDIO_STREAM_NOTIFICATION | 通知音 |
AUDIO_STREAM_DTMF | DTMF 音(撥號盤按鍵音) |
AudioTrack Native API 輸出標識:
AUDIO_OUTPUT_FLAG | Description |
---|---|
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 對外提供的主要的服務接口如下:
Interface | Description |
---|---|
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 將音頻數據寫入到輸出流設備時。
最後看個金典的架構