1. 程式人生 > >Android多媒體系統分析-Audio Framework 分析

Android多媒體系統分析-Audio Framework 分析

轉載請註明,From LXS, http://blog.csdn.net/uiop78uiop78/article/details/8796492

Android的音訊系統在很長一段時間內都是外界詬病的焦點。的確,早期的Android系統在音訊處理上相比於IOS有一定的差距,這也是很多專業的音樂播放軟體開發商沒有推出Android平臺產品的一個重要原因。但這並不代表它的音訊框架一無是處,相反,基於Linux系統的Android平臺有很多值得我們學習的地方。

在計算機發展的早期,電腦的聲音處理裝置是由一個非常簡易的loudspeaker外加發聲器(Tone Generator)構成的,功能相對侷限。後來人們想到了以plug-in的形式來擴充套件音訊裝置,“Sound blaster”就是其中很有名的一個。這種早期的音效卡以外掛方式連線到電腦主機板上,並提供了更多複雜的音訊裝置。可想而知,獨立的硬體設計也意味著成本的增加,於是隨著技術的發展,便又出現了板載音效卡,也就是我們俗稱的“整合音效卡”。板載音效卡又分為“軟音效卡”和“硬音效卡”。如果音效卡本身沒有主處理晶片,而只有解碼晶片,需要通過CPU運算來執行處理工作,那麼就是“軟音效卡”,反之就是“硬音效卡”。通常面向低端市場的計算機都會包含一個整合的音效卡裝置以降低成本。

一個典型的音效卡通常包含三個部分:

·          Connectors

用於音效卡與外放裝置,如揚聲器、耳機的連線,又被稱為“jacks”

·          Audio Circuits

音效卡的主要實現體,它負責訊號的放大、混音、以及模擬數字轉換等操作

·          Interface

連線音效卡與計算機匯流排的單元,比如PCI匯流排

我們可以通過“cat/proc/asound/cards”命令來檢視計算機中安裝的音效卡裝置,如下例子所示:

目前市面上音效卡的種類眾多,既有複雜的高效能的,也有低端的簡易的,那麼對於一個作業系統來說,它如何管理這些音訊裝置,並向上層應用提供統一的介面呢?

Android嚴格來講只是一個Linux系統,它依賴於核心提供的各種驅動支援,包括音訊驅動。因此我們有必要先花點時間來學習下Linux平臺下的兩種主要的音訊驅動架構:

·        OSS (Open Sound System)

早期Linux版本採用的是OSS框架,它也是Unix及類Unix系統中廣泛使用的一種音訊體系。OSS既可以指OSS介面本身,也可以用來表示介面的實現。OSS的作者是Hannu Savolainen,就職於4Front Technologies公司。由於涉及到智慧財產權問題,OSS後期的支援與改善不是很好,這也是Linux核心最終放棄OSS的一個原因。

另外,OSS在某些方面也遭到了人們的質疑,比如:

對新音訊特性的支援不足;

缺乏對最新核心特性的支援等等。

當然,OSS做為Unix下統一音訊處理操作的早期實現,本身算是比較成功的。它符合“一切都是檔案”的設計理念,而且做為一種體系框架,其更多地只是規定了應用程式與作業系統音訊驅動間的互動,因而各個系統可以根據實際的需求進行定製開發。總的來說,OSS使用瞭如下表所示的裝置節點:

表格 13‑1 OSS採用的裝置節點

裝置節點

說明

/dev/dsp

向此檔案寫資料à輸出到外放Speaker

向此檔案讀資料à從Microphone進行錄音

/dev/mixer

混音器,用於對音訊裝置進行相關設定,比如音量調節

/dev/midi00

第一個MIDI埠,還有midi01,midi02等等

/dev/sequencer

用於訪問合成器(synthesizer),常用於遊戲等效果的產生

·        ALSA(Advanced Linux Sound Architecture)

ALSA是Linux社群為了取代OSS而提出的一種框架,是一個原始碼完全開放的系統(遵循GNU GPL和GNU LGPL)。ALSA在Kernel 2.5版本中被正式引入後,OSS就逐步被排除在核心之外。當然,OSS本身還是在不斷維護的,只是不再為Kernel所採用而已。

ALSA相對於OSS提供了更多,也更為複雜的API介面,因而開發難度相對來講加大了一些。為此,ALSA專門提供了一個供開發者使用的工具庫,以幫助他們更好地使用ALSA的API。根據官方文件的介紹,ALSA有如下特性:

Ø  高效支援大多數型別的audio interface(不論是消費型或者是專業型的多聲道音效卡)

Ø  高度模組化的聲音驅動

Ø  SMP及執行緒安全(thread-safe)設計

