AudioFocus 音频焦点
前言
本文大致要讲的是AudioFocus音频焦点问题,大致会分为如下几个部分来讲:
1.AudioFocus的申请流程,app申请焦点后,service是如何管理焦点,并通过四个场景来分析audiofocus几种申请方式
2.释放焦点流程(待补充)
⾸先第⼀章会讲⼀下焦点是如何申请的,再将焦点是如何change的…下图为简单图示:
⾸先如果要搞清楚Framework对⾳频焦点的处理,要了解APP是如何申请焦点的,如下所示:
APP申请焦点简单示例
(from:https://developer.android.com/reference/android/media/AudioFocusRequest)
重定义onAudioFocusChange:
待补充:简单解释下这段APP申请代码:
requestAudioFocus申请焦点:
可见通常通AudioMangager.requestAudioFocus(AudioFocusRequest afr)来调⽤,所以我们从framework/base/media/java/media/AudioManager.java开始分析,这⾥是app直接调⽤的api
//params OnAudioFocusChangeListener: app侧的一个interface,由app负责实现相应逻辑,
//当音频焦点发生改变时会执行用户侧方法。
//params durationHint:焦点类型:
//public static final int AUDIOFOCUS_GAIN = 1; //使用场景:应用需要聚焦音频的时长会根据用户的使用时长改变,属于不确定期限。例如:多媒体播放或者播客等应用。
//public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; //使用场景:应用只需短暂的音频聚焦,但包含了不同响应情况,例如:电话、QQ、微信等通话应用。
//public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; //使用场景:应用只需短暂的音频聚焦,来播放一些提示类语音消息,或录制一段语音。例如:闹铃,导航等应用。
//public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; //使用场景:同样您的应用只是需要短暂的音频聚焦。未知时长,但不允许被其它应用截取音频焦点。例如:录音软件。
//return AUDIOFOCUS_REQUEST_GRANTED(成功)
public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) {
PlayerBase.deprecateStreamTypeForPlayback(streamType,
"AudioManager", "requestAudioFocus()");
int status = AUDIOFOCUS_REQUEST_FAILED;
// status is guaranteed to be either AUDIOFOCUS_REQUEST_FAILED or
// AUDIOFOCUS_REQUEST_GRANTED as focus is requested without the
// AUDIOFOCUS_FLAG_DELAY_OK flag
status = requestAudioFocus(l,
new AudioAttributes.Builder()
.setInternalLegacyStreamType(streamType).build(),
durationHint,0);
return status;
}
public int requestAudioFocus(OnAudioFocusChangeListener l,
@NonNull AudioAttributes requestAttributes,
int durationHint,
int flags) throws IllegalArgumentException {
//AUDIOFOCUS_FLAG_DELAY_OK|AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS
//AUDIOFOCUS_FLAG_DELAY_OK--> 在请求音频焦点时使用此标志以指示请求者可以不立即授予音频焦点
//系统处于焦点不能改变的状态,但稍后被授予焦点时此条件结束
return requestAudioFocus(l, requestAttributes, durationHint,
flags & AUDIOFOCUS_FLAGS_APPS,null);;
}
public int requestAudioFocus(OnAudioFocusChangeListener l,
@NonNull AudioAttributes requestAttributes,
int durationHint,
int flags,
AudioPolicy ap) throws IllegalArgumentException {
//新构建一个AudioFocusRequest(从AudioFocusRequest.java中构建)
final AudioFocusRequest afr = new AudioFocusRequest.Builder(durationHint)
.setOnAudioFocusChangeListenerInt(l, null)
.setAudioAttributes(requestAttributes)
.setAcceptsDelayedFocusGain((flags & AUDIOFOCUS_FLAG_DELAY_OK)
== AUDIOFOCUS_FLAG_DELAY_OK)
.setWillPauseWhenDucked((flags & AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS)
== AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS)
.setLocksFocus((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK)
.build();
//afr为新构建的afr,ap=null
return requestAudioFocus(afr, ap);
}
Class AudioFocusRequest
//class AudioFocusRequest包含
//private final @Nullable OnAudioFocusChangeListener mFocusListener;
mFocusListener就是app的interface
//private final @Nullable Handler mListenerHandler;
//private final @NonNull AudioAttributes mAttr;
//private final int mFocusGain;
mFocusGain就是焦点类型,详见附录
//private final int mFlags;
flag包括申请AudioFocus的一些额外行为,可以用作组合:
AudioManager. AUDIOFOCUS_FLAG_DELAY_OK:
当系统的AudioFcous被某些应用(例如电话)占有时,app无法申请到音频焦点,但可以通过Builder#setAcceptsDelayedFocusGain指定该次申请的AudioFocus可以当其他app接触时被申请到,当申请成功时系统会由 OnAudioFocusChangeListener 传入的AudioManager#AUDIOFOCUS_REQUEST_DELAYED告知app音频焦点被延迟申请到。
AudioManager. AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS:系统在处理 AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK类型的申请时,会自动将其他持有音频焦点的播放进行降低音量。但由setWillPauseWhenDucked接口所指定的AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS参数会告知系统当本次申请的焦点被其他app抢走时,播放的音频会由onAudioFocusChange回调给应用进行处理而不是自动地被系统降低音量。
AudioManager. AUDIOFOCUS_FLAG_LOCK:只有系统app可访问的hide接口setLocksFocus所设置,当设置后音频焦点处于被锁定状态,其他app将无法申请到音焦。
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
registerAudioFocusRequest(afr);
final IAudioService service = getService();
final int status;
//clientid 申请audiofocus的app的焦点
final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
final BlockingFocusResultReceiver focusReceiver;
synchronized (mFocusRequestsLock) {
try {
// TODO status contains result and generation counter for ext policy
status = service.requestAudioFocus(afr.getAudioAttributes(),
afr.getFocusGain()//焦点类型
mICallBack,
mAudioFocusDispatcher,//AudioFocus调度接口(aidl)
clientId,
getContext().getOpPackageName() /* package name */, afr.getFlags(),
ap != null ? ap.cb() : null,
sdk);
}
focusReceiver = new BlockingFocusResultReceiver(clientId);
mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
}
focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
synchronized (mFocusRequestsLock) {
mFocusRequestsAwaitingResult.remove(clientId);
}
return focusReceiver.requestResult();
}
--registerAudioFocusRequest(afr);
public void registerAudioFocusRequest(@NonNull AudioFocusRequest afr) {
//得到AudioFocusRequester的app端的监听handler
final Handler h = afr.getOnAudioFocusChangeListenerHandler();
//ServiceEventHandlerDelegate是一个消息处理代表
final FocusRequestInfo fri = new FocusRequestInfo(afr, new ServiceEventHandlerDelegate(h).getHandler());
final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
//将handler强转成string,和service处理存放在成员变量mAudioFocusIdListenerMap中
mAudioFocusIdListenerMap.put(key, fri);
}
当APP申请焦点后,AudioManager主要的作⽤有如下两方面:
1.将焦点的各类信息打包发送给AudioService并且
2.将AudioFocusListenenID和该app AudioFoucs change HandlerDelegate处理服务存放在
AudioManger的成员变量mAudioFocusIdListenerMap中。
接下来看是AudioService是继续进行什么处理:
form:framework/base/service/core/java/com/android/server/audio/AudioService.java
public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
IAudioPolicyCallback pcb, int sdk) {
final int uid = Binder.getCallingUid();
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "focus")
.setUid(uid)
//.putInt("durationHint", durationHint)
.set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
.set(MediaMetrics.Property.CLIENT_NAME, clientId)
.set(MediaMetrics.Property.EVENT, "requestAudioFocus")
.set(MediaMetrics.Property.FLAGS, flags);
// permission checks
...
mmi.record();
return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
clientId, callingPackageName, flags, sdk,
forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/);
}
framework/base/service/core/java/com/android/server/audio/MediaFocusControl.java
protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
int flags, int sdk, boolean forceDuck, int testUid) {
new MediaMetrics.Item(mMetricsId)
.setUid(Binder.getCallingUid())
.set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
.set(MediaMetrics.Property.CLIENT_NAME, clientId)
.set(MediaMetrics.Property.EVENT, "requestAudioFocus")
.set(MediaMetrics.Property.FLAGS, flags)
.set(MediaMetrics.Property.FOCUS_CHANGE_HINT,
AudioManager.audioFocusToString(focusChangeHint))
.record();
synchronized(mAudioFocusLock) {
//判断焦点栈是否已经满了,如果满了就直接失败退出
if (mFocusStack.size() > MAX_STACK_SIZE) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
// handle delayed focus
boolean focusGrantDelayed = false;
if (!canReassignAudioFocus()) {
if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
} else {
focusGrantDelayed = true;
}
}
AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
//如果当前cilent id已经存在了,并且申请焦点的原因和以前一致,则不再申请
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
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;
}
if (!focusGrantDelayed) {
mFocusStack.pop();
// the entry that was "popped" is the same that was "peeked" above
fr.release();
}
}
// focus requester might already be somewhere below in the stack, remove it
removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
clientId, afdh, callingPackageName, uid, this, sdk);
if (focusGrantDelayed) {
// focusGrantDelayed being true implies we can't reassign focus right now
// which implies the focus stack is not empty.
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
propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
//focus requester放在栈顶
mFocusStack.push(nfr);
nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
}
notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
}
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
从上面代码可以看到可以分为如下几个步骤:
1.propagateFocusLossFromGain_syncAf通知stack中其他app申请的焦点有焦点要来申请了
//propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck)
/* focusChangeHint:准备申请焦点的类型
* nfr:准备申请焦点的FocusRequester实体类
*/
private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
boolean forceDuck) {
final List<String> clientsToRemove = new LinkedList<String>();
if (!mFocusStack.empty()) {
for (FocusRequester focusLoser : mFocusStack) {
//focusLoser是要被争抢的焦点,fr是准备申请的焦点
final boolean isDefinitiveLoss =
focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
if (isDefinitiveLoss) {
clientsToRemove.add(focusLoser.getClientId());
}
}
}
//将要通知的app的client id加到clientsToRemove
for (String clientToRemove : clientsToRemove) {
removeFocusStackEntry(clientToRemove, false /*signal*/,
true);
}
}
private void removeFocusStackEntry(String clientToRemove, boolean signal,
boolean notifyFocusFollowers) {
AudioFocusInfo abandonSource = null;
if (signal) {
// notify the new top of the stack it gained focus
notifyTopOfAudioFocusStack();
}
}
被抢占的焦点要如何处理失去的焦点呢?
|–focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck)
boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)
{
//通过申请焦点app的申请类型得到要失去焦点app的焦点类型
final int focusLoss = focusLossForGainRequest(focusGain);
//处理失去焦点
handleFocusLoss(focusLoss, frWinner, forceDuck);
return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
}
void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner, boolean forceDuck)
{
if (focusLoss != mFocusLossReceived) {
mFocusLossReceived = focusLoss;
}
if (PlaybackActivityMonitorStub.get().ignoreFocusRequest(frWinner,
getClientUid())) {
mFocusLossReceived = mLastFocusLossReceived;
return;
}
if (frWinner != null) {
//处理些duck相关的焦点类型
handled = frameworkHandleFocusLoss(focusLoss, frWinner, forceDuck);
}
if (handled) {
Log.d(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+ " to " + mClientId + ", response handled by framework");
mFocusController.notifyExtPolicyFocusLoss_syncAf(
toAudioFocusInfo(), false /* wasDispatched */);
return; // with mFocusLossWasNotified = false
}
final IAudioFocusDispatcher fd = mFocusDispatcher;
//处理AudioFocus change相关逻辑
fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
}
在frameworkHandleFocusLoss函数中处理,如果申请焦点为AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK的话,则进行相关逻辑处理,分为被抢占app焦点的flag是否为AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS,若包含AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS则不进行降低音量处理,若没有则进行降低音量处理(mFocusController.duckPlayers)
private boolean frameworkHandleFocusLoss(int focusLoss, @NonNull final FocusRequester frWinner,
boolean forceDuck) {
if (frWinner.mCallingUid == this.mCallingUid) {
return false;
}
if (focusLoss == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
if (!MediaFocusControl.ENFORCE_DUCKING) {
return false;
}
//如果被抢占app是mGrantFlags是AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK,则进入,
//避免走到duck处理逻辑中(造成的效果就是提示音和被抢占的音一起播放)
if (!forceDuck && ((mGrantFlags
& AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0)) {
Log.d(TAG, "not ducking uid " + this.mCallingUid + " - flags");
return false;
}
//再次申请
if (!forceDuck && (MediaFocusControl.ENFORCE_DUCKING_FOR_NEW
&& this.getSdkTarget() <= MediaFocusControl.DUCKING_IN_APP_SDK_LEVEL)) {
Log.d(TAG, "not ducking uid " + this.mCallingUid + " - old SDK");
return false;
}
//如果要被抢占的app申请的flag不需要在AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK,在DUCK
//类型中暂停的话直接调用duckPlayers去降低音量
return mFocusController.duckPlayers(frWinner, /*loser*/ this, forceDuck);
}
if (focusLoss == AudioManager.AUDIOFOCUS_LOSS) {
if (!MediaFocusControl.ENFORCE_FADEOUT_FOR_FOCUS_LOSS) {
return false;
}
// candidate for fade-out before a receiving a loss
boolean playersAreFaded = mFocusController.fadeOutPlayers(frWinner, /* loser */ this);
if (playersAreFaded) {
mFocusLossFadeLimbo = true;
mFocusController.postDelayedLossAfterFade(this,
FadeOutManager.FADE_OUT_DURATION_MS);
return true;
}
}
return false;
}
每一个APP在申请焦点的时候在AudioManager中都会registerAudioFocusRequest(afr);
//frameworks/base/media/java/android/media/AudioManager.java
public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
//
registerAudioFocusRequest(afr);
}
//注册一个监听器以在音频焦点发生变化时调用并跟踪相关的焦点请求(包括用于监听器的处理程序)。
public void registerAudioFocusRequest(@NonNull AudioFocusRequest afr)
{
final Handler h = afr.getOnAudioFocusChangeListenerHandler();
final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null
new ServiceEventHandlerDelegate(h).getHandler());
final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
//把clientid和FocusRequestInfo对象存放在map中
mAudioFocusIdListenerMap.put(key, fri);
}
fd.dispatchAudioFocusChange
AudioManager::dispatchAudioFocusChange
—|AudioService::dispatchFocusChange
—|MediaFocusContorl:: dispatchFocusChange
—|fr. dispatchFocusChange
int dispatchFocusChange(int focusChange) {
//fd AudioManager初始化的时候就会构建FocusDispatcher
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd == null) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
if (focusChange == AudioManager.AUDIOFOCUS_NONE) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
} else if ((focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
|| focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
|| focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
|| focusChange == AudioManager.AUDIOFOCUS_GAIN)
&& (mFocusGainRequest != focusChange)){
Log.w(TAG, "focus gain was requested with " + mFocusGainRequest
+ ", dispatching " + focusChange);
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
|| focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
|| focusChange == AudioManager.AUDIOFOCUS_LOSS) {
mFocusLossReceived = focusChange;
}
fd.dispatchAudioFocusChange(focusChange, mClientId);
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {
@Override
public void dispatchAudioFocusChange(int focusChange, String id) {
final FocusRequestInfo fri = findFocusRequestInfo(id);
if (fri != null) {
final OnAudioFocusChangeListener listener =
fri.mRequest.getOnAudioFocusChangeListener();
if (listener != null) {
final Handler h = (fri.mHandler == null) ?
mServiceEventHandlerDelegate.getHandler() : fri.mHandler;
//发送消息 等到service接收
final Message m = h.obtainMessage(
MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/,
id/*obj*/);
h.sendMessage(m);
}
}
}
接下里看一下ServiceEventHandlerDelegate,帮助类处理将音频服务事件转发到适当的侦听器,主要是接受MSSG_FOCUS_CHANGE类的消息
主要是用于当foucs 改变的时候进行app的onAudioFocusChange(三方app接口)调用操作
private class ServiceEventHandlerDelegate {
private final Handler mHandler;
ServiceEventHandlerDelegate(Handler handler) {
if (looper != null) {
// implement the event handler delegate to receive events from audio service
mHandler = new Handler(looper) {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSSG_FOCUS_CHANGE: {
final FocusRequestInfo fri = findFocusRequestInfo((String)msg.obj);
if (fri != null) {
final OnAudioFocusChangeListener listener =fri.mRequest.getOnAudioFocusChangeListener();
if (listener != null) {
Log.d(TAG, "dispatching onAudioFocusChange("+ msg.arg1 + ") to " + msg.obj);listener.onAudioFocusChange(msg.arg1);
listener.onAudioFocusChange(msg.arg1);
}
}
}
}
}
实例分析
场景一 打开斗鱼直播(切后台仍然占据焦点)、再打开BilBil
04-26 16:01:31.340 1621 5640 I MediaFocusControl: abandonAudioFocus() from uid/pid 10255/12551 clientId=android.media.AudioManager@e2de18dcom.douyu.sdk.liveshell.player.BasePlayerPresenter$2@4502f42
04-26 16:01:31.411 1621 4648 E AS.AudioService: AudioService.requestAudioFocus
04-26 16:01:31.412 1621 4648 I MediaFocusControl: requestAudioFocus() from uid/pid 10255/12551 AA=USAGE_MEDIA/CONTENT_TYPE_MUSIC clientId=android.media.AudioManager@e2de18dcom.douyu.sdk.liveshell.player.BasePlayerPresenter$2@4502f42 callingPack=air.tv.douyu.android req=1 flags=0x0 sdk=28
//斗鱼AudioTrack 创建
04-26 16:01:31.487 12551 13350 D AudioTrack: set: mPowerTrackStatus=0
04-26 16:01:31.488 924 17110 I AudioFlinger_Threads: create audiotrack for air.tv.douyu.android uid 10255
//AudioTrack Start
04-26 16:01:31.494 12551 13353 D AudioTrack: start(87): 0x73b8682a00, prior state:STATE_STOPPED
//再次打开BilBil
04-26 16:01:41.940 13711 14009 D AudioTrack: set: mPowerTrackStatus=0
04-26 16:01:41.944 924 12520 I AudioFlinger_Threads: create audiotrack for tv.danmaku.bili uid 10223
//BilBil申请短暂焦点--AUDIOFOCUS_GAIN_TRANSIENT
04-26 16:01:42.142 1621 5728 I MediaFocusControl: requestAudioFocus() from uid/pid 10223/13547 AA=USAGE_MEDIA/CONTENT_TYPE_MOVIE clientId=android.media.AudioManager@2b64eb7x3.a.e.o.a$a@ce5b1b6 callingPack=tv.danmaku.bili req=2 flags=0x3 sdk=33
//斗鱼handler dispatch,由于B站申请的是短暂焦点,所以斗鱼listener回调给APP并不会做什么处理,例如把斗鱼AudioTrack:Pause或者stop掉
04-26 16:01:42.146 12551 12551 D AudioManager: dispatching onAudioFocusChange(-2) to android.media.AudioManager@e2de18dcom.douyu.sdk.liveshell.player.BasePlayerPresenter$2@4502f42
场景二 打开斗鱼直播(切后台仍然占据焦点)、再打开QQ音乐
04-26 16:09:54.991 1621 1637 I MediaFocusControl: abandonAudioFocus() from uid/pid 10255/14699 clientId=android.media.AudioManager@50155e8com.douyu.sdk.liveshell.player.BasePlayerPresenter$2@32e7401
04-26 16:09:55.063 1621 5313 I MediaFocusControl: requestAudioFocus() from uid/pid 10255/14699 AA=USAGE_MEDIA/CONTENT_TYPE_MUSIC clientId=android.media.AudioManager@50155e8com.douyu.sdk.liveshell.player.BasePlayerPresenter$2@32e7401 callingPack=air.tv.douyu.android req=1 flags=0x0 sdk=28
04-26 16:09:55.150 14699 15396 D AudioTrack: set: mPowerTrackStatus=0
04-26 16:09:55.159 14699 15406 D AudioTrack: start(94): 0x7392abee00, prior state:STATE_STOPPED
//从QQ音乐开始抢占斗鱼焦点开始分析
//96 为QQ音乐开始播放
04-26 16:11:12.854 1218 16365 D AudioTrack: set: mPowerTrackStatus=0
04-26 16:11:12.858 924 1916 I AudioFlinger_Threads: create audiotrack for com.miui.player uid 10214
04-26 16:11:12.875 1218 16365 D AudioTrack: start(96): 0xf4849900, prior state:STATE_STOPPED
//QQ音乐存申请的焦点为AUDIOFOCUS_GAIN=1,时长不定
04-26 16:11:13.524 1621 4648 I MediaFocusControl: requestAudioFocus() from uid/pid 10214/15708 AA=USAGE_MEDIA/CONTENT_TYPE_MUSIC clientId=android.media.AudioManager@5428e45com.tencent.qqmusicsdk.player.listener.AudioFocusListener$1@30dc99a callingPack=com.miui.player req=1 flags=0x0 sdk=31
//斗鱼dispatch是-1,回调给斗鱼app
04-26 16:11:14.532 14699 14699 D AudioManager: dispatching onAudioFocusChange(-1) to android.media.AudioManager@50155e8com.douyu.sdk.liveshell.player.BasePlayerPresenter$2@32e7401
//斗鱼listener收到-1 放弃焦点的事件之后就会调用abandonAudioFocus,并stop AudioTrack
04-26 16:11:14.536 1621 5313 I MediaFocusControl: abandonAudioFocus() from uid/pid 10255/14699 clientId=android.media.AudioManager@50155e8com.douyu.sdk.liveshell.player.BasePlayerPresenter$2@32e7401
//斗鱼AudioTrack Stop掉之后
04-26 16:11:14.649 14699 15406 D AudiTrack: stop(94): 0x7392abee00, prior state:STATE_ACTIVE
总结,需要看申请APP的申请焦点的类型是什么,被抢占的焦点的app会根据申请焦点的类型
场景三 打开BilBil、接受到微信消息
//BilBil申请焦点
04-27 11:41:16.555 1629 3594 I MediaFocusControl: abandonAudioFocus() from uid/pid 10224/29578 clientId=android.media.AudioManager@7ac73f1x3.a.e.o.a$a@c189418
04-27 11:41:16.653 1629 1647 E AS.AudioService: AudioService.requestAudioFocus
//注意这里的flag是0x3 包含了AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS,即打算在DUCK的时候pause的
04-27 11:41:16.653 1629 1647 I MediaFocusControl: requestAudioFocus() from uid/pid 10224/29578 AA=USAGE_MEDIA/CONTENT_TYPE_MOVIE clientId=android.media.AudioManager@7ac73f1x3.a.e.o.a$a@c189418 callingPack=tv.danmaku.bili req=2 flags=0x3 sdk=33
//微信消息提示音申请焦点 req=3 flag=0
MediaFocusControl: requestAudioFocus() from uid/pid 1000/3761 AA=USAGE_NOTIFICATION/CONTENT_TYPE_SONIFICATION clientId=android.media.AudioManager@6785bdf callingPack=com.android.systemui req=3 flags=0x0 sdk=33
//不进入mFocusController.duckPlayers逻辑,不会进行提示音DUCK处理(被抢占的焦点音量降低)
04-27 11:41:27.818 1629 1916 D MediaFocusControl: not ducking uid 10224 - flags
04-27 11:41:27.819 29578 29610 D AudioManager: dispatching onAudioFocusChange(-3) to android.media.AudioManager@7ac73f1x3.a.e.o.a$a@c189418
这种场景再接搜微信消息的时候,B站音量不会变小。
总结 被抢占焦点APP的flag支持AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS要dispatch给给自己让其做相应的处理动作。所以不会在frameworkHandleFocusLoss函数中做mFocusController.duckPlayers处理。
场景四 打开QQ音乐、接受到微信消息
//有QQ音乐在申请flag的时候是0x0
04-27 11:58:35.294 1629 1647 I MediaFocusControl: requestAudioFocus() from uid/pid 10215/32017 AA=USAGE_MEDIA/CONTENT_TYPE_MUSIC clientId=android.media.AudioManager@14900aecom.tencent.qqmusicsdk.player.listener.AudioFocusListener$1@ede7e4f callingPack=com.miui.player req=1 flags=0x0 sdk=31
//所以会走到mFocusController.duckPlayers做音量降低处理
04-27 11:59:31.441 1629 5706 I MediaFocusControl: requestAudioFocus() from uid/pid 1000/3761 AA=USAGE_NOTIFICATION/CONTENT_TYPE_SONIFICATION clientId=android.media.AudioManager@8094ed7 callingPack=com.android.systemui req=3 flags=0x0 sdk=33
04-27 11:59:31.444 1629 5706 D MediaFocusControl: ready to mFocusController duckPlayers
04-27 11:59:31.445 1629 5706 D MediaFocusControl: NOT dispatching LOSS_TRANSIENT_CAN_DUCK to android.media.AudioManager@14900aecom.tencent.qqmusicsdk.player.listener.AudioFocusListener$1@ede7e4f, response handled by framework
这种场景再接搜微信消息的时候,QQ音乐会瞬时音量变小,然后等消息结束后在音量变大。
待补充其他场景…
附录
焦点类型
//focusChange 列表
public static final int AUDIOFOCUS_NONE = 0;
public static final int AUDIOFOCUS_GAIN = 1;
public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2;
public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3;
public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4;
public static final int AUDIOFOCUS_LOSS = -1 * AUDIOFOCUS_GAIN;=-2
public static final int AUDIOFOCUS_LOSS_TRANSIENT
= -1 * AUDIOFOCUS_GAIN_TRANSIENT;=-2
public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK =
-1 * AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;=-3
public static final int AUDIOFOCUS_GAIN = 1; //使用场景:应用需要聚焦音频的时长会根据用户的使用时长改变,属于不确定期限。例如:多媒体播放或者播客等应用。
public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; //使用场景:应用只需短暂的音频聚焦,但包含了不同响应情况,例如:电话、QQ、微信等通话应用。
public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; //使用场景:应用只需短暂的音频聚焦,来播放一些提示类语音消息,或录制一段语音。例如:闹铃,导航等应用。
public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; //使用场景:同样您的应用只是需要短暂的音频聚焦。未知时长,但不允许被其它应用截取音频焦点。例如:录音软件。
获取焦点返回值
public static final int AUDIOFOCUS_REQUEST_FAILED = 0; //失败
public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; //成功
public static final int AUDIOFOCUS_REQUEST_DELAYED = 2; //延迟焦点
对于延迟焦点举个例子:
当你打电话的时候想玩游戏,是只是想操作游戏并不想听到游戏的声音,因为你此时正在打电话,这时候游戏app就可以申请一个延迟焦点,当你打完电话的时候才会正在授权焦点给游戏app。
音频焦点状态
AUDIOFOCUS_GAIN:重新获取音频焦点
对应场景:如果通话结束,恢复播放;获取音量并且恢复音量
AUDIOFOCUS_LOSS:永久性失去音频焦点,则其他应用会播放音频。您的应用应立即暂停播放,清理资源;因为它不会收到 AUDIOFOCUS_GAIN 回调。
对应场景:当永久丢失焦点,比如同时打开播放器,则停止或者暂停播放,否则出现两个声音
AUDIOFOCUS_ LOSS_TRANSIENT:暂时失去音频焦点,但是很快就会重新获得,在此状态应该暂停所有音频播放,但是不能清除资源
对应场景:比如比如来了电话或者微信视频音频聊天等等,则暂停或者停止播放
AUDIOFOCUS_ LOSS_TRANSIENT _CAN_DUCK:暂时失去音频焦点,应用应该降低音量(如果您不依赖于自动降低音量),允许持续播放音频(以很小的声音),不需要完全停止播放
对应场景:比如手机来了通知/导航。前提是你的通知是震动或者声音时,会短暂地将媒体音量减小一半