Android P 音频焦点管理

Android 音频焦点管理

*
什么是音频焦点管理
音频焦点的申请
音频焦点的申请流程
外部音频策略

+ 外部音频焦点策略
+ 外部音频路由策略
流程图
响应音频焦点更改
音频焦点的放弃

开始之前先来一个
Google Developer 链接

镇楼,本文主要基于Android P版本的音频焦点机制的梳理学习

什么是音频焦点管理

官方的解释是两个或两个以上的 Android 应用可同时向同一输出流播放音频。系统会将所有音频流混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,Android 引入了”音频焦点”的概念。 一次只能有一个应用获得音频焦点。

当你的应用程序需要输出音频时,它需要请求音频焦点,获得焦点后,就可以播放声音了。但是,在您的应用程序获得音频焦点后,您可能无法将其保持到回放完成。其他应用程序可以请求焦点占据您持有的音频焦点。如果发生这种情况,你的应用程序应该暂停播放或降低音量,以便用户能够听到新的音频源。

[En]

When your application needs to output audio, it needs to request audio focus, and after getting focus, you can play the sound. However, after your application gets audio focus, you may not be able to hold it until the playback is complete. Other applications can request focus to occupy the audio focus you hold. If this happens, your app should pause playback or lower the volume so that users can hear the new audio source.

音频焦点处于协作模式。建议应用程序遵循音频聚焦准则,但系统不强制执行这些准则。如果应用程序在失去音频焦点后想要继续大声播放,系统无法停止它。在这种情况下,它会给用户带来糟糕的体验。

[En]

The audio focus is in cooperative mode. It is recommended that applications follow audio focus guidelines, but these guidelines are not enforced by the system. If the application wants to continue to play loudly after losing audio focus, the system cannot stop it. In this case, it will create a bad experience for users.

音频焦点的申请

android 8.0前后有差异,我们主要看android 8.0及其以后的

//1、先获取一个AudioManager
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);

//2、创建AudioAttributes,AudioAttributes 描述了应用的用例。系统会在应用获得和失去音频焦点时查看这些属性。这些属性取代了音频流类型的概念。
mAudioAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();

