音頻焦點九問

一 、爲什麼要發明音頻焦,它是什麼?
答:兩個或兩個以上的 Android App可同時向同一輸出流(比如手機的藍牙、手機的喇叭)播放音頻,系統會將所有音頻流(就是音頻數據了)混合在一起。這是一項有意思的技術,但卻會出現混音。爲了避免所有音樂應用同時播放,Android 引入了“音頻焦點”的概念。 音頻焦點機制是Android系統提供的一種道德約定,它倡導的東西有三點:
    1、 只有一個App持有音頻焦點;
    2 、播放聲音前申請音頻焦點,不需要播放的時候釋放音頻焦點;
    3 、失去音頻焦點應該暫停播放或者降低音量。
音頻焦點是Android系統進程管理的一個值,這個值就記錄了當前音頻焦點屬於哪個應用,類型等。
二、音頻焦點在Android系統中是怎麼表示,怎麼管理的?
答:音頻焦點的管理以棧的形式維護在系統進程SystemServer->MediaFocusControl中
private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
棧頂FocusRequester對象對應的App就是當前持有音頻焦點的App。
App成功申請到音頻焦點時,會在mFocusStack棧頂添加一個FocusRequester對象 ,然後通知棧頂對應的App音頻焦點申請成功,通知棧中其他FocusRequester對象對應的App音頻焦點丟失。
FocusRequester這個類就是音頻焦點表示類

/**
 * @hide
 * Class to handle all the information about a user of audio focus. The lifecycle of each
 * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus
 * stack, or the map of focus owners for an external focus policy, to its release.
 * 隱藏類 所有音頻焦點相關信息封裝類。
 * 每個音頻焦點實例都被MediaFocusControl類鎖管理,它管理着音頻焦點的新增與釋放,音頻焦點棧,外部策略的焦點擁有者等等;
 */
public class FocusRequester {

    // on purpose not using this classe's name, as it will only be used from MediaFocusControl
    private static final String TAG = "MediaFocusControl";
    private static final boolean DEBUG = false;
 /**
     * 它包含一個iBinder,可以感知焦點申請方(App)是否存活
     * 如果App 進程被殺掉,就會通過iBinder通知到對應的FocusRequester,一般就是
     * 通知MediaFocusControl-> mFocusStack中的FocusRequester對象,從而從mFocusStack移除對應的
     * FocusRequester   釋放音頻焦點, 且以後其他進程釋放焦點,也不會分發或者通知給它對應的App
     */
    private AudioFocusDeathHandler mDeathHandler; // may be null
   /**
     * 客戶端回調,當這個FocusRequester對應的App 音頻焦點發生變化
     * 比如重新獲取、丟失時候會給客戶端回調
     */
    private IAudioFocusDispatcher mFocusDispatcher; // may be null
   /**
     * 這個iBinder就是這個FocusRequester 對應的binder服務端-App
     * 它作用有 :
     * 1 當FocusRequester從焦點管理棧退出,比如Abdon後,就進行釋放,那麼以後App生死都不會通知到 MediaFocusControl
     * 2 比較兩個FocusRequester一般也是通過他們持有的iBinder對比,比如當一個應用死了,從焦點棧中移除FocusRequester
     * 就是通過對比他們持有的iBinder
     */
    private final IBinder mSourceRef; // may be null
    /**
     * App傳過來的音頻焦點回調的hashcode值,可以認爲就是代表一個具體的回調- OnAudioFocusChangeListener
     * 因爲一個App可能設置多個的音頻焦點回調 
     */
    private final @NonNull String mClientId;
   /**
     * App包名-ApplicationId
     */
    private final @NonNull String mPackageName;
   /**
     * 申請音頻焦點的App的uid
     * 音頻焦點的申請一般是App跨進程向系統服務SystemServer進程申請的
     * Binder類提供了可以獲取調用方uid的方法,然後寫入到這個FocusRequester中。 
     */
    private final int mCallingUid;
    /**
     * 這個就是管理這個FocusRequester所在的MediaFocusControl,所以這邊是不能爲null
     */
    private final MediaFocusControl mFocusController; // never null
  /**
     * 申請焦點的App的targetSdkVersion(App最佳運行Android版本,在這個版本上做了充分測試和適配)
     */
    private final int mSdkTarget;

