1.1.1 音頻設備的管理
雖然AudioFlinger實體已經成功創建並初始化,但到目前為止它還是一塊靜態的內存空間,沒有涉及到具體的工作。
從職能分布上來講,AudioPolicyService是策略的制定者,比如什么時候打開音頻接口設備、某種Stream類型的音頻對應什么設備等等。而AudioFlinger則是策略的執行者,例如具體如何與音頻設備通信,如何維護現有系統中的音頻設備,以及多個音頻流的混音如何處理等等都得由它來完成。
目前Audio系統中支持的音頻設備接口(Audio Interface)分為三大類,即:
/*frameworks/av/services/audioflinger/AudioFlinger.cpp*/
static const char * const audio_interfaces[] = {
AUDIO_HARDWARE_MODULE_ID_PRIMARY, //主音頻設備,必須存在
AUDIO_HARDWARE_MODULE_ID_A2DP, //藍牙A2DP音頻
AUDIO_HARDWARE_MODULE_ID_USB, //USB音頻,早期的版本不支持
};
每種音頻設備接口由一個對應的so庫提供支持。那么AudioFlinger怎么會知道當前設備中支持上述的哪些接口,每種接口又支持哪些具體的音頻設備呢?這是AudioPolicyService的責任之一,即根據用戶配置來指導AudioFlinger加載設備接口。
當AudioPolicyManagerBase(AudioPolicyService中持有的Policy管理者,后面小節有詳細介紹)構造時,它會讀取廠商關於音頻設備的描述文件(audio_policy.conf),然后據此來打開以上三類音頻接口(如果存在的話)。這一過程最終會調用loadHwModule@AudioFlinger,如下所示:
/*frameworks/av/services/audioflinger*/
audio_module_handle_t AudioFlinger::loadHwModule(const char *name)/*name就是前面audio_interfaces 數組
成員中的字符串*/
{
if (!settingsAllowed()) {
return 0;
}
Mutex::Autolock _l(mLock);
returnloadHwModule_l(name);
}
這個函數沒有做實質性的工作,只是執行了加鎖動作,然后接着調用下面的函數:
audio_module_handle_t AudioFlinger::loadHwModule_l(const char *name)
{
/*Step 1. 是否已經添加了這個interface?*/
for (size_t i = 0; i <mAudioHwDevs.size(); i++) {
if(strncmp(mAudioHwDevs.valueAt(i)->moduleName(), name, strlen(name)) == 0) {
ALOGW("loadHwModule() module %s already loaded", name);
returnmAudioHwDevs.keyAt(i);
}
}
/*Step 2. 加載audio interface*/
audio_hw_device_t *dev;
int rc =load_audio_interface(name, &dev);
…
/*Step 3. 初始化*/
mHardwareStatus = AUDIO_HW_INIT;
rc = dev->init_check(dev);
mHardwareStatus = AUDIO_HW_IDLE;
…
if ((mMasterVolumeSupportLvl !=MVS_NONE) && (NULL != dev->set_master_volume)) {
AutoMutex lock(mHardwareLock);
mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
dev->set_master_volume(dev, mMasterVolume);
mHardwareStatus = AUDIO_HW_IDLE;
}
/*Step 4. 添加到全局變量中*/
audio_module_handle_t handle = nextUniqueId();
mAudioHwDevs.add(handle,new AudioHwDevice(name, dev));
return handle;
}
Step1@ loadHwModule_l. 首先查找mAudioHwDevs是否已經添加了變量name所指示的audio interface,如果是的話直接返回。第一次進入時mAudioHwDevs的size為0,所以還會繼續往下執行。
Step2@ loadHwModule_l. 加載指定的audiointerface,比如“primary”、“a2dp”或者“usb”。函數load_audio_interface用來加載設備所需的庫文件,然后打開設備並創建一個audio_hw_device_t實例。音頻接口設備所對應的庫文件名稱是有一定格式的,比如a2dp的模塊名可能是audio.a2dp.so或者audio.a2dp.default.so等等。查找路徑主要有兩個,即:
/** Base path of the hal modules */
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
當然,因為Android是完全開源的,各開發商可以根據自己的需要來進行相應的修改,比如下面是某手機設備的音頻庫截圖:
圖 13‑12 音頻庫實例
Step3@ loadHwModule_l,進行初始化操作。其中init_check是為了確定這個audio interface是否已經成功初始化,0是成功,其它值表示失敗。接下來如果這個device支持主音量,我們還需要通過set_master_volume進行設置。在每次操作device前,都要先改變mHardwareStatus的狀態值,操作結束后將其復原為AUDIO_HW_IDLE(根據源碼中的注釋,這樣做是為了方便dump時正確輸出內部狀態,這里我們就不去深究了)。
Step4@ loadHwModule_l. 把加載后的設備添加入mAudioHwDevs鍵值對中,其中key的值是由nextUniqueId生成的,這樣做保證了這個audiointerface擁有全局唯一的id號。
完成了audiointerface的模塊加載只是萬里長征的第一步。因為每一個interface包含的設備通常不止一個,Android系統目前支持的音頻設備如下列表所示:
Device Name |
Description |
AUDIO_DEVICE_OUT_EARPIECE |
聽筒 |
AUDIO_DEVICE_OUT_SPEAKER |
喇叭 |
AUDIO_DEVICE_OUT_WIRED_HEADSET |
帶話筒的耳機 |
AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
耳機 |
AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
SCO 藍牙 |
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
SCO 藍牙耳機 |
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT |
SCO 車載套件 |
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
A2DP 藍牙 |
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
A2DP 藍牙耳機 |
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
A2DP 藍牙喇叭 |
AUDIO_DEVICE_OUT_AUX_DIGITAL |
AUX IN |
AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET |
模擬dock headset |
AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET |
數字dock headset |
AUDIO_DEVICE_OUT_USB_ACCESSORY |
USB配件 |
AUDIO_DEVICE_OUT_USB_DEVICE |
USB設備 |
AUDIO_DEVICE_OUT_DEFAULT |
默認設備 |
AUDIO_DEVICE_OUT_ALL |
上述每種設備只占int值一個bit位,這里是指上述設備的集合 |
AUDIO_DEVICE_OUT_ALL_A2DP |
上述設備中與A2DP藍牙相關的設備集合 |
AUDIO_DEVICE_OUT_ALL_SCO |
上述設備中與SCO藍牙相關的設備集合 |
AUDIO_DEVICE_OUT_ALL_USB |
上述設備中與USB相關的設備集合 |
大家可能會有疑問:
Ø 這么多的輸出設備,那么當我們回放音頻流(錄音也是類似的情況)時,該選擇哪一種呢?
Ø 而且當前系統中audio interface也很可能不止一個,應該如何選擇?
顯然這些決策工作將由AudioPolicyService來完成,我們會在下一小節做詳細闡述。這里先給大家分析下,AudioFlinger是如何打開一個Output通道的(一個audiointerface可能包含若干個output)。
打開音頻輸出通道(output)在AF中對應的接口是openOutput(),即:
audio_io_handle_t AudioFlinger::openOutput(audio_module_handle_tmodule, audio_devices_t *pDevices,
uint32_t *pSamplingRate,audio_format_t *pFormat,
audio_channel_mask_t *pChannelMask,
uint32_t *pLatencyMs, audio_output_flags_t flags)
{
/*入參中的module是由前面的loadHwModule 獲得的,它是一個audiointerface的id號,可以通過此id在mAudioHwDevs中查找到對應的AudioHwDevice對象*/
status_t status;
PlaybackThread *thread =NULL;
…
audio_stream_out_t *outStream = NULL;
audio_hw_device_t*outHwDev;
…
/*Step 1. 查找相應的audio interface
outHwDev = findSuitableHwDev_l(module, *pDevices);
…
/*Step 2. 為設備打開一個輸出流*/
mHardwareStatus =AUDIO_HW_OUTPUT_OPEN;
status = outHwDev->open_output_stream(outHwDev, id, *pDevices,(audio_output_flags_t)flags,
&config, &outStream);
mHardwareStatus =AUDIO_HW_IDLE;
…
if (status == NO_ERROR &&outStream != NULL) {
/*Step 3.生成AudioStreamOut*/
AudioStreamOut *output = newAudioStreamOut(outHwDev, outStream);
/*Step 4.創建PlaybackThread*/
if ((flags & AUDIO_OUTPUT_FLAG_DIRECT) ||(config.format != AUDIO_FORMAT_PCM_16_BIT) ||
(config.channel_mask != AUDIO_CHANNEL_OUT_STEREO)) {
thread = new DirectOutputThread(this, output, id, *pDevices);
} else {
thread = new MixerThread(this, output, id, *pDevices);
}
mPlaybackThreads.add(id,thread); //添加播放線程
…
/*Step 5.Primary output情況下的處理*/
if ((mPrimaryHardwareDev== NULL) &&flags & AUDIO_OUTPUT_FLAG_PRIMARY)) {
ALOGI("Usingmodule %d has the primary audio interface", module);
mPrimaryHardwareDev = outHwDev;
AutoMutexlock(mHardwareLock);
mHardwareStatus =AUDIO_HW_SET_MODE;
outHwDev->set_mode(outHwDev, mMode);
…
float initialVolume = 1.0;
mMasterVolumeSupportLvl = MVS_NONE;
mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME; //測試設備是否支持主音量獲取
if ((NULL !=outHwDev->get_master_volume) &&
(NO_ERROR ==outHwDev->get_master_volume (outHwDev, &initialVolume))) {
mMasterVolumeSupportLvl = MVS_FULL;
} else {
mMasterVolumeSupportLvl = MVS_SETONLY;
initialVolume= 1.0;
}
mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;//測試是否支持主音量設置
if ((NULL ==outHwDev->set_master_volume) ||
(NO_ERROR !=outHwDev->set_master_volume (outHwDev, initialVolume))) {
mMasterVolumeSupportLvl = MVS_NONE;
}
for (size_t i = 0; i <mAudioHwDevs.size(); i++) {
audio_hw_device_t *dev = mAudioHwDevs.valueAt(i)->hwDevice();
if ((dev !=mPrimaryHardwareDev) &&
(NULL !=dev->set_master_volume)) {
dev->set_master_volume(dev,initialVolume);
}
}
mHardwareStatus =AUDIO_HW_IDLE;
mMasterVolumeSW =(MVS_NONE == mMasterVolumeSupportLvl)? initialVolume: 1.0;
mMasterVolume = initialVolume;
}
return id;
}
return 0;
}
上面這段代碼中,顏色加深的部分是我們接下來分析的重點,主要還是圍繞outHwDev這個變量所做的一系列操作,即:
· 查找合適的音頻接口設備(findSuitableHwDev_l)
· 創建音頻輸出流(通過open_output_stream獲得一個audio_stream_out_t)
· 利用AudioStreamOut來封裝audio_stream_out_t與audio_hw_device_t
· 創建播放線程(PlaybackThread)
· 如果當前設備是主設備,則還需要進行相應的設置,包括模式、主音量等等
顯然,outHwDev用於記錄一個打開的音頻接口設備,它的數據類型是audio_hw_device_t,是由HAL規定的一個音頻接口設備所應具有的屬性集合,如下所示:
struct audio_hw_device {
struct hw_device_t common;
…
int (*set_master_volume)(struct audio_hw_device *dev, float volume);
int (*set_mode)(struct audio_hw_device *dev, audio_mode_t mode);
int (*open_output_stream)(struct audio_hw_device *dev,
audio_io_handle_t handle,
audio_devices_t devices,
audio_output_flags_t flags,
struct audio_config *config,
struct audio_stream_out **stream_out);
…}
其中common代表了HAL層所有設備的共有屬性;set_master_volume、set_mode、open_output_stream分別為我們設置audio interface的主音量、設置音頻模式類型(比如AUDIO_MODE_RINGTONE、AUDIO_MODE_IN_CALL等等)、打開輸出數據流提供了接口。
接下來我們分步來闡述。
Step1@ AudioFlinger::openOutput. 在openOutput中,設備outHwDev是通過查找當前系統來得到的,代碼如下:
audio_hw_device_t* AudioFlinger::findSuitableHwDev_l(audio_module_handle_tmodule, uint32_t devices)
{
if (module == 0) {
for (size_t i = 0; i< ARRAY_SIZE(audio_interfaces); i++) {
loadHwModule_l(audio_interfaces[i]);
}
} else {
AudioHwDevice*audioHwdevice = mAudioHwDevs.valueFor(module);
if (audioHwdevice !=NULL) {
returnaudioHwdevice->hwDevice();
}
}
// then try to find amodule supporting the requested device.
for (size_t i = 0; i <mAudioHwDevs.size(); i++) {
audio_hw_device_t *dev= mAudioHwDevs.valueAt(i)->hwDevice();
if((dev->get_supported_devices(dev) & devices) == devices)
return dev;
}
return NULL;
}
變量module值為0的情況,是為了兼容之前的Audio Policy而特別做的處理。當module等於0時,首先加載所有已知的音頻接口設備,然后再根據devices來確定其中符合要求的。入參devices的值實際上來源於“表格 13‑4 Android系統支持的音頻設備(輸出)”所示的設備。可以看到,enum中每個設備類型都對應一個特定的比特位,因而上述代碼段中可以通過“與運算”來找到匹配的設備。
當modules為非0值時,說明Audio Policy指定了具體的設備id號,這時就通過查找全局的mAudioHwDevs變量來確認是否存在符合要求的設備。
DefaultKeyedVector<audio_module_handle_t, AudioHwDevice*> mAudioHwDevs;
變量mAudioHwDevs是一個Vector,以audio_module_handle_t為key,每一個handle值唯一確定了已經添加的音頻設備。那么在什么時候添加設備呢?
一種情況就是前面看到的modules為0時,會load所有潛在設備,另一種情況就是AudioPolicyManagerBase在構造時會預加載所有audio_policy.conf中所描述的output。不管是哪一種情況,最終都會調用loadHwModuleàloadHwModule_l,這個函數我們開頭就分析過了。
如果modules為非0,且從mAudioHwDevs中也找不到符合要求的設備,程序並不會就此終結——它會退而求其次,遍歷數組中的所有元素尋找支持devices的任何一個audio interface。
Step2@ AudioFlinger::openOutput,調用open_output_stream打開一個audio_stream_out_t。如果直接講解這個函數的作用,大家可能覺得很抽象,所以這里我們提供一個具體硬件方案上的實現。原生態代碼中就包括了一些具體音頻設備的實現,如samsung的tuna,其源碼實現如下:
/*device/samsung/tuna/audio/Audio_hw.c*/
static int adev_open_output_stream(…structaudio_stream_out **stream_out)
{
struct tuna_audio_device*ladev = (struct tuna_audio_device *)dev;
struct tuna_stream_out *out;
…
*stream_out = NULL;
out = (structtuna_stream_out *)calloc(1, sizeof(struct tuna_stream_out));
…
out->stream.common.set_parameters = out_set_parameters;…
*stream_out =&out->stream;
…
}
我們去掉了其中的大部分代碼,只留下核心部分。可以看到,tuna_stream_out類型包含了audio_stream_out,后者就是最后要返回的結果。這種方式在HAL層實現中非常多見,讀者應該要熟悉這樣的寫法。而對於audio_stream_out的操作,無非就是根據入參需要,為它的函數指針做初始化,比如set_parameters的實現就最終指向了out_set_parameters。接下來的實現就涉及linux驅動了,我們這里先不往下分析,后面音量調節小節還會再遇到這個函數。
Step3@ AudioFlinger::openOutput,生成AudioStreamOut對象。這個變量沒什么特別的,它把audio_hw_device_t和audio_stream_out_t做為一個整體來封裝。
Step4@ AudioFlinger::openOutput. 既然通道已經打開,那么由誰來往通道里放東西呢?這就是PlaybackThread。這里分兩種不同的情況:
· DirectOutput
如果不需要混音
· Mixer
需要混音
這兩種情況分別對應DirectOutputThread和MixerThread兩種線程。我們以后者為例來分析下PlaybackThread的工作模式,也會后面小節打下基礎。
圖 13‑13 Playback各線程類關系
如上圖所示,用於Playback的線程種類不少,它們的基類都是Thread。
AudioFlinger中用於記錄Record和Playback線程的有兩個全局變量,如下:
DefaultKeyedVector< audio_io_handle_t, sp<PlaybackThread>> mPlaybackThreads;
DefaultKeyedVector< audio_io_handle_t, sp<RecordThread>> mRecordThreads;
在openOutput中,加入mPlaybackThreads的是一個新建的線程類實例,比如MixerThread。它的構造函數如下:
AudioFlinger::MixerThread::MixerThread(…): PlaybackThread(audioFlinger,output, id, device, type),…
{ …
mAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);
if (mChannelCount == 1) {
ALOGE("Invalidaudio hardware channel count");
}
mOutputSink = newAudioStreamOutSink(output->stream);
…
if (initFastMixer) {
…
} else {
mFastMixer = NULL;
}
…
}
首先生成一個AudioMixer對象,這是混音處理的關鍵,我們會在后面有詳細介紹。然后檢查聲道數量,在Mixer情況下肯定不止一個聲道。接着創建一個NBAIO(Non-blockingaudio I/O interface) sink(即AudioStreamOutSink),並進行negotiate。最后根據配置(initFastMixer)來判斷是否使用fast mixer。
可以想象一下,一個放音線程的任務就是不斷處理上層的數據請求,然后將其傳遞到下一層,最終寫入硬件設備。但是在上面這個函數中,似乎並沒有看到程序去啟動一個新線程,也沒有看到進入線程循環的地方,或者去調用其它可能引起線程創建的函數。那么究竟在什么情況下MixerThread才會真正進入線程循環呢?
不知大家有沒有注意到之前mPlaybackThreads的定義,我們再次列出如下:
DefaultKeyedVector< audio_io_handle_t, sp<PlaybackThread> > mPlaybackThreads;
它實際上是由audio_io_handle_t和PlaybackThread強指針所組成的鍵值對。同時也可以判斷出,PlaybackThread類的祖先中一定會有RefBase。具體來說,就是它的父類Thread繼承自RefBase:
/*frameworks/native/include/utils/Thread.h*/
class Thread : virtual public RefBase
{…
根據強指針的特性,目標對象在第一次被引用時是會調用onFirstRef的,這點在前面小節分析AudioFlinger時我們也見過。這個函數實現如下:
void AudioFlinger::PlaybackThread::onFirstRef()
{
run(mName,ANDROID_PRIORITY_URGENT_AUDIO);
}
很簡單,只是調用了run方法,從而啟動一個新線程並間接調用threadLoop,不斷地處理Mix業務。這樣我們就明白了一個PlaybackThread是如何進入線程循環的了,至於循環中需要做些什么,留在下一小節做詳細介紹。
Step5@ AudioFlinger::openOutput,到目前為止,我們已經成功的建立起一個音頻通道,就等着AudioTrack往里丟數據了。不過假如當前的output是“primary”的,則還有一些額外的工作要做。程序接着會對此音頻設備設置主音量,前提是mMasterVolumeSupportLvl不為MVS_NONE(表示既不支持主音量的設置和獲取。另外MVS_SETONLY表示只支持設置不能獲取,MVS_FULL表示同時支持設置和獲取)。
“測試設備是否支持主音量設置/獲取”部分的代碼很簡單,我們就不詳細說明了。不過要注意的是,當確定了主音量后,需要主動為系統當前已經存在的音頻設置主音量(也就是openOutput最后的for循環部分)。這和loadHwModule_l中的設置主音量並不矛盾,試想一下主音量在什么時候被設置是不確定的,因而一旦設定后就先將系統已有的設備先做主音量設置,而后加的設備則由loadHwModule_l來完成。
我們來整理下這個小節所闡述的內容。
· 當AudioPolicyManagerBase構造時,它會根據用戶提供的audio_policy.conf來分析系統中有哪些audio interface(primary,a2dp以及usb),然后通過AudioFlinger::loadHwModule加載各audio interface對應的庫文件,並依次打開其中的output(openOutput)和input(openInput)
· 我們詳細分析了openOutput所做的工作,包括打開一個audio_stream_out_t通道,生成AudioStreamOut對象,以及新建PlaybackThread等等。此時“萬事俱備,只欠東風”,只要AudioTrack不斷和AudioFlinger傳遞數據,整個音頻回放就開始了。當然,這其中還涉及很多狀態的管理、路由切換、以及數據的跨進程交互等等,這些都是我們后面內容所要解決的。