一. 获取焦点流程
1. 电话焦点只有系统可以申请,如果是电话焦点,系统会把所有多媒体和游戏的音频流实例全部mute。同理电话焦点释放会解除mute操作
2. 系统管理的焦点栈有大小限制限制为100.大于100,抢占焦点失败。
3. 电话焦点状态下,其他app的所有抢占焦点的操作都会失败。
4. 我们传的OnAudioFocusListener决定了ClientID,相同的ClientID焦点栈中不会重复存储,OnAudioFocusListener最好进行复用,除了特殊的业务场景。电话焦点的ClientID由系统写死了。
我们的手机里经常会安装一些媒体类的应用,例如网易云音乐,QQ音乐,爱奇艺视频,优酷视频等等,你有没有想过,当我们听QQ音乐的歌曲时,切换到网易云音乐播放歌曲,或者打开爱奇艺观看视频时,QQ音乐播放的歌曲就会暂停,这是为什么呢?又是如何实现的呢?如果不暂停会是什么效果呢?
AudioFocus机制。AudioFoucs机制的设计主要是为了解决每个音源之间播放冲突的问题。
1. 应用获取焦点
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
pause();
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Resume playback
resume();
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// mAm.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
mAm.abandonAudioFocus(afChangeListener);
// Stop playback
stop();
}
}
};
private boolean requestFocus() {
// Request audio focus for playback
int result = mAm.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
获取 requestAudioFocus的第三个参数:
第三个参数:durationHint,获得焦点的时间长短,定义了四种类型
a、AUDIOFOCUS_GAIN //长时间获得焦点,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS
b、AUDIOFOCUS_GAIN_TRANSIENT //短暂性获得焦点,用完应立即释放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
c、AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK //短暂性获得焦点并降音,可混音播放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
d、AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE //短暂性获得焦点,录音或者语音识别,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
我们通常使用前面三种类型就可以了
去除音频焦点
// 拨完音乐后,去除音频焦点
OnCompletionListener completionListener = new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer player) {
if(!player.isLooping()){
mAm.abandonAudioFocus(afChangeListener);
}
}
};
private void play() {
if (requestFocus()) {
if (mediaPlayer == null) {
try {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(url);
mediaPlayer.prepare();
mediaPlayer.setOnCompletionListener(completionListener);
} catch (IOException e) {
e.printStackTrace();
}
}
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
2. 获取音频焦点源码
- frameworks/base/media/java/android/media/AudioManager.java
public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) {
PlayerBase.deprecateStreamTypeForPlayback(streamType,
"AudioManager", "requestAudioFocus()");
int status = AUDIOFOCUS_REQUEST_FAILED;
try {
// 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,
// AudioAttributes
new AudioAttributes.Builder()
.setInternalLegacyStreamType(streamType).build(),
// AUDIOFOCUS_GAIN
durationHint,
0 /* flags, legacy behavior */);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Audio focus request denied due to ", e);
}
return status;
}
// 系统api,需要电话权限
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public int requestAudioFocus(OnAudioFocusChangeListener l,
@NonNull AudioAttributes requestAttributes,
int durationHint,
int flags) throws IllegalArgumentException {
if (flags != (flags & AUDIOFOCUS_FLAGS_APPS)) {
throw new IllegalArgumentException("Invalid flags 0x"
+ Integer.toHexString(flags).toUpperCase());
}
return requestAudioFocus(l, requestAttributes, durationHint,
// flags 为 0
flags & AUDIOFOCUS_FLAGS_APPS,
null /* no AudioPolicy*/);
}
-----------------
public int requestAudioFocus(OnAudioFocusChangeListener l,
@NonNull AudioAttributes requestAttributes,
int durationHint,
int flags,
AudioPolicy ap) throws IllegalArgumentException {
// parameter checking
if (requestAttributes == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes argument");
}
if (!AudioFocusRequest.isValidFocusGain(durationHint)) {
throw new IllegalArgumentException("Invalid duration hint");
}
if (flags != (flags & AUDIOFOCUS_FLAGS_SYSTEM)) {
throw new IllegalArgumentException("Illegal flags 0x"
+ Integer.toHexString(flags).toUpperCase());
}
if (((flags & AUDIOFOCUS_FLAG_DELAY_OK) == AUDIOFOCUS_FLAG_DELAY_OK) && (l == null)) {
throw new IllegalArgumentException(
"Illegal null focus listener when flagged as accepting delayed focus grant");
}
if (((flags & AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS)
== AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) && (l == null)) {
throw new IllegalArgumentException(
"Illegal null focus listener when flagged as pausing instead of ducking");
}
if (((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK) && (ap == null)) {
throw new IllegalArgumentException(
"Illegal null audio policy when locking audio focus");
}
// 主要代码如下:
final AudioFocusRequest afr = new AudioFocusRequest.Builder(durationHint)
.setOnAudioFocusChangeListenerInt(l, null /* no Handler for this legacy API */)
.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();
return requestAudioFocus(afr, ap);
}
----------------
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
if (afr == null) {
throw new NullPointerException("Illegal null AudioFocusRequest");
}
// this can only be checked now, not during the creation of the AudioFocusRequest instance
if (afr.locksFocus() && ap == null) {
throw new IllegalArgumentException(
"Illegal null audio policy when locking audio focus");
}
// 1. 将所有抢占焦点的实例存放在一个map集合里面
registerAudioFocusRequest(afr);
final IAudioService service = getService();
final int status;
int sdk;
try {
sdk = getContext().getApplicationInfo().targetSdkVersion;
} catch (NullPointerException e) {
// some tests don't have a Context
sdk = Build.VERSION.SDK_INT;
}
// 2. 根据传进来的 OnAudioFocusChangeListener 计算 clientId
final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
final BlockingFocusResultReceiver focusReceiver;
synchronized (mFocusRequestsLock) {
try {
// TODO status contains result and generation counter for ext policy
// 3. 调用 AudioService函数 requestAudioFocus
status = service.requestAudioFocus(afr.getAudioAttributes(),
afr.getFocusGain(), mICallBack,
// mAudioFocusDispathcer 是一个本地的binder对象
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;
}
if (mFocusRequestsAwaitingResult == null) {
mFocusRequestsAwaitingResult =
new HashMap<String, BlockingFocusResultReceiver>(1);
}
focusReceiver = new BlockingFocusResultReceiver(clientId);
mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
}
focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
if (DEBUG && !focusReceiver.receivedResult()) {
Log.e(TAG, "requestAudio response from ext policy timed out, denying request");
}
synchronized (mFocusRequestsLock) {
mFocusRequestsAwaitingResult.remove(clientId);
}
return focusReceiver.requestResult();
}
// 1. 将所有抢占焦点的实例存放在一个map集合里面
public void registerAudioFocusRequest(@NonNull AudioFocusRequest afr) {
final Handler h = afr.getOnAudioFocusChangeListenerHandler();
final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :
new ServiceEventHandlerDelegate(h).getHandler());
// key 就是 clientId
final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
mAudioFocusIdListenerMap.put(key, fri);
}
// 2. 根据传进来的 OnAudioFocusChangeListener 计算 clientId
private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) {
if (l == null) {
return new String(this.toString());
} else {
return new String(this.toString() + l.toString());
}
}
// 3. 调用 AudioService函数 requestAudioFocus
- frameworks/base/services/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);
。。。主要代码
mmi.record();
return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
clientId, callingPackageName, flags, sdk,
forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/);
}
- frameworks/base/services/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))
//.set(MediaMetrics.Property.SDK, sdk)
.record();
// when using the test API, a fake UID can be injected (testUid is ignored otherwise)
// note that the test on flags is not a mask test on purpose, AUDIOFOCUS_FLAG_TEST is
// supposed to be alone in bitfield
// 会有以下log 打印
// 如:MediaFocusControl: requestAudioFocus() from uid/pid 10203/19193
final int uid = (flags == AudioManager.AUDIOFOCUS_FLAG_TEST)
? testUid : Binder.getCallingUid();
mEventLogger.log((new AudioEventLogger.StringEvent(
"requestAudioFocus() from uid/pid " + uid
+ "/" + Binder.getCallingPid()
+ " AA=" + aa.usageToString() + "/" + aa.contentTypeToString()
+ " clientId=" + clientId + " callingPack=" + callingPackageName
+ " req=" + focusChangeHint
+ " flags=0x" + Integer.toHexString(flags)
+ " sdk=" + sdk))
.printLog(TAG));
// we need a valid binder callback for clients
if (!cb.pingBinder()) {
Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
if ((flags != AudioManager.AUDIOFOCUS_FLAG_TEST)
// note we're using the real uid for appOp evaluation
&& (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
callingPackageName) != AppOpsManager.MODE_ALLOWED)) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
synchronized(mAudioFocusLock) {
// private static final int MAX_STACK_SIZE = 100;
// 焦点栈是有大小限制的最大是100个
if (mFocusStack.size() > MAX_STACK_SIZE) {
Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
// 是否进入到来电call 模式,进入到电话状态
// String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls";
boolean enteringRingOrCall = !mRingOrCallActive
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
// 设置为 true
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, uid,
clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
flags, sdk);
} else {
afiForExtPolicy = null;
}
// handle delayed focus
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;
}
}
// external focus 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;
}
}
// handle the potential premature death of the new holder of the focus
// (premature death == death before abandoning focus)
// Register for client death notification
AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
try {
cb.linkToDeath(afdh, 0);
} catch (RemoteException e) {
// client has already died!
Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
// 如果焦点栈不为空,且焦点栈的栈顶元素和当前的clientId一样,则返回 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();
}
}
// 1. 如果clientID 不是在栈顶的话,移除栈里面的 clientId
// 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 (mMultiAudioFocusEnabled
&& (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
if (enteringRingOrCall) {
if (!mMultiAudioFocusList.isEmpty()) {
for (FocusRequester multifr : mMultiAudioFocusList) {
multifr.handleFocusLossFromGain(focusChangeHint, nfr, forceDuck);
}
}
} else {
boolean needAdd = true;
if (!mMultiAudioFocusList.isEmpty()) {
for (FocusRequester multifr : mMultiAudioFocusList) {
if (multifr.getClientUid() == Binder.getCallingUid()) {
needAdd = false;
break;
}
}
}
if (needAdd) {
mMultiAudioFocusList.add(nfr);
}
nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
}
// 现在不能重新分配焦点,意味着焦点堆栈不是空的
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
// 2. propagateFocusLossFromGain_syncAf
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);
// 3. 如果是电话的话,check下电话焦点
if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
}
}//synchronized(mAudioFocusLock)
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
// 1. 如果clientID 不是在栈顶的话,移除栈里面的 clientId removeFocusStackEntry
@GuardedBy("mAudioFocusLock")
// 后面 2 个参数都是 false
private void removeFocusStackEntry(String clientToRemove, boolean signal,
boolean notifyFocusFollowers) {
AudioFocusInfo abandonSource = null;
// is the current top of the focus stack abandoning focus? (because of request, not death)
// 如果焦点栈不为空,并且在栈顶,则移除栈顶 ClientID
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
{
//Log.i(TAG, " removeFocusStackEntry() removing top of stack");
FocusRequester fr = mFocusStack.pop();
fr.maybeRelease();
if (notifyFocusFollowers) {
abandonSource = fr.toAudioFocusInfo();
}
// 这个signal在requestAudioFocus时传的时false,栈顶元素肯定时当前抢占焦点的实例,没必要通知
// 只有在abandonAudioFocus的时候才会传true
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)
// 如果不在栈顶,就通过迭代器去迭代,找到相同的clientId的然后出站,移除掉
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();
if (notifyFocusFollowers) {
abandonSource = fr.toAudioFocusInfo();
}
// stack entry not used anymore, clear references
fr.maybeRelease();
}
}
}
// focus followers still want to know focus was abandoned, handled as a loss
if (abandonSource != null) {
abandonSource.clearLossReceived();
notifyExtPolicyFocusLoss_syncAf(abandonSource, false);
}
if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
Iterator<FocusRequester> listIterator = mMultiAudioFocusList.iterator();
while (listIterator.hasNext()) {
FocusRequester fr = listIterator.next();
if (fr.hasSameClient(clientToRemove)) {
listIterator.remove();
fr.release();
}
}
if (signal) {
// notify the new top of the stack it gained focus
notifyTopOfAudioFocusStack();
}
}
}
2. propagateFocusLossFromGain_syncAf
@GuardedBy("mAudioFocusLock")
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
if (!mFocusStack.empty()) {
for (FocusRequester focusLoser : mFocusStack) {
// 调用FocusRequester处理
final boolean isDefinitiveLoss =
focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
if (isDefinitiveLoss) {
// 增加到链表中
clientsToRemove.add(focusLoser.getClientId());
}
}
}
if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
for (FocusRequester multifocusLoser : mMultiAudioFocusList) {
final boolean isDefinitiveLoss =
multifocusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
if (isDefinitiveLoss) {
clientsToRemove.add(multifocusLoser.getClientId());
}
}
}
for (String clientToRemove : clientsToRemove) {
// 移除 对应 clientID
removeFocusStackEntry(clientToRemove, false /*signal*/,
true /*notifyFocusFollowers*/);
}
}
- frameworks/base/services/core/java/com/android/server/audio/FocusRequester.java
@GuardedBy("MediaFocusControl.mAudioFocusLock")
boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)
{
final int focusLoss = focusLossForGainRequest(focusGain);
handleFocusLoss(focusLoss, frWinner, forceDuck);
return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
}
// handleFocusLoss
@GuardedBy("MediaFocusControl.mAudioFocusLock")
void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner, boolean forceDuck)
{
try {
if (focusLoss != mFocusLossReceived) {
mFocusLossReceived = focusLoss;
mFocusLossWasNotified = false;
// before dispatching a focus loss, check if the following conditions are met:
// 1/ the framework is not supposed to notify the focus loser on a DUCK loss
// (i.e. it has a focus controller that implements a ducking policy)
// 2/ it is a DUCK loss
// 3/ the focus loser isn't flagged as pausing in a DUCK loss
// if they are, do not notify the focus loser
if (!mFocusController.mustNotifyFocusOwnerOnDuck()
&& mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
&& (mGrantFlags
& AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
if (DEBUG) {
Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+ " to " + mClientId + ", to be handled externally");
}
mFocusController.notifyExtPolicyFocusLoss_syncAf(
toAudioFocusInfo(), false /* wasDispatched */);
return;
}
// check enforcement by the framework
boolean handled = false;
if (frWinner != null) {
handled = frameworkHandleFocusLoss(focusLoss, frWinner, forceDuck);
}
if (handled) {
if (DEBUG) {
Log.v(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;
if (fd != null) {
if (DEBUG) {
Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
+ mClientId);
}
mFocusController.notifyExtPolicyFocusLoss_syncAf(
toAudioFocusInfo(), true /* wasDispatched */);
mFocusLossWasNotified = true;
//去通知 app 焦点变化
fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
}
}
} catch (android.os.RemoteException e) {
Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
}
}
- frameworks/base/media/java/android/media/AudioManager.java
private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {
@Override
public void dispatchAudioFocusChange(int focusChange, String id) {
final FocusRequestInfo fri = findFocusRequestInfo(id);
if (fri != null) {
// 回调 OnAudioFocusChangeListener
final OnAudioFocusChangeListener listener =
fri.mRequest.getOnAudioFocusChangeListener();
if (listener != null) {
final Handler h = (fri.mHandler == null) ?
mServiceEventHandlerDelegate.getHandler() : fri.mHandler;
final Message m = h.obtainMessage(
MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/,
id/*obj*/);
h.sendMessage(m);
}
}
}
//----------
if (looper != null) {
// implement the event handler delegate to receive events from audio service
mHandler = new Handler(looper) {
@Override
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);
// 回调 onAudioFocusChange
listener.onAudioFocusChange(msg.arg1);
}
}
} break;
// 3. 如果是电话的话,check下电话焦点 runAudioCheckerForRingOrCallAsync(
private void runAudioCheckerForRingOrCallAsync(final boolean enteringRingOrCall) {
new Thread() {
public void run() {
if (enteringRingOrCall) {
try {
Thread.sleep(RING_CALL_MUTING_ENFORCEMENT_DELAY_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mAudioFocusLock) {
// since the new thread starting running the state could have changed, so
// we need to check again mRingOrCallActive, not enteringRingOrCall
if (mRingOrCallActive) {
// static int[] USAGES_TO_MUTE_IN_RING_OR_CALL =
{ AudioAttributes.USAGE_MEDIA, AudioAttributes.USAGE_GAME };
mFocusEnforcer.mutePlayersForCall(USAGES_TO_MUTE_IN_RING_OR_CALL);
} else {
mFocusEnforcer.unmutePlayersForCall();
}
}
}
}.start();
}
- frameworks/base/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
public void mutePlayersForCall(int[] usagesToMute) {
if (DEBUG) {
String log = new String("mutePlayersForCall: usages=");
for (int usage : usagesToMute) { log += " " + usage; }
Log.v(TAG, log);
}
synchronized (mPlayerLock) {
final Set<Integer> piidSet = mPlayers.keySet();
final Iterator<Integer> piidIterator = piidSet.iterator();
// find which players to mute
while (piidIterator.hasNext()) {
final Integer piid = piidIterator.next();
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null) {
continue;
}
final int playerUsage = apc.getAudioAttributes().getUsage();
boolean mute = false;
for (int usageToMute : usagesToMute) {
if (playerUsage == usageToMute) {
mute = true;
break;
}
}
if (mute) {
try {
// 会打印 event log
sEventLogger.log((new AudioEventLogger.StringEvent("call: muting piid:"
+ piid + " uid:" + apc.getClientUid())).printLog(TAG));
// 循环找到所有的音频流,去设置音量为 0
apc.getPlayerProxy().setVolume(0.0f);
mMutedPlayers.add(new Integer(piid));
} catch (Exception e) {
Log.e(TAG, "call: error muting player " + piid, e);
}
}
}
}
}
3. 去除音频焦点源码 abandonAudioFocus
- frameworks/base/media/java/android/media/AudioManager.java
public int abandonAudioFocus(OnAudioFocusChangeListener l) {
return abandonAudioFocus(l, null /*AudioAttributes, legacy behavior*/);
}
---
public int abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa) {
int status = AUDIOFOCUS_REQUEST_FAILED;
unregisterAudioFocusRequest(l);
final IAudioService service = getService();
try {
// 调用 audioService
status = service.abandonAudioFocus(mAudioFocusDispatcher,
getIdForAudioFocusListener(l), aa, getContext().getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return status;
}
- frameworks/base/services/core/java/com/android/server/audio/AudioService.java
public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa,
String callingPackageName) {
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "focus")
.set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
.set(MediaMetrics.Property.CLIENT_NAME, clientId)
.set(MediaMetrics.Property.EVENT, "abandonAudioFocus");
if (aa != null && !isValidAudioAttributesUsage(aa)) {
Log.w(TAG, "Request using unsupported usage.");
mmi.set(MediaMetrics.Property.EARLY_RETURN, "unsupported usage").record();
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
mmi.record();
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
}
- frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java
protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
String callingPackageName) {
new MediaMetrics.Item(mMetricsId)
.setUid(Binder.getCallingUid())
.set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
.set(MediaMetrics.Property.CLIENT_NAME, clientId)
.set(MediaMetrics.Property.EVENT, "abandonAudioFocus")
.record();
// AudioAttributes are currently ignored, to be used for zones / a11y
// 打印对应的 event log
mEventLogger.log((new AudioEventLogger.StringEvent(
"abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+ "/" + Binder.getCallingPid()
+ " clientId=" + clientId))
.printLog(TAG));
try {
// this will take care of notifying the new focus owner if needed
synchronized(mAudioFocusLock) {
// external focus policy?
if (mFocusPolicy != null) {
final AudioFocusInfo afi = new AudioFocusInfo(aa, Binder.getCallingUid(),
clientId, callingPackageName, 0 /*gainRequest*/, 0 /*lossReceived*/,
0 /*flags*/, 0 /* sdk n/a here*/);
if (notifyExtFocusPolicyFocusAbandon_syncAf(afi)) {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
}
boolean exitingRingOrCall = mRingOrCallActive
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
if (exitingRingOrCall) { mRingOrCallActive = false; }
// 如果栈中是A-B-C 这个时候B释放焦点的时候 最后焦点栈中是A-C 只会移除释放那个
removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
if (ENFORCE_MUTING_FOR_RING_OR_CALL & exitingRingOrCall) {
// 退出电话焦点
runAudioCheckerForRingOrCallAsync(false/*enteringRingOrCall*/);
}
}
} catch (java.util.ConcurrentModificationException cme) {
// Catching this exception here is temporary. It is here just to prevent
// a crash seen when the "Silent" notification is played. This is believed to be fixed
// but this try catch block is left just to be safe.
Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme);
cme.printStackTrace();
}
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
@GuardedBy("mAudioFocusLock")
private void removeFocusStackEntry(String clientToRemove, boolean signal,
boolean notifyFocusFollowers) {
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
{
//Log.i(TAG, " removeFocusStackEntry() removing top of stack");
FocusRequester fr = mFocusStack.pop();
fr.maybeRelease();
if (notifyFocusFollowers) {
abandonSource = fr.toAudioFocusInfo();
}
// signal 为true,通知到栈顶元素获取音频焦点
if (signal) {
// notify the new top of the stack it gained focus
notifyTopOfAudioFocusStack();
}
if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
Iterator<FocusRequester> listIterator = mMultiAudioFocusList.iterator();
while (listIterator.hasNext()) {
FocusRequester fr = listIterator.next();
if (fr.hasSameClient(clientToRemove)) {
listIterator.remove();
fr.release();
}
}
if (signal) {
// notify the new top of the stack it gained focus
notifyTopOfAudioFocusStack();
}
}
}
---------
@GuardedBy("mAudioFocusLock")
private void notifyTopOfAudioFocusStack() {
// notify the top of the stack it gained focus
if (!mFocusStack.empty()) {
if (canReassignAudioFocus()) {
mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
}
}
二、播放音乐的几种方式
常见的几种方式:
- 对于延迟度要求不高,并且希望能够更全面的控制音乐的播放,MediaPlayer比较适合
- 声音短小,延迟度小,并且需要几种声音同时播放的场景,适合使用SoundPool
- 对于简单的播放,不需要复杂控制的播放,可以给使用AsyncPlayer,所有操作均在子线程不阻塞UI
- 播放大文件音乐,如WAV无损音频和PCM无压缩音频,可使用更底层的播放方式AudioTrack。它支持流式播放,可以读取(可来自本地和网络)音频流,却播放延迟较小。
- 对于系统类声音的播放和操作,Ringtone更适合(主要是掌握好RingtoneManager)
1. MediaPlayer
实例化:
方法:
注意点:
- 设置完数据源,不要忘记prepare(),尽量使用异步prepareAync(),这样不会阻塞UI线程。
- 播放完毕即使释放资源
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
MediaPlayer 资源占用量较高、延迟时间较长、不支持多个音频同时播放等
插一个关于 Lambda 表达式的例子:
- Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。
- Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
// 相当于A实现了接口,实例化了A,并且重写了对应的方法
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
}
// 定义一个简单的接口
interface MathOperation {
int operation(int a, int b);
}
// 操作对应的方法
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
执行:
10 + 5 = 15 10 - 5 = 5 10 x 5 = 50 10 / 5 = 2
public class Java8Tester {
final static String salutation = "Hello! ";
public static void main(String args[]){
// greetService1 相当于实现了GreetingService 并且实例化了,
// -> 后面函数相当于 重写了 sayMessage
GreetingService greetService1 = message ->
System.out.println(salutation + message);
// 然后后直接可以调用 sayMessage 方法
greetService1.sayMessage("Runoob");
}
interface GreetingService {
void sayMessage(String message);
}
}
安卓 FrameWork 框架层的例子:
- packages/services/Telecomm/src/com/android/server/telecom/InCallTonePlayer.java
public interface MediaPlayerFactory {
// 接口对应的方法 get,返回值为 MediaPlayerAdapter
MediaPlayerAdapter get (int resourceId, AudioAttributes attributes);
}
---------------------
// MediaPlayerAdapter -> 对应的实现类是:MediaPlayerAdapterImpl
// 传入的参数是: MediaPlayer
public interface MediaPlayerAdapter {
void setLooping(boolean isLooping);
void setOnCompletionListener(MediaPlayer.OnCompletionListener listener);
void start();
void release();
int getDuration();
}
public static class MediaPlayerAdapterImpl implements MediaPlayerAdapter {
private MediaPlayer mMediaPlayer;
/**
* Create new media player adapter backed by a real mediaplayer.
* Note: Its possible for the mediaplayer to be null if
* {@link MediaPlayer#create(Context, Uri)} fails for some reason; in this case we can
* continue but not bother playing the audio.
* @param mediaPlayer The media player.
*/
public MediaPlayerAdapterImpl(@Nullable MediaPlayer mediaPlayer) {
mMediaPlayer = mediaPlayer;
}
packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java
// 相当于实现实例化了 MediaPlayerFactory ,并实现方法 get
InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory =
(resourceId, attributes) ->
new InCallTonePlayer.MediaPlayerAdapterImpl(
// 实例化对应的 MediaPlayer
MediaPlayer.create(mContext, resourceId, attributes,
audioManager.generateAudioSessionId()));
然后就可以在 :InCallTonePlayer, 中使用了:
private void playMediaTone(int stream, int toneResourceId) {
synchronized (this) {
if (mState != STATE_STOPPED) {
mState = STATE_ON;
}
Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId);
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.setLegacyStreamType(stream)
.build();
// 获取对应的 MediaPlayerAdapterImpl ,依据 toneResourceId, attributes:
mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes);
mToneMediaPlayer.setLooping(false);
int durationMillis = mToneMediaPlayer.getDuration();
final CountDownLatch toneLatch = new CountDownLatch(1);
// 回调 MediaPlayer 的播放完成回调函数
mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
Log.i(this, "playMediaTone: toneResourceId=%d completed.", toneResourceId);
synchronized (InCallTonePlayer.this) {
mState = STATE_OFF;
}
mToneMediaPlayer.release();
mToneMediaPlayer = null;
toneLatch.countDown();
}
});
mToneMediaPlayer.start();
try {
// Wait for the tone to stop playing; timeout at 2x the length of the file just to
// be on the safe side.
toneLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS);
} catch (InterruptedException ie) {
Log.e(this, ie, "playMediaTone: tone playback interrupted.");
}
}
}
2. SoundPool
使用场景:
- SoundPool虽然可以一次性加载多个声音,但由于内存限制,因此应该避免使用SoundPool来播放歌曲或者做游戏背景音乐,只有那些短促、密集的声音才考虑使用SoundPool进行播放。
- SoundPool用于 播放密集、急促而又短暂的音效(如游戏音效)
- SoundPool使用音效池的概念来管理多个短促的音效,例如它可以开始就加载20个音效,以后在程序中按音效的ID进行播放
SoundPool提供了一个构造器,该构造器可以指定它总共支持多少个声音(也就是池的大小)、声音的品质等。构造器如下:
SoundPool(int maxStreams, int streamType, int srcQuality):第一个参数指定支持多少个声音;第二个参数指定声音类型:第三个参数指定声音品质。
/**
* 创建SoundPool ,注意 api 等级
*/
private void createSoundPoolIfNeeded() {
if (mSoundPool == null) {
// 5.0 及 之后
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes audioAttributes = null;
audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
mSoundPool = new SoundPool.Builder()
.setMaxStreams(16)
.setAudioAttributes(audioAttributes)
.build();
} else { // 5.0 以前
mSoundPool = new SoundPool(16, AudioManager.STREAM_MUSIC, 0); // 创建SoundPool
}
mSoundPool.setOnLoadCompleteListener(this); // 设置加载完成监听
}
}
————————————————
创建完 SoundPool 后,通过setOnLoadCompleteListener设置监听,用来监听资源加载完毕的事件发生。
SoundPool提供的播放指定声音的方法:
int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate):该方法的第一个参数指定播放哪个声音;leftVolume、rightVolume指定左、右的音量:priority指定播放声音的优先级,数值越大,优先级越高;loop指定是否循环,0为不循环,-1为循环;rate指定播放的比率,数值可从0.5到2, 1为正常比率。
————————————————
onLoadComplete方法 是用来播放的合理位置,它的调用预示着资源已经就绪,可以进行播放了,调用 SoundPool 的 play 方法即可进行播放
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
if (mSoundPool != null) {
if (mStreamID == DEFAULT_INVALID_STREAM_ID)
mStreamID = mSoundPool.play(mSoundId, mCruLeftVolume, mCurRightVolume, 16, -1, 1.0f);
}
}
资源释放:
// 通过unload 方法来卸载之前load的资源 , 并通过 release 方法释放SoundPool占用的资源
/**
* 释放资源
*/
private void releaseSoundPool() {
if (mSoundPool != null) {
mSoundPool.autoPause();
mSoundPool.unload(mSoundId);
mSoundId = DEFAULT_INVALID_SOUND_ID;
mSoundPool.release();
mSoundPool = null;
}
}
3. AsyncPlayer
AsyncPlayer 是对 MediaPlayer 的封装
frameworks/base/media/java/android/media/AsyncPlayer.java
private void startSound(Command cmd) {
// Preparing can be slow, so if there is something else
// is playing, let it continue until we're done, so there
// is less of a glitch.
try {
if (mDebug) Log.d(mTag, "Starting playback");
MediaPlayer player = new MediaPlayer();
player.setAudioAttributes(cmd.attributes);
player.setDataSource(cmd.context, cmd.uri);
player.setLooping(cmd.looping);
player.prepare();
player.start();
if (mPlayer != null) {
mPlayer.release();
}
mPlayer = player;
long delay = SystemClock.uptimeMillis() - cmd.requestTime;
if (delay > 1000) {
Log.w(mTag, "Notification sound delayed by " + delay + "msecs");
}
}
catch (Exception e) {
Log.w(mTag, "error loading sound for " + cmd.uri, e);
}
}
private final class Thread extends java.lang.Thread {
Thread() {
super("AsyncPlayer-" + mTag);
}
public void run() {
while (true) {
Command cmd = null;
synchronized (mCmdQueue) {
if (mDebug) Log.d(mTag, "RemoveFirst");
cmd = mCmdQueue.removeFirst();
}
switch (cmd.code) {
case PLAY:
if (mDebug) Log.d(mTag, "PLAY");
startSound(cmd);
break;
case STOP:
if (mDebug) Log.d(mTag, "STOP");
if (mPlayer != null) {
long delay = SystemClock.uptimeMillis() - cmd.requestTime;
if (delay > 1000) {
Log.w(mTag, "Notification stop delayed by " + delay + "msecs");
}
mPlayer.stop();
mPlayer.release();
mPlayer = null;
} else {
Log.w(mTag, "STOP command without a player");
}
break;
}
synchronized (mCmdQueue) {
if (mCmdQueue.size() == 0) {
// nothing left to do, quit
// doing this check after we're done prevents the case where they
// added it during the operation from spawning two threads and
// trying to do them in parallel.
mThread = null;
releaseWakeLock();
return;
}
}
}
}
}
4. RingTone
参考:
Android 电话拨入音频焦点(Audio Focus)_周刚的专栏-CSDN博客_android 音频焦点
分析Android Framework源码--彻底了解Android AudioFocus机制,肯定有你不知道的知识点(基于Android10.0)_braintt的博客-CSDN博客