Android音頻系統之音頻框架

1.1 音頻框架

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

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

1.1.1 Linux下的音頻框架

在計算機發展的早期,電腦的聲音處理設備是由一個非常簡易的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主要的文件節點如下:

Information Interface (/proc/asound)

Control Interface (/dev/snd/controlCX)

Mixer Interface (/dev/snd/mixerCXDX)

PCM Interface (/dev/snd/pcmCXDX)

Raw MIDI Interface (/dev/snd/midiCXDX)

Sequencer Interface (/dev/snd/seq)

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利用到極致。

1.1.3 Android系統上的音頻框架

 

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

 

圖 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等等庫來解決的,而不需要大規模地修改上層實現。換句話說,廠商在定制時的重點就是如何提供這部分庫的高效實現瞭。

 

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *