Audio AudioFocus流程

     AudioFocus是Android引入的一個Audio協調機制,當多方需要使用Audio資源時,可以通過AudioFocus機制來協調配合,提高用戶的體驗。    該機制需要開發者主動去遵守,比如A應用沒遵守該機制,則其它遵守了該機制的應用是完全沒辦法影響A應用的。    試想下後臺在播放着音樂的時候你點開了某個視頻,使得後臺的音樂和視頻的聲音一起播放,毫無關聯的聲音一同播放會給用戶帶來極差的體驗,此時我們就可以通過AudioFocus機制來解決這樣的問題。

       使用AudioFocus機制主要是通過android.media.AudioManager這個類來進行的 public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) 方法請求獲取焦點,

如果獲取成功,會返回int值AudioManager.AUDIOFOCUS_REQUEST_GRANTED,

失敗則返回AudioManager.AUDIOFOCUS_REQUEST_FAILED。

通過abandonAudioFocus(OnAudioFocusChangeListener l)方法放棄焦點。 由於音頻焦點是唯一的,所以可以在需要播放音樂時去申請音頻焦點,如果獲取到了則播放,同時正在播放的音頻在失去音頻焦點時停止播放或者調低音量,從而達到音頻播放間的相互協調。

       對requestAudioFocus方法的參數進行解析:  OnAudioFocusChangeListener是一個接口,在這個接口裏面只有一個方法需要實現 public void onAudioFocusChange(int focusChange);

該方法會在焦點狀態變化的時候被調用 參數focusChange代表變化後當前的狀態,一共有以下四個值:

AUDIOFOCUS_GAIN 重新獲取到音頻焦點時觸發的狀態。

AUDIOFOCUS_LOSS 失去音頻焦點時觸發的狀態,且該狀態應該會長期保持,此時應當暫停音頻並釋放音頻相關的資源。

AUDIOFOCUS_LOSS_TRANSIENT 失去音頻焦點時觸發的狀態,但是該狀態不會長時間保持,此時應該暫停音頻,且當重新獲取音頻焦點的時候繼續播放。

AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 失去音頻焦點時觸發的狀態,在該狀態的時候不需要暫停音頻,但是應該降低音頻的聲音。

streamType(STREAM_RING,STREAM_MUSIC,STREAM_ALARM,STREAM_NOTIFICATION,STREAM_BLUETOOTH_SCO,STREAM_DTMF,STREAM_TTS)

durationHint用來表示獲取焦點的時長,同時通知其它獲取了音頻焦點的OnAudioFocusChangeListener該如何相互配合,有以下幾個值: AUDIOFOCUS_GAIN 代表此次申請的音頻焦點需要長時間持有,原本獲取了音頻焦點的OnAudioFocusChangeListener 接口將會回調onAudioFocusChange(int focusChange) 方法,傳入的參數爲AUDIOFOCUS_LOSS。

AUDIOFOCUS_GAIN_TRANSIENT 代表此次申請的音頻焦點只需短暫持有,原本獲取了音頻焦點的OnAudioFocusChangeListener 接口將會回調onAudioFocusChange(int focusChange) 方法,傳入的參數爲AUDIOFOCUS_LOSS_TRANSIENT。按照官方註釋:適用於短暫的音頻,在接收到事件通知等情景時可使用該durationHint。

AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 代表此次申請的音頻焦點只需短暫持有,原本獲取了音頻焦點的OnAudioFocusChangeListener 接口將會回調onAudioFocusChange(int focusChange) 方法,傳入的參數爲AUDIOFOCUS_LOSS_TRANSIENT。按照官方註釋:在需要錄音或語音識別等情景時可使用該durationHint。

AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 代表此次申請不需要暫停其它申請的音頻播放,應用跟其他應用共用焦點但播放的時候其他音頻會降低音量。原本獲取了音頻焦點的OnAudioFocusChangeListener 接口將會回調onAudioFocusChange(int focusChange) 方法,傳入的參數爲AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK。

 

在AudioManager的requestAudioFocus調用內部重載後的方法,根據傳進來的streamType,構造了一個AudioAttributes對象向下傳遞,這個AudioAttributes主要是存儲了一些音頻流信息的屬性. 這裏面對falgs進行了與操作,由於之前傳進來的是0,所以轉換後的結果還是0.

繼續AudioManager.requestAudioFocus中 public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) 裏面首先在registerAudioFocusRequest中註冊所有的

AudioFocusRequest private final ConcurrentHashMap<String, FocusRequestInfo> mAudioFocusIdListenerMap =         new ConcurrentHashMap<String, FocusRequestInfo>();        

 final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :new ServiceEventHandlerDelegate(h).getHandler());

