android系统允许2个或以上的android应用同时向同一个输出流播放音频,系统会将所有的系统会将所有音频流混合在一起。
以下分析是基于android-10.0.0_r36代码
AudioFocus采用合作模式,行为恰当的音频应用应根据以下一般准则来管理音频焦点:
关于android AudioFocus的使用,先看官网的一段代码说明
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
playbackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(playbackAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(afChangeListener, handler)
.build();
mediaPlayer = new MediaPlayer();
final Object focusLock = new Object();
boolean playbackDelayed = false;
boolean playbackNowAuthorized = false;
// ...
int res = audioManager.requestAudioFocus(focusRequest);
synchronized(focusLock) {
if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
playbackNowAuthorized = false;
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
playbackNowAuthorized = true;
playbackNow();
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
playbackDelayed = true;
playbackNowAuthorized = false;
}
}
// ...
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
if (playbackDelayed || resumeOnFocusGain) {
synchronized(focusLock) {
playbackDelayed = false;
resumeOnFocusGain = false;
}
playbackNow();
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
synchronized(focusLock) {
resumeOnFocusGain = false;
playbackDelayed = false;
}
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
synchronized(focusLock) {
resumeOnFocusGain = true;
playbackDelayed = false;
}
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// ... pausing or ducking depends on your app
break;
}
}
}
关于onAudioFocusChange回调的说明:
调用requestAudioFocus的时候,如果获取成功,是立即返回的,不会触发onAudioFocusChange里面的AudioManager.AUDIOFOCUS_GAIN这个场景;onAudioFocusChange是这之后的状态发生变化的时候才会触发,比较说主动调用abandonAudioFocus、或者是其它应用也调用了requestAudioFocus,相应的状态变化的时候才触发
相关代码路径
frameworks/base/media/java/android/media/AudioFocusRequest.java
frameworks/base/media/java/android/media/AudioAttributes.java
frameworks/base/media/java/android/media/AudioManager.java
AudioManager.OnAudioFocusChangeListener
frameworks/base/media/java/android/media/AudioSystem.java
frameworks/base/media/java/android/media/AudioFocusInfo.java
frameworks/base/services/core/java/com/android/server/audio/AudioService.java
frameworks/base/services/core/java/com/android/server/audio/FocusRequest.java
frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java
先了解一些基础定义
支持的stream
public static final String[] STREAM_NAMES = new String[] {
"STREAM_VOICE_CALL",
"STREAM_SYSTEM",
"STREAM_RING",
"STREAM_MUSIC",
"STREAM_ALARM",
"STREAM_NOTIFICATION",
"STREAM_BLUETOOTH_SCO",
"STREAM_SYSTEM_ENFORCED",
"STREAM_DTMF",
"STREAM_TTS",
"STREAM_ACCESSIBILITY"
};
获取音频焦点状态可能的结果,具体的使用看代码里面的说明,很清楚
其中GAIN有4种
LOSS有3种
/**
* Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration.
*/
public static final int AUDIOFOCUS_GAIN = 1;
/**
* Used to indicate a temporary gain or request of audio focus, anticipated to last a short
* amount of time. Examples of temporary changes are the playback of driving directions, or an
* event notification.
*/
public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2;
/**
* Used to indicate a temporary request of audio focus, anticipated to last a short
* amount of time, and where it is acceptable for other audio applications to keep playing
* after having lowered their output level (also referred to as "ducking").
* Examples of temporary changes are the playback of driving directions where playback of music
* in the background is acceptable.
*/
public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3;
/**
* Used to indicate a temporary request of audio focus, anticipated to last a short
* amount of time, during which no other applications, or system components, should play
* anything. Examples of exclusive and transient audio focus requests are voice
* memo recording and speech recognition, during which the system shouldn't play any
* notifications, and media playback should have paused.
*/
public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4;
/**
* Used to indicate a loss of audio focus of unknown duration.
*/
public static final int AUDIOFOCUS_LOSS = -1 * AUDIOFOCUS_GAIN;
/**
* Used to indicate a transient loss of audio focus.
*/
public static final int AUDIOFOCUS_LOSS_TRANSIENT = -1 * AUDIOFOCUS_GAIN_TRANSIENT;
/**
* Used to indicate a transient loss of audio focus where the loser of the audio focus can
* lower its output volume if it wants to continue playing (also referred to as "ducking"), as
* the new focus owner doesn't require others to be silent.
*/
public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK =
-1 * AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
关键属性和方法说明
调用流程说明
AudioManager.requestAudioFocus --> AudioService.requestAudioFocus --> MediaFocusControl.requestAudioFocus
先看一下AudioManager.requestAudioFocus方法,里面有一个地方要关注,就是我们实现的Listner对象保存在mAudioFocusIdListenerMap里面
private final ConcurrentHashMap<String, FocusRequestInfo> mAudioFocusIdListenerMap =
new ConcurrentHashMap<String, FocusRequestInfo>();
public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
// 将Listener保存到mAudioFocusIdListenerMap里面
registerAudioFocusRequest(afr);
final IAudioService service = getService();
final int status;
final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
final BlockingFocusResultReceiver focusReceiver;
synchronized (mFocusRequestsLock) {
try {
// 调用AudioService的requestAudioFocus
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();
}
if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
// default path with no external focus policy
return status;
}
}
}
最终的实现在MediaFocusControl里面
protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
int flags, int sdk, boolean forceDuck) {
//1.做前期的一些检查动作
if (!cb.pingBinder()) {
Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
callingPackageName) != AppOpsManager.MODE_ALLOWED) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
synchronized(mAudioFocusLock) {
// 2.超过stack的最大值100,则直接返回失败
if (mFocusStack.size() > MAX_STACK_SIZE) {
Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
// 3.判断通话状态
boolean enteringRingOrCall = !mRingOrCallActive
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
if (enteringRingOrCall) { mRingOrCallActive = true; }
final AudioFocusInfo afiForExtPolicy;
if (mFocusPolicy != null) {
// construct AudioFocusInfo as it will be communicated to audio focus policy
afiForExtPolicy = new AudioFocusInfo(aa, Binder.getCallingUid(),
clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
flags, sdk);
} else {
afiForExtPolicy = null;
}
// 4.判断是否需要延时处理
boolean focusGrantDelayed = false;
if (!canReassignAudioFocus()) {
if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
} else {
// request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
// granted right now, so the requester will be inserted in the focus stack
// to receive focus later
focusGrantDelayed = true;
}
}
// 5.如果外部的焦点policy,则直接返回
if (mFocusPolicy != null) {
if (notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, fd, cb)) {
// stop handling focus request here as it is handled by external audio
// focus policy (return code will be handled in AudioManager)
return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
} else {
// an error occured, client already dead, bail early
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
}
// 6.如果当前栈顶的应用和请求的客户端是一致的,则直接返回AUDIOFOCUS_REQUEST_GRANTED
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;
}
// 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();
}
}
// 7.如果在栈中已经存在,则删除
removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
clientId, afdh, callingPackageName, Binder.getCallingUid(), 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 {
// 8.先回调栈中Request的Listener来处理焦点失去的结果,然后把当前的请求添加到栈顶
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);
// 9.如果是通话状态,先延时100ms,然后再检查通话状态,如果是在通话中,则mutePlayersForCall
if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
}
}//synchronized(mAudioFocusLock)
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
里面定义了各种场景下的处理原则:
另外propagateFocusLossFromGain_syncAf方法里面的功能需要重点看一下,和其它应用协调焦点控制的一个关键点。此方法的调用是在mFocusStack.push(nfr)之前的,所以调用requestAudioFocus的请求client不在栈中,根据handleFocusLossFromGain的结果来决定是否从栈中删除其它的request对象,如果是AUDIOFOCUS_LOSS(长时间失去焦点),则从focusStack里面移除
private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
boolean forceDuck) {
final List<String> clientsToRemove = new LinkedList<String>();
// going through the audio focus stack to signal new focus, traversing order doesn't
// matter as all entries respond to the same external focus gain
for (FocusRequester focusLoser : mFocusStack) {
final boolean isDefinitiveLoss =
focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
if (isDefinitiveLoss) {
clientsToRemove.add(focusLoser.getClientId());
}
}
for (String clientToRemove : clientsToRemove) {
removeFocusStackEntry(clientToRemove, false /*signal*/,
true /*notifyFocusFollowers*/);
}
}
removeFocusStackEntry方法里面的实现说明:
private void removeFocusStackEntry(String clientToRemove, boolean signal,
boolean notifyFocusFollowers) {
// is the current top of the focus stack abandoning focus? (because of request, not death)
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
{
//Log.i(TAG, " removeFocusStackEntry() removing top of stack");
FocusRequester fr = mFocusStack.pop();
fr.release();
if (notifyFocusFollowers) {
final AudioFocusInfo afi = fr.toAudioFocusInfo();
afi.clearLossReceived();
notifyExtPolicyFocusLoss_syncAf(afi, false);
}
if (signal) {
// notify the new top of the stack it gained focus
notifyTopOfAudioFocusStack();
}
} else {
// focus is abandoned by a client that's not at the top of the stack,
// no need to update focus.
// (using an iterator on the stack so we can safely remove an entry after having
// evaluated it, traversal order doesn't matter here)
Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
while(stackIterator.hasNext()) {
FocusRequester fr = stackIterator.next();
if(fr.hasSameClient(clientToRemove)) {
Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
+ clientToRemove);
stackIterator.remove();
// stack entry not used anymore, clear references
fr.release();
}
}
}
}
具体的过程可以参考一下如下Log打印
// 录像的时候,首次调用requestAudioFocus的时候,mFocusStack.empty()为true
08-22 18:42:17.318 1282 1751 I MediaFocusControl: requestAudioFocus() from uid/pid 10090/3381 clientId=android.media.AudioManager@32cf82c callingPack=com.android.camera2 req=1 flags=0x0 sdk=28
// 第7步中stack为空,直接返回
08-22 18:42:17.323 1282 1751 W MediaFocusControl: removeFocusStackEntry clientToRemove=android.media.AudioManager@32cf82c ; signal = false ; notifyFocusFollowers = false
// 走到第8步,调用mFocusStack.push(nfr),把当前请求加入栈中
08-22 18:42:17.326 1282 1751 W MediaFocusControl: mFocusStack.push(nfr) source:android.os.BinderProxy@a2d47f3 -- pack: com.android.camera2 -- client: android.media.AudioManager@32cf82c -- gain: GAIN -- flags: -- loss: none -- notified: true -- uid: 10090 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28
// push之后打印stack里面的元素,只有上一步添加的
08-22 18:42:17.327 1282 1751 W MediaFocusControl: after push itr.next() = source:android.os.BinderProxy@a2d47f3 -- pack: com.android.camera2 -- client: android.media.AudioManager@32cf82c -- gain: GAIN -- flags: -- loss: none -- notified: true -- uid: 10090 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28
// 再次录像的时候,调用requestAudioFocus,走的第6步,当前栈顶和请求的是同一个client,直接返回了
08-22 18:42:25.012 1282 1777 I MediaFocusControl: requestAudioFocus() from uid/pid 10090/3381 clientId=android.media.AudioManager@32cf82c callingPack=com.android.camera2 req=1 flags=0x0 sdk=28
08-22 18:42:25.014 1282 1777 W MediaFocusControl: mFocusStack.peek() = source:android.os.BinderProxy@a2d47f3 -- pack: com.android.camera2 -- client: android.media.AudioManager@32cf82c -- gain: GAIN -- flags: -- loss: none -- notified: true -- uid: 10090 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28
// 调用jcvideoplayer播放之前录制的视频
08-22 18:43:01.777 1313 2746 I MediaFocusControl: requestAudioFocus() from uid/pid 10105/3961 clientId=android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 callingPack=com.xghotplay.bluedo req=2 flags=0x0 sdk=28
// 在第7步前添加了打印,新的请求没有加入的时候,栈中只有一个元素
08-22 18:43:01.779 1313 2746 V MediaFocusControl: dispatching LOSS_TRANSIENT to android.media.AudioManager@1ea53f5
08-22 18:43:01.779 1313 2746 W MediaFocusControl: mFocusStack.push(nfr) source:android.os.BinderProxy@e8cbb7b -- pack: com.xghotplay.bluedo -- client: android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 -- gain: GAIN_TRANSIENT -- flags: -- loss: none -- notified: true -- uid: 10105 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28
08-22 18:43:01.779 1313 2746 V AudioService.PlaybackActivityMonitor: unduckPlayers: uids winner=10105
08-22 18:43:01.779 1313 2746 V AudioService.PlaybackActivityMonitor: DuckingManager: unduckUid() uid:10105
// 把jcvideoplayer的client添加到stack里面,里面有2个元素,因为jcvideoplayer请求的是LOSS_TRANSIENT ,所以在propagateFocusLossFromGain_syncAf没有删除,所以有2个元素
08-22 18:43:01.780 1313 2746 W MediaFocusControl: after push itr.next() = source:android.os.BinderProxy@5b076bc -- pack: com.android.camera2 -- client: android.media.AudioManager@1ea53f5 -- gain: GAIN -- flags: -- loss: LOSS_TRANSIENT -- notified: true -- uid: 10090 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28
08-22 18:43:01.780 1313 2746 W MediaFocusControl: after push itr.next() = source:android.os.BinderProxy@e8cbb7b -- pack: com.xghotplay.bluedo -- client: android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 -- gain: GAIN_TRANSIENT -- flags: -- loss: none -- notified: true -- uid: 10105 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28
// jcvideoplayer再次请求,因为当前栈栈元素就是jcvideoplayer的,直接返回了
08-22 18:43:12.358 1313 2868 I MediaFocusControl: requestAudioFocus() from uid/pid 10105/3961 clientId=android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 callingPack=com.xghotplay.bluedo req=2 flags=0x0 sdk=28
08-22 18:43:12.359 1313 2868 W MediaFocusControl: mFocusStack.peek() = source:android.os.BinderProxy@e8cbb7b -- pack: com.xghotplay.bluedo -- client: android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 -- gain: GAIN_TRANSIENT -- flags: -- loss
// camera再次请求焦点
08-22 18:43:42.734 1313 2868 I MediaFocusControl: requestAudioFocus() from uid/pid 10090/3453 clientId=android.media.AudioManager@1ea53f5 callingPack=com.android.camera2 req=1 flags=0x0 sdk=28
// propagateFocusLossFromGain_syncAf里面把之前的jcvideoplayer的请求删除,因为是LOSS
08-22 18:43:42.737 1313 2868 I MediaFocusControl: AudioFocus removeFocusStackEntry(): removing entry for android.media.AudioManager@1ea53f5
08-22 18:43:42.737 1313 2868 V MediaFocusControl: dispatching LOSS to android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8
// 回调JCVideoPlayer的onAudioFocusChange
08-22 18:43:42.738 3961 3961 D AudioManager: dispatching onAudioFocusChange(-1) to android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8