Android java層音頻相關的分析與理解(五)音頻通路的切換



轉載http://blog.csdn.net/u012440406/article/details/54883220

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播放的例子如下:

[java] view plain copy
  1. AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 11025/2,   
  2.                                 AudioFormat.CHANNEL_CONFIGURATION_MONO,   
  3.                                 AudioFormat.ENCODING_PCM_16BIT,  
  4.                                 audioLength, AudioTrack.MODE_STREAM);  
  5. audioTrack.play();  
  6. audioTrack.write(audioData, 0, sizeInBytes);  


AudioTrack在接收參數創建的時候,就會將設置的steamtype保存在對應的AudioAttributes當中(AudioAttributes是一個描述關於音頻流的信息的屬性集合的類)。
 

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

[java] view plain copy
  1. static jint  
  2. android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this,  
  3.         jobject jaa,  
  4.         jint sampleRateInHertz, jint channelPositionMask, jint channelIndexMask,  
  5.         jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession) {  
  6.   
  7.     //……  
  8.     // create the native AudioTrack object  
  9.     sp<AudioTrack> lpTrack = new AudioTrack();  
  10.   
  11.     //……  
  12.     // initialize the native AudioTrack object  
  13.     status_t status = NO_ERROR;  
  14.     switch (memoryMode) {  
  15.     case MODE_STREAM:  
  16.   
  17.         status = lpTrack->set(  
  18.                 AUDIO_STREAM_DEFAULT,  
  19.                 sampleRateInHertz,  
  20.                 format,  
  21.                 nativeChannelMask,  
  22.                 frameCount,  
  23.                 AUDIO_OUTPUT_FLAG_NONE,  
  24.                 audioCallback, &(lpJniStorage->mCallbackData),  
  25.                 0,  
  26.                 0,  
  27.                 true,  
  28.                 sessionId,  
  29.                 AudioTrack::TRANSFER_SYNC,  
  30.                 NULL,                           
  31.                 -1, -1,                         
  32.                 paa);  
  33.         break;  
  34.     //……  
  35.     if (status != NO_ERROR) {  
  36.         ALOGE("Error %d initializing AudioTrack", status);  
  37.         goto native_init_failure;  
  38.     }  
  39.     // save our newly created C++ AudioTrack in the "nativeTrackInJavaObj" field  
  40.     // of the Java object (in mNativeTrackInJavaObj)  
  41.     setAudioTrack(env, thiz, lpTrack);  
  42.    //……  
  43.   
  44. }  


從上面的代碼可以看出,在創建java層的AudioTrack對象時,對應的jni也創建出一個C++的AudioTrack對象,並且傳入了部分參數和調用了其方法。

接下來我們來看看C++的AudioTrack對象的構造方法:

[java] view plain copy
  1. AudioTrack::AudioTrack()  
  2.     : mStatus(NO_INIT),  
  3.       mIsTimed(false),  
  4.       mPreviousPriority(ANDROID_PRIORITY_NORMAL),  
  5.       mPreviousSchedulingGroup(SP_DEFAULT),  
  6.       mPausedPosition(0),  
  7.       mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE),  
  8.       mPlaybackRateSet(false)  
  9. {  
  10.     mAttributes.content_type = AUDIO_CONTENT_TYPE_UNKNOWN;  
  11.     mAttributes.usage = AUDIO_USAGE_UNKNOWN;  
  12.     mAttributes.flags = 0x0;  
  13.     strcpy(mAttributes.tags, "");  
  14. }  

我們可以看到,AudioTrack的無參構造方法只是進行了一些參數的初始化,那麼,具體是AudioTrack初始化是進行在哪裏呢?

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