其中mAudioFocusIdListenerMap根據AudioFocusRequest中OnAudioFocusChangeListener對象的來生成一個key,value則爲根據afr生成的FocusRequestInfo ,存儲在了mAudioFocusIdListenerMap對象中。 而之前所述的abandonAudioFocus中有unregisterAudioFocusRequest做的操作就是remove掉mAudioFocusIdListenerMap中的OnAudioFocusChangeListener 

在AudioManager中還有一個電話相關的調用:

public void requestAudioFocusForCall(int streamType, int durationHint)

 這個調用的也是AudioService中的requestAudioFocus並且往其中設置了一個clientId爲AudioSystem.IN_VOICE_COMM_FOCUS_ID的狀態。 繼續往下就是進入到AudioService.requestAudioFocus中首先會進行權限檢查,這裏面就用到了AudioSystem.IN_VOICE_COMM_FOCUS_ID 也就是說如果clientId等於AudioSystem.IN_VOICE_COMM_FOCUS_ID,且要申請到MODIFY_PHONE_STATE的權限,否則會申請焦點失敗。

AudioService也只是做了中轉,並沒有做實際的操作,具體實現都是在MediaFocusControl.requestAudioFocus中大致過程如下:

private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>(); 最主要的是對mFocusStack棧的操作,用來維護各client的申請和釋放。

1.判斷mFocusStack.size() 不能超過100

2.檢查當前棧頂的元素是否是Phone應用佔用,如果Phone處於佔用狀態,那麼focusGrantDelayed = true。

3. 壓棧之前,需要檢查當前棧中是否已經有這個應用的記錄,如果有的話就刪除掉。  

如果mFocusStack不爲空,並且棧頂的clientId與要申請焦點的clientId相同,得到棧頂元素即FocusRequester對象。如果申請的時長和flags都相同,則表示重複申請,直接返回成功,如果如果申請的時長和flags有一個不相同,則認爲需要重新申請,此時如果focusGrantDelayed = false則需要將棧頂的元素出棧並將其釋放

4. // focus requester might already be somewhere below in the stack, remove it          

  removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);    

移除可能在棧中(棧頂或者棧中)其他位置存在着相同clientId的元素

a:如果要釋放的應用是在棧頂,則釋放之後,還需要通知先在棧頂應用,其獲得了audiofocus;

b:如果要釋放的應用不是在棧頂,則只是移除這個記錄,不需要更改當前audiofocus的佔有情況。

5.創建FocusRequester實例將請求包含的各種信息傳入

AudioAttributes aa, int focusChangeHint, IBinder cb,  IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags, int sdk

6.如果focusGrantDelayed = true,那麼就會延遲申請,並把此次請求FocusRequester實例入棧,但是此時記錄不是被壓在棧頂,而是放在lastLockedFocusOwnerIndex這個位置,也就是打電話這個記錄的後面;如果focusGrantDelayed = false即不需要延遲獲得焦點,同樣創建FocusRequester實例,但是先要通知棧裏其他記錄失去焦點,然後壓入棧頂,最後通知自己獲得焦點成功

遍歷mFocusStack,調用FocusRequester對象的handleExternalFocusGain方法 通知棧中其他元素丟失焦點流程. stackIterator.next()得到的是FocusRequester對象,因此查看FocusRequester中handleExternalFocusGain的代碼

主要關注兩個變量gainRequest和mFocusLossReceived, mFocusLossReceived這個值在handleFocusLoss中進行賦值的,默認值是AudioManager.AUDIOFOCUS_NONE。

 * gainRequest這個是傳進來的,例如

AudioManager.AUDIOFOCUS_GAIN

 * return AudioManager.AUDIOFOCUS_LOSS

 * 若傳進來的參數是 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT

 * 則return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT

在handleFocusLoss中

fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);

通過mFocusDispatcher對象調用了dispatchAudioFocusChange方法,將mFocusLossReceived和mClientId傳了進去。

3.1 FocusRequester構造方法的第四個參數IAudioFocusDispatcher afl

3.2 MediaFocusControl的requestAudioFocus方法的第四個參數IAudioFocusDispatcher fd。

3.3 AudioService的requestAudioFocus方法的第四個參數IAudioFocusDispatcher fd 最終在AudioManager中實現。IAudioFocusDispatcher 的回調dispatchAudioFocusChange方法。

在dispatchAudioFocusChange中通過Handler發送MSSG_FOCUS_CHANGE

在ServiceEventHandlerDelegate 的創建handleMessage在其中根據MSSG_FOCUS_CHANGE來回調,而msg.arg1爲focusChange即mFocusLossReceived

listener.onAudioFocusChange(msg.arg1);

最終app中實現onAudioFocusChange根據焦點的切換做相應的控制。

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