Android音訊系統之AudioFlinger(二)
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系統目前支援的音訊裝置如下列表所示:
表格 13‑4 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傳遞資料,整個音訊回放就開始了。當然,這其中還涉及很多狀態的管理、路由切換、以及資料的跨程式互動等等,這些都是我們後面內容所要解決的。
相關文章
- ANDROID音訊系統散記之四:4.0音訊系統HAL初探Android音訊
- Android音視訊之MediaPlayer音視訊播放Android
- Android音視訊之MediaRecorder音視訊錄製Android
- Android Framework 音訊子系統(12)HAL層分析AndroidFramework音訊
- Android系統之Binder通訊機制Android
- Android音視訊處理之MediaMuxerAndroidUX
- Android音視訊之AudioRecordAndroid
- 深入剖析Android音訊之AudioPolicyServiceAndroid音訊
- Android音訊開發之MediaRecorder/MediaPlayerAndroid音訊
- Android音視訊處理之MediaCodecAndroid
- Android音訊開發之AudioRecord錄音實現Android音訊
- 12┃音視訊直播系統之 WebRTC 實現1對1直播系統實戰Web
- 8┃音視訊直播系統之 WebRTC 信令系統實現以及通訊核心並實現視訊通話Web
- android音視訊指南-管理音訊焦點Android音訊
- Android短影片系統硬編碼—實現音影片編碼(二)Android
- Android 多媒體之 Silk 格式音訊解碼Android音訊
- Android音訊開發之AudioTrack實時播放Android音訊
- android 音訊播放 SoundPoolAndroid音訊
- Android 音視訊 - MediaCodec 編解碼音視訊Android
- Android音訊實時傳輸與播放(二):服務端Android音訊服務端
- WIN10怎麼把音訊裁剪 WIN10系統如何裁剪音訊Win10音訊
- IOS音視訊(二)AVFoundation視訊捕捉iOS
- Android端實現多人音視訊聊天應用(二):多人視訊通話Android
- Android 音視訊開發 視訊編碼,音訊編碼格式Android音訊
- 6┃音視訊直播系統之 WebRTC 核心驅動SDP規範協商Web
- 音視訊系列之iOS: 音訊採集 AudioUnitiOS音訊
- 修改Android系統字號(二)Android
- Android跨程式通訊之非AIDL(二)AndroidAI
- Android音訊處理知識(一)MediaRecorder錄製音訊Android音訊
- Android音訊(三)AudioPolicyServiceAndroid音訊
- Android 音訊應用框架Android音訊框架
- Linux系統程式設計之程式間通訊方式:管道(二)Linux程式設計
- 如何使用系統音訊錄製Mac螢幕?音訊Mac
- 10┃音視訊直播系統之 WebRTC 中的資料統計和繪製統計圖形Web
- 音訊訊號處理入門-第二週音訊
- Android多媒體之SoundPool+pcm流的音訊操作Android音訊
- android音視訊指南-處理音訊輸出的變化Android音訊
- Linux系統程式設計之程式間通訊方式:命名管道(二)Linux程式設計