Android音頻通路的切換(轉)java
轉自大神博客android
Android支持多種設備的的輸出。一臺正常的機子,自己就自帶話筒,揚聲器,麥克風等多個聲音輸入輸出設備,再加上五花八門的外置設備(經過耳機,藍牙,wifi等方式鏈接),使聲音的輸出更具多樣性。Android支持如此多的設備鏈接,那麼android內部是怎樣對設備的輸出輸出進行控制的呢?這一次咱們主要來看看音頻通路的切換。c++
音頻流、設備、音頻策略
要想知道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++層均有定義。而且對應的值是保持一致的。session
![](http://static.javashuo.com/static/loading.gif)
device與stream_type同樣,在java層和C++層均有定義,而且會根據使用的狀況不一樣蘊含多個定義:數據結構
![](http://static.javashuo.com/static/loading.gif)
相對stream_type,routing_strategy只是一個定義在RountingStrategy.h的一個簡單的枚舉結構體:架構
![](http://static.javashuo.com/static/loading.gif)
usecase只是qcom內部定義的一個數據結構,位於hal層,用做處理處理內部聲卡邏輯和輸出方案。輸出方案與聲卡中的mixer_path_xxx.xml相聯。而mixer_path等相關文件,纔是具體的音頻輸出方案。app
![](http://static.javashuo.com/static/loading.gif)
咱們經過查看當前的聲卡狀況肯定了當前具體的mixer_path文件——mixer_path_skue.xml。xml文件內部就是咱們預約義的usecase的具體狀況:ui
![](http://static.javashuo.com/static/loading.gif)
在mixer_path類文件中,一個標準的path就如上面的紅框那樣。有名字,有必定的參數。另外,一個patch之中,還能夠嵌套另一個patch。this
因爲usecase只是目前高通hal層獨有的定義,因此本文不會花太多時間和精力去探討usecase的相關設置和內容。目前來講,對這個有必定的認知就可。
AudioPolicy和AudioPolicyService
在瞭解完Audio一些基本的定義設定以後,咱們來看一下Android的Audio總體架構。
Audio內部系統從上到下包含各方面的東西。對於聲音輸出的設備的選擇與切換,咱們主要須要關注2個地方。第一處,是外接設備如耳機,藍牙設備等鏈接的通知。第二處就是Audio系統中核心的AudioFinger與AudioPolicyService的處理內容。
AudioFinger是Audio系統的工做引擎,管理者系統中輸入輸出音頻流,並承擔音頻數據混音,以及讀寫Audio硬件等工做以實現數據的輸入輸出功能。AudioPolicyService是Audio系統策略控制中心,具體負責掌管系統中聲音設備的選擇和切換,音量控制等功能。
AudioFinger與AudioPolicyService的類圖關係:
![](http://static.javashuo.com/static/loading.gif)
在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) {
-
-
-
- sp<AudioTrack> lpTrack = new AudioTrack();
-
-
-
- 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;
- }
-
-
- 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();
- 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的建立過程:
![](http://static.javashuo.com/static/loading.gif)
根據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);
-
-
- routing_strategy strategy = (routing_strategy) getStrategyForAttr(&attributes);
- audio_devices_t device = getDeviceForStrategy(strategy, false
-
- 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這個參數的數據結構:
![](http://static.javashuo.com/static/loading.gif)
從audio_attributes_t的結構咱們能夠看出,audio_attributes_t保存着須要輸出音頻的應用的相關配置信息。
而後,根據剛剛的代碼,咱們來了解一下strategy的獲取:
- uint32_t AudioPolicyManager::getStrategyForAttr(const audio_attributes_t *attr) {
-
- 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;
- }
-
- 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)
- {
-
-
-
-
-
-
-
- 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)) {
-
- device = getDeviceForStrategy(STRATEGY_PHONE);
- break;
- }
-
- if (strategy != STRATEGY_SONIFICATION) {
-
- 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) 最後按照優先級匹配設備。
而後就這樣,選擇設備的流程就此結束。簡單來講,選擇設備的流程,主要是幾個參數一步一步去肯定而後最後肯定合適的設備。具體選擇設備的簡單流程如圖:
![](http://static.javashuo.com/static/loading.gif)