1. 程式人生 > >簡單聊一下Android音訊通路的切換

簡單聊一下Android音訊通路的切換

Android支援多種裝置的的輸出。一臺正常的機子,本身就自帶話筒,揚聲器,麥克風等多個聲音輸入輸出裝置,再加上五花八門的外接裝置(通過耳機,藍芽,wifi等方式連線),使聲音的輸出更具多樣性。Android支援如此多的裝置連線,那麼android內部是怎樣對裝置的輸出輸出進行控制的呢?這一次我們主要來看看音訊通路的切換。

音訊流、裝置、音訊策略

要想知道Andorid是怎樣對裝置的輸出輸出進行控制的,我們首先來了解一些音訊相關的基本知識: stream_type、content_type、devices、routing_strategy。
stream_type:音訊流的型別。在當前系統中,Android(6.0)一共定義了11種stream_type以供開發者使用。Android上層開發要想要發出聲音,都必須先確定當前當前的音訊型別。
content_type:具體輸出型別。雖然當前一共有11種stream_type,但一旦進入到Attribute,Android就只將其整理成幾種型別。這才是實際的型別。
device:音訊輸入輸出裝置。Android定義了多種裝置輸入輸出裝置(具體物理裝置可能還是那幾個,但是輸出場景不盡相同)。
routing_strategy:音訊路由策略。預設情況下,Android是根據路由策略去選擇裝置負責輸出輸入音訊的。

stream_type在android中java層與c++層均有定義。並且對應的值是保持一致的。

device與stream_type一樣,在java層和C++層均有定義,並且會根據使用的情況不同蘊含多個定義:


相對stream_type,routing_strategy只是一個定義在RountingStrategy.h的一個簡單的列舉結構體:

usecase只是qcom內部定義的一個數據結構,位於hal層,用作處理處理內部音效卡邏輯和輸出方案。輸出方案與音效卡中的mixer_path_xxx.xml相聯。而mixer_path等相關檔案,才是具體的音訊輸出方案。

我們通過檢視當前的音效卡情況確定了當前具體的mixer_path檔案——mixer_path_skue.xml。xml檔案內部就是我們預定義的usecase的具體情況:

在mixer_path類檔案中,一個標準的path就如上面的紅框那樣。有名字,有一定的引數。另外,一個patch之中,還可以巢狀另外一個patch。

由於usecase只是目前高通hal層獨有的定義,所以本文不會花太多時間和精力去探討usecase的相關設定和內容。目前來說,對這個有一定的認知就可。

AudioPolicy和AudioPolicyService

在瞭解完Audio一些基本的定義設定之後,我們來看一下Android的Audio整體架構。
Audio內部系統從上到下包含各方面的東西。對於聲音輸出的裝置的選擇與切換,我們主要需要關注2個地方。第一處,是外接裝置如耳機,藍芽裝置等連線的通知。第二處就是Audio系統中核心的AudioFinger與AudioPolicyService的處理內容。
AudioFinger是Audio系統的工作引擎,管理者系統中輸入輸出音訊流,並承擔音訊資料混音,以及讀寫Audio硬體等工作以實現資料的輸入輸出功能。AudioPolicyService是Audio系統策略控制中心,具體負責掌管系統中聲音裝置的選擇和切換,音量控制等功能。
AudioFinger與AudioPolicyService的類圖關係:
在AudioFlinger和AudioPolicyService的運作中其實包含著很多類,但同時,我們也可以發現,在Audio系統中, AudioFinger與AudioPolicyService是緊密相連的。總得來說,AudioFinger與AudioPolicyService是Audio系統的核心。所以下面我們很多內容的主角,都是他們2個。基本的聲音輸出呼叫
發出聲音是Android機器的一個最基本的功能。但是,Android是怎麼發出聲音的呢?就算不連線外設,Android最基本還有聽筒和揚聲器2個裝置。那麼,Android內部,是怎麼控制他們2個發出聲音的呢?下面我們來具體看 一下Android一般情況下發出聲音時選擇裝置的過程。我們要想分析Android中的聲音輸出,當然是先通過播放音訊去一步一步瞭解Android是怎惡魔輸出聲音的。下面我們以一個最簡單的AudioTrack播放音訊為例,來看下Android的發生過程。一個簡單的AudioTrack播放的例子如下:
        AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 11025/2, 
                                        AudioFormat.CHANNEL_CONFIGURATION_MONO, 
                                        AudioFormat.ENCODING_PCM_16BIT,
                                        audioLength, AudioTrack.MODE_STREAM);
        audioTrack.play();
        audioTrack.write(audioData, 0, sizeInBytes);


