Android音訊系統之AudioFlinger(二)

林學森發表於2013-04-15

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

 

相關文章