//3、创建AudioFocusRequest,其中有如下重要的字段
mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setFocusGain(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(mAudioAttributes)
        .setWillPauseWhenDucked(true)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener(new AudioManager.OnAudioFocusChangeListener() {
            @Override
            public void onAudioFocusChange(int focusChange) {
                Log.d("kevin", " foucs change type = " + focusChange);
                switch (focusChange) {
                    case AudioManager.AUDIOFOCUS_LOSS:
                        Log.d("kevin", "AudioManager.AUDIOFOCUS_LOSS");
                        //失去焦点,暂停处理,暂停播放当前音乐
                        //你会长时间的失去焦点,所以不要指望在短时间内能获得。请结束自己的相关音频工作并做好收尾工作。比如另外一个音乐播放器开始播放音乐了,前提是这个另外的音乐播放器他也实现了音频焦点的控制,
                        break;
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                        Log.d("kevin", "AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
                        //你的焦点会短暂失去,但是你可以与新的使用者共同使用音频焦点
                        break;
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        Log.d("kevin", "AudioManager.AUDIOFOCUS_LOSS_TRANSIENT");
                        //你会短暂的失去音频焦点,你可以暂停音乐,但不要释放资源,因为你一会就可以夺回焦点并继续使用
                        //如听音乐过程中,收到电话
                        break;
                    case AudioManager.AUDIOFOCUS_GAIN:
                        Log.d("kevin", "AudioManager.AUDIOFOCUS_GAIN");
                        //播放操作
                        //你已经完全获得了音频焦点
                        break;
                    default:
                        Log.d("kevin", "Unknown audio focus change code");
                }
            }
        })
        .build();

//4、请求获得音频焦点
audioManager.requestAudioFocus(mFocusRequest);

看完上面的代码后,可能会有几个问题:

[En]

After looking at the above code, there may be several questions:

1、音频流类型的概念,Android为不同的应用在不同的场合定义了不同的流类型
电话:STREAM_VOICE_CALL
系统:STREAM_SYSTEM
铃声:STREAM_RING
音乐:STREAM_MUSIC
闹钟:STREAM_ALARM
通知:STREAM_NOTIFICATION
蓝牙:STREAM_BLUETOOTH_SCO
其他国家的提示音:STREAM_SYSTEM_ENFORCED
双音多频:STREAM_DTMF
TTS: STREAM_TTS
这部分Stream Type可以自己扩展

2、 setFocusGain() 每个请求中必要的字段,可以理解为告诉系统,该应用需要使用焦点多长时间
AUDIOFOCUS_GAIN 长时间持有音频焦点
AUDIOFOCUS_GAIN_TRANSIENT 只希望在短时间内播放音频,类似通知
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 只希望在短时间内播放音频,并允许前一个持有焦点的应用在降低其音频输出的情况下继续播放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 用于表示对音频焦点的临时请求,类似录音等请求操作

3、 setWillPauseWhenDucked() 当其他应用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 请求焦点时,持有焦点的应用通常不会收到 onAudioFocusChange() 回调,因为系统可以自行降低音量。如果您需要暂停播放而不是降低音量,请调用 setWillPauseWhenDucked(true),然后创建并设置 OnAudioFocusChangeListener

4、 setAcceptsDelayedFocusGain当焦点被其他应用锁定时,对音频焦点的请求可能会失败。此方法可实现延迟获取焦点,即在焦点可用时异步获取焦点。请注意,要使”延迟获取焦点”起作用,您还必须在音频请求中指定 AudioManager.OnAudioFocusChangeListener,因为您的应用必须收到回调才能知道自己获取了焦点

5、 setOnAudioFocusChangeListener 一般来说,在原生请求中指定了 willPauseWhenDucked(true) 或 setAcceptsDelayedFocusGain(true) 时,才需要 OnAudioFocusChangeListener。

音频焦点的申请流程

AudioManager requestAudioFocus(AudioFocusRequest focusRequest) –> requestAudioFocus(AudioFocusRequest afr, AudioPolicy ap)

public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
    //将AudioFocusRequest 中listener 保存到一个ConcurrentHashMap中
    registerAudioFocusRequest(afr);
    //关于AudioPolicy ap 这个参数我们后面再说
    ...

    //获取IAudioService
    final IAudioService service = getService();
    ...

    //通过binder 调用AudioService的requestAudioFocus
    synchronized (mFocusRequestsLock) {
        try {
            // TODO status contains result and generation counter for ext policy
            status = service.requestAudioFocus(afr.getAudioAttributes(),
                    afr.getFocusGain(), mICallBack,
                    mAudioFocusDispatcher,
                    clientId,
                    getContext().getOpPackageName() /* package name */, afr.getFlags(),
                    ap != null ? ap.cb() : null,
                    sdk);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        //看返回结果是不是支持外部处理AudioFocus,如果不是就返回原生的结果
        if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
             // default path with no external focus policy
             return status;
         }
    }
    ...

    return focusReceiver.requestResult();
}

上述方法主要做了三件事:

[En]

Three main things have been done in the above methods:

1、参数校验
2、调用到了AudioService的requestAudioFocus
3、判断是否有外部的AudioPolicy,如果没有的话,就返回原生的结果。如果有,就等待外部的AudioPolicy的处理结果。
下面看下AudioService的requestAudioFocus方法

public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
        IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
        IAudioPolicyCallback pcb, int sdk) {
    ...
    //调用 MediaFocusControl 的 requestAudioFocus
    return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
            clientId, callingPackageName, flags, sdk,
            forceFocusDuckingForAccessibility(aa, durationHint, uid));
}

接着调用 MediaFocusControl 的 requestAudioFocus,这个方法很长,挑重要的地方看,如下:

protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
         IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
         int sdk, boolean forceDuck) {

     synchronized(mAudioFocusLock) {
        //申请成功的焦点都放到焦点栈中维护起来,焦点栈的存储的音频焦点信息不能超过MAX_STACK_SIZE
         if (mFocusStack.size() > MAX_STACK_SIZE) {
             Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
         }
         //...

         //判断是否使用外部的策略,这个地方比较重要
         // external focus policy?

         if (notifyExtFocusPolicyFocusRequest_syncAf(
                 afiForExtPolicy, fd, cb)) {
             // stop handling focus request here as it is handled by external audio focus policy
             return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
         }

        //如果申请的焦点已经在栈顶,则直接返回成功
         if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
             // if focus is already owned by this client and the reason for acquiring the focus
             // hasn't changed, don't do anything
             final FocusRequester fr = mFocusStack.peek();
             if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
                 // unlink death handler so it can be gc'ed.

                 // linkToDeath() creates a JNI global reference preventing collection.

                 cb.unlinkToDeath(afdh, 0);
                 notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
                         AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
             }

             //这个地方可以理解为同一个listener申请了两次焦点,将第一次的移除
             // the reason for the audio focus request has changed: remove the current top of
             // stack and respond as if we had a new focus owner
             if (!focusGrantDelayed) {
                 mFocusStack.pop();
                 // the entry that was "popped" is the same that was "peeked" above
                 fr.release();
             }
         }

         if (focusGrantDelayed) {
             // focusGrantDelayed being true implies we can't reassign focus right now
             // which implies the focus stack is not empty.

             //对于申请delay的焦点直接放入栈中被delay的焦点下面
             final int requestResult = pushBelowLockedFocusOwners(nfr);
             if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
                 notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
             }
             return requestResult;
         } else {
             // propagate the focus change through the stack
             //如果不是delay的焦点,那么就和其他的焦点比较
             if (!mFocusStack.empty()) {
                 propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
             }

             // push focus requester at the top of the audio focus stack
             mFocusStack.push(nfr);
             nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
         }
         notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                 AudioManager.AUDIOFOCUS_REQUEST_GRANTED);

         if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
             runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
         }
     }//synchronized(mAudioFocusLock)

     return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
 }

关于上述中的mFocusPolicy,如果不为null的话,将会使用外部AudioPolicy,这个部分可以作为AudioPolicy客制化使用。可以看到焦点的申请流程大概到这里就结束了。其中有涉及到很多焦点的仲裁处理,就需要我们自己慢慢的去看了。