AudioTrack在接收引數建立的時候,就會將設定的steamtype儲存在對應的AudioAttributes當中(AudioAttributes是一個描述關於音訊流的資訊的屬性集合的類)。

我們知道,在android系統中,系統封裝的物件是一層一層往下呼叫的。所以,在我們建立了一個java的AudioTrack物件的時候,其實在同時,在C++當中,我們已經做了很多操作了。下面我們來看一下,AudioTrack物件建立時,主要做了什麼:

static jint
android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this,
        jobject jaa,
        jint sampleRateInHertz, jint channelPositionMask, jint channelIndexMask,
        jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession) {

    //……
    // create the native AudioTrack object
    sp<AudioTrack> lpTrack = new AudioTrack();

    //……
    // initialize the native AudioTrack object
    status_t status = NO_ERROR;
    switch (memoryMode) {
    case MODE_STREAM:

        status = lpTrack->set(
                AUDIO_STREAM_DEFAULT,
                sampleRateInHertz,
                format,
                nativeChannelMask,
                frameCount,
                AUDIO_OUTPUT_FLAG_NONE,
                audioCallback, &(lpJniStorage->mCallbackData),
                0,
                0,
                true,
                sessionId,
                AudioTrack::TRANSFER_SYNC,
                NULL,                         
                -1, -1,                       
                paa);
        break;
    //……
    if (status != NO_ERROR) {
        ALOGE("Error %d initializing AudioTrack", status);
        goto native_init_failure;
    }
    // save our newly created C++ AudioTrack in the "nativeTrackInJavaObj" field
    // of the Java object (in mNativeTrackInJavaObj)
    setAudioTrack(env, thiz, lpTrack);
   //……

}


從上面的程式碼可以看出,在建立java層的AudioTrack物件時,對應的jni也創建出一個C++的AudioTrack物件,並且傳入了部分引數和呼叫了其方法。

接下來我們來看看C++的AudioTrack物件的構造方法:

AudioTrack::AudioTrack()
    : mStatus(NO_INIT),
      mIsTimed(false),
      mPreviousPriority(ANDROID_PRIORITY_NORMAL),
      mPreviousSchedulingGroup(SP_DEFAULT),
      mPausedPosition(0),
      mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE),
      mPlaybackRateSet(false)
{
    mAttributes.content_type = AUDIO_CONTENT_TYPE_UNKNOWN;
    mAttributes.usage = AUDIO_USAGE_UNKNOWN;
    mAttributes.flags = 0x0;
    strcpy(mAttributes.tags, "");
}
我們可以看到,AudioTrack的無參構造方法只是進行了一些引數的初始化,那麼,具體是AudioTrack初始化是進行在哪裡呢?

我們再回到上面,發現jni層在建立完AudioTrack物件後,根據memoryMode的不同而進行了不同的AudioTrack->set()操作,只是因為AudioTrack提供2種不同的輸出方式(對記憶體的影響和要求不同)。我來看看看set中主要的操作:

