~~~~~~~~~~~~~~~~~~~~~~~~~~~~zzZ
文章目錄
- 前言
- 一、Audio基礎
- 1.音頻基礎屬性
- 2.音頻格式
- 3.音頻處理
- 二、整體架構
- 1.概述
- 2.Audio架構
- 三、重要模塊
- 1.概述
- 2.AudioTrack
- 3.AudioRecord
- 4.AudioManager
- 5.AudioService
- 6.AudioSystem
- 7.AudioPolicyService
- 8.AudioFlinger
- 四、專案實體(汽車)
- 1.前言
- 2.CarAudio
- 3.CarAudioManager
- 4.CarAudioService
- 總結
前言
Audio是安卓里面非常重要的模塊,對于學習安卓開發不管是做APP或是系統層以及BSP的同行都可以進行學習,拓寬知識維度,為以后的作業提供便利,
本文語言通俗易懂,內容由簡入繁,看不懂的地方可以反復琢磨,多多動腦思考,
堅持看完哦!
一、Audio基礎
1.音頻基礎屬性
音頻指人耳可以聽到的聲音頻率在 20HZ~20kHz 之間的聲波,
聲音的三要素:
1、音量(Volume)
也叫做響度(Loudness),人耳對聲音強弱的主觀感覺就是響度,響度和聲波 振動的幅度有關,
2、音調(Pitch)
人耳對聲音高低的感覺稱為音調(也叫音頻),音調主要與聲波的頻率有關,聲波的頻率高,則音調也高,
3、音色(Quality)
不同的發聲體由于其材料、結構不同,則發出聲音的音色也不同,
2.音頻格式
音頻格式是指要在計算機內播放或是處理音頻檔案,是對聲音檔案進行數、模轉換的程序,
常見音頻格式:
1、WAV
是微軟公司專門為Windows開發的一種標準數字音頻檔案,該檔案能記錄各種單聲道或立體聲的聲音資訊,并能保證聲音不失真,但是,占用空間太大,
2、MIDI
MIDI是Musical Instrument Digital Interface的縮寫,又稱作樂器數字介面,是數字音樂/電子合成樂器的統一國際標準,
MIDI 傳輸的不是聲音信號, 而是音符、控制引數等指令, 它指示MIDI 設備要做什么,怎么做, 如演奏哪個音符、多大音量等,它們被統一表示成MIDI 訊息(MIDI Message) ,
3、MP3
MP3是利用人耳對高頻聲音信號不敏感的特性,將時域波形信號轉換成頻域信號,并劃分成多個頻段,對不同的頻段使用不同的壓縮率,對高頻加大壓縮比(甚至忽略信號)對低頻信號使用小壓縮比,保證信號不失真,從而將聲音用1∶10甚至1∶12的壓縮率壓縮,
這種壓縮方式的全稱叫MPEG Audio Player3,簡稱為MP3,
4、AAC
出現于1997年,基于MPEG-2的音頻編碼技術,由Fraunhofer IIS、杜比實驗室、AT&T、索尼等公司共同開發,目的是取代MP3格式,
AAC,全稱Advanced Audio Coding,是一種專為聲音資料設計的檔案壓縮格式,與MP3不同,它采用了全新的演算法進行編碼,更加高效,具有更高的“性價比”,利用AAC格式,可使人感覺聲音質量沒有明顯降低的前提下,更加小巧,蘋果ipod、諾基亞手機支持AAC格式的音頻檔案,
3.音頻處理
日常生活中我們聽到的聲波波形信號都是時間連續的,我們稱這種信號為模擬信號,模擬信號需要量化成數字信號(離散、不連續的)以后才能被我們的計算機識別,
1、音頻的量化
可以簡單分為五個步驟:
(1)模擬信號采集
現實生活中的聲音表現為連續的、平滑的波形,其橫坐標為時間軸,縱坐標表示聲音的強弱,
(2)采樣
按照一定的時間間隔在連續的波上進行采樣取值,
(3)量化
將采樣得到的值進行量化處理,也就是給縱坐標定一個刻度,記錄下每個采樣的縱坐標的值,
(4)編碼
將每個量化后的樣本值轉換成二進制編碼,可以看到模擬信號經過采樣、量化、編碼后形成的二進制序列就是數字音頻信號,
(5)數字信號
將所有樣本二進制編碼連起來存盤在計算機上就形成了數字信號,

