开始之前先来一个Google Developer 链接镇楼,本文主要基于AndroID P版本的音频焦点机制的梳理学习什么是音频焦点管理
官方的解释是两个或两个以上的 AndroID 应用可同时向同一输出流播放音频。系统会将所有音频流混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,AndroID 引入了“音频焦点”的概念。 一次只能有一个应用获得音频焦点。
当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在你的应用获得音频焦点后,可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有你持有的音频焦点。如果发生这种情况,你的应用应暂停播放或降低音量,以便于用户听到新的音频源。
音频焦点采用合作模式。建议应用遵守音频焦点准则,但系统不会强制执行这些准则。如果应用想要在失去音频焦点后继续大声播放,系统无法阻止它。在这种情况下,就会给用户造成一种不好的体验。
音频焦点的申请androID 8.0前后有差异,我们主要看androID 8.0及其以后的
//1、先获取一个AudioManageraudioManager = (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);
看了上述代码,可能会有几个疑问:
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();}
上述方法中主要做了三件事:
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 @R_419_6843@movIE版本支持对外部音频策略,主要包括外部音频焦点策略和外部音频路由策略两部分。原因大概就是对车载系统而言,音频焦点需求更复杂 同时 音频路由相比手机版而言更简洁。
外部音频焦点策略我们看到在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); //将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_syncAf
和 notifyExtPolicyFocusGrant_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
的实现
@OverrIDepublic 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) { //将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_DUCK
或 AUdioFOCUS_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 音频焦点管理全部内容,希望文章能够帮你解决Android P 音频焦点管理所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)