Ø  在使用者空間提供了alsa-lib來簡化應用程式的編寫

Ø  與OSS API保持相容,這樣子可以保證老的OSS程式在系統中正確執行

ALSA主要由下表所示的幾個部分組成:

表格 13‑2 Alsa-project Package

Element

Description

alsa-driver

核心驅動包

alsa-lib

使用者空間的函式庫

alsa-utils

包含了很多實用的小程式,比如

alsactl:用於儲存裝置設定

amixer:是一個命令列程式,用於聲量和其它聲音控制

alsamixer:amixer的ncurses版

acconnect和aseqview:製作MIDI連線,以及檢查已連線的埠列表

aplay和arecord:兩個命令列程式,分別用於播放和錄製多種格式的音訊

alsa-tools

包含一系列工具程式

alsa-firmware

音訊韌體支援包

alsa-plugins

外掛包,比如jack,pulse,maemo

alsa-oss

用於相容OSS的模擬包

pyalsa

用於編譯Python版本的alsa lib

Alsa主要的檔案節點如下:

  1. Information Interface (/proc/asound)
  2. Control Interface (/dev/snd/controlCX)
  3. Mixer Interface (/dev/snd/mixerCXDX)
  4. PCM Interface (/dev/snd/pcmCXDX)
  5. Raw MIDI Interface (/dev/snd/midiCXDX)
  6. Sequencer Interface (/dev/snd/seq)
  7. Timer Interface (/dev/snd/timer)

關於ALSA的更多知識,建議大家可以自行參閱相關文件,這對於後面理解整個Audio系統是不無裨益的。

1.1.2 TinyAlsa

一看“Tiny”這個詞,大家應該能猜到這是一個ALSA的縮減版本。實際上在Android系統的其它地方也可以看到類似的做法——既想用開源專案,又嫌工程太大太繁瑣,怎麼辦?那就只能瘦身了,於是很多Tiny-XXX就出現了。

在早期版本中,Android系統的音訊架構主要是基於ALSA的,其上層實現可以看做是ALSA的一種“應用”。後來可能是由於ALSA所存在的一些不足,Android後期版本開始不再依賴於ALSA提供的使用者空間層的實現,因而我們在它的庫資料夾中已經找不到alsa相關的lib了,如下圖所示:

 

圖 13‑7 Android4.1與早期版本在音訊庫上的區別

而取代它的是tinyalsa相關的庫檔案,如下圖所示:


同時我們可以看到externl目錄下多了一個“tinyalsa”資料夾,其中包含了為數不多的幾個原始碼檔案,如下所示:

表格 13‑3 Tiny-alsa工程檔案

Source File

Description

Android.mk

makefile

mixer.c

Mixer Interface實現

pcm.c

PCM Interface實現

tinycap.c

Caputer工具

tinymix.c

Mixer工具

tinyplay.c

Play工具

include/tinyalsa/asoundlib.h

標頭檔案

可見TinyAlsa與原版Alsa的差異還是相當大的,它只是部分支援了其中的兩種Interface,而像Raw MIDI、Sequencer、Timer等Interface則沒有涉及到——當然這對於一般的嵌入式裝置還是足夠了。

TinyAlsa作為Alsa-lib的一個替代品,自面世已來得到的公眾評價有褒有貶,不能一概而論——對於每個廠商來說,合適自己的就是最好的。而且各廠商也可以在此基礎上擴充套件自己的功能,真正的把ALSA利用到極致。

一個好的系統架構,需要儘可能地降低上層與具體硬體的耦合,這既是作業系統的設計目的,對於音訊系統也是如此。音訊系統的雛形框架可以簡單的用下圖來表示:


圖 13‑8 音訊系統的雛形框架

在這個圖中,除去Linux本身的Audio驅動外,整個Android音訊實現都被看成了User。因而我們可以認為Audio Driver就是上層與硬體間的“隔離板”。但是如果單純採用上圖所示的框架來設計音訊系統,對上層應用使用音訊功能是不小的負擔,顯然Android開發團隊還會根據自身的實際情況來進一步細化“User”部分。

細化的根據自然還是Android的幾個層次結構,包括應用層、framework層、庫層以及HAL層,如下圖所示:


圖 13‑9 音訊框架在Android系統中的進一步細化

我們可以結合目前已有的知識,想一下每一個層次都會包含哪些模組(先不考慮藍芽音訊部分)?

·        APP

這是整個音訊體系的最上層,因而並不是Android系統實現的重點。比如廠商根據特定需求自己寫的一個音樂播放器,遊戲中使用到聲音,或者調節音訊的一類軟體等等。

·        Framework