2、音頻處理引數
(1)采樣率
單位時間內對模擬信號的采樣次數,也就是采樣頻率,采樣頻率越高,聲音的還原就越真實越自然,當然資料量就越大,
我們日常生活中常見的采樣率:
5kHz:僅能滿足人們講話的聲音質量
8KHz:電話所用采樣率, 對于人的說話已經足夠
22.05KHz:達到 FM 廣播的聲音品質(適用于語音和中等品質的音樂)
44.1KHz:最常見的采樣率標準,理論上的 CD 音質界限,可以達到很好的聽覺效果
48KHz:比 CD 音質更加精確一些
對于高于 48KHz 的采樣頻率人耳已無法辨別出來了,所以在電腦上沒有多少使用價值,
(2)采樣位數
每個采樣點能夠表示的資料范圍,用多少個 bit 表示,采樣位數通常有 8 bits 或 16 bits 兩種,采樣位數越大,所能記錄聲音的變化度就越細膩,相應的資料量就越大,8 位字長量化(低品質)和 16 位字長量化(高品質),16 bit 是最常見的采樣精度,
采樣位數也被叫做采樣精度、量化級、量化資料位數等,
比較一下,一段相同的音樂資訊,16位聲卡能把它分為64K個精度單位進行處理,而8位聲卡只能處理256個精度單位, 造成了較大的信號損失,最終的采樣效果自然是無法相提并論的,
(3)通道數
為了播放聲音時能夠還原真實的聲場,在錄制聲音時在前后左右幾個不同的方位同時獲取聲音,每個方位的聲音就是一個聲道,聲道數是聲音錄制時的音源數量或回放時相應的揚聲器數量,有單聲道、雙聲道、多聲道,
(4)碼率
碼率高低直接影響音質,碼率高音質好,碼率低音質差,
碼率就是資料傳輸時單位時間傳送的資料位數,一般我們用的單位是kbps即千位每秒,
就是一種音樂每秒播放的資料量,單位用bit表示,也就是二進制位, bps就是位元率,一個位元組相當于8個二進制位,也就是說128bps的4分鐘的歌曲的檔案大小是這樣計算的:
碼率 = 采樣率 * 采樣位數 * 聲道數
(128/8)460=3840kB=3.8MB
(5)幀
音頻資料是流式的,本身沒有明確的一幀幀的概念,在實際的應用中,為了音頻演算法處理/傳輸的方便,一般約定俗成取 2.5ms~60ms 為單位的資料量為一幀音頻,這個時間被稱之為 “采樣時間”,其長度沒有特別的標準,它是根據編解碼器和具體應用的需求來決定的,
音頻幀的播放時間 = 一個AAC幀對應的采樣樣本的個數 / 采樣頻率(單位為s),
音頻在量化得到二進制的碼字后,需要進行變換,而變換(MDCT)是以塊為單位(block)進行的,一個塊由多個(120或128)樣本組成,而一幀內會包含一個或者多個塊,幀的常見大小有960、1024、2048、4096等,一幀記錄了一個聲音單元,它的長度是樣本長度和聲道數的乘積,
例如,最常見的音頻格式 MP3 的資料通常由兩部分組成,一部分為 “ID3” 用來存盤歌名、演唱者、專輯、音軌數等資訊,另一部分為音頻資料,音頻資料部分以幀(frame)為單位存盤,每個音頻都有自己的幀頭,每個幀頭中存盤了采樣率等解碼必須的資訊,所以每一個幀都可以獨立于檔案存在和播放,這個特性加上高壓縮比使得 MP3 檔案成為了音頻流播放的主流格式,
最后給出一個公式:
檔案時長 = (檔案總大小 - 頭資訊)/ (采樣頻率 * 采樣位數 * 通道數 / 8)
3、音頻編碼
從資訊論的觀點來看,描述信源的資料是資訊和資料冗余之和,即:資料=資訊+資料冗余,音頻信號在時域和頻域上具有相關性,也即存在資料冗余,將音頻作為一個信源,音頻編碼的實質是減少音頻中的冗余,
根據編碼方式的不同,音頻編碼技術分為三種:波形編碼、引數編碼和混合編碼,
一般來說,波形編碼的話音質量高,但編碼率也很高;引數編碼的編碼率很低,產生的合成語音的音質不高;混合編碼使用引數編碼技術和波形編碼技術,編碼率和音質介于它們之間,

PCM:Pulse Code Modulation,脈沖編碼調制,
ADPCM:Adaptive Differential Pulse Code Modulation,自適應差分脈沖編碼調制,
SB-ADPCM:Sub band Adaptive Differential Pulse Code Modulation,子帶-自適應差分脈沖編碼調制,
LPC:Linear Predictive Coding,線性預測編碼,
CELPC:Code Excited Linear Predictive Coding,碼激勵線性預測編碼,
VSELPC:Vector Sum Excited Linear Predictive Coding,矢量和激勵線性預測編碼,
RPE-LTP:Regular Pulse Excited-Long Term Predictive,規則脈沖激勵長時預測,
LD-CELP:Low Delay-Code Excited Linear Predictive,低時延碼激勵線性預測,
MPE:Multi-Pulse Excited,多脈沖激勵,
PSTN:Public Switched Telephone Network,公共交換電話網,
ISDN:Integrated Services Digital Network,綜合業務數字網,
二、整體架構
1.概述
Audio系統在Android中負責音頻方面的資料流傳輸和控制功能,也負責音頻設備的管理,這個部分作為Android的Audio系統的輸入/輸出層次,一般負責播放PCM聲音輸出和從外部獲取PCM聲音,以及管理聲音設備和設定,注意,解碼功能不在這里實作,在android系統里音頻視頻的解碼是 opencore 或 stagefright 完成的,在解碼之后才呼叫音頻系統的介面,創建音頻流并播放,
1、功能
·音頻管理:負責音量調節、音頻設備選擇、響鈴模式選擇等;
·聲音播放:負責一個音頻流的創建、引數設定、播放、暫停、釋放;
·聲音錄音:負責一個錄音音軌的創建、管理;
·聲音音效:負責控制聲音的效果,
(Android 系統對audio的實作是比較復雜的,但實作的方法還是對音頻系統的抽象)
2、服務
Audio是安卓系統中一個非常重要的模塊,所有音頻的操作都要經過它,它不僅為用戶提供服務,而且還為應用開發者提供服務,要時刻記住所做的一切開發作業都是為了別人能更好的使用這個模塊,服務好別人,并且保證我們的應用和系統能夠穩定運行,但做到這些也不是一件簡單的事,
從系統開機的那一刻起,Audio系統服務就隨之啟動,伴隨其運行,當被需要時,要求實時做出回應來,給底層和用戶一個合理的“交代”,
比如說:用戶聽歌時覺得音量過小,就要按手機上音量+鍵,Audio系統需要處理好用戶的這次請求,做到準確,迅速,
再比如說,汽車正在播放歌曲,來了導航播報,多媒體需要和導航混音并且降低音量,然后再來藍牙電話,多媒體和導航都需要停止播放,這些都是需要Audio系統來協調處理,
2.Audio架構

android 音頻播放從app開始:
在framework層創建播放器
在audio library層做音頻流和輸出流控制
在Hal層將音頻資料寫入到輸出設備進行聲音輸出,
其中audio library層是音頻處理的核心,
App --> Frameworks --> Audio Library --> HAL
音頻播放/錄音流程圖:

Audio系統的各層次介紹:
1、JAVA介面層
·AudioManager.java
為APP提供音量控制和響鈴模式控制等功能,是音頻管理器,
·AudioTrack .java
為APP提供管理和播放一個音頻流的功能(必須是PCM流,因為不提供解碼功能),
·AudioRecord.java
為APP提供錄音功能,
·AudioEffect.java
為APP提供控制音效的功能,
2、JAVA服務層
音頻在java層的服務只有AudioService,他實作了IAudioService介面,AudioManager 是這個服務的客戶端,
AudioService服務向下呼叫AudioSystem.java,AudioSystem.java再通過JNI呼叫到本地層,
Q1:AudioTrack 、AudioRecord和AudioEffect的呼叫情況?
他們是直接呼叫本地介面的,
Q2:為什么AudioTrack 、AudioRecord和AudioEffect沒有java服務層?
這是因為一般的音頻檔案比如mp3、ogg等都是壓縮的音頻流,需要經過解碼才能得到pcm資料流,解碼是opencore或stagefright完成的,一般是播放器的服務,比如MediaPlayer,先解碼音頻檔案,然后去創建AudioTrack、設定音效等,然后播放音樂,這些內容的實作一般是在本地服務層,所以AudioTrack 、AudioRecord和AudioEffect的服務層在本地層的AudioFlinger里實作就夠了,
3、JNI層
Android的Audio部分通過JNI向Java層提供介面,在Java層可以通過JNI介面完成Audio系統的大部分操作,Audio JNI部分的代碼路徑為:frameworks/base/core/jni,
其中,主要實作的3個檔案為:android_media_AudioSystem.cpp、android_media_Audio Track.cpp和android_media_AudioRecord.cpp,它們分別對應了Android Java框架中的3個類的支持:
·android.media.AudioSystem:負責Audio系統的總體控制;
·android.media.AudioTrack:負責Audio系統的輸出環節;
·android.media.AudioRecorder:負責Audio系統的輸入環節,
提示:
現在基本都用HIDL代替了,
4、NATIVE層
Native層的代碼和邏輯是整個音頻模塊的核心!!!
Android的Audio系統的核心框架在media庫中提供,對上面主要實作AudioSystem、AudioTrack和AudioRecorder三個類,這其實是Audio系統的本地介面層,
AudioFlinger提供了IAudioFlinger類介面,在這個類中,可以獲得IAudioTrack和IAudioRecorder兩個介面,分別用于聲音的播放和錄制,AudioTrack和AudioRecorder分別通過呼叫IAudioTrack和IAudioRecorder來實作,
Native層采用了C/S的架構方式,AudioTrack是屬于Client端的,AudioFlinger和AudioPolicyService是屬于服務端的,AudioFlinger是唯一可以呼叫HAL層介面的地方,而AudioPolicyService主要是對一些音頻策略的選擇和一些邏輯呼叫的中轉,
5、硬體抽象層(HAL)
HAL 定義了音頻服務會呼叫且您必須實作才能使音頻硬體正常運行的標準介面
android audio framework中的audio flinger是通過操作audio hal層對間接的對底層設備進行操作的,(音頻資料的讀寫以及各種引數的設定),
音頻 HAL 由以下介面組成:
hardware/libhardware/include/hardware/audio.h 表示音頻設備的主函式,
hardware/libhardware/include/hardware/audio_effect.h 表示可應用于音頻的效果,如碩訓、回音或噪音抑制,
現在HIDL用的很廣泛了,HAL層也不用做一些重復的開發了,減輕了一些負擔,
6、驅動層
Audio的驅動是基于Linux標準的音頻驅動:OSS(Open Sound System)或者ALSA(Advanced Linux Sound Architecture)驅動程式來實作的,
手機上用的也有用TINYALSA,是ALSA的精簡版,主要功能都在,
三、重要模塊
1.概述
之前了解了安卓整個Audio系統的分層,整體架構是怎么樣的,大概在心里也已經有了一些初步的輪廓,現在開始細化,挑選出幾個重要的模塊,逐個分析,找出其中的聯系,并且知道其作業原理,目的就算達到了,
在這幾個重要的類中,有提供給apk開發者使用的介面,還有核心的幾個Audio系統服務,
當我們要使用Audio的API時,要回想一下第一章提到過的那幾個音頻引數的具體含義,
2.AudioTrack
AudioTrack是最靈活、高級的音頻API,用它可以實作將原始音頻資料直接送到硬體的操作,是管理和播放單一音瞥澩的類,僅僅能播放已經解碼的PCM流,用于PCM音頻流的回放,
想使用好這個API播放音頻資料,可以分為五個步驟:
1、 配置基本引數
(1)StreamType音頻流型別:最主要的幾種STREAM
AudioManager.STREAM_MUSIC:用于音樂播放的音頻流,
AudioManager.STREAM_SYSTEM:用于系統聲音的音頻流,
AudioManager.STREAM_RING:用于電話鈴聲的音頻流,
AudioManager.STREAM_VOICE_CALL:用于電話通話的音頻流,
AudioManager.STREAM_ALARM:用于警報的音頻流,
AudioManager.STREAM_NOTIFICATION:用于通知的音頻流,
AudioManager.STREAM_BLUETOOTH_SCO:用于連接到藍牙電話的手機音頻流,
AudioManager.STREAM_SYSTEM_ENFORCED:在某些國家實施的系統聲音的音頻流,
AudioManager.STREAM_DTMF:DTMF音調的音頻流,
AudioManager.STREAM_TTS:文本到語音轉換(TTS)的音頻流,
(2)MODE模式(static和stream兩種)
AudioTrack.MODE_STREAM:
STREAM的意思是由用戶在應用程式通過write方式把資料一次一次得寫到AudioTrack中,
AudioTrack.MODE_STATIC:
STATIC就是資料一次性交付給接收方,
(3)采樣率:mSampleRateInHz
采樣率 (MediaRecoder 的采樣率通常是8000Hz AAC的通常是44100Hz, 設定采樣率為44100,目前為常用的采樣率,官方檔案表示這個值可以兼容所有的設定)
(4)通道數目:mChannelConfig
聲道設定:android支持雙聲道立體聲和單聲道,MONO單聲道,STEREO立體聲.
CHANNEL_OUT_MONO;
CHANNEL_OUT_STEREO,
(5)音頻量化位數:mAudioFormat(只支持8bit和16bit兩種)
ENCODING_PCM_16BIT;
ENCODING_PCM_8BIT;
采樣大小越大,那么資訊量越多,音質也越高,現在主流的采樣大小都是16bit,在低質量的語音傳輸的時候8bit足夠了,
2、獲取最小緩沖區大小
根據采樣率,采樣精度,單雙聲道來得到frame的大小,
計算最小緩沖區:
AudioTrack.getMinBufferSize(mSampleRateInHz,mChannelConfig, mAudioFormat);
注意,按照數字音頻的知識,這個算出來的是一秒鐘buffer的大小,**
3、創建AudioTrack物件
取到mMinBufferSize后,我們就可以創建一個AudioTrack物件了,它的建構式原型是:
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode);
建構式里面的引數我們之前都講過了,所以創建時根據需求自行指定即可,
4、獲取PCM檔案,轉成DataInputStream
根據存放PCM的路徑獲取到PCM檔案,
File file = new File(path);
mDis = new DataInputStream(new FileInputStream(file));
5、開啟/停止播放
(1)開始播放
先把音頻資料放到AudioTrack獲取到的緩沖中,
readCount = mDis.read(MinBufferSize);
先呼叫AudioTrack.play();
之后就可以寫資料了,AudioTrack.write(MinBufferSize, 0, readCount);
(2)停止播放
停止播放音頻資料,如果是STREAM模式,會等播放完最后寫入buffer的資料才會停止,如果立即停止,要呼叫pause()方法,然后呼叫flush方法,會舍棄還沒有播放的資料,
(3)釋放資源
AudioTrack.release();
3.AudioRecord
看完AudioTrack了,再看AudioRecord的話就會非常簡單,
一般情況下錄音實作的簡單流程如下:(實作不難,不具體說了)
1、創建一個資料流,
2、構造一個AudioRecord物件,其中需要的最小錄音快取buffer大小可以通getMinBufferSize方法得到,如果buffer容量過小,將導致物件構造的失敗,
3、初始化一個buffer,該buffer大于等于AudioRecord物件用于寫聲音資料的buffer大小,
4、開始錄音,
5、從AudioRecord中讀取聲音資料到初始化buffer,將buffer中資料匯入資料流,
6、停止錄音,
7、關閉資料流,
4.AudioManager
AudioManager實作了AudioSystem類與JAVA層系統服務AudioService的封裝,主要作用為向應用程式層提供Audio系統的控制介面,提供了豐富的API讓開發者對應用的音量和鈴聲模式等進行控制以及訪問,主要內容涉及到音頻流、聲音、藍牙、擴音器、耳機等等,
常用功能:
1、音量控制
調音量有兩個比較重要的介面,
(1)adjustStreamVolume(int streamType, int direction, int flags);
這個方法主要是用于按鍵調音,音量+/-,按照規定好的步進值和方向調整指定流的音量大小,
streamType:是你要調節的流,比如說,音樂,鈴聲,鬧鐘等,
direction:調節的方向,也就是增大/減小,或者保持不變,(ADJUST_LOWER;
ADJUST_RAISE;ADJUST_SAME),
flags:常用就是是否在調節音量時顯示UI,
(2)setStreamVolume(int streamType, int index, int flags);
這函式是可以直接設定特定流的音量值,主要是用在系統設定中進行調節,
這個函式和上一個就一個引數不同,但是很簡單,
index代表的就是設定的音量值,
2、靜音控制
(1) setMasterMute(boolean mute, int flags);
這個函式的作用是設定全域靜音,一直設定到BSP,
引數一個是mute的布林值,另一個flags之前說過,
(2)setStreamMute(int streamType, boolean state);
設定指定流靜音,其內部靜音實作就是在FW把該流的音量設定為0,
3、音頻模式/狀態
AudioMode用于描述整個Audio部分的當前狀態,
MODE_NORMAL:正常狀態(默認),
MODE_RINGTONE:響鈴狀態(來電),
MODE_IN_CALL:呼叫電話狀態(撥出未接通),
MODE_IN_COMMUNICATION:電話狀態(接通),
這些狀態的改變會影響音頻資料在Audio各輸出設備中的路由選擇,
還有各種鈴聲狀態也都很常用:
RINGER_MODE_NORMAL:正常模式,可以聽到鈴聲,
RINGER_MODE_SILENT:靜音模式,無鈴聲,無震動,
RINGER_MODE_VIBRATE:震動模式,無鈴聲,有震動,
4、音頻焦點
(1)目的:
AudioFocus機制是為解決系統中Audio資源的競爭問題而引入的,
(2)要求:
需要所有參與Audio競爭的競爭者主動去遵循,Audio Focus機制才能更好的作業,
(3)申請焦點:
現在只說安卓8.0以上的介面,
public int requestAudioFocus(@NonNull AudioFocusRequest focusRequest);
可以看到引數只有一個AudioFocusRequest,那么重點肯定就在這里邊了,其中一定設定了許多引數,包括音源型別、監聽器等,
所以先看這個AudioFocusRequest,
AudioFocusRequest類是一個封裝有關音頻焦點請求的資訊的類,
看個例子更加直觀:
mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setWillPauseWhenDucked(true)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(mListener, mHandler)
.setAudioAttributes(mAttribute)
.build();
AUDIOFOCUS_GAIN:當前聲音的唯一的音頻來源,持續多長時間未知,結束后也不希望恢復另一個音頻流,
AUDIOFOCUS_GAIN_TRANSIENT:應用程式暫時從當前所有者那里獲取焦點,音頻播放完后恢復以前的音頻播放, 表示請求焦點時已經有人在持有焦點
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:和上面的一樣臨時獲取焦點,但他可以在擁有焦點期間,另一個應用可以降低音量繼續播放,俗稱 躲閃,
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:也是臨時請求焦點,在擁有焦點期間不希望設備播放任何內容,就是在持有焦點期間,在有請求焦點時禁止把焦點分給其他人,
setWillPauseWhenDucked():宣告應用程式在音頻回避方面的預期行為,也就是說如果我不希望系統自動給我降低音量,而是想自己暫停音頻相關的作業,可以調這個函式取消系統的默認行為,這樣通過監聽音頻焦點變化,來自己處理,
setAcceptsDelayedFocusGain():這個是為了能夠延遲獲取到焦點的必須條件,但是同時也必須要設定AudioManager.OnAudioFocusChangeListener才能得知何時獲取到焦點,
setOnAudioFocusChangeListener():設定音頻焦點變化監聽器,值得一提的是這個方法有個多載的方法,有一個多載方法有兩個引數,第二個引數為Handler物件,看到Handler應該明白了,是為了使用它的訊息佇列來順序處理這個回呼,
setAudioAttributes(): 這個方法是用來描述app的使用情況,這方法需要傳入一個AudioAttributes物件,這個物件也是使用Builder模式來構造,
例如使用AudioAttributes.Builder.setUsage()來描述使用這個音頻來干什么,我們可以傳入一個AudioAttributes.USAGE_MEDIA來表明用這個音頻來作為媒體檔案來播放,也可以傳入一個AudioAttributes.USAGE_ALARM來表明用這個來作為鬧鈴,
(4)釋放焦點:
public int abandonAudioFocus(OnAudioFocusChangeListener l);
引數就是之前說過的音頻焦狀態變化監聽器,
5.AudioService
AudioService這個系統服務包含或者使用了幾乎所有與音頻有關的內容,AudioService是音頻系統在java層的大本營
1、介紹
(1)音頻系統在java層中基本上不參與資料流的,AudioService這個系統服務包含或者使用了幾乎所有與音頻有關的內容,所以說AudioService是音頻系統在java層的大本營,
(2)AudioManager擁有AudioService的Bp端,是AudioService在客戶端的一個代理,幾乎所有客戶端對AudioManager進行的請求,最終都會交由AudioService實作,
(3)AudioService的功能實作依賴于AudioSystem類,AudioSystem無法實體化,它是java層到native層的代理,AudioService通過它與AudioPolicyService以及AudioFlinger進行通信,
2、音量管理
(1)AudioService音量管理的核心是VolumeStreamState,它保存了一個流型別所有的音量資訊,
(2)VolumeStreamState保存了運行時的音量資訊,而音量的生效則是在底層AudioFlinger完成的,
所以進行音量設定需要做兩件事情:更新VolumeStreamState存盤的音量值,設定音量到Audio底層系統,
6.AudioSystem
AudioSystem在Audio系統中屬于一個承上啟下的類,在java和native都有它,所以我們在上層呼叫的很多功能都經過它,再經過它流轉到native服務,
1、介紹
(1)AudioSystem是Audio子系統面向framework的介面,這里面有很多一竿子戳到底的函式,同樣,Audio子系統內部也往往使用AudioSystem進行通信,比如AF和APM,
(2)AudioService通過對AudioSystem進行函式呼叫與Audio系統進行通信,
(3)AudioSystem和AudioFlinger以及AudioPolicyService的雙向通信機制,
7.AudioPolicyService
我們知道,在 Audio系統中,核心的兩塊是 AudioPolicy 和 AudioFlinger,其中 AudioPolicy 相當于軍師的角色,專門來制定 Audio 播放時的相關的策略及設定相關的引數,而 AudioFlinger 相當于將軍,會根據軍師的策略來執行,
所以重點都在這兩個服務上,那么就先看AudioPolicyService這個負責“指揮“的服務,最好的辦法就是看它啟動時都分別干了什么,
啟動流程分析:
1、AudioServer
(1)main_audioserver.cpp會編譯成可執行檔案audioserver,在主執行緒中,主要是監控子行程的狀態,而在它的子行程中分別呼叫 AudioFlinger::instantiate() 和 AudioPolicyService::instantiate(),分別對AudioFlinger 和 AudioPolicyService 進行初始化,
(2)同樣會被它初始化的還有語音識別 SoundTriggerHwService::instantiate()和 VR 語音模塊 VRAudioServiceNative::instantiate(),不過這些不是我們目前的重點,后續再看,
具體代碼如下:
xref: /frameworks/av/media/audioserver/main_audioserver.cpp
int main(int argc __unused, char **argv)
{
// TODO: update with refined parameters
limitProcessMemory(
"audio.maxmem", /* "ro.audio.maxmem", property that defines limit */
(size_t)512 * (1 << 20), /* SIZE_MAX, upper limit in bytes */
20 /* upper limit as percentage of physical RAM */);
signal(SIGPIPE, SIG_IGN);
bool doLog = (bool) property_get_bool("ro.test_harness", 0);
pid_t childPid;
// FIXME The advantage of making the process containing media.log service the parent process of
// the process that contains the other audio services, is that it allows us to collect more
// detailed information such as signal numbers, stop and continue, resource usage, etc.
// But it is also more complex. Consider replacing this by independent processes, and using
// binder on death notification instead.
if (doLog && (childPid = fork()) != 0) {
// media.log service
//prctl(PR_SET_NAME, (unsigned long) "media.log", 0, 0, 0);
// unfortunately ps ignores PR_SET_NAME for the main thread, so use this ugly hack
strcpy(argv[0], "media.log");
sp<ProcessState> proc(ProcessState::self());
MediaLogService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
......
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
ALOGI("ServiceManager: %p", sm.get());
AudioFlinger::instantiate();
AudioPolicyService::instantiate();
// AAudioService should only be used in OC-MR1 and later.
// And only enable the AAudioService if the system MMAP policy explicitly allows it.
// This prevents a client from misusing AAudioService when it is not supported.
aaudio_policy_t mmapPolicy = property_get_int32(AAUDIO_PROP_MMAP_POLICY,
AAUDIO_POLICY_NEVER);
if (mmapPolicy == AAUDIO_POLICY_AUTO || mmapPolicy == AAUDIO_POLICY_ALWAYS) {
AAudioService::instantiate();
}
SoundTriggerHwService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
2、AudioPolicyService::instantiate()
首先注冊一個名為 media.audio_policy 的服務,在注冊服務程序中會首先呼叫 AudioPolicyService::onFirstRef() 函式,它是我們 AudioPolicySerivce 啟動時的核心代碼,
3、AudioPolicyService::onFirstRef()
在該代碼中,主要作業如下:
(1)創建了三個AudioCommandThread,名字分別為"ApmTone",“ApmAudio”,“ApmOutput”,
(2)實體化 AudioPolicyClient 物件
(3)初始化 AudioPolicyManager ,傳參就是 AudioPolicyClient物件,
(4)AudioPolicyEffects 音效初始化,
void AudioPolicyService::onFirstRef()
{
{
Mutex::Autolock _l(mLock);
// start tone playback thread,用于播放tone音,tone是音調的意思
mTonePlaybackThread = new AudioCommandThread(String8("ApmTone"), this);
// start audio commands thread,用于執行audio命令
mAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this);
// start output activity command thread,用于執行audio輸出命令
mOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);
//實體化AudioPolicyClient物件
mAudioPolicyClient = new AudioPolicyClient(this);
//實體化AudioPolicyManager物件
mAudioPolicyManager = createAudioPolicyManager(mAudioPolicyClient);
}
// load audio processing modules
//初始化音效相關
sp<AudioPolicyEffects>audioPolicyEffects = new AudioPolicyEffects();
{
mAudioPolicyEffects = audioPolicyEffects;
}
}
4、在 AudioPolicyManager 初始化程序中
(1)決議Audio 組態檔,audio_policy.xml 和 audio_policy.conf,決議組態檔中當前系統所支持的輸出設備mAvailableOutputDevices、輸入設備mAvailableInputDevices、默認輸出設備mDefaultOutputDevice,
(2)呼叫 config.setDeafult() 初始化 mHwModules ,配置 Module 的名字為 “primary”,配置默認輸出設備為 AUDIO_DEVICE_OUT_SPEAKER,默認輸入設備為 AUDIO_DEVICE_IN_BUILTIN_MIC,
(3)通過 getName() 加載模塊,呼叫loadHwModule_l 函式初始化 HwModule 模塊,呼叫 openDevice 初始化 hardware 層的audio配置,hardware 層會回傳給上層 Audio 操作方法,及初始化 輸入鏈表 streams_input_cfg_list 和輸出流鏈表 streams_output_cfg_list ,
(4)初始化默認音量解級引數,包括 通話、系統、鈴聲、鬧鐘、通知、藍牙、撥號盤等音量等級,
(5)給每一個輸入 和 輸出設備,都分配一個執行緒,及對應的輸入 和 輸出 stream 流,根據 audio型別呼叫不同的函式(Playback 、 OFFLOAD 、 DIRECT 型別等,
(6) 最終所有的資訊保存在 HwModule[ ] 中,將所有的輸出設備保存在 mDeviceForStrategy[ ] 中,
至此,所有的輸入設備和輸出設備 都有對應的stream及單獨的 thread ,
5、AudioPolicyEffects 音效初始化
決議audio_effects.conf 檔案,得到并加載 系統支持的音效庫,初始化各個音效對應的引數,將各音效和 對應的輸入 和輸出流系結在一起,這樣,當上層要初始化使用音效時,就會在對應的threadloop中呼叫process_l音效處理函式,創建一個AudioFlinger客戶端,將 effect 和 AudioFlinger 客戶端系結在一起,
注:
聲音通路配置,sources 是聲音輸信埠,sink 是聲音輸出埠
播放:source(MixerPort)->sink(DevicePort)
錄音:source(DevicePort)->sink(MixerPort)
(借用幾張圖片)
加載完系統定義的所有音頻介面,并生成相應的資料物件,如👇圖所示:

打開音頻輸出后,在AudioFlinger與AudioPolicyService中的表現形式如👇圖:


6、總結
打開音頻輸出時創建一個audio_stream_out通道,并創建AudioStreamOut物件以及新建PlaybackThread播放執行緒,
打開音頻輸入時創建一個audio_stream_in通道,并創建AudioStreamIn物件以及創建RecordThread錄音執行緒,
8.AudioFlinger
AudioFlinger 是音頻系統策略的執行者,負責音頻流設備的管理及音頻流資料的處理傳輸,所以 AudioFlinger 也被認為是 Android 音頻系統的引擎,
最近的版本AudioFlinger這一塊變得更加模塊化了,把其中的子類提取出為獨立的檔案,而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 對外提供的主要的服務介面如下:

可以歸納出 AudioFlinger 回應的服務請求主要有:
① 獲取硬體設備的配置資訊
② 音量調節
③ 靜音操作
④ 音頻模式切換
⑤ 音頻引數設定
⑥ 輸入輸出流設備管理
⑦ 音頻流管理
現在,我們介紹了AudioFlinger它的各個子類以及它作為服務自身對外提供的介面,之前在說AudioPolicyService的時候也知道了它們都是在audioserver里啟動的,所以我們要看AudioFlinger這個服務時啟動時都做了什么,
AudioFlinger::instantiate() 呼叫如下:
1、注冊 AudioFlinger 服務名為 “media.audio_flinger”,
2、呼叫 AudioFlinger 建構式,在建構式中,主要是做一下初始化,初始化了 mDevicesFactoryHal 設備介面 和 mEffectsFactoryHal 音效介面,
3、呼叫 AudioFlinger::onFirstRef 函式,在其中構造一個 PatchPanel 物件;修改 mode 為 AUDIO_MODE_NORMAL,
AudioFlinger 服務啟動后,其他行程可以通過 ServiceManager 來獲取其代理物件 IAudioFlinger,通過 IAudioFlinger 可以向 AudioFlinger 發出各種服務請求,從而完成自己的音頻業務,
看來它在初始化時做的作業沒有AudioPolicyService那么復雜,但是AudioFlinger有著更重要的作業,我們繼續來看,
AndioFlinger 作為 Android 的音頻系統引擎,重任之一是負責輸入輸出流設備的管理及音頻流資料的處理傳輸,這是由回放執行緒(PlaybackThread 及其派生的子類)和錄制執行緒(RecordThread)進行的,
我們簡單看看回放執行緒和錄制執行緒類關系:

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 實體,其他型別的輸出流設備并非必須宣告支持的,主要是看硬體上有無這個能力,
下圖簡單描述 AudioTrack、PlaybackThread、輸出流設備三者的對應關系:

輸出流設備決定了它對應的 PlaybackThread 是什么型別,只有支持了該型別的輸出流設備,那么該型別的 PlaybackThread 才有可能被創建,
應用行程與 AudioFlinger 并不在一個行程上,這就需要 AudioFlinger 提供音頻流管理功能,并提供一套通訊介面可以讓應用行程跨行程控制 AudioFlinger 中的音頻流狀態,
AudioFlinger 音頻流管理由 AudioFlinger::PlaybackThread::Track 實作,Track 與 AudioTrack 是一對一的關系,一個 AudioTrack 創建后,那么 AudioFlinger 會創建一個 Track 與之對應,PlaybackThread 與 AudioTrack/Track 是一對多的關系,一個 PlaybackThread 可以掛著多個 Track,
AudioTrack 創建后,AudioPolicyManager 根據 AudioTrack 的輸出標識和流型別,找到對應的輸出流設備和 PlaybackThread(如果沒有找到的話,則系統會打開對應的輸出流設備并新建一個 PlaybackThread),然后創建一個 Track 并掛到這個 PlaybackThread 下面,
PlaybackThread 有兩個私有成員向量與此強相關:
mTracks:該 PlaybackThread 創建的所有 Track 均添加保存到這個向量中,
mActiveTracks:只有需要播放(設定了 ACTIVE 狀態)的 Track 會添加到這個向量中,
PlaybackThread 會從該向量上找到所有設定了 ACTIVE 狀態的 Track,把這些 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里面涉及和包含的東西很多很多,在這里只是冰山一角,沒有深入到具體的代碼中去看,只是給大家介紹了一下其作業原理,
四、專案實體(汽車)
1.前言
目前移動作業系統主要分為Android和IOS兩大陣營,IOS系統從出現的時候就給出了人們很驚艷的感覺,其內部細節的打磨以及用戶體驗都是有口皆碑,而安卓在一經推出到現在也一直是人們“討論”的物件,主要是一開始的系統生硬,BUG居多,互動不夠人性化此等印象在人們的腦海中先入為主,但是代碼開源,linux內核這兩個重要的原因給了安卓系統龐大的成長空間和生態基礎,
時光荏苒,移動互聯網成為世界科技的主旋律,谷歌開始重視起來自己當時收購的這個“玩意“,不斷更迭,不斷優化,不斷成長,世界已經不是那個世界了,安卓系統成為了我們生活中必不可少的組成部分,在移動設備上(例如手機)發光發熱了近十年光景,但是它的使命還沒有結束,
汽車,屬于傳統產業,如今人們對它的要求也日益提高,自動駕駛概念炒的火熱,互聯網公司當然亦不會放過這次機會,
安卓系統的加入也仿佛是理所當然,谷歌公司更加確切了未來的方向,面向汽車提供了大量的功能介面,而在Audio模塊里面,就有這么一個新加入的“成員”,
2.CarAudio
CarAudioService,是谷歌專門為汽車音頻這塊專門建的一個服務,處理一些汽車上專門的設定和專案定制化的一些東西,
但是,由于剛剛推出還不是很成熟,因此每個版本變更時Car這部分都會有很大變化,為了方便理解,基于Android9.0(P)簡單分析下CarAudio這部分,
先來看看這個服務的啟動流程:
1、SystemServer
startOtherServices()中啟動CarService,代碼如下:
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
traceBeginAndSlog("StartCarServiceHelperService");
mSystemServiceManager.startService(CAR_SERVICE_HELPER_SERVICE_CLASS);
traceEnd();
}
其實就是呼叫了service的onstart方法,那么我們去CarServiceHelperService中看看onstart方法中做了什么,
2、 CarServiceHelperService
@Override
public void onStart() {
Intent intent = new Intent();
intent.setPackage("com.android.car");
intent.setAction(CAR_SERVICE_INTERFACE);
if (!getContext().bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
UserHandle.SYSTEM)) {
Slog.wtf(TAG, "cannot start car service");
}
System.loadLibrary("car-framework-service-jni");
}
在這里系結的CarService,而在里面重點是new 了一個 ICarImpl物件,所以直接看這個類,
android/packages/services/Car/service/src/com/android/car/ICarImpl.java
3、 ICarImpl
在其構造方法中:
mCarAudioService = new CarAudioService(serviceContext,mCarPowerManagementService, mCarPropertyService);
allServices.add(mCarAudioService);
mAllServices = allServices.toArray(new CarServiceBase[allServices.size()]);
再看init函式里面:
@MainThread
void init() {
traceBegin("VehicleHal.init");
mHal.init();
traceEnd();
traceBegin("CarService.initAllServices");
for (CarServiceBase service : mAllServices) {
service.init();
}
traceEnd();
}
整個啟動流程很清晰也很簡單,也可以推算出其他Car服務的啟動流程,所以了解這一塊還是很有用的,也為我們接下來的內容做好了前期準備,
3.CarAudioManager
CarAudioManager是專門用于處理特定于汽車的音頻用例的API,
提供了一組CAR_AUDIO_USAGE_*的常量,根據這些常量將音頻路由到汽車,重要程度超出了正常的AudioManager類方法,因為它處理多聲道音頻,
包括一些例子,例如僅將電話音頻資料在駕駛位播放,而不是通過所有揚聲器播放,
用戶和開發者要通過CarAudioManager來呼叫CarAudioService里面的功能,可以看一下它的建構式,里面獲得了CarAudioService,不難,很清晰,因為重點都在這個服務里面,
/** @hide */
public CarAudioManager(IBinder service, Context context, Handler handler) {
mContentResolver = context.getContentResolver();
mService = ICarAudio.Stub.asInterface(service);
}
}
4.CarAudioService
CarAudioService實作了ICarAudio.aidl介面,目的也是為了方便遠程呼叫以及跨行程通信,主要看這里面都有什么方法,還有就是它都做了什么作業,
1、init
/**
* Dynamic routing and volume groups are set only if
* {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode.
*/
@Override
public void init() {
synchronized (mImplLock) {
if (!mUseDynamicRouting) {
Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not configured, run in legacy mode");
setupLegacyVolumeChangedListener();
} else {
setupDynamicRouting();
setupVolumeGroups();
}
}
}
其中有個變數mUseDynamicRouting是控制是否使用動態路由,默認關閉,需要時要在組態檔中改為true,
如果使用了動態路由那么就要執行setupDynamicRouting()和setupVolumeGroups()這兩個方法了,我們一個一個來看,
2、setupDynamicRouting
private void setupDynamicRouting() {
final IAudioControl audioControl = getAudioControl();
if (audioControl == null) {
return;
}
AudioPolicy audioPolicy = getDynamicAudioPolicy(audioControl);
int r = mAudioManager.registerAudioPolicy(audioPolicy);
if (r != AudioManager.SUCCESS) {
throw new RuntimeException("registerAudioPolicy failed " + r);
}
mAudioPolicy = audioPolicy;
}
這個AudioControl是HIDL定義的Hal服務,看名字就是音頻控制相關的,現在知道是啥就行,后續專案一般都會在這做定制化功能,
AudioPolicy 是Android很重要的一個組成部分,做為native對外的api,我們可以直接拿來使用,定制自己聲音路由策略,以及音頻焦點優先級策略,
這部分代碼邏輯不是很復雜,只是將AudioControl的實體傳給了getDynamicAudioPolicy()方法,繼續分析getDynamicAudioPolicy()這個方法,
3、 getDynamicAudioPolicy
這塊的代碼很長,就不粘貼出來了,用文字敘述一下,大家了解大致流程即可,
(1)獲取device是輸出設備的的設備資訊,而這些設備的資訊,是存在audiopolicy組態檔中的, 在Android7.0之后使用的是audio_policy_configuration.xml組態檔,拿到這些輸出的outdevice資訊后,繼續過濾出device是AUDIO_DEVICE_OUT_BUS的設備資訊,
創建CarAudioDeviceInfo 并將這些放入集合中,BusNumber就是audio_policy_configuration中device標簽下 address屬性里bus后面的那個數字,一般定義都是BUS1,BUS2或者BUS001,BUS002等BUS后面的數字最多3位,
(2)開始做路由bus策略的映射,可以簡單理解為AudioAttribute的usage與AUDIO_OUT_DEVICE_BUS的映射,將contextNumber通過AudioControl與busNumber做map映射,同時存入集合,
(3)我們拿到了out的device,又拿到了contextNumber和busNumber的映射,那么第三部分就是真正實作他們的組裝,
(4)最后一步就是設定動態路由音量回呼,
上述核心的東西就是把AudioAttribute和AUDIO_DEVICE_OUT_BUS映射起來,存入AudioMix并通過mAudioManager.registerAudioPolicy(audioPolicy)注冊下去,最侄訓在Audio PolicyManagerj進行路由策略時優先對應我們注冊下來的這些策略,
使用外部音頻路由策略對比安卓原生可以縮短開發風險和難度,因為原生在追加定義時要從AudioStream開始,一步一步通過AudioTrack到jni到AudioPolicy,到Engine的strategy,很麻煩也不容易理解,但是使用外部音頻路由也有缺點,那就是不能再使用軟體調音量了,
4、setupVolumeGroups
private void setupVolumeGroups() {
Preconditions.checkArgument(mCarAudioDeviceInfos.size() > 0,
"No bus device is configured to setup volume groups");
final CarVolumeGroupsHelper helper = new CarVolumeGroupsHelper(
mContext, R.xml.car_volume_groups);
mCarVolumeGroups = helper.loadVolumeGroups();
for (CarVolumeGroup group : mCarVolumeGroups) {
for (int contextNumber : group.getContexts()) {
int busNumber = mContextToBus.get(contextNumber);
group.bind(contextNumber, busNumber, mCarAudioDeviceInfos.get(busNumber));
}
// Now that we have all our contexts, ensure the HAL gets our intial value
group.setCurrentGainIndex(group.getCurrentGainIndex());
Log.v(CarLog.TAG_AUDIO, "Processed volume group: " + group);
}
// Perform validation after all volume groups are processed
if (!validateVolumeGroups()) {
throw new RuntimeException("Invalid volume groups configuration");
}
}
這個方法先構造一個CarVolumeGroupsHelper物件:
final CarVolumeGroupsHelper helper = new CarVolumeGroupsHelper(mContext, R.xml.car_volume_groups);
這個物件會去決議car_volume_groups.xml檔案的音頻別名對應輸出設備的分組,通過helper.loadVolumeGroups可以得到分組的具體情況,最后再呼叫group.bind(contextNumber, busNumber, mCarAudioDeviceInfos.get(busNumber))將CONTEXT - OUTPUT - CarAudioDeviceInfo關聯起來,
5、setGroupVolume
最后看一個比較重要的介面,這個是用來調音量的,從名字上看區別于原生的地方是Group這個詞(原生用的是Stream),還有之前說的,一個是軟調,一個是硬調,
上一個方法我們了解了音頻是怎么分組的,現在我們調節音量的時候會直接把groupId當作引數,代碼如下:
/**
* @see {@link android.car.media.CarAudioManager#setGroupVolume(int, int, int)}
*/
@Override
public void setGroupVolume(int groupId, int index, int flags) {
synchronized (mImplLock) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
callbackGroupVolumeChange(groupId, flags);
// For legacy stream type based volume control
if (!mUseDynamicRouting) {
mAudioManager.setStreamVolume(STREAM_TYPES[groupId], index, flags);
return;
}
CarVolumeGroup group = getCarVolumeGroup(groupId);
group.setCurrentGainIndex(index);
}
}
這樣通過setGroupVolume的引數groupId就可以方便的控制調節哪些音頻型別對應的輸出設備的硬體音量了,
硬體音量曲線可以在CarVolumeGroup.java中實作,也可以在Hal層中實作,在CarAudioService.java中,定義mUseDynamicRouting會使得setGroupVolume方法走上截然不同的道路——設定硬體音量,在CarVolumeGroup.java中根據setGroupVolume方法傳入的值和audio_Policy_Configuration.xml中提供的資訊,進行計算得到音量gainInMillibels,之后將gainInMillibels一路傳到Hal層,在Hal層根據音頻曲線再次計算音量值,最后呼叫BSP提供的介面設定音量,
最后還是想說,掌握前三章才是重中之重,它們是Audio的主題,樹干,所以希望可以好好理解前面的內容,可以自己查看這部分原始碼,
總結
堅持看完的同學我這里提出表揚,Audio開發是一件很重要的作業,庖丁解牛似的研究代碼,知其所以然,在實際作業中才會更加得心應手,
安卓從一開始推出,經過代代更迭,代碼重構,但其基本框架還在那,未來也不會變,掌握其背后核心組成的東西才是關鍵,即使我們無法記住具體的某一行代碼的實作,但是學習以上這些,我們卻能更清晰的看待問題,定位問題,解決問題,也可以使其它模塊的同行更加了解Audio系統,從而在未來的作業當中會互相理解,從全域的視角來看待事物,提高作業效率,
覺得看完有幫助的請一鍵三連吧!
您的支持將是我繼續下去的動力
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/295205.html
標籤:其他
