Android FrameWork音频管理AudioManager的一点解析(续二)

         上面两篇简单的分析了,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的操作监听在源码中都有提到,想了解的可以去了解下。

        至此,我理解的一部分音频控制源码就到此为止了,如有不对的可告知。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。