简单的流程就是:
–> AudioManager.java requestAudioFocus
–> AudioService.java requestAudioFocus
–> MediaFocusControl.java -->requestAudioFocus 然后在MediaFocusControl中判断是否走外部音频策略
–>如果是外部策略 notifyExtFocusPolicyFocusRequest_syncAf
–> mFocusPolicy.notifyAudioFocusRequest(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
–> mFocusListener.onAudioFocusRequest((AudioFocusInfo) msg.obj, msg.arg1); listener由外部策略实现

外部音频策略

根据Google官方的文档说明就是Android P automovie版本支持对外部音频策略,主要包括外部音频焦点策略和外部音频路由策略两部分。原因大概就是对车载系统而言,音频焦点需求更复杂 同时 音频路由相比手机版而言更简洁。

外部音频焦点策略

我们看到在requestAudioFocus的过程中,MediaFocusControl部分有一个AudioPolicy的判断。我们简单的看一下这个mAudioPolicy是如何而来的。

下面我们看先在Car模块中是如何使用的:
我们先看Car音频相关的CarAudioService
在CarAudioService 的 init() 方法中有一个 setupDynamicRouting()方法实现了注册

private AudioPolicy getDynamicAudioPolicy(@NonNull IAudioControl audioControl) {

    //创建policy build对象
    AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
    builder.setLooper(Looper.getMainLooper());

    // 1st, enumerate all output bus device ports
    AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
    if (deviceInfos.length == 0) {
        Log.e(CarLog.TAG_AUDIO, "getDynamicAudioPolicy, no output device available, ignore");
        return null;
    }
    for (AudioDeviceInfo info : deviceInfos) {
        Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
                info.getId(), info.getAddress(), info.getType()));
        if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
            final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
            // See also the audio_policy_configuration.xml and getBusForContext in
            // audio control HAL, the bus number should be no less than zero.

            if (carInfo.getBusNumber() >= 0) {
                mCarAudioDeviceInfos.put(carInfo.getBusNumber(), carInfo);
                Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
            }
        }
    }

    // 2nd, map context to physical bus
    try {
        for (int contextNumber : CONTEXT_NUMBERS) {
            int busNumber = audioControl.getBusForContext(contextNumber);
            mContextToBus.put(contextNumber, busNumber);
            CarAudioDeviceInfo info = mCarAudioDeviceInfos.get(busNumber);
            if (info == null) {
                Log.w(CarLog.TAG_AUDIO, "No bus configured for context: " + contextNumber);
            }
        }
    } catch (RemoteException e) {
        Log.e(CarLog.TAG_AUDIO, "Error mapping context to physical bus", e);
    }

    // 3rd, enumerate all physical buses and build the routing policy.

    // Note that one can not register audio mix for same bus more than once.

    for (int i = 0; i < mCarAudioDeviceInfos.size(); i++) {
        int busNumber = mCarAudioDeviceInfos.keyAt(i);
        boolean hasContext = false;
        CarAudioDeviceInfo info = mCarAudioDeviceInfos.valueAt(i);

        //&#x5C06;mix&#x89C4;&#x5219;&#x4E0E;&#x8BBE;&#x5907;&#x5173;&#x8054;&#x8D77;&#x6765;&#x5E76;&#x521B;&#x5EFA;Mix&#x5BF9;&#x8C61;
        //&#x5176;&#x4E2D;info&#x4E3A;&#x8BBE;&#x5907;&#x7684;&#x76F8;&#x5173;&#x4FE1;&#x606F;&#xFF0C;&#x6BD4;&#x5982;&#x91C7;&#x6837;&#x7387;&#x3001;&#x683C;&#x5F0F;&#x3001;&#x901A;&#x9053;&#x6570;
        AudioFormat mixFormat = new AudioFormat.Builder()
                .setSampleRate(info.getSampleRate())
                .setEncoding(info.getEncodingFormat())
                .setChannelMask(info.getChannelCount())
                .build();

        // &#x521B;&#x5EFA;Mix&#x89C4;&#x5219; build&#x5BF9;&#x8C61;
        AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
        for (int j = 0; j < mContextToBus.size(); j++) {
            if (mContextToBus.valueAt(j) == busNumber) {
                hasContext = true;
                int contextNumber = mContextToBus.keyAt(j);
                int[] usages = getUsagesForContext(contextNumber);
                for (int usage : usages) {
                //&#x6CE8;&#x518C;&#x8DEF;&#x7531;&#x89C4;&#x5219;&#xFF0C;&#x89C4;&#x5219;&#x652F;&#x6301;&#x591A;&#x79CD;&#xFF0C;&#x89C1;AudioMixingRule&#x3002;&#x8FD9;&#x91CC;&#x91C7;&#x7528;usage&#x5339;&#x914D;&#x89C4;&#x5219;&#xFF0C;&#x610F;&#x601D;&#x662F;
                // &#x6839;&#x636E;&#x5E94;&#x7528;&#x64AD;&#x53D1;&#x97F3;&#x9891;&#x65F6;&#x6307;&#x5B9A;&#x7684;usage/streamType&#x6765;&#x9009;&#x62E9;&#x5BF9;&#x5E94;&#x7684;&#x8F93;&#x51FA;&#x8BBE;&#x5907;&#x3002;
                    mixingRuleBuilder.addRule(
                            new AudioAttributes.Builder().setUsage(usage).build(),
                            AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
                }
            }
        }
        if (hasContext) {
            // It's a valid case that an audio output bus is defined in
            // audio_policy_configuration and no context is assigned to it.

            // In such case, do not build a policy mix with zero rules.

            //deviceinfo&#x4E3A;&#x5177;&#x4F53;&#x7684;&#x8BBE;&#x5907;&#xFF0C;RouteFlags&#x8868;&#x793A;&#x4E3A;ROUTE_FLAG_RENDER&#x8868;&#x793A;&#x5BF9;&#x5E94;&#x8F93;&#x51FA;&#xFF0C;&#x540C;&#x4E00;&#x6211;&#x4EEC;&#x53EF;&#x4EE5;&#x9488;&#x5BF9;&#x8F93;&#x5165;&#x5EFA;&#x7ACB;&#x89C4;&#x5219;
            AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
                    .setFormat(mixFormat)
                    .setDevice(info.getAudioDeviceInfo())
                    .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
                    .build();
            //&#x6DFB;&#x52A0;mix&#x89C4;&#x5219;&#xFF0C;&#x53EF;&#x4EE5;&#x6DFB;&#x52A0;&#x591A;&#x4E2A;
            builder.addMix(audioMix);
        }
    }

    // 4th, attach the {@link AudioPolicyVolumeCallback}
    builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
//&#x521B;&#x5EFA;AudioPolicy&#x5BF9;&#x8C61;
    return builder.build();
}

