Android 4.1 Audio系統變化說明之一

Android 4.1,英文代號簡稱JB。在國人眼裏,JB這個詞還和動物有點關係。Google如此頻繁修改Android,終於推出了一個能夠被你們成天JB JB掛在嘴上的版本。之後個人文章也能夠一面用JB表示版本號,一面用JB表示毛主席常說的」戰略上的鄙視了「。請你們根據上下文揣摩我寫下JB一詞的心情。
今天將稍深刻得介紹一下JB 4.1在Audio系統作的翻天覆地的改動。這裏先囉嗦幾句:就像80後常常抱怨本身晚生了幾年同樣,立刻就會有不少碼農抱怨接觸Android太晚了。爲什麼?JB Audio系統的難度相對4.0, 2.3, 2.2已經很是很是大了。99%的狀況下,在你沒有看到這個NB(這不是髒話,4.1 Audio系統中就有一個類叫NBAIO,籃球控不要搞錯成NBA了,原意是Non-block Audio I/O。看到了吧, 非阻塞I/O,各位自問下,有多少人對這個東西有深入理解?)東西演化的基礎上,不太可能能看懂JB Audio系統。因此,建議這99%中的沒有見識過Audio演化歷史的屌絲同窗們,先仔細研究(之前我僅僅建議你們看看,如今提升要求爲仔細研究)《深刻理解Android 卷I》Audio系統。 BTW,此書在某個章節裏特地提醒過你們要去研究下各類I/O模型,不知道有幾我的屌過我了。
本文將分幾個部分,事前沒有打草稿,因此會有點亂。
先從Java層AudioTrack類提及
一 AudioTrack Java類變化說明
  • 聲道數上,之前只有單聲道(MONO)和立體聲(STEREO),如今拓展到最NB八聲道(7.1 HiFi啊)。參數名爲CHANNEL_OUT_7POINT1_SURROUND。看到這個參數,我下巴咣噹就掉下來了。這玩意,一時半會我還弄不明白是個什麼道理。有知曉的屌絲碼農們不妨告訴你們。 固然,最終的輸出仍是雙聲道。多聲道(大於2)的時候會使用downmixer處理(下變換處理,同窗們可搜索之)
  • 其餘的變化也有,但不大了。我這裏先挑一些吸引眼球的。BTW,放心,不會像那個瀧澤蘿拉首秀片子同樣只讓你們看見大鼻孔的。
