音频焦点AudioFocus

本文详细介绍了Android的AudioFocus机制,包括如何通过AudioManager的requestAudioFocus方法申请焦点,OnAudioFocusChangeListener接口的使用,以及焦点的释放过程。重点讨论了AudioFocus的四种状态变化及其在音频播放中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介-AudioFocus

AudioFocus是一个Audio协调机制,当多方需要使用Audio资源时可以通过AudioFocus机制来协调配合。

AudioFocus机制

使用AudioFocus机制主要通过android.media.AudioManager中的requestAudioFocus方法请求获取焦点,若获取成功返回int值AudioManager.AUDIOFOCUS_REQUEST_GRANTED,失败则返回AudioManager.AUDIOFOCUS_REQUEST_FAILED

实现流程

在这里插入图片描述

requestAudioFocus方法:
  1. OnAudioFocusChangeListener: 是一个接口,仅定义了一个方法onAudioFocusChange,具体实现如下文
  2. requestAttributes: 根据传进来的streamType构造AudioAttributes对象向下传递,该对象存储了一些音频流信息的属性,对flag进行&操作,(由于之前传入为0,则转换后还是0)
  3. 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。
- frameworks/base/media/java/com/android/media/AudioManager.java
 @SystemApi
public int requestAudioFocus(OnAudioFocusChangeListener l,
        @NonNull AudioAttributes requestAttributes,
        int durationHint,
        int flags,
        AudioPolicy ap) throws IllegalArgumentException {
   
   
    // parameter checking
    if (requestAttributes == null) {
   
   
        throw new IllegalArgumentException("Illegal null AudioAttributes argument");
    }
    // durationHint 表示获取焦点的时长
    if ((durationHint < AUDIOFOCUS_GAIN) ||
            (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
   
   
        throw new IllegalArgumentException("Invalid duration hint");
    }
    if (flags != (flags & AUDIOFOCUS_FLAGS_SYSTEM)) {
   
   
        throw new IllegalArgumentException("Illegal flags 0x"
            + Integer.toHexString(flags).toUpperCase());
    }
    if (((flags & AUDIOFOCUS_FLAG_DELAY_OK) == AUDIOFOCUS_FLAG_DELAY_OK) && (l == null)) {
   
   
        throw new IllegalArgumentException(
                "Illegal null focus listener when flagged as accepting delayed focus grant");
    }
    if (((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK) && (ap == null)) {
   
   
        throw new IllegalArgumentException(
                "Illegal null audio policy when locking audio focus");
    }

    int status = AUDIOFOCUS_REQUEST_FAILED;
    // 注册所有的AudioFocusRequest,具体方法见下
    registerAudioFocusListener(l);
    IAudioService service = getService();
    try {
   
   
        // 通过IAudioService通信实现audio焦点的获取
        status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
                mAudioFocusDispatcher, getIdForAudioFocusListener(l),
                getContext().getOpPackageName() /* package name */, flags,
                ap != null ? ap.cb() : null);
    } catch (RemoteException e) {
   
   
        throw e.rethrowFromSystemServer();
    }
    return status;
}
---------------------------------------------------------------
registerAudioFocusListener()
/**
 * @hide
 * Registers a listener to be called when audio focus changes. Calling this method is optional
 * before calling {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, as it
 * will register the listener as well if it wasn't registered already.
 * @param l the listener to be notified of audio focus changes.
 */
public void registerAudioFocusListener(OnAudioFocusChangeListener l) {
   
   
    synchronized(mFocusListenerLock) {
   
   
        // mAudioFocusIdListenerMap根据AudioFocusRequest中OnAudioFocusChangeListener对象生成一个key,value存储在mAudioFocusIdListenerMap对象中
        if (mAudioFocusIdListenerMap.containsKey(getIdForAudioFocusListener(l))) {
   
   
            return;
        }
        mAudioFocusIdListenerMap.put(getIdForAudioFocusListener(l), l);
    }
}

OnAudioFocusChangeListener:

是一个接口,仅定义了一个方法onAudioFocusChange(int focusChange),该方法在焦点状态变化时被调用,参数focusChange代表变化后当前状态,共有四个值:

  • AUDIOFOCUS_GAIN 重新获取到音频焦点时触发的状态。
  • AUDIOFOCUS_LOSS 失去音频焦点时触发的状态,且该状态应该会长期保持,此时应当暂停音频并释放音频相关的资源。
  • AUDIOFOCUS_LOSS_TRANSIENT 失去音频焦点时触发的状态,但是该状态不会长时间保持,此时应该暂停音频,且当重新获取音频焦点的时候继续播放。
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 失去音频焦点时触发的状态,在该状态的时候不需要暂停音频,但是应该降低音频的声音
/**
 * Interface definition for a callback to be invoked when the audio focus of the system is
 * updated.
 */
public interface OnAudioFocusChangeListener {
   
   
    /**
     * Called on the listener to notify it the audio focus for this listener has been changed.
     * The focusChange value indicates whether the focus was gained,
     * whether the focus was lost, and whether that loss is transient, or whether the new focus
     * holder will hold it for an unknown amount of time.
     * When losing focus, listeners can use the focus change information to decide what
     * behavior to adopt when losing focus. A music player could for instance elect to lower
     * the volume of its music stream (duck) for transient focus losses, and pause otherwise.
     * @param focusChange the type of focus change, one of {@link AudioManager#AUDIOFOCUS_GAIN},
     *   {@link AudioManager#AUDIOFOCUS_LOSS}, {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}
     *   and {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
     */
    public void onAudioFocusChange(int focusChange);
}
AudioManager中还有一个电话相关的调用

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的权限,否则会申请焦点失败。

/**
 * @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(new AudioAttributes.Builder()
                    .setInternalLegacyStreamType(streamType).build(),
                durationHint, mICallBack, null,
                AudioSystem.IN_VOICE_COMM_FOCUS_ID,
                getContext().getOpPackageName(),
                AUDIOFOCUS_FLAG_LOCK,
                null /* policy token */);
    } catch (RemoteException e) {
   
   
        throw e.rethrowFromSystemServer();
    }
}
requestAudioFocus方法实际的实现在AudioService.java
- frameworks/base/service/core/java/com/android/sever/audio/AudioService.java
//==========================================================================================
// Audio Focus
//==========================================================================================
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
        IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
        IAudioPolicyCallback pcb) {
   
   
    // permission checks
    if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
   
   
        if 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值