创建好了AudioPolicy对象之后,通过AudioManager来注册 registerAudioPolicy

public int registerAudioPolicy(@NonNull AudioPolicy policy) {
    //...

    final IAudioService service = getService();
    try {
            //getConfig             &#x8DEF;&#x7531;&#x7B56;&#x7565;Mix&#x7684;&#x5C01;&#x88C5;
            //cb                    &#x97F3;&#x9891;&#x7126;&#x70B9;&#x56DE;&#x8C03;&#x5BF9;&#x8C61;
            //hasFocusListener      &#x662F;&#x5426;&#x7531;&#x7126;&#x70B9;&#x76D1;&#x542C;&#x5BF9;&#x8C61;&#xFF0C;&#x4E0E;&#x4E0A;&#x6587;&#x5BF9;&#x5E94;
            //isVolumeController    &#x97F3;&#x91CF;&#x56DE;&#x8C03;&#x5BF9;&#x8C61;&#xFF0C;&#x5373;&#x97F3;&#x91CF;&#x52A0;&#x3001;&#x51CF;&#x3001;&#x9759;&#x97F3;&#xFF0C;&#x6709;&#x5174;&#x8DA3;&#x81EA;&#x5DF1;&#x67E5;&#x770B;&#x5B9E;&#x73B0;&#x3002;
        String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
                policy.hasFocusListener(), policy.isFocusPolicy(), policy.isVolumeController());
        if (regId == null) {
            return ERROR;
        } else {
            //&#x6CE8;&#x518C;&#x6210;&#x529F;&#x4E4B;&#x540E;&#x8BBE;&#x7F6E;&#x72B6;&#x6001;
            policy.setRegistration(regId);
        }
        // successful registration
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
    return SUCCESS;
}

我们可以从上面的代码中看到是通过AudioService来 registerAudioPolicy

public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb,
        boolean hasFocusListener, boolean isFocusPolicy, boolean isVolumeController) {

    //&#x6CE8;&#x518C;&#x56DE;&#x8C03;&#xFF0C;&#x5F53;native Mix&#x6CE8;&#x518C;&#x6210;&#x529F;&#x65F6;&#x4F1A;&#x901A;&#x77E5;&#x4E0A;&#x5C42;&#x72B6;&#x6001;&#x66F4;&#x65B0;
    AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback);
    //...

    synchronized (mAudioPolicies) {
        try {
            //...

            //&#x4E8C;&#x6B21;&#x5C01;&#x88C5;&#x4E3A;AudioPolicyProxy&#x3001;&#x5B9E;&#x9645;&#x4E0A;&#x5728;AudioPolicyProxy&#x6784;&#x9020;&#x65B9;&#x6CD5;&#x5185;&#x90E8;&#xFF0C;&#x624D;&#x662F;&#x771F;&#x6B63;&#x7684;&#x6CE8;&#x518C;
            AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener,
                    isFocusPolicy, isVolumeController);
            //binder&#x7684;&#x6B7B;&#x4EA1;&#x76D1;&#x542C;
            pcb.asBinder().linkToDeath(app, 0/*flags*/);
            regId = app.getRegistrationId();
            //&#x4ECE;&#x8FD9;&#x53EF;&#x4EE5;&#x770B;&#x51FA;&#x80FD;&#x591F;&#x652F;&#x6301;&#x591A;&#x4E2A;&#x7B56;&#x7565;
            mAudioPolicies.put(pcb.asBinder(), app);
        } catch (RemoteException e) {
            // audio policy owner has already died!

            Slog.w(TAG, "Audio policy registration failed, could not link to " + pcb +
                    " binder death", e);
            return null;
        }
    }
    return regId;
}