[java] view plain copy
  1. status_t AudioTrack::set(…){  
  2.     //……  
  3.     status_t status = createTrack_l();  
  4.     if (status != NO_ERROR) {  
  5.         if (mAudioTrackThread != 0) {  
  6.             mAudioTrackThread->requestExit();   // see comment in AudioTrack.h  
  7.             mAudioTrackThread->requestExitAndWait();  
  8.             mAudioTrackThread.clear();  
  9.         }  
  10.         return status;  
  11.    //……  
  12.     }  

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

[java] view plain copy
  1. status_t AudioTrack::createTrack_l()  
  2. {  
  3.     const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();  
  4.     if (audioFlinger == 0) {  
  5.         ALOGE("Could not get audioflinger");  
  6.         return NO_INIT;  
  7.     }  
  8.   
  9.     audio_io_handle_t output;  
  10.     audio_stream_type_t streamType = mStreamType;  
  11.     audio_attributes_t *attr = (mStreamType == AUDIO_STREAM_DEFAULT) ? &mAttributes : NULL;  
  12.     //……  
  13.     audio_offload_info_t tOffloadInfo = AUDIO_INFO_INITIALIZER;  
  14.     if (mPlaybackRateSet == true && mOffloadInfo == NULL && mFormat == AUDIO_FORMAT_PCM_16_BIT) {  
  15.         mOffloadInfo = &tOffloadInfo;  
  16.     }  
  17.     status_t status = AudioSystem::getOutputForAttr(attr, &output,  
  18.                                            (audio_session_t)mSessionId, &streamType, mClientUid,  
  19.                                            mSampleRate, mFormat, mChannelMask,  
  20.                                            mFlags, mSelectedDeviceId, mOffloadInfo);  
  21.   
  22.     //……  
  23.     IAudioFlinger::track_flags_t trackFlags = IAudioFlinger::TRACK_DEFAULT;  
  24.     //……  
  25.     sp<IAudioTrack> track = audioFlinger->createTrack(streamType,  
  26.                                                       mSampleRate,  
  27.                                                       mFormat,  
  28.                                                       mChannelMask,  
  29.                                                       &temp,  
  30.                                                       &trackFlags,  
  31.                                                       mSharedBuffer,  
  32.                                                       output,  
  33.                                                       tid,  
  34.                                                       &mSessionId,  
  35.                                                       mClientUid,  
  36.                                                       &status);  
  37.    //……  


上面的代碼可以看出,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():

[java] view plain copy
  1. status_t AudioSystem::getOutputForAttr()  
  2. {  
  3.     const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();  
  4.     if (aps == 0return NO_INIT;  
  5.     return aps->getOutputForAttr(attr, output, session, stream, uid,  
  6.                                  samplingRate, format, channelMask,  
  7.                                  flags, selectedDeviceId, offloadInfo);  
  8. }  

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

[java] view plain copy
  1. status_t AudioPolicyManager::getOutputForAttr()  
  2. {  
  3.     //……  
  4.     *stream = streamTypefromAttributesInt(&attributes);  
  5.     sp<DeviceDescriptor> deviceDesc;  
  6.     for (size_t i = 0; i < mAvailableOutputDevices.size(); i++) {  
  7.         if (mAvailableOutputDevices[i]->getId() == selectedDeviceId) {  
  8.             deviceDesc = mAvailableOutputDevices[i];  
  9.             break;  
  10.         }  
  11.     }  
  12.     mOutputRoutes.addRoute(session, *stream, SessionRoute::SOURCE_TYPE_NA, deviceDesc, uid);  
  13.       
  14.     //根據strategy獲取device  
  15.     routing_strategy strategy = (routing_strategy) getStrategyForAttr(&attributes);  
  16.     audio_devices_t device = getDeviceForStrategy(strategy, false /*fromCache*/);  
  17.   
  18.     if ((attributes.flags & AUDIO_FLAG_HW_AV_SYNC) != 0) {  
  19.         flags = (audio_output_flags_t)(flags | AUDIO_OUTPUT_FLAG_HW_AV_SYNC);  
  20.     }  
  21.   
  22.     *output = getOutputForDevice(device, session, *stream,  
  23.                                  samplingRate, format, channelMask,  
  24.                                  flags, offloadInfo);  
  25.     //……  

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


從audio_attributes_t的結構我們可以看出,audio_attributes_t保存着需要輸出音頻的應用的相關配置信息。

然後,根據剛剛的代碼,我們來了解一下strategy的獲取:
[java] view plain copy
  1. uint32_t AudioPolicyManager::getStrategyForAttr(const audio_attributes_t *attr) {  
  2.     // flags to strategy mapping  
  3.     if ((attr->flags & AUDIO_FLAG_BEACON) == AUDIO_FLAG_BEACON) {  
  4.         return (uint32_t) STRATEGY_TRANSMITTED_THROUGH_SPEAKER;  
  5.     }  
  6.     if ((attr->flags & AUDIO_FLAG_AUDIBILITY_ENFORCED) == AUDIO_FLAG_AUDIBILITY_ENFORCED) {  
  7.         return (uint32_t) STRATEGY_ENFORCED_AUDIBLE;  
  8.     }  
  9.     // usage to strategy mapping  
  10.     return static_cast<uint32_t>(mEngine->getStrategyForUsage(attr->usage));  


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

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

先繼續看AudioPolicyManager的getDeviceForStrategy():

[java] view plain copy
  1. audio_devices_t AudioPolicyManager::getDeviceForStrategy(routing_strategy strategy,  
  2.                                                          bool fromCache)  
  3. {  
  4.     // Routing  
  5.     // see if we have an explicit route  
  6.     // scan the whole RouteMap, for each entry, convert the stream type to a strategy  
  7.     // (getStrategy(stream)).  
  8.     // if the strategy from the stream type in the RouteMap is the same as the argument above,  
  9.     // and activity count is non-zero  
  10.     // the device = the device from the descriptor in the RouteMap, and exit.  
  11.     for (size_t routeIndex = 0; routeIndex < mOutputRoutes.size(); routeIndex++) {  
  12.         sp<SessionRoute> route = mOutputRoutes.valueAt(routeIndex);  
  13.         routing_strategy strat = getStrategy(route->mStreamType);  
  14.         bool strategyMatch = (strat == strategy) ||  
  15.                              ((strategy == STRATEGY_ACCESSIBILITY) &&  
  16.                               ((mEngine->getStrategyForUsage(  
  17.                                       AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY) == strat) ||  
  18.                                (strat == STRATEGY_MEDIA)));  
  19.         if (strategyMatch && route->isActive()) {  
  20.             return route->mDeviceDescriptor->type();  
  21.         }  
  22.     }  
  23.     if (fromCache) {  
  24.         ALOGVV("getDeviceForStrategy() from cache strategy %d, device %x",  
  25.               strategy, mDeviceForStrategy[strategy]);  
  26.         return mDeviceForStrategy[strategy];  
  27.     }  
  28.     return mEngine->getDeviceForStrategy(strategy);  
  29. }  


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

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

[java] view plain copy
  1. audio_devices_t Engine::getDeviceForStrategy(routing_strategy strategy) const  
  2. {  
  3.     const DeviceVector &availableOutputDevices = mApmObserver->getAvailableOutputDevices();  
  4.     const DeviceVector &availableInputDevices = mApmObserver->getAvailableInputDevices();  
  5.   
  6.     const SwAudioOutputCollection &outputs = mApmObserver->getOutputs();  
  7.   
  8.     uint32_t device = AUDIO_DEVICE_NONE;  
  9.     uint32_t availableOutputDevicesType = availableOutputDevices.types();  
  10.   
  11.     switch (strategy) {  
  12.     //……  
  13.     case STRATEGY_MEDIA: {  
  14.         uint32_t device2 = AUDIO_DEVICE_NONE;  
  15.   
  16.         if (isInCall() && (device == AUDIO_DEVICE_NONE)) {  
  17.             // when in call, get the device for Phone strategy  
  18.             device = getDeviceForStrategy(STRATEGY_PHONE);  
  19.             break;  
  20.         }  
  21.   
  22.         if (strategy != STRATEGY_SONIFICATION) {  
  23.             // no sonification on remote submix (e.g. WFD)  
  24.             if (availableOutputDevices.getDevice(AUDIO_DEVICE_OUT_REMOTE_SUBMIX, String8("0")) != 0) {  
  25.                 device2 = availableOutputDevices.types() & AUDIO_DEVICE_OUT_REMOTE_SUBMIX;  
  26.             }  
  27.         }  
  28.         if (isInCall() && (strategy == STRATEGY_MEDIA)) {  
  29.             device = getDeviceForStrategy(STRATEGY_PHONE);  
  30.             break;  
  31.         }  
  32.         if ((device2 == AUDIO_DEVICE_NONE) &&  
  33.                 (mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&  
  34.                 (outputs.getA2dpOutput() != 0)) {  
  35.             device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;  
  36.             if (device2 == AUDIO_DEVICE_NONE) {  
  37.                 device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;  
  38.             }  
  39.            //……  
  40.     ALOGVV("getDeviceForStrategy() strategy %d, device %x", strategy, device);  
  41.     return device;  
  42. }  

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

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

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

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

4)  最後按照優先級匹配設備。

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



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


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