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傳遞數據,整個音頻回放就開始了。當然,這其中還涉及很多狀態的管理、路由切換、以及數據的跨進程交互等等,這些都是我們後面內容所要解決的。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章