我们看下AudioPolicyProxy的构造方法,很快我们就能知道在MediaFocusControl中的mAudioPolicy对象是哪儿来的了

AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token,
        boolean hasFocusListener, boolean isFocusPolicy, boolean isVolumeController) {
    //...

    //&#x6709;&#x5916;&#x90E8;&#x7126;&#x70B9;&#x7B56;&#x7565;&#x610F;&#x5473;&#x7740;mHasFocusListener&#x4E0D;&#x4E3A;&#x7A7A;
    if (mHasFocusListener) {
        mMediaFocusControl.addFocusFollower(mPolicyCallback);
        // can only ever be true if there is a focus listener
        //&#x53EA;&#x6709;&#x5F53;&#x660E;&#x786E;&#x8BBE;&#x7F6E;&#x5916;&#x90E8;&#x7B56;&#x7565;&#x65F6;&#x624D;&#x4F1A;&#x91C7;&#x7528;&#x5916;&#x90E8;&#x7126;&#x70B9;&#x7B56;&#x7565;
        if (isFocusPolicy) {
            mIsFocusPolicy = true;
            //&#x8BBE;&#x7F6E;FocusPolicy&#xFF0C;&#x4E5F;&#x5C31;&#x662F;&#x5728;MediaFocusControl&#x4E2D;&#x7684;mAudioPolicy&#x7684;&#x6765;&#x5904;
            mMediaFocusControl.setFocusPolicy(mPolicyCallback);
        }
    }
    //&#x8BBE;&#x7F6E;&#x97F3;&#x91CF;&#x63A7;&#x5236;&#x7684;&#x56DE;&#x8C03;
    if (mIsVolumeController) {
        setExtVolumeController(mPolicyCallback);
    }
    //&#x6CE8;&#x518C;mix&#x7B56;&#x7565;
    connectMixes();
}

感觉在AudioPolicyProxy的构造方法中的几个方法都挺重要的,我们就简单的看下,先看 addFocusFollower

void addFocusFollower(IAudioPolicyCallback ff) {
//...
            //addFocusFollower&#x5C06;&#x76D1;&#x542C;&#x52A0;&#x5165;mFocusFollowers&#x96C6;&#x5408;
            mFocusFollowers.add(ff);
            notifyExtPolicyCurrentFocusAsync(ff);
}

我们可以全局搜索到mFocusFollowers的使用的地方是在 notifyExtPolicyFocusLoss_syncAfnotifyExtPolicyFocusGrant_syncAf 两个方法中,分别代表的是通知应用获丢失焦点和通知应用获得焦点

接下来我们看下AudioPolicyProxy的下一个方法 mMediaFocusControl.setFocusPolicy(mPolicyCallback);
这就是会把焦点赋值给MediaFocusControl中的mFocusPolicy对象。在MediaFocusControl中,当应用requestAudioFocus的时候会判断mFocusPolicy是否为null,如果不为null的话,就会通过外部的焦点策略来实现逻辑判断。当外部焦点策略逻辑走完之后会利用如下的API将结果告知给系统