     /**
     * the audio focus gain request that caused the addition of this object in the focus stack.
     * 標記這個FocusRequester是申請哪種類型的音頻焦點,比如正常焦點類型-AudioManager#AUDIOFOCUS_GAIN
     * 短暫獲取的焦點申請-AudioManager#AUDIOFOCUS_GAIN_TRANSIENT 等等
     */
    private final int mFocusGainRequest;
  /**
     * the flags associated with the gain request that qualify the type of grant (e.g. accepting
     * delay vs grant must be immediate)
     * 音頻焦點是否可以延遲獲取到,通過AudioFocusRequest的 build的方法setAcceptsDelayedFocusGain(true)設置
     * 如果設置爲true ,那麼如果申請的時候沒有立即給到申請者,那麼當其他應用釋放後,申請者依舊可以收到音頻焦點
     */
    private final int mGrantFlags;
    /**
     * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
     * it never lost focus.
     * 音頻焦點丟失事件是否已經收到,如果從來沒有收到就是AudioManager.AUDIOFOCUS_NONE,如果已經收到
     * 那就通過這個保證不重複收到,只有申請者下次重新申請音頻焦點後恢復狀態纔可能會繼續收到焦點丟失事件
     */
    private int mFocusLossReceived;
      /**
     *  whether this focus owner listener was notified when it lost focus
     *  這個值根mFocusLossReceived 關聯的,記錄是否收到過音頻焦點丟失事件,如果已經收到過
     *  那音頻焦點棧,最上面應用釋放音頻焦點後,如果這個FocusRequester在最上面就,那就重新獲取了音頻焦點
     *  需要通知到對應的client,如果沒有收到過音頻焦點丟失通知,那麼當最上面的應用釋放音頻焦點後,就算
     *  這個FocusRequester在最上面,也不會通知對應的client重新獲取音頻焦點
     */
    private boolean mFocusLossWasNotified;
    /**
     * the audio attributes associated with the focus request
     * 音頻屬性 -不做展開
     */
    private final @NonNull AudioAttributes mAttributes;
}

三、App失去音頻焦點,還可以播放聲音嗎?
答:可以,但不推薦。上文講到音頻焦點機制是Android系統提供的一種道德約定,所以也可以不必遵守,當失去音頻焦點的時候依舊我行我素繼續播放,但這種體驗很不好。
    比如你是一個視頻應用,一般在應用退到後臺的時候或者來電話的時候會失去音頻焦點,這之後應用繼續播放用戶明顯能感覺到是這個視頻應用有問題。
    再比如一個放音樂的應用,在後臺放音樂,你打開愛奇藝看電影,這時候這個音樂程序雖失去了音頻焦點,但依舊繼續放音樂,那我肯定也非常不爽這個音樂應用。
    綜合來說雖然音頻焦點機制是Android系統提供的一種道德約定,失去音頻焦點依舊可以播放聲音,但是這樣產生的不好體驗很容易被發現,所以大家還是遵守的好。
四、應用通過AudioManager.requestAudioFocus申請音頻焦點後,系統是同步給出申請結果的嗎?
答:是的,除非當時電話類應用佔用着音頻焦點+App設置了 可以延遲獲取焦點:

mAudioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                        .setAudioAttributes(mAudioAttributes)
                        .setAcceptsDelayedFocusGain(true)   // 可以延遲獲取焦點
                        .setOnAudioFocusChangeListener(mOnAudioFocusChangeListener)
                        .build();
      mAudioManager.requestAudioFocus(mAudioFocusRequest);

這樣等其他應用釋放焦點後,如果當前申請在焦點管理棧最上方,那就會接收到獲得到音頻焦點的回調。
五、看到類似Log: 09-24 17:05:01.116 W/MediaFocusControl( 2742): requestAudioFocus() from uid/pid 10060/15667 clientId=android~~~ 就代表申請音頻焦點成功了嗎
答:不是的,這個只代表申請流程走到了系統SystemServer進程,如果有電話類應用佔用,Binder通信異常等問題都會導致焦點申請失敗,但大部分情況下可以認爲是申請成功。
六、音頻焦點申請的詳細流程是怎麼樣的?

七、如果播放結束忘記釋放音頻焦點,會有什麼影響?系統會回收嗎?
答:會有影響,比如你就需要短暫獲取下焦點做個提示音,就會導致比如你播放完提示音
本來應該繼續放音樂的App獲取不了焦點不繼續播放。系統會回收,但是需要進程死掉。
八、看到類似Log:09-24 17:05:01.116 W/MediaFocusControl :abandonAudioFocus() from uid/pid代表釋放焦點釋放了嗎?
答:同問題五,這個只代表App申請音頻焦點釋放流程走到系統SystemServer進程,如果出現binder通信異常等問題,也會導致音頻焦點釋放失敗,不過絕大部分時候我們可以認爲成功釋放了。
九、 音頻焦點釋放的流程是怎麼樣的?

十、音頻焦點申請類型AUDIOFOCUS_GAIN_TRANSIENT和AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK的區別?
答:AUDIOFOCUS_GAIN_TRANSIENT 對應 AUDIOFOCUS_LOSS_TRANSIENT
AUDIOFOCUS_GAIN_TRANSIENT 表示 短暫獲得,一會就釋放焦點,比如你只是想發個notification時用下一秒不到的鈴聲。
AUDIOFOCUS_LOSS_TRANSIENT 表示 短暫的失去音頻焦點,不要自己主動去放棄焦點,可以暫停音樂,但不要釋放資源,因爲過系統會把焦點分發給繼續使用。

AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 對應 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
duck 、ducking英文是鴨子,鑽入水中,低頭的意思,在這裏就是我們就可以理解低頭的意思。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 表示短暫獲音頻焦點,之前的音頻焦點使用者雖然會丟失音頻焦點,但無需暫停播放,只需要降低音量就好;
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 表示臨時失去了音頻焦點,但可以以較低的音量的播放音頻; 嗨嗨 低頭播放 不與爭鋒

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