相信大家可以馬上想到MediaPlayer和MediaRecorder,因為這是我們在開發音訊相關產品時使用最廣泛的兩個類。實際上,Android也提供了另兩個相似功能的類,即AudioTrack和AudioRecorder,MediaPlayerService內部的實現就是通過它們來完成的,只不過MediaPlayer/MediaRecorder提供了更強大的控制功能,相比前者也更易於使用。我們後面還會有詳細介紹。

除此以外,Android系統還為我們控制音訊系統提供了AudioManager、AudioService及AudioSystem類。這些都是framework為便利上層應用開發所設計的。

·        Libraries

我們知道,framework層的很多類,實際上只是應用程式使用Android庫檔案的“中介”而已。因為上層應用採用java語言編寫,它們需要最直接的java介面的支援,這就是framework層存在的意義之一。而作為“中介”,它們並不會真正去實現具體的功能,或者只實現其中的一部分功能,而把主要重心放在庫中來完成。比如上面的AudioTrack、AudioRecorder、MediaPlayer和MediaRecorder等等在庫中都能找到相對應的類。

這一部分程式碼集中放置在工程的frameworks/av/media/libmedia中,多數是C++語言編寫的。

除了上面的類庫實現外,音訊系統還需要一個“核心中控”,或者用Android中通用的實現來講,需要一個系統服務(比如ServiceManager、LocationManagerService、ActivityManagerService等等),這就是AudioFlinger和AudioPolicyService。它們的程式碼放置在frameworks/av/services/audioflinger,生成的最主要的庫叫做libaudioflinger。

音訊體系中另一個重要的系統服務是MediaPlayerService,它的位置在frameworks/av/media/libmediaplayerservice。

因為涉及到的庫和相關類是非常多的,建議大家在理解的時候分為兩條線索。

其一,以庫為線索。比如AudioPolicyService和AudioFlinger都是在libaudioflinger庫中;而AudioTrack、AudioRecorder等一系列實現則在libmedia庫中。

其二,以程序為線索。庫並不代表一個程序,程序則依賴於庫來執行。雖然有的類是在同一個庫中實現的,但並不代表它們會在同一個程序中被呼叫。比如AudioFlinger和AudioPolicyService都駐留於名為mediaserver的系統程序中;而AudioTrack/AudioRecorder和MediaPlayer/MediaRecorder一樣實際上只是應用程序的一部分,它們通過binder服務來與其它系統程序通訊。

在分析原始碼的過程中,一定要緊抓這兩條線索,才不至於覺得混亂。

·        HAL

從設計上來看,硬體抽象層是AudioFlinger直接訪問的物件。這說明了兩個問題,一方面AudioFlinger並不直接呼叫底層的驅動程式;另一方面,AudioFlinger上層(包括和它同一層的MediaPlayerService)的模組只需要與它進行互動就可以實現音訊相關的功能了。因而我們可以認為AudioFlinger是Android音訊系統中真正的“隔離板”,無論下面如何變化,上層的實現都可以保持相容。

音訊方面的硬體抽象層主要分為兩部分,即AudioFlinger和AudioPolicyService。實際上後者並不是一個真實的裝置,只是採用虛擬裝置的方式來讓廠商可以方便地定製出自己的策略。

抽象層的任務是將AudioFlinger/AudioPolicyService真正地與硬體裝置關聯起來,但又必須提供靈活的結構來應對變化——特別是對於Android這個更新相當頻繁的系統。比如以前Android系統中的Audio系統依賴於ALSA-lib,但後期就變為了tinyalsa,這樣的轉變不應該對上層造成破壞。因而Audio HAL提供了統一的介面來定義它與AudioFlinger/AudioPolicyService之間的通訊方式,這就是audio_hw_device、audio_stream_in及audio_stream_out等等存在的目的,這些Struct資料型別內部大多隻是函式指標的定義,是一些“殼”。當AudioFlinger/AudioPolicyService初始化時,它們會去尋找系統中最匹配的實現(這些實現駐留在以audio.primary.*,audio.a2dp.*為名的各種庫中)來填充這些“殼”。

根據產品的不同,音訊裝置存在很大差異,在Android的音訊架構中,這些問題都是由HAL層的audio.primary等等庫來解決的,而不需要大規模地修改上層實現。換句話說,廠商在定製時的重點就是如何提供這部分庫的高效實現了。

基於上面的分析,我們給出一個完整的Android音訊系統框架來給大家參考(沒有列出Linux層的實現,比如ALSADriver等等),如下所示:


 圖 13‑10 Android音訊系統框架全圖

接下來的小節,我們將分別介紹上述框架圖中的幾個重點模組,包括AudioFlinger,AudioTrack/AudioRecorder,AudioManager/AudioPolicyService,並簡單地介紹上層的一些模組如MediaPlayerService等等。