public void setFocusRequestResult(@NonNull AudioFocusInfo afi,
        @FocusRequestResult int requestResult, @NonNull AudioPolicy ap) {
    if (afi == null) {
        throw new IllegalArgumentException("Illegal null AudioFocusInfo");
    }
    if (ap == null) {
        throw new IllegalArgumentException("Illegal null AudioPolicy");
    }
    final IAudioService service = getService();
    try {
        service.setFocusRequestResultFromExtPolicy(afi, requestResult, ap.cb());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
public void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult,
        IAudioPolicyCallback pcb) {
    if (afi == null) {
        throw new IllegalArgumentException("Illegal null AudioFocusInfo");
    }
    if (pcb == null) {
        throw new IllegalArgumentException("Illegal null AudioPolicy callback");
    }
    synchronized (mAudioPolicies) {
        if (!mAudioPolicies.containsKey(pcb.asBinder())) {
            throw new IllegalStateException("Unregistered AudioPolicy for external focus");
        }
        mMediaFocusControl.setFocusRequestResultFromExtPolicy(afi, requestResult);
    }
}
void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult) {
    synchronized (mExtFocusChangeLock) {
        if (afi.getGen() > mExtFocusChangeCounter) {
            return;
        }
    }
    final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
    if (fr != null) {
        fr.dispatchFocusResultFromExtPolicy(requestResult);
    }
}

当系统拿到结果后,通过FocusRequester的dispatchFocusResultFromExtPolicy返回,在FocusRequester中调用的是IAudioFocusDispatcher的dispatchFocusResultFromExtPolicy方法,这

void dispatchFocusResultFromExtPolicy(int requestResult) {
    //...

    try {
        mFocusDispatcher.dispatchFocusResultFromExtPolicy(requestResult, mClientId);
    } catch (android.os.RemoteException e) {
        Log.e(TAG, "dispatchFocusResultFromExtPolicy: error talking to focus listener"
                + mClientId, e);
    }
}

而上述代码中的mFocusDispatcher是在MediaFocusControl的requestAudioFocus方法中通过FocusRequester的构造方法传入,用到了AudioManager中的 IAudioFocusDispatcher,我们来看下在AudioManager中 dispatchFocusResultFromExtPolicy的实现

@Override
public void dispatchFocusResultFromExtPolicy(int requestResult, String clientId) {
    synchronized (mFocusRequestsLock) {
        // TODO use generation counter as the key instead
        final BlockingFocusResultReceiver focusReceiver =
                mFocusRequestsAwaitingResult.remove(clientId);
        if (focusReceiver != null) {
            //&#x5C06;request&#x7684;&#x7ED3;&#x679C;&#x66F4;&#x65B0;&#x7ED9;mFocusRequestResult&#xFF0C;&#x7136;&#x540E;&#x5728;AudioManager requestAudioFocus&#x7684;&#x65F6;&#x5019;&#x8FD4;&#x56DE;&#x7ED9;&#x5E94;&#x7528;
            focusReceiver.notifyResult(requestResult);
        } else {
            Log.e(TAG, "dispatchFocusResultFromExtPolicy found no result receiver");
        }
    }
}

还记得我们在AudioManager中 requestAudioFocus方法中有一个地方会 focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);等待200ms,也就意味着上述的外部音频焦点策略理应在200ms内完成。

简单流程如下:
–> AudioManager.java registerAudioPolicy
–> AudioService.java registerAudioPolicy
–> new AudioPolicyProxy
–> MediaFocusControl.java setFocusPolicy
–> 走的是外部音频焦点策略,策略逻辑完成后需要调用 AudioManager.java setFocusRequestResult
–> AudioService.java setFocusRequestResultFromExtPolicy
–> MediaFocusControl.java setFocusRequestResultFromExtPolicy
–> FocusRequester.java dispatchFocusResultFromExtPolicy
–> AudioManager.java dispatchFocusResultFromExtPolicy
–> focusReceiver.notifyResult 更新mFocusRequestResult的结果
–>在requestAudioFocus的最后通过 focusReceiver.requestResult() 返回结果

外部音频路由策略

在Android中,将声音区分为不同的流类型,不同的流类型往往使用不同的设备进行输出,这就是音频策略。对于外部音频路由策略,可以粗略的理解为如何将声音输入输出的一个策略。本来打算主要先看音频焦点相关的,所以关于外部音频路由就简单的看一下

上述代码我们看到getDynamicAudioPolicy中有关于Mix Builder相关的,然后在AudioPolicyProxy的构造方法中,我们看到有个 connectMixes方法。在该方法内部会调用 AudioSystem.registerPolicyMixes(mMixes, true); ,然后在AudioSystem中调用native的 registerPolicyMixes方法。简单流程如下:

