Android 音频焦点管理

  • 什么是音频焦点管理
  • 音频焦点的申请
  • 音频焦点的申请流程
  • 外部音频策略
    • 外部音频焦点策略
    • 外部音频路由策略
  • 流程图
  • 响应音频焦点更改
  • 音频焦点的放弃

开始之前先来一个 Google Developer 链接镇楼,本文主要基于Android P版本的音频焦点机制的梳理学习

什么是音频焦点管理

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

当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在你的应用获得音频焦点后,可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有你持有的音频焦点。如果发生这种情况,你的应用应暂停播放或降低音量,以便于用户听到新的音频源。

音频焦点采用合作模式。建议应用遵守音频焦点准则,但系统不会强制执行这些准则。如果应用想要在失去音频焦点后继续大声播放,系统无法阻止它。在这种情况下,就会给用户造成一种不好的体验。

音频焦点的申请

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() {@Overridepublic 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);

看了上述代码,可能会有几个疑问:
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 这个参数我们后面再说...//获取IAudioServicefinal IAudioService service = getService();...//通过binder 调用AudioService的requestAudioFocussynchronized (mFocusRequestsLock) {try {// TODO status contains result and generation counter for ext policystatus = 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 policyreturn status;}}...return focusReceiver.requestResult();
}

上述方法中主要做了三件事:
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 的 requestAudioFocusreturn 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_SIZEif (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 policyreturn 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 anythingfinal 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 ownerif (!focusGrantDelayed) {mFocusStack.pop();// the entry that was "popped" is the same that was "peeked" abovefr.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 stackmFocusStack.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 portsAudioDeviceInfo[] 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 bustry {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);//将mix规则与设备关联起来并创建Mix对象//其中info为设备的相关信息,比如采样率、格式、通道数AudioFormat mixFormat = new AudioFormat.Builder().setSampleRate(info.getSampleRate()).setEncoding(info.getEncodingFormat()).setChannelMask(info.getChannelCount()).build();// 创建Mix规则 build对象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) {//注册路由规则,规则支持多种,见AudioMixingRule。这里采用usage匹配规则,意思是// 根据应用播发音频时指定的usage/streamType来选择对应的输出设备。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为具体的设备,RouteFlags表示为ROUTE_FLAG_RENDER表示对应输出,同一我们可以针对输入建立规则AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build()).setFormat(mixFormat).setDevice(info.getAudioDeviceInfo()).setRouteFlags(AudioMix.ROUTE_FLAG_RENDER).build();//添加mix规则,可以添加多个        builder.addMix(audioMix);}}// 4th, attach the {@link AudioPolicyVolumeCallback}builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
//创建AudioPolicy对象return builder.build();
}

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

public int registerAudioPolicy(@NonNull AudioPolicy policy) {//...final IAudioService service = getService();try {//getConfig             路由策略Mix的封装//cb                    音频焦点回调对象//hasFocusListener      是否由焦点监听对象,与上文对应//isVolumeController    音量回调对象,即音量加、减、静音,有兴趣自己查看实现。String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),policy.hasFocusListener(), policy.isFocusPolicy(), policy.isVolumeController());if (regId == null) {return ERROR;} else {//注册成功之后设置状态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) {//注册回调,当native Mix注册成功时会通知上层状态更新AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback);//...synchronized (mAudioPolicies) {try {//...//二次封装为AudioPolicyProxy、实际上在AudioPolicyProxy构造方法内部,才是真正的注册AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener,isFocusPolicy, isVolumeController);//binder的死亡监听pcb.asBinder().linkToDeath(app, 0/*flags*/);regId = app.getRegistrationId();//从这可以看出能够支持多个策略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) {//...//有外部焦点策略意味着mHasFocusListener不为空if (mHasFocusListener) {mMediaFocusControl.addFocusFollower(mPolicyCallback);// can only ever be true if there is a focus listener//只有当明确设置外部策略时才会采用外部焦点策略if (isFocusPolicy) {mIsFocusPolicy = true;//设置FocusPolicy,也就是在MediaFocusControl中的mAudioPolicy的来处mMediaFocusControl.setFocusPolicy(mPolicyCallback);}}//设置音量控制的回调if (mIsVolumeController) {setExtVolumeController(mPolicyCallback);}//注册mix策略connectMixes();
}

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

void addFocusFollower(IAudioPolicyCallback ff) {
//...//addFocusFollower将监听加入mFocusFollowers集合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 insteadfinal BlockingFocusResultReceiver focusReceiver =mFocusRequestsAwaitingResult.remove(clientId);if (focusReceiver != null) {//将request的结果更新给mFocusRequestResult,然后在AudioManager requestAudioFocus的时候返回给应用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 从BpAudioPolicyService转到BnAudioPolicyService中的registerPolicyMixes
–> AudioPolicyService.h 继承BnAudioPolicyService 然后由AudioPolicyIntefaceImpl.cpp去实现
–> AudioPolicyManager.cpp registerPolicyMixes

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

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

流程图

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

响应音频焦点更改

当应用获得音频焦点后,它必须能够在其他应用为自己请求音频焦点时释放该焦点。出现这种情况时,您的应用会收到对 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,在这种情况下,应用程序降低音量继续播放,不需要暂停。再次获取后,恢复原来的音量。

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

Android P 音频焦点管理相关推荐

  1. android汽车音频焦点方案,管理音频焦点  |  Android 开发者  |  Android Developers

    两个或两个以上的 Android 应用可同时向同一输出流播放音频.系统会将所有音频流混合在一起.虽然这是一项出色的技术,但却会给用户带来很大的困扰.为了避免所有音乐应用同时播放,Android 引入了 ...

  2. Android请求音频焦点

    申请音频焦点 private var mAudioFocusRequest: AudioFocusRequest? = nullprivate fun requestAudioFocus() {if ...

  3. Android蓝牙开发音频焦点

    在车机开发中,蓝牙模块一般是定制的,而蓝牙的音频输出,包括蓝牙电话,蓝牙音乐,都要制定声音策略,进行音频焦点的管理. 音频焦点的管理,这一点类似于android多媒体开发时的音频焦点管理,也是通过Au ...

  4. Android音频焦点处理

    概要 Android系统允许多个应用同时播放音频,这种特性有利有弊.例如当我们正在听音乐的时候突然点开了一个视频,如果我们发现音乐的声音和视频的的声音混合了在一起,这显然让我们非常不爽.而如果我们在播 ...

  5. 音频焦点(AudioFocus)应用与源码解析

    音频焦点(AudioFocus) 基础介绍 在我们的Android设备中会安装不止一个多媒体应用,如果不制定一个有效合理的规则,应用程序各自为政,那么可能就会出现各种音视频软件的混音,这是非常影响用户 ...

  6. Android播放音频之按钮控制

    控制音量和播放 良好的用户体验是可预测的. 如果您的应用程式播放媒体,您的使用者必须使用装置,蓝牙耳机或耳机的硬体或软体音量控制来控制应用程式的音量. 类似地,在适当和可用的情况下,播放,停止,暂停, ...

  7. Android官方开发文档Training系列课程中文版:管理音频播放之管理音频焦点

    原文地址:http://android.xsoftlab.net/training/managing-audio/audio-focus.html 因为可能会存在多个APP播放音频,所以考虑它们之间的 ...

  8. Android音乐编程:管理音频焦点

    转自:http://mobile.51cto.com/android-309321.htm 因为系统中可能会有多个应用程序会播放音频,所以需要考虑他们之间该如何交互,为了避免多个应用程序同时播放音乐, ...

  9. Android音频焦点申请处理

    为了便于理解,我们以android的8.0以前的版本为例,8.0以后有一定改动,但是基本思路一样. 关于管理音频焦点(8.0以前和更高版本)的官方文档:https://developer.androi ...

  10. Android 音频焦点(Audio Focus)

    原址 CONTENTS 引子 音频焦点 一个简单的示例 注意: 引子 说 Audio Focus 前先说个很简单需求:来电时暂停正在播放的音乐,电话结束时恢复播放. 看到这个需求,第一反应肯定是:监听 ...

最新文章

  1. 2021年春季学期-信号与系统-第十五次作业参考答案-第十一小题参考答案
  2. hdu 6034 B - Balala Power! 贪心
  3. 从搜索引擎到核心交易数据库,详解阿里云神龙如何支撑双11
  4. 【VC编程技巧】窗口☞3.4利用bitmap改变对话框的背景。
  5. PowerDesigner 学习
  6. 解决IE6中 PNG图片透明的终极方案-八种方案!
  7. 要些一个在win98下的socket服务程序,大家推荐一下用啥控件?Indy?
  8. 华三交换机基本配置命令
  9. html+css基础教程之CSS 透明边框和样式
  10. Electron编译报错:include: could not find: ****StdUtils.nsh“的解决
  11. 猿创征文|ZooKeeper(伪)集群搭建
  12. 橡皮擦的英语_2019年成人高考考试,语文数学英语政治想得高分答题技巧看这里...
  13. EF搜索数据自动将表名变复数问题
  14. 一款基于Webgl实现的3D类网页游戏
  15. 贪心算法---礼堂的安排
  16. GhostXP_SP2电脑公司经典版_v8.0
  17. 美国的网络空间安全国家战略
  18. 16Java基础面试练习题整理
  19. android屏幕灯光app,屏幕边缘LED灯光
  20. 详细介绍Android中Parcelable的原理和使用方法

热门文章

  1. VBlog 静态页面 动态博客
  2. 【日常】关于爬虫中iframe节点处理小结(以超星课件下载为例)
  3. React Native 微博登陆
  4. html编辑中,出现报错 semi-.colon excepted css(………)
  5. matlab 如何设置工作路径
  6. linux开机出现repaire filesystem
  7. 有哪些好的科研工具软件?
  8. 拳王公社:最新虚拟资源项目赚钱成交系统,1.2W字干货大揭秘!
  9. T检验、F检验和统计学意义(P值或sig值)
  10. Linux iptables常用命令