本篇介绍
在开发Android Audio的时候,免不了需要修改音量类型,可是setMode真的可以每次都能生效吗?本篇就从源码层面回答下这个问题。
setMode实现
先看代码实现:
代码语言:javascript复制 /**
* Sets the audio mode.
* <p>
* The audio mode encompasses audio routing AND the behavior of
* the telephony layer. Therefore this method should only be used by applications that
* replace the platform-wide management of audio settings or the main telephony application.
* In particular, the {@link #MODE_IN_CALL} mode should only be used by the telephony
* application when it places a phone call, as it will cause signals from the radio layer
* to feed the platform mixer.
*
* @param mode the requested audio mode.
* Informs the HAL about the current audio state so that
* it can route the audio appropriately.
*/
public void setMode(@AudioMode int mode) {
final IAudioService service = getService();
try {
service.setMode(mode, mICallBack, mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
正如注释所介绍,setMode会修改音频路由等行为,而且并不是可以随意任何mode,一般外部设置的就是MODE_NORMAL和MODE_IN_COMMUNICATION:
代码语言:javascript复制 public static final int MODE_INVALID = -2;
/** @hide */
public static final int MODE_CURRENT = -1;
/** @hide */
public static final int MODE_NORMAL = 0;
/** @hide */
public static final int MODE_RINGTONE = 1;
/** @hide */
public static final int MODE_IN_CALL = 2;
/** @hide */
public static final int MODE_IN_COMMUNICATION = 3;
/** @hide */
public static final int MODE_CALL_SCREENING = 4;
/** @hide */
public static final int MODE_CALL_REDIRECT = 5;
/** @hide */
public static final int MODE_COMMUNICATION_REDIRECT = 6;
/** @hide */
public static final int NUM_MODES = 7;
接下来看下AudioService的实现:
代码语言:javascript复制 public void setMode(int mode, IBinder cb, String callingPackage) {
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
if (DEBUG_MODE) {
Log.v(TAG, "setMode(mode=" mode ", pid=" pid
", uid=" uid ", caller=" callingPackage ")");
}
if (!checkAudioSettingsPermission("setMode()")) {. // 权限检查
return;
}
if (cb == null) {
Log.e(TAG, "setMode() called with null binder");
return;
}
if (mode < AudioSystem.MODE_CURRENT || mode >= AudioSystem.NUM_MODES) {
Log.w(TAG, "setMode() invalid mode: " mode);
return;
}
if (mode == AudioSystem.MODE_CURRENT) {
mode = getMode();
}
if (mode == AudioSystem.MODE_CALL_SCREENING && !mIsCallScreeningModeSupported) {
Log.w(TAG, "setMode(MODE_CALL_SCREENING) not permitted "
"when call screening is not supported");
return;
}
final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_PHONE_STATE)
== PackageManager.PERMISSION_GRANTED;
if ((mode == AudioSystem.MODE_IN_CALL
|| mode == AudioSystem.MODE_CALL_REDIRECT
|| mode == AudioSystem.MODE_COMMUNICATION_REDIRECT)
&& !hasModifyPhoneStatePermission) {
Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode("
AudioSystem.modeToString(mode) ") from pid=" pid
", uid=" Binder.getCallingUid());
return;
}
SetModeDeathHandler currentModeHandler = null;
synchronized (mDeviceBroker.mSetModeLock) {
for (SetModeDeathHandler h : mSetModeDeathHandlers) { // 关键逻辑
if (h.getPid() == pid) {
currentModeHandler = h;
break;
}
}
if (mode == AudioSystem.MODE_NORMAL) { // 如果是媒体音量,那么就清理currentModeHandler, 也就是设置媒体音量不会成为modeOwner
if (currentModeHandler != null) {
if (!currentModeHandler.isPrivileged()
&& currentModeHandler.getMode() == AudioSystem.MODE_IN_COMMUNICATION) {
mAudioHandler.removeEqualMessages(
MSG_CHECK_MODE_FOR_UID, currentModeHandler);
}
mSetModeDeathHandlers.remove(currentModeHandler); // 通过是设置媒体音量 ,清理mode owner
if (DEBUG_MODE) {
Log.v(TAG, "setMode(" mode ") removing hldr for pid: " pid);
}
try {
currentModeHandler.getBinder().unlinkToDeath(currentModeHandler, 0);
} catch (NoSuchElementException e) {
Log.w(TAG, "setMode link does not exist ...");
}
}
} else {
if (currentModeHandler != null) {
currentModeHandler.setMode(mode); // 设置通话音量会保留mode owner
if (DEBUG_MODE) {
Log.v(TAG, "setMode(" mode ") updating hldr for pid: " pid);
}
} else {
currentModeHandler = new SetModeDeathHandler(cb, pid, uid,
hasModifyPhoneStatePermission, callingPackage, mode);
// Register for client death notification
try {
cb.linkToDeath(currentModeHandler, 0);
} catch (RemoteException e) {
// Client has died!
Log.w(TAG, "setMode() could not link to " cb " binder death");
return;
}
mSetModeDeathHandlers.add(currentModeHandler);
if (DEBUG_MODE) {
Log.v(TAG, "setMode(" mode ") adding handler for pid=" pid);
}
}
if (mode == AudioSystem.MODE_IN_COMMUNICATION) {
// Force active state when entering/updating the stack to avoid glitches when
// an app starts playing/recording after settng the audio mode,
// and send a reminder to check activity after a grace period.
if (!currentModeHandler.isPrivileged()) { // 这儿是为了 保证每次掉用setMode设置通话音量 都可以立马生效,但是 6s 后会检查,如果还是没有启动采集或播放,那么就会回收 通话音量,重置为媒体音量
currentModeHandler.setPlaybackActive(true);
currentModeHandler.setRecordingActive(true);
sendMsg(mAudioHandler,
MSG_CHECK_MODE_FOR_UID,
SENDMSG_QUEUE,
0,
0,
currentModeHandler,
CHECK_MODE_FOR_UID_PERIOD_MS);
}
}
}
sendMsg(mAudioHandler,
MSG_UPDATE_AUDIO_MODE,
SENDMSG_REPLACE,
mode,
pid,
callingPackage,
0);
}
}
从这块逻辑可以看出以下几点:
- 设置媒体音量不会关联 mode owner,因此不保证可以设置成功
- 设置通话音量会关联mode owner,这样可以保证设置成功,不过也需要采集或播放才能长久保持住通话音量,6s后会进行检查,如果还是没有采集或播放,那么就会重新设置回媒体音量
接下来继续看下MSG_UPDATE_AUDIO_MODE 的逻辑:
代码语言:javascript复制 void onUpdateAudioMode(int requestedMode, int requesterPid, String requesterPackage,
boolean force) {
if (requestedMode == AudioSystem.MODE_CURRENT) {
requestedMode = getMode();
}
int mode = AudioSystem.MODE_NORMAL;
int uid = 0;
int pid = 0;
SetModeDeathHandler currentModeHandler = getAudioModeOwnerHandler(); // 获取当前的mode owner,也就是每次setMode其实是以mode owner 为准的,并不一定是当前应用
if (currentModeHandler != null) {
mode = currentModeHandler.getMode();
uid = currentModeHandler.getUid();
pid = currentModeHandler.getPid();
}
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode() new mode: " mode ", current mode: "
mMode.get() " requested mode: " requestedMode);
}
if (mode != mMode.get() || force) {
final long identity = Binder.clearCallingIdentity();
int status = mAudioSystem.setPhoneState(mode, uid); // 这儿设置下去可以保证一定可以设置成功,会通过audioflinger设置到hal 层。
Binder.restoreCallingIdentity(identity);
if (status == AudioSystem.AUDIO_STATUS_OK) {
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode: mode successfully set to " mode);
}
sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_MODE, SENDMSG_REPLACE, mode, 0,
/*obj*/ null, /*delay*/ 0);
int previousMode = mMode.getAndSet(mode);
// Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
mModeLogger.log(new PhoneStateEvent(requesterPackage, requesterPid,
requestedMode, pid, mode)); // 记录dumpsys audio 日志
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
int device = getDeviceForStream(streamType);
int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true,
requesterPackage, true /*hasModifyAudioSettings*/); //更新音量值
updateStreamVolumeAlias(true /*updateVolumes*/, requesterPackage);
// change of mode may require volume to be re-applied on some devices
updateAbsVolumeMultiModeDevices(previousMode, mode);
// Forcefully set LE audio volume as a workaround, since the value of 'device'
// is not DEVICE_OUT_BLE_* even when BLE is connected.
setLeAudioVolumeOnModeUpdate(mode);
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
mDeviceBroker.postSetModeOwnerPid(pid, mode);
} else {
Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " mode);
}
}
}
这儿关键逻辑就是 查找mode owner,mode owner并不一定是当前掉用setMode的应用,可以看下面逻辑:
代码语言:javascript复制 private SetModeDeathHandler getAudioModeOwnerHandler() {
// The Audio mode owner is:
// 1) the most recent privileged app in the stack
// 2) the most recent active app in the tack
SetModeDeathHandler modeOwner = null;
SetModeDeathHandler privilegedModeOwner = null;
for (SetModeDeathHandler h : mSetModeDeathHandlers) {
if (h.isActive()) {
// privileged apps are always active
if (h.isPrivileged()) {
if (privilegedModeOwner == null
|| h.getUpdateTime() > privilegedModeOwner.getUpdateTime()) {
privilegedModeOwner = h;
}
} else {
if (modeOwner == null
|| h.getUpdateTime() > modeOwner.getUpdateTime()) {
modeOwner = h;
}
}
}
}
return privilegedModeOwner != null ? privilegedModeOwner : modeOwner;
}
这儿有两个信息,一个是isActive,一个是UpdataTime,isActive就是检查是否有活动的采集或播放:
代码语言:javascript复制 /**
* An app is considered active if:
* - It is privileged (has MODIFY_PHONE_STATE permission)
* or
* - It requests mode MODE_IN_COMMUNICATION, and it is either playing
* or recording for VOICE_COMMUNICATION.
* or
* - It requests a mode different from MODE_IN_COMMUNICATION or MODE_NORMAL
* Note: only privileged apps can request MODE_IN_CALL, MODE_CALL_REDIRECT
* or MODE_COMMUNICATION_REDIRECT.
*/
public boolean isActive() {
return mIsPrivileged
|| ((mMode == AudioSystem.MODE_IN_COMMUNICATION)
&& (mRecordingActive || mPlaybackActive))
|| mMode == AudioSystem.MODE_RINGTONE
|| mMode == AudioSystem.MODE_CALL_SCREENING;
}
而UpdataTime 是在setMode的时候会更新。 这儿还有一个疑问,那播放和采集状态如何更新的呢? 这儿有一个回调 onPlaybackConfigChange
代码语言:javascript复制 private void onPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
boolean voiceActive = false;
for (AudioPlaybackConfiguration config : configs) {
final int usage = config.getAudioAttributes().getUsage();
if ((usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
|| usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
&& config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
voiceActive = true;
break;
}
}
if (mVoicePlaybackActive.getAndSet(voiceActive) != voiceActive) {
updateHearingAidVolumeOnVoiceActivityUpdate();
}
// Update playback active state for all apps in audio mode stack.
// When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
// and request an audio mode update immediately. Upon any other change, queue the message
// and request an audio mode update after a grace period.
synchronized (mDeviceBroker.mSetModeLock) {
boolean updateAudioMode = false;
int existingMsgPolicy = SENDMSG_QUEUE;
int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
for (SetModeDeathHandler h : mSetModeDeathHandlers) {
boolean wasActive = h.isActive();
h.setPlaybackActive(false);
for (AudioPlaybackConfiguration config : configs) {
final int usage = config.getAudioAttributes().getUsage();
if (config.getClientUid() == h.getUid()
&& (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
|| usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
&& config.getPlayerState()
== AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
h.setPlaybackActive(true);
break;
}
}
if (wasActive != h.isActive()) { // 如果状态不一样,那么就会触发更新
updateAudioMode = true;
if (h.isActive() && h == getAudioModeOwnerHandler()) {
existingMsgPolicy = SENDMSG_REPLACE;
delay = 0;
}
}
}
if (updateAudioMode) { 这时候就会重新查找mode owner并设置音量类型了,如果没有mode owner,那么就设置成媒体音量
sendMsg(mAudioHandler,
MSG_UPDATE_AUDIO_MODE,
existingMsgPolicy,
AudioSystem.MODE_CURRENT,
android.os.Process.myPid(),
mContext.getPackageName(),
delay);
}
}
}
同样也有一个 onRecordingConfigChange,也是同样逻辑,就不重复了。 就以onPlaybackConfigChange 为线索继续看,这个是以回调形式注册到PlaybackActivityMonitor中了。而PlaybackActivityMonitor也是由AudioService通知的:
代码语言:javascript复制 public void playerEvent(int piid, int event, int deviceId) {
mPlaybackMonitor.playerEvent(piid, event, deviceId, Binder.getCallingUid());
}
那再看下playerEvent的源头,这时候会发现是在frameworks/base/media/java/android/media/PlayerBase.java
代码语言:javascript复制 private void updateState(int state, int deviceId) {
final int piid;
synchronized (mLock) {
mState = state;
piid = mPlayerIId;
mDeviceId = deviceId;
}
try {
getService().playerEvent(piid, state, deviceId);
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, "
AudioPlaybackConfiguration.toLogFriendlyPlayerState(state)
" state will not be tracked for piid=" piid, e);
}
}
会发现在baseStart,baseStop,basePause中会掉用, 而PlayerBase本身又被AudioTrack继承,在AudioTrack中调用父类方法就可以了: frameworks/base/media/java/android/media/AudioTrack.java
代码语言:javascript复制 private void startImpl() {
synchronized (mRoutingChangeListeners) {
if (!mEnableSelfRoutingMonitor) {
mEnableSelfRoutingMonitor = testEnableNativeRoutingCallbacksLocked();
}
}
synchronized(mPlayStateLock) {
baseStart(0); // unknown device at this point
native_start();
// FIXME see b/179218630
//baseStart(native_getRoutedDeviceId());
if (mPlayState == PLAYSTATE_PAUSED_STOPPING) {
mPlayState = PLAYSTATE_STOPPING;
} else {
mPlayState = PLAYSTATE_PLAYING;
mOffloadEosPending = false;
}
}
}
对于Native接口如何感知到呢?这儿还有一个逻辑:
代码语言:javascript复制void initMonitor() {
AudioSystem.setRecordingCallback(this);
}
这儿会注册Native的通知,Native感知到采集和播放变化后,就会通知上来,具体流程本篇先略过。
这时候就基本理清楚了setMode 的mode owner 机制了。可以总结成如下:
- setMode 设置媒体音量不一定能成功,因为如果有其他应用是通话音量的mode owner,并且有活动的采集或播放,或者是系统应用,那么就还是会继续设置通话音量
- setMode设置通话音量一定可以成功,同时自己也会成为mode owner,但是如果不启动 采集或播放,通话音量也不会一直生效,过上一会儿(最新代码是6s)后就会刷新一次,被重置成不生效,这时候也就不会被认为是mode owner了,如果没有其他通话音量的mode owner,那么就会被设置成媒体音量