–> AudioSystem.cpp registerPolicyMixes
–> IAudioPolicyService.cpp &#x4ECE;BpAudioPolicyService&#x8F6C;&#x5230;BnAudioPolicyService&#x4E2D;&#x7684;registerPolicyMixes
–> AudioPolicyService.h &#x7EE7;&#x627F;BnAudioPolicyService &#x7136;&#x540E;&#x7531;AudioPolicyIntefaceImpl.cpp&#x53BB;&#x5B9E;&#x73B0;
–> AudioPolicyManager.cpp registerPolicyMixes

本质就是通过 mPolicyMixes.registerMix(address, mixes[i], desc) 分别将LOOP_BACK、RENDER对应的AudioMix注册到mPolicyMixes对象中,后面根据如何根据输入输出执行策略的部分就暂时不展开了

需要注意的是策略不一定需要通过Java注册,也可以直接native方式,当设备被移除时,也需要删除此策略。

流程图

最后关于requestAudioFocus和外部AudioPolicy交互,画了个简单的流程图:

Android P 音频焦点管理

; 响应音频焦点更改

当应用获得音频焦点后,它必须能够在其他应用为自己请求音频焦点时释放该焦点。出现这种情况时,您的应用会收到对 AudioFocusChangeListener 中的 onAudioFocusChange() 方法的调用,该方法是在上述讲到的应用调用 requestAudioFocus() 时指定的。

传递给 onAudioFocusChange() 的 focusChange 参数表示所发生的更改类型。它对应于获取焦点的应用所使用的持续时间提示。主要分以下两种:

1、暂时性失去焦点
如果焦点更改是暂时性的( AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKAUDIOFOCUS_LOSS_TRANSIENT),你的应用应该降低音量(如果您不依赖于自动降低音量)或暂停播放,否则保持相同的状态。
在暂时性失去音频焦点后,继续监控音频焦点的变化,并准备好在重新获得焦点后恢复正常播放。当抢占焦点的应用放弃焦点时,你的应用会收到一个回调 (AUDIOFOCUS_GAIN)。然后重新获得焦点后,就可以继续播放。

2、永久性失去焦点
如果是永久性失去音频焦点 (AUDIOFOCUS_LOSS),则其他应用会播放音频。那么你的应用需要立即暂停播放,因为它不会收到 AUDIOFOCUS_GAIN 回调。

音频焦点的放弃

AudioManager abandonAudioFocus() 就简单的梳理下流程,有兴趣的自行去看源码实现:

–> AudioManager.java abandonAudioFocus
–> AudioService.java abandonAudioFocus
–> MediaFocusControl.java abandonAudioFocus 同样的会通过mFocusPolicy来判断是否走外部AudioPolicy
–>如果是外部AudioPolicy走 notifyExtFocusPolicyFocusAbandon_syncAf
–> mFocusPolicy.notifyAudioFocusAbandon(afi);
–> AudioPolicy.java mFocusListener.onAudioFocusAbandon((AudioFocusInfo) msg.obj);
–>通过应用注册进来的listener回调 mFocusListener.onAudioFocusAbandon((AudioFocusInfo) msg.obj)
是不是觉得和requestAudioFocus差不多

需要注意的是:播放完毕一定要禁止掉请求的音频焦点也就是 abandonAudioFocus(afChangeListener),否则,如果播放完毕后的某个时段刚好有个通话结束,并且此时没有其他的应用占用了焦点,系统会重新通知服务里的afChangeListener,导致音频再次的播放。如果丢失的短暂音频焦点允许DUCK状态AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK,在这种情况下,应用程序降低音量继续播放,不需要暂停。再次获取后,恢复原来的音量。

释放音频焦点时有两种情况:

[En]

There are two situations when releasing audio focus:

1、如果要释放的应用是在栈顶,则释放之后,还需要通知先在栈顶应用,其获得了audiofocus;
2、如果要释放的应用不是在栈顶,则只是移除这个记录,不需要更改当前audiofocus的占有情况。

Original: https://blog.csdn.net/eatlemon/article/details/110426550
Author: eatlemon
Title: Android P 音频焦点管理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/525413/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球