標籤:framework audiomanger 音頻焦點 源碼解析
上面兩篇簡單的分析了,android系統中FrameWork中對於音量的調節的部分代碼,可能有些更深入的東西並沒有涉及,因為初次嘗試分析,並不是所有東西都能完全明白和透徹的理解,所以暫時只能將我所瞭解到並用到的部分做瞭解析。
這篇主要想解析下在AudioManager中對於音頻競爭機制的部分內容和使用方式。
首先來說下這中競爭機制的產生原因,根據我的理解因為Android系統中存在眾多音訊類型,並且他們相互之間都保持獨立的存在,並不會相互影響。如音樂的聲音,按鍵的觸摸聲音,響鈴的聲音,通話的聲音,藍芽通話的聲音,錯誤判警的聲音等等。都會有一個響應的聲音類型。因此他們之間也會有獨立的音頻流來控制播放。為了防止這些聲音會相互產生影響,競爭機制就產生了,這個機制有效控制了這部分聲音的播放的優先順序已經是否存在互斥,共存等獨立的關係,每個音頻流在播放時都會通過這中機制擷取播放的許可,使其可以被監控狀態,便於管理。
對於這種機制,其中核心內容就是音頻焦點的管理。
下面會貼出部分代碼
/** * Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) */ public static final int AUDIOFOCUS_GAIN = 1; /** * Used to indicate a temporary gain or request of audio focus, anticipated to last a short * amount of time. Examples of temporary changes are the playback of driving directions, or an * event notification. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) */ public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; /** * Used to indicate a temporary request of audio focus, anticipated to last a short * amount of time, and where it is acceptable for other audio applications to keep playing * after having lowered their output level (also referred to as "ducking"). * Examples of temporary changes are the playback of driving directions where playback of music * in the background is acceptable. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) */ public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; /** * Used to indicate a loss of audio focus of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) */ public static final int AUDIOFOCUS_LOSS = -1 * AUDIOFOCUS_GAIN; /** * Used to indicate a transient loss of audio focus. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) */ public static final int AUDIOFOCUS_LOSS_TRANSIENT = -1 * AUDIOFOCUS_GAIN_TRANSIENT; /** * Used to indicate a transient loss of audio focus where the loser of the audio focus can * lower its output volume if it wants to continue playing (also referred to as "ducking"), as * the new focus owner doesn't require others to be silent. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) */ public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -1 * AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
上述代碼也是存在於android\frameworks\base\media\java\android\media\AudioManager.java中的,先給出部分定義。
AUDIOFOCUS_GAIN -- 持久焦點,詳情見注釋部分,用來播放較長一段時間的音頻,之前的音頻焦點使用者需停止聲音。
AUDIOFOCUS_GAIN_TRANSIENT --瞬態焦點,用來播放很短時間的音頻,之前的焦點使用者需暫停聲音。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK --瞬態焦點,用來播放時間較短的音頻,之前的音頻焦點使用者需降低本身音量。
AUDIOFOCUS_LOSS -- 失去焦點,表明持續焦點使用者失去音頻焦點。
AUDIOFOCUS_LOSS_TRANSIENT -- 失去焦點,表明瞬態焦點使用者失去音頻焦點。
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -- 失去焦點,表明瞬態焦點使用者即降低本身音量的焦點使用者失去音頻焦點。
上述定義都是需要在請求音頻焦點時指定的參數,接下來繼續解析音頻焦點的請求。
/** * A failed focus change request. */ public static final int AUDIOFOCUS_REQUEST_FAILED = 0; /** * A successful focus change request. */ public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; /** * Request audio focus. * Send a request to obtain the audio focus * @param l the listener to be notified of audio focus changes * @param streamType the main audio stream type affected by the focus request * @param durationHint use {@link #AUDIOFOCUS_GAIN_TRANSIENT} to indicate this focus request * is temporary, and focus will be abandonned shortly. Examples of transient requests are * for the playback of driving directions, or notifications sounds. * Use {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} to indicate also that it's ok for * the previous focus owner to keep playing if it ducks its audio output. * Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such * as the playback of a song or a video. * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED} */ public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) { int status = AUDIOFOCUS_REQUEST_FAILED; if ((durationHint < AUDIOFOCUS_GAIN) || (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) { Log.e(TAG, "Invalid duration hint, audio focus request denied"); return status; } registerAudioFocusListener(l); //TODO protect request by permission check? IAudioService service = getService(); try { status = service.requestAudioFocus(streamType, durationHint, mICallBack, mAudioFocusDispatcher, getIdForAudioFocusListener(l), mContext.getPackageName() /* package name */); }catch (RemoteException e) { Log.e(TAG, "Can't call requestAudioFocus() on AudioService due to "+e); } return status; } /** * @hide * Used internally by telephony package to request audio focus. Will cause the focus request * to be associated with the "voice communication" identifier only used in AudioService * to identify this use case. * @param streamType use STREAM_RING for focus requests when ringing, VOICE_CALL for * the establishment of the call * @param durationHint the type of focus request. AUDIOFOCUS_GAIN_TRANSIENT is recommended so * media applications resume after a call */ public void requestAudioFocusForCall(int streamType, int durationHint) { IAudioService service = getService(); try { service.requestAudioFocus(streamType, durationHint, mICallBack, null, AudioService.IN_VOICE_COMM_FOCUS_ID, "system" /* dump-friendly package name */); }catch (RemoteException e) { Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService due to "+e); } } /** * @hide * Used internally by telephony package to abandon audio focus, typically after a call or * when ringing ends and the call is rejected or not answered. * Should match one or more calls to {@link #requestAudioFocusForCall(int, int)}. */ public void abandonAudioFocusForCall() { IAudioService service = getService(); try { service.abandonAudioFocus(null, AudioService.IN_VOICE_COMM_FOCUS_ID); } catch (RemoteException e) { Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService due to "+e); } } /** * Abandon audio focus. Causes the previous focus owner, if any, to receive focus. * @param l the listener with which focus was requested. * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED} */ public int abandonAudioFocus(OnAudioFocusChangeListener l) { int status = AUDIOFOCUS_REQUEST_FAILED; unregisterAudioFocusListener(l); IAudioService service = getService(); try { status = service.abandonAudioFocus(mAudioFocusDispatcher, getIdForAudioFocusListener(l)); } catch (RemoteException e) { Log.e(TAG, "Can't call abandonAudioFocus() on AudioService due to "+e); } return status; } 上面的代碼主要貼出了請求和釋放音頻焦點的兩個操作的函數,對於請求操作
requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)有三個參數
第一個參數:需要寫一個音頻焦點的監聽,可以如下
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT // Pause playback } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Resume playback } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { am.unregisterMediaButtonEventReceiver(RemoteControlReceiver); am.abandonAudioFocus(afChangeListener); // Stop playback } } }; 第二個參數:需要制定音頻流的類型,如第一篇所描述的STREAM_MUSIC,STREAM_RING,STREAM_SYSTEM等等。
第三個參數:這個參數就是這篇開始的時候所列舉的前三個音頻焦點類型了。
請求函數如果成功的話會返回AUDIOFOCUS_REQUEST_GRANTED 失敗則返回 AUDIOFOCUS_REQUEST_FAILED
請求的方法可參考如下:
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE); ... // Request audio focus for playback int result = am.requestAudioFocus(afChangeListener, // Use the music stream. AudioManager.STREAM_MUSIC, // Request permanent focus. AudioManager.AUDIOFOCUS_GAIN); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { am.unregisterMediaButtonEventReceiver(RemoteControlReceiver); // Start playback. } 返回成功,則可以開始播放我們需要播放的音頻流了。但是當播放完成後必須要調用abandonAudioFocus()來釋放音頻焦點,同時還要登出
AudioManager.OnAudioFocusChangeListener。這樣當應用程式釋放了音頻焦點後,其他被中斷的應用程式將被重新允許擷取焦點繼續播放。如下即可
// Abandon audio focus when playback complete am.abandonAudioFocus(afChangeListener);
在前面介紹過音頻焦點的類型時,對於短暫瞬態的音頻焦點提到了兩種,即一種是失去焦點時暫停,另一種是失去焦點時降低本身播放音量。這兩種的用途主要在於兩種音頻流是否可以共存並是否有主次之分。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK這種瞬態的音頻焦點,適合於短暫的警告類,播報類的音頻流,如導航聲音,提示等等。
每次這類音頻焦點被其他程式所請求後,在監聽中都會收到通知,從而我們可以做相應的操作。
例如,當我們收到了AUDIOFOCUS_LOSS_TRANSIENT這樣的訊息,則說明我們只是暫時失去了音頻焦點,當另一方施放了音頻焦點後,本程式會重新擷取音頻焦點並且 可以繼續恢複播放音頻流。因此我們在收到這個訊息時應該去暫停當前聲音,而等收到AUDIOFOCUS_GAIN一類訊息時重新啟動播放。
當我們收到了AUDIOFOCUS_LOSS後則表明我們可能很長時間內無法再次擷取音頻焦點,另一方程式會長時間佔用音頻焦點。此時我們需要停止我們的音頻流播放,並且停止音頻焦點的監聽,以及登出所有媒體按鈕監聽器。此時相當於將程式轉入後台,停止了該應用。如果需要再次恢複則需要重新開啟一次應用。
當我們收到了AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK這個訊息,則需要我們將自己的音頻播放音量降低一定的數值,當再次監聽到AUDIOFOCUS_GAIN時,則重新將聲音恢複到之前的數值即可。
上述解析就是整個音頻焦點的請求,釋放,監聽的操作流程。具體情況需要在應用中根據實際情況進行選擇以達到更好的體驗了。另外關於多媒體按鈕即MediaButton的操作監聽在源碼中都有提到,想瞭解的可以去瞭解下。
至此,我理解的一部分音頻控制源碼就到此為止了,如有不對的可告知。
Android FrameWork音頻管理AudioManager的一點解析(續二)