二 AudioTrack JNI層變化說明
這一層包括JNI層和AudioTrack自己
  • JNI層變化不大。
  • Audio Native核心代碼移到了framework/av下。對,你沒看錯。真的是av。這就是JB Audio一個比較大的變化。Audio Native核心代碼所有移到了frameworks/AV目錄下。
  • AudioTrack增長了一個變量,用於控制使用它的進程的調度優先級(前文說錯了,這裏確實設置的是nicer值)。若是處於播放狀態的話,將設置進程調度優先級爲ANDROID_PRIORITY_AUDIO。就像大家看到馬賽克時必定會嘟喃同樣。我這裏也要特別囉嗦幾句。在單核CPU的狀況下,設置優先級是比較愚蠢的(ANDROID_PRIORITY_AUDIO的值爲-16,優先級極高,單核設置個這麼高的怪物,不知道其餘app還怎麼玩。若是你不知道我在說什麼,先看看這篇文章吧,http://blog.csdn.net/innost/article/details/6940136)。但如今2核,4核已經比較常見了,這裏就能夠來玩玩調度方面的事情。對屌絲碼農的真正考驗是:多核並行編程,linux os的原理,須要各位屌絲同窗努力掌握。Audio已經不那麼能輕易被大家任意蹂躪了。另外,低端手機,求求大家別移植4.1了,這個真的不是低端能玩的。
  • AudioTrack升級爲父親了。JB爲它定義了一個莫名其妙的的TimedAudioTrack子類。這個類在編解碼的aah_rtp(我如今還不知道aah是什麼)裏邊用到了。從註釋上看,該類是一個帶時間戳(有時間戳,就能夠作同步了)的音頻輸出接口。詳細理解的話,就須要結合具體應用場景去分析了(主要是rtp這一塊)。搞編解碼的同窗們,抓緊了!
  • 另一個超級複雜的變化,是Audio定義了幾個輸出flag(見audio.h的audio_output_flags_t枚舉定義)。根據註釋,該值有兩個做用,一個是AT的使用者能夠指明本身想使用怎樣的outputDevice。另一個是設備廠商能夠經過它聲明本身支持的輸出設備(看來,設備初始化的時候,又增添了參數讀取和配置這方面的工做)。不過,從該枚舉的定義來看,我還看不出它和硬件有什麼關係。它定義的值以下:
typedef enum {
AUDIO_OUTPUT_FLAG_NONE = 0x0, // no attributes
AUDIO_OUTPUT_FLAG_DIRECT = 0x1, // this output directly connects a track
// to one output stream: no software mixer
AUDIO_OUTPUT_FLAG_PRIMARY = 0x2, // this output is the primary output of
// the device. It is unique and must be
// present. It is opened by default and
// receives routing, audio mode and volume
// controls related to voice calls.
AUDIO_OUTPUT_FLAG_FAST = 0x4, // output supports "fast tracks", 《==什麼叫fast track?太難理解了!目前,java層的audiotrack只會使用第一個標誌。
// defined elsewhere
AUDIO_OUTPUT_FLAG_DEEP_BUFFER = 0x8 // use deep audio buffers 《==deep buffer是個什麼玩意?這個馬賽克是否是太大了點?如今徹底看不清楚啊??!
} audio_output_flags_t;
  • AudioTrack其餘變化不大。AudioTrack.cpp總共才1600多行,so easy!
OK,上面有好幾個馬賽克,日常看看日本大片的時候也就擼過去了,但分析Audio可不行。把去馬賽克的但願寄託在下一步AudioFlinger的分析上吧!
三 AudioFlinger變化說明
咱們將根據AF工做的主要流程來介紹下變化狀況:
  • AF建立,包括其onFirstRef函數
  • openOutput函數及MixerThread對象的建立
  • AudioTrack調用createTrack函數
  • AudioTrack調用start函數
  • AF混音,而後輸出
3.1 AF建立和onFirstRef
恩,沒什麼太大變化。有三個點:
  • 如今對Primary設備的音量有了更爲細緻的控制,例若有些設備能設音量,有些不能設置音量,因此定義了一個master_volume_support(AudioFlinger.h)枚舉,用來判斷Primary設備的音量控制能力。
  • 之前播放過程的standby時間(就是爲了節電而用)是寫死的,如今可由ro.audio.flinger_standbytime_ms控制,若是沒有這個屬性,則默認是3秒。AF還增長了其餘變量控制,例若有一個gScreenState變量,用來表示屏幕是開仍是關。可經過AudioSystem::setParameters來控制。另外還定義了一個和藍牙SCO相關的mBtNrecIsOff變量,是用於控制藍牙SCO(錄音時用,藍牙上的一個專業術語叫,NREC。不知道是什麼,用懂的人告訴我一下)時禁止AEC和NS特效的。請參考AudioParameter.cpp
3.2 openOutput函數
openOutput函數比較關鍵,其中會見到之前的老朋友MixerThread,AudioStreamOutput等。整個流程包括加載Audio相關的硬件so。這部分工做在4.0的時候就有了,談不上太多的變化。但物是人非,老朋友已經發生巨大變化了。先來看MixerThread家族。

圖1 PlaybackThread家族
圖1稍加解釋:
  • ThreadBase從Thread派生,因此它會運行在一個單獨的線程中(囉嗦一句,線程和對象其實沒有關係的,不懂多線程編程的碼農們請務必認真學習多線程)。它定義了一個枚舉type_t,用來表示子類的類型,這幾個類型包括MIXER,DIRECT,RECORD,DUPLICATING等。這個應該比較好懂吧?
  • ThreadBase的內部類TrackBase從ExtendedAudioBufferProvider派生,這個應該是新增長的。TrackBase嘛,你們把它理解成一個Buffer Container就行了。
  • ThreadBase的內部類PMDeathRecipient用來監聽PowerManagerService的死亡消息。這個設計有點搞,由於PMS運行在SystemService中,只有SS掛了,PMS纔會掛。而SS掛了,mediaserver也會被init.rc的規則給弄死,因此AudioFlinger也會死。既然你們都一塊兒死,速度很快。故,設置這個PMDeathRecipient有何大的意義呢?
再來看ThreadBase的一個重要子類PlaybackThread,這個類應該是作過大整容了。
  • 其定義了一個枚舉mixer_state,用來反映當前混音工做的狀態,有MIXER_IDLE,MIXER_READY和MIXER_ENABLED
  • 定義了幾個虛函數,須要子類實現,包括threadLoop_mix,prepareTracks_l等。這幾個函數的抽象工做作得仍是能夠。但變化之大讓人防不勝防啊。
  • Track類增長了從VolumeProvider派生,這個VP是用來控制音量的。根據前面的介紹,在JB中,音量管理比之前來得細緻
  • 新增定義了TimedTrack。這個類的做用和前面提到的rtp aah有關。等同窗們學完本篇,便可開展相應研究,打響殲滅戰!
接下來看圖2。

圖2 MixerThread和它的弟兄們
圖2,簡單介紹一下:
  • MixerThread從PlaybackThread派生,這個關係至始至終不會變化,相信之後也不會。
  • MT最大的變化是其中幾個重要的成員變量。你們確定認識其中的AudioMixer,它是用來混音的。
  • 新增一個Soaker對象(由編譯宏控制),它是一個線程。這個單詞的前綴soak在webster詞典(相信經歷過,那些年,咱們一塊兒GRE的日子 的人知道什麼是webster)中最貼切的一條解釋是to cause to pay an exorbitant amount。仍是不很明白是幹嗎的?再一看代碼。原來,soaker就是一個專職玩弄CPU的線程。它的工做就是不斷得作運算,拉高CPU使用率。它的存在應該是爲了測試新AF框架在多核CPU上的效率等等等的問題。因此,低端智能機們,大家不要玩JB了。
  • 另一條證實低端智能機不能隨便玩JB的鐵證就是:咱們看到MT中新增了一個FastMixer,它也是一個線程。明白了?在JB中,多核智能機上,混音工做能夠放到FastMixer所在的線程來作,固然速度,效率會高了。
  • FastMixer工做流程比較複雜,又牽扯到多線程同步。因此,這裏定義了一個FastMixerStateQueue,它由typedef StateQueue<FastMixerState>獲得。首先它是一個StateQueue(簡單把它當作數組吧)。其數組元素的類型爲FastMixerState。一個StateQueue經過mStats變量保存4個FasetMixerState成員。
  • FasetMixerState相似狀態機,有一個enum Command,用來控制狀態的。FastMixerState中含有一個八元組的FastTracks數組。FastTrack是用來完成FastMixer的一個功能類。
  • 每一個FastTrack都有一個mBufferProvider,該成員類型爲SourceAudioBufferProvider。
以上的內容已經比較複雜了,下面來介紹下MixerThread對象建立中碰到的其餘內容:
3.3 MixerThread建立
經過圖1和圖2,應該對AF的幾個主要成員有了認識。惋惜啊,上面MixerThread中還有一個mOutputSink成員,沒看到吧?它就和咱們前面提到的NBAIO(Non-block Audio I/O )有重大關係。NBAIO的存在,是爲了想實現非阻塞的音頻輸入輸出操做。下面是這個類的註釋:
NBAIO註釋:
// This header file has the abstract interfaces only. Concrete implementation classes are declared
// elsewhere. Implementations _should_ be non-blocking for all methods, especially read() and
// write(), but this is not enforced. In general, implementations do not need to be multi-thread
// safe, and any exceptions are noted in the particular implementation.
NBAIO只是定義了一個接口,須要去實現具體的實現類。固然,它要求read/write函數是非阻塞的,真實實現究竟是不是阻塞,由實現者去控制。
我的感受這部分框架尚未徹底成熟,但NBIO的引入,須要同窗們當心,相對而言,難度也比較大。下面咱們經過圖3來看看NBAIO的一些內容。

圖3 NBAIO相關內容
圖3解釋以下:
  • NBAIO包括三個主要類,一個是NBAIO_Port,表明I/O端點,其中定義了一個negotiate函數,用於調用者和I/O端點進行參數協調。注意,並非爲I/O端點設置參數。由於I/O端點每每和硬件相關,而硬件有些參數是不能像軟件通常隨意變化的。例如硬件只支持最多44.1KHZ的採樣率,而調用者傳遞48KHz的採樣率,這直接就須要一個協商和匹配的過程。這個函數的比較難用,主要是規則較多。同窗們能夠參考其註釋說明。
  • NBAIO_Sink對應output端點,其定義了write和writeVia函數,writeVia函數須要傳遞一個回調函數via,其內部將調用這個via函數獲取數據。相似數據的推/拉兩種模式。
  • NBAIO_Source對應input端點,其定義了read和readVia函數。意義同NBAIO_Sink。
  • 定義一個MonoPipe和MonoPipeReader。Pipe即管道,MonoPipe和LINUX中的IPC通訊Pipe沒毛關係,只不過借用了這個管道概念和思路。MonoPipe即只支持單個讀者的Pipe(AF中,它是MonoPipeReader)。這兩個Pipe,表明了Audio的Output和Input端點。
  • MT中由mOutputSink指向AudioStreamOutSink,此類用NBAIO_Sink派生,用於普通的mixer的輸出。mPipeSink指向MonoPipe,本意是用於FastMixer的。另外,還有一個變量mNormalSink,它將根據FastMixer的狀況,指向mPipeSink,或者是mOutputSink。這段控制的邏輯以下:
switch (kUseFastMixer) { //kUseFastMixer用於控制FastMixer的使用狀況,一共4種:
case FastMixer_Never: //永遠不使用FastMixer,這個選項用於調試,即關閉FastMixer的狀況
case FastMixer_Dynamic: //根據狀況,動態使用。根據註釋,這個功能彷佛尚未徹底實現好
mNormalSink = mOutputSink;
break;
case FastMixer_Always: //永遠使用FastMixer,調試用
mNormalSink = mPipeSink;
break;
case FastMixer_Static://靜態。默認就是這個。但具體是否使用mPipeSink,將收到initFastMixer的控制
mNormalSink = initFastMixer ? mPipeSink : mOutputSink;
break;
}
由上所述,kUseFastMixer默認是FastMixer_Static,但mNormalSink是否指向mPipeSink,還由initFastMixer控制。這個變量自己又有mFrameCount和
mNormalFrameCount的大小決定,只有mFrameCount小於mNormalFrameCount時,initFastMixer才爲真。暈了....這兩個frameCount由PlaybackThread的
readOutputParameters獲得。請同窗們本身研究這段代碼吧,就是一些簡單的計算。想要搞明白的話,最好帶着參數進去,把值都算出來。
好了,MixerThread的建立就分析到此,最好仍是把這段代碼多研究研究。瞭解幾個兄弟對象是作什麼的....
3.4 createTrack和start說明
createTrack中最大的變化就是新增了對MediaSyncEvent同步機制的處理。MediaSyncEvent的目的很簡單,其Java API的解釋以下:startRecording(MediaSyncEvent) is used to start capture only when the playback on a particular audio session is complete. The audio session ID is retrieved from a player (e.g MediaPlayer, AudioTrack or ToneGenerator) by use of the getAudioSessionId() method. 簡單點講,就是必須等上一個player工做完畢了,才能開始下一個播放或者錄製。這個機制解決了Android長久以來的聲音常常混着出來的問題(目前一個噁心但卻實效的方法就是加一個sleep,以錯開多個player不一樣步的問題。)。注意,iPhone上就沒有這個問題。
另外,這個機制的潛在好處就是解放了作AudioPolicy AudioRoute工做的同窗們, 彷佛(我的感受是能夠解決這個問題的)能夠不用再去琢磨到底sleep多少時間,在哪加sleep的問題了
在AF中,MediaSyncEvent機制的表明是SyncEvent。你們本身看看就好。
start函數的變化不大,其中加了對SyncEvent的處理。
另外,createTrack中還涉及到FastMixer和TimedTrack處理。核心在PlaybackThread的createTrack_l和Track構造函數中。尤爲是和FastMixer的關係。
根據圖2,FM(FastMixer簡寫)內部用得數據結構是FastTrack,而MT用得是Track,因此這裏存在一一對應的關係。FM的FastTrack是保存在數組中的,因此
使用FM的Track將經過mFastIndex來指向這個FastTrack。
如今搞清楚FastTrack和Track之間的關係便可,後續的數據流動還須要詳細討論
下面來看看MixerThread的工做流程。這部分是重頭戲!
3.5 MixerThread的工做流程
這部分難的仍是在FastMixer的工做原理上。 不過這裏提早和你們說:目前這個功能尚未作完,代碼裏邊一堆的FIXME...。但屌絲們不要happy太早了,
估計立刻、很快、必須得下個版本就行了。如今看看這個不成熟的東西,能夠緩解之後看到成熟的東西的心理壓力。
MT是一個線程,其工做內容主要在threadLoop中完成,而這個函數是由其基類PlaybackThread定義的,大致變化以下:
  • PlaybackThread的threadLoop定義了整個音頻處理的大致流程,具體的細節經過幾個虛函數(如prepareTracks_l,threadLoop_mix,threadLoop_write)交給子類去實現了
  • MT變化大的首先是prepareTracks_l,首先處理的是FastMix類型的Track,判斷標準是該Track是否設置了TRACK_FAST標誌(爽了,目前JB中尚未哪一個地方使用了這個標誌)。這部分判斷比較複雜。首先FastMixer維護了一個狀態機,另外,這個FastMixer運行在本身的線程裏,因此線程同步是必須的。這裏採用的是狀態來控制FastMixer的工做流程。因爲涉及到多線程,因此音頻的underrun,overrun狀態(不知道是什麼嗎?看前面提到的參考書!)也是一個須要處理的棘手問題。另外,一個MT是帶一個AudioMixer對象,這個對象將完成數據的混音,下變換等等超難度,數字音頻處理等方面的工做。也就是說,對於混音來講,前期的prepare工做仍是由MT線程來完成,由於這樣能夠作到統一管理(有些Track並不須要使用FastMixer。但仔細一想,誰都但願處理越快越好,在多核CPU上,將混音工做交給多個線程處理是充分利用CPU資源的典範,這應該是將來Android演化的趨勢。因此,我估計這個JB還沒徹底長大....)。對FastMixer感興趣的屌絲們,請務必認真研究prepareTracks_l函數。
  • MT下一個重要函數就是threadLoop_mix了,因爲存在一個TimedTrack類,那麼AudioMixer的process函數就帶上了一個時間戳,PTS,presentation timestamp。從編解碼角度來講,還有一個DTS,Decode timestamp。這裏要閒扯下PTS和DTS的區別了。DTS是解碼時間,但編碼的時候因爲有可能會根據將來幀來編碼當前幀。因此,解碼的時候會先解將來幀,而後解出當前幀,可是。你播放的時候可不能先播將來幀。只能老老實實得按播放順序來先播當前幀,而後播將來幀(儘管先解出來的是將來幀)。關於PTS/DTS,請屌絲們研究下IBP相關的知識吧。回到MT,這個PTS是從硬件hal對象取的,應該是HAL內部維護的時間戳。這個時間戳原則上會比較準確。
  • 混音完了,再作特效處理(和之前的版本差很少),而後調用threadLoop_write。MT的threadLoop_write函數的輸出端點就是前面那個坑爹的mNormalSink,若是不爲空,就調用它的write函數。想着是調用NBAIO_Sink的非阻塞的write函數。根據圖2的分析,它有多是那個MonoPipe,也有可能就是AudioStreamOutputSink,這個sink節點用得就是之前的AudioStreamOutput。而MonoPipe的write其內部就是一個buffer。並無和真實的AUDIO HAL Output掛上關係。這.....咋整??(大膽假設,當心求證。只能是FastMixer把這個buffer取出來,而後再寫到真實的Audio HAL中了。由於在MixerThread構造函數中,曾經爲FastTrack保存過mOutputSink,這個就是用來和AudioStreamOutput聯繫的)
另外,DulicatingThread,DirectOuptutThread沒有太大變化。
四 FastMixer工做原理簡單說明
我之前想得是:混音工做由FastMixer線程和MixerThread線程共同完成,但輸出工做依然在MixerThread作。從上面MonoPipe的分析來看,這個判斷可能不許。
既有多是輸出工做也交給FastMixer來作,而MixerThread僅作一部分混音工做,而後把數據經過MonoPipe傳給FastMixer線程。FastMixer線程將本身的FastTrack的混音結果和MT的混音結果再作一次混音,而後再由FastMixer輸出。
FM定義在FastMixer.cpp中,核心就是一個ThreadLoop。因爲AF全部Track的預備工做由MT線程來作,因此FM的threadLoop基本上就是根據狀態來作對應處理。
這裏的同步使用了LINUX中很底層的futex(Fast Userspace Mutex)。暈,futex是POSIX Mutex的實現基礎。不知道寫這段代碼的人爲什麼不直接用Mutex(估計仍是嫌效率的問題,可是 媽的,用了Mutex效率能差多少?代碼是寫給人看的,太B4咱們了...)。玩多線程玩到這種地步,佩服啊!不懂多線程編程的屌絲們,請仔細研究Posix MultiThread Programming吧
  • FastMixer內部還使用了一個AudioMixer,用於它的混音
  • 而後再write出去.....
這裏是FM的簡單說明,詳細內容,沒有拿個真機給我,我也無法整啊....歡迎樂善好施的兄弟們刷個4.1的機器,而後借給我研究下...
(這玩意,我的感受也不是太難。東西嘛,耐不住琢磨,總能搞透的)。兄弟們今天知道FM和MT的大致工做流程就能夠了。
五 其餘變化
其餘變化包括:
  • 很是注重調試了,加了大量的XXXDump類。看來,Google本身開發的時候也碰到很多問題。簡單的功能,誰會想着去dump呢?
  • 增長AudioWatchdog類,用來監控AF性能的,如CPU使用狀況等。
六 總結
我記得在研究2.2 AF的時候,AudioFlinger才3k多行,而JB已經有9K多行了。還沒算其餘的輔助類。從總體上看,JB變化趨勢爲:
  • 要充分利用多核資源,因此FastMixer的出現是必然。還包括NBAIO接口。感受對HAL編寫會有大的挑戰。
  • 增長TimedTrack和SyncEvent,對於RTP或者多個player間的同步會帶來比較好的用戶體驗。
  • 增長native層往java層通知的接口。
還有其餘的東西.....今天先到這了。
對屌絲的考驗:
  1. LINUX OS編程和POSIX編程必須熟練掌握。
  2. 複雜代碼分析能力必須儘快提升。不然,後面根本看不懂。
相關文章
相關標籤/搜索