status_t AudioTrack::set(…){
    //……
    status_t status = createTrack_l();
    if (status != NO_ERROR) {
        if (mAudioTrackThread != 0) {
            mAudioTrackThread->requestExit();   // see comment in AudioTrack.h
            mAudioTrackThread->requestExitAndWait();
            mAudioTrackThread.clear();
        }
        return status;
   //……
    }

在AudioTrack的set()中,除了部分的引數判斷和設定之外,我們可以看到,他呼叫了自身的createTrack_l()進行了進一步的設定。

status_t AudioTrack::createTrack_l()
{
    const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
    if (audioFlinger == 0) {
        ALOGE("Could not get audioflinger");
        return NO_INIT;
    }

    audio_io_handle_t output;
    audio_stream_type_t streamType = mStreamType;
    audio_attributes_t *attr = (mStreamType == AUDIO_STREAM_DEFAULT) ? &mAttributes : NULL;
    //……
    audio_offload_info_t tOffloadInfo = AUDIO_INFO_INITIALIZER;
    if (mPlaybackRateSet == true && mOffloadInfo == NULL && mFormat == AUDIO_FORMAT_PCM_16_BIT) {
        mOffloadInfo = &tOffloadInfo;
    }
    status_t status = AudioSystem::getOutputForAttr(attr, &output,
                                           (audio_session_t)mSessionId, &streamType, mClientUid,
                                           mSampleRate, mFormat, mChannelMask,
                                           mFlags, mSelectedDeviceId, mOffloadInfo);

    //……
    IAudioFlinger::track_flags_t trackFlags = IAudioFlinger::TRACK_DEFAULT;
    //……
    sp<IAudioTrack> track = audioFlinger->createTrack(streamType,
                                                      mSampleRate,
                                                      mFormat,
                                                      mChannelMask,
                                                      &temp,
                                                      &trackFlags,
                                                      mSharedBuffer,
                                                      output,
                                                      tid,
                                                      &mSessionId,
                                                      mClientUid,
                                                      &status);
   //……


上面的程式碼可以看出,AudioTrack從這裡開始,與AudioFlinger等進行大量的互動:獲取控制代碼,獲取輸出,建立IAudioTrack指標物件等等。所以接下來,就是AudioFlinger的相關內容了。在這裡,我們先簡單總結下AudioTrack的建立過程:

根據AudioTrack的性質,Java層在建立完成AudioTrack物件直接呼叫play()和write()操作,那麼其實從另一方面我們可以猜想,在Java層建立完成AudioTrack之後,系統已經設定好輸出的裝置等等操作,只等呼叫play()和write方法進行播放。所以為了驗證我們的猜想,接下來針對AudioFlinger&AudioSystem的相關具體分析驗證。

AudioFlinger&AudioPolicyService的控制過程

回到上面的內容,我們可以看到,AudioTrack在呼叫createTrack_l()的方法的時候,開始通過AudioSystem獲取output。所以下面我們來看看AudioSystem的getOutputForAttr():

status_t AudioSystem::getOutputForAttr()
{
    const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
    if (aps == 0) return NO_INIT;
    return aps->getOutputForAttr(attr, output, session, stream, uid,
                                 samplingRate, format, channelMask,
                                 flags, selectedDeviceId, offloadInfo);
}

從上面我們可以看到,AudioSystem只是作為一個過渡,然後通過獲取AudioPolicyService的控制代碼去getOutputForAttr()。我們繼續跟蹤AudioPolicyService的情況,會發現其實他只是在AudioPolicyService中也只是作為一個過渡,真正進行getOutputForAttr()的,在AudioPolicyManager之中。

status_t AudioPolicyManager::getOutputForAttr()
{
    //……
    *stream = streamTypefromAttributesInt(&attributes);
    sp<DeviceDescriptor> deviceDesc;
    for (size_t i = 0; i < mAvailableOutputDevices.size(); i++) {
        if (mAvailableOutputDevices[i]->getId() == selectedDeviceId) {
            deviceDesc = mAvailableOutputDevices[i];
            break;
        }
    }
    mOutputRoutes.addRoute(session, *stream, SessionRoute::SOURCE_TYPE_NA, deviceDesc, uid);
    
    //根據strategy獲取device
    routing_strategy strategy = (routing_strategy) getStrategyForAttr(&attributes);
    audio_devices_t device = getDeviceForStrategy(strategy, false /*fromCache*/);

    if ((attributes.flags & AUDIO_FLAG_HW_AV_SYNC) != 0) {
        flags = (audio_output_flags_t)(flags | AUDIO_OUTPUT_FLAG_HW_AV_SYNC);
    }

    *output = getOutputForDevice(device, session, *stream,
                                 samplingRate, format, channelMask,
                                 flags, offloadInfo);
    //……

在AudioPolicyManager的getOutputForAttr()中,我們可以發現關鍵點在strategy的獲取與device的獲取當中。而在這當中,關鍵的引數恰恰是在先前從java層一步一步封裝的過來的attributes。我們先來簡單看一下attributes這個引數的資料結構:

從audio_attributes_t的結構我們可以看出,audio_attributes_t儲存著需要輸出音訊的應用的相關配置資訊。

然後,根據剛剛的程式碼,我們來了解一下strategy的獲取:
uint32_t AudioPolicyManager::getStrategyForAttr(const audio_attributes_t *attr) {
    // flags to strategy mapping
    if ((attr->flags & AUDIO_FLAG_BEACON) == AUDIO_FLAG_BEACON) {
        return (uint32_t) STRATEGY_TRANSMITTED_THROUGH_SPEAKER;
    }
    if ((attr->flags & AUDIO_FLAG_AUDIBILITY_ENFORCED) == AUDIO_FLAG_AUDIBILITY_ENFORCED) {
        return (uint32_t) STRATEGY_ENFORCED_AUDIBLE;
    }
    // usage to strategy mapping
    return static_cast<uint32_t>(mEngine->getStrategyForUsage(attr->usage));

雖然在這裡,會先對flags引數進行比較,但是,在實際上flags大部分時候都是0。所以最後,都是根據“mEngine->getStrategyForUsage(attr->usage)”去選擇StrategyForUsage。當然,再到下一步就到了就是switch和case的過程,這裡就不繼續展開了。

在獲取到strategy之後,我們來看看Audio接著是怎麼來確定device的。

先繼續看AudioPolicyManager的getDeviceForStrategy():

audio_devices_t AudioPolicyManager::getDeviceForStrategy(routing_strategy strategy,
                                                         bool fromCache)
{
    // Routing
    // see if we have an explicit route
    // scan the whole RouteMap, for each entry, convert the stream type to a strategy
    // (getStrategy(stream)).
    // if the strategy from the stream type in the RouteMap is the same as the argument above,
    // and activity count is non-zero
    // the device = the device from the descriptor in the RouteMap, and exit.
    for (size_t routeIndex = 0; routeIndex < mOutputRoutes.size(); routeIndex++) {
        sp<SessionRoute> route = mOutputRoutes.valueAt(routeIndex);
        routing_strategy strat = getStrategy(route->mStreamType);
        bool strategyMatch = (strat == strategy) ||
                             ((strategy == STRATEGY_ACCESSIBILITY) &&
                              ((mEngine->getStrategyForUsage(
                                      AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY) == strat) ||
                               (strat == STRATEGY_MEDIA)));
        if (strategyMatch && route->isActive()) {
            return route->mDeviceDescriptor->type();
        }
    }
    if (fromCache) {
        ALOGVV("getDeviceForStrategy() from cache strategy %d, device %x",
              strategy, mDeviceForStrategy[strategy]);
        return mDeviceForStrategy[strategy];
    }
    return mEngine->getDeviceForStrategy(strategy);
}

呼叫AudioPolicyManager的getDeviceForStrategy()的時候,一般會先查下一下當前的RouteMap,看看有沒有匹配的情況的。但由於我們新申請一個output的時候,傳入的引數是false,所以這個時候,是會直接通過mEngine去直接獲取device。

而在mEngine中,getDeviceForStrategy()又是一堆的選擇判斷,然後返回裝置:

audio_devices_t Engine::getDeviceForStrategy(routing_strategy strategy) const
{
    const DeviceVector &availableOutputDevices = mApmObserver->getAvailableOutputDevices();
    const DeviceVector &availableInputDevices = mApmObserver->getAvailableInputDevices();

    const SwAudioOutputCollection &outputs = mApmObserver->getOutputs();

    uint32_t device = AUDIO_DEVICE_NONE;
    uint32_t availableOutputDevicesType = availableOutputDevices.types();

    switch (strategy) {
    //……
    case STRATEGY_MEDIA: {
        uint32_t device2 = AUDIO_DEVICE_NONE;

        if (isInCall() && (device == AUDIO_DEVICE_NONE)) {
            // when in call, get the device for Phone strategy
            device = getDeviceForStrategy(STRATEGY_PHONE);
            break;
        }

        if (strategy != STRATEGY_SONIFICATION) {
            // no sonification on remote submix (e.g. WFD)
            if (availableOutputDevices.getDevice(AUDIO_DEVICE_OUT_REMOTE_SUBMIX, String8("0")) != 0) {
                device2 = availableOutputDevices.types() & AUDIO_DEVICE_OUT_REMOTE_SUBMIX;
            }
        }
        if (isInCall() && (strategy == STRATEGY_MEDIA)) {
            device = getDeviceForStrategy(STRATEGY_PHONE);
            break;
        }
        if ((device2 == AUDIO_DEVICE_NONE) &&
                (mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
                (outputs.getA2dpOutput() != 0)) {
            device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
            if (device2 == AUDIO_DEVICE_NONE) {
                device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
            }
           //……
    ALOGVV("getDeviceForStrategy() strategy %d, device %x", strategy, device);
    return device;
}

我們就其中一個strategty(STRATEGY_MEDIA)來具體看看Audio系統的選擇輸出裝置:

1)  首先我們會獲取當前存在的裝置集合availableOutputDevices

2)  然後根據傳入的strategty型別進行匹配選擇

3)  在選擇之前會先檢測是否處於特殊情況下(如通話中)

4)  最後按照優先順序匹配裝置。

然後就這樣,選擇裝置的流程就此結束。簡單來說,選擇裝置的流程,主要是幾個引數一步一步去確定然後最後確定合適的裝置。具體選擇裝置的簡單流程如圖:



針對音訊通路的切換,我們就簡單聊到這裡。謝謝。