上一篇文章描述了在app开发中怎么调节系统不同音频流的音量大小。这次我们看下在android10中系统调节音量的实现和系统默认音量的修改。(本次源码以Android10为主梳理系统音量修改的实现)
一 按实体音量键修改音量
// frameworks/base/services/core/java/com/android/server/policy/ //以下按键事件中 if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) { if (mUseTvRouting || mHandleVolumeKeysInWM) { // On TVs or when the configuration is enabled, volume keys never // go to the foreground app. dispatchDirectAudioEvent(event); return -1; }
private void dispatchDirectAudioEvent(KeyEvent event) {
// When System Audio Mode is off, volume keys received by AVR can be either consumed by AVR
// or forwarded to the TV. It's up to Amplifier manufacturer’s implementation.
HdmiControlManager hdmiControlManager = getHdmiControlManager();
if (null != hdmiControlManager
&& !()
&& shouldCecAudioDeviceForwardVolumeKeysSystemAudioModeOff()) {
HdmiAudioSystemClient audioSystemClient = ();
if (audioSystemClient != null) {
(
(), () == KeyEvent.ACTION_DOWN);
return;
}
}
if (() != KeyEvent.ACTION_DOWN) {
return;
}
int keyCode = ();
int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
| AudioManager.FLAG_FROM_KEY;
String pkgName = ();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
try {
getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
} catch (Exception e) {
(TAG, "Error dispatching volume up in dispatchTvAudioEvent.", e);
}
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
try {
getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
} catch (Exception e) {
(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e);
}
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
try {
if (() == 0) {
getAudioService().adjustSuggestedStreamVolume(
AudioManager.ADJUST_TOGGLE_MUTE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
}
} catch (Exception e) {
(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e);
}
break;
}
}
以上方法dispatchDirectAudioEvent()方法体中,根据不同的按键事件处理音量调节(增大、减小、静音) 说明:
按键类型 | Audio Service操作类型 | 含义 |
KEYCODE_VOLUME_UP | AudioManager.ADJUST_RAISE | 音量减 |
KEYCODE_VOLUME_DOWN | AudioManager.ADJUST_LOWER | 音量加 |
KEYCODE_VOLUME_MUTE | AudioManager.ADJUST_TOGGLE_MUTE | 改变静音状态 |
二 进入AudioService
getAudioService()获取AudioService实例,
接下来就到了AudioService中的adjustSuggestedStreamVolume方法里面了:
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller) {
final IAudioPolicyCallback extVolCtlr;
synchronized (mExtVolumeControllerLock) {
extVolCtlr = mExtVolumeController;
}
if (extVolCtlr != null) {
sendMsg(mAudioHandler, MSG_NOTIFY_VOL_EVENT, SENDMSG_QUEUE,
direction, 0 /*ignored*/,
extVolCtlr, 0 /*delay*/);
} else {
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
caller, ());
}
}
以上代码中,extVolCtrlr是外部设备(如蓝牙设备,有线耳机等可操作音量的设备)实例,
1)若extVolCtrlr!=null,执行
onNotifyVolumeEvent((IAudioPolicyCallback) , msg.arg1);
然后外部设备发送信号到系统。系统再调用adjustSuggestedStreamVolume方法实现音量调节(该部分未找到源码麻烦指正)
2)若extVolCtrlr == null 执行
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, String caller, int uid) {
//...省略部分代码
final int streamType;
synchronized (mForceControlStreamLock) {
// Request lock in case mVolumeControlStream is changed by other thread.
if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
streamType = mVolumeControlStream;
} else {
final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
final boolean activeForReal;
if (maybeActiveStreamType == AudioSystem.STREAM_RING
|| maybeActiveStreamType == AudioSystem.STREAM_NOTIFICATION) {
activeForReal = wasStreamActiveRecently(maybeActiveStreamType, 0);
} else {
activeForReal = (maybeActiveStreamType, 0);
}
if (activeForReal || mVolumeControlStream == -1) {
streamType = maybeActiveStreamType;
} else {
streamType = mVolumeControlStream;
}
}
}final boolean isMute = isMuteAdjust(direction);
//确认音频流合法
ensureValidStreamType(streamType);
//将我们获取到的流,进行流映射,拿到最终需要操作的流类型
final int resolvedStream = mStreamVolumeAlias[streamType];
// Play sounds on STREAM_RING only.
if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
resolvedStream != AudioSystem.STREAM_RING) {
flags &= ~AudioManager.FLAG_PLAY_SOUND;
}// For notifications/ring, show the ui before making any adjustments
// Don't suppress mute/unmute requests
// Don't suppress adjustments for single volume device//通知和响铃,调整音量之前先显示UI。
if ((resolvedStream, flags, isMute)
&& !mIsSingleVolume) {
direction = 0;
flags &= ~AudioManager.FLAG_PLAY_SOUND;
flags &= ~AudioManager.FLAG_VIBRATE;
if (DEBUG_VOL) (TAG, "Volume controller suppressed adjustment");
}//这里设置音量
adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
}
1、mUserSelectedVolumeControlStream:这个属性表示,用户是否已通过单击音量进度条选择音量流来更改由音量键控制的音量,如果mVolumeControlStream为-1,那么mUserSelectedVolumeControlStream 为false。说简单点,当用户点击了某个音量条,这时再去按下音量加减,这个时候调节的是你点击的那个流类型。
2、getActiveStreamType:获取我们要控制的流的类型,当然,只是可能需要控制的流类型,还需要进一步确认。
3、mStreamVolumeAlias[streamType]:进行流映射,获取最终需要调整的流类型
4、suppressAdjustment:字面意思为抑制调整,为什么抑制调整呢,说白了,当我们没有显示音量的UI进度条的时候,不管我们是加音量还是减音量(注意:静音和解静音除外),这个时候都是先显示音量条,而不去改变音量的大小。所以当这个方法返回true的时候, direction = 0,这里direction为0就表示我们的操作为ADJUST_SAME,大家可以在AudioManager里面查看ADJUST_SAME的注释就知道这个操作表示只弹出UI但是不调整音量大小。
5、adjustStreamVolume:进行音量的调整
adjustStreamVolume方法如下:
protected void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {//TIGO运营商状态是否支持
if (checkTigoCmasStatus(callingPackage)) {
return;
}//mUseFixedVolume=true是音量固定,无法修改,则return
if (mUseFixedVolume) {
return;
}
ensureValidDirection(direction);
ensureValidStreamType(streamType);boolean isMuteAdjust = isMuteAdjust(direction);
if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {
return;
}// If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure
// that the calling app have the MODIFY_PHONE_STATE permission.//若是STREAM_VOICE_CALL 或STREAM_BLUETOOTH_SCO,查看权限是否允许
if (isMuteAdjust &&
(streamType == AudioSystem.STREAM_VOICE_CALL ||
streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
(
.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from p, uadjustStreamVolume() safe volume index = " + oldIndex);
(flags);
} else if (((device & mFullVolumeDevices) == 0)
&& ((direction * step, device, caller)
|| )) {
// Post message to set system volume (it in turn will post a
// message to persist).
if () {
// Unmute the stream if it was previously muted
if (direction == AudioManager.ADJUST_RAISE) {
// unmute immediately for volume up
(false);
} else if (direction == AudioManager.ADJUST_LOWER) {
if (mIsSingleVolume) {
sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
}
}
}//该方法执行设置音量逻辑
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
}int newIndex = mStreamStates[streamType].getIndex(device);
// Check if volume update should be send to AVRCP
if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
(device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
if (DEBUG_VOL) {
(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index="
+ newIndex + "stream=" + streamType);
}
(newIndex / 10);
}// Check if volume update should be send to Hearing Aid
if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
// only modify the hearing aid attenuation when the stream to modify matches
// the one expected by the hearing aid
if (streamType == getHearingAidStreamType()) {
if (DEBUG_VOL) {
(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index="
+ newIndex + " stream=" + streamType);
}
(newIndex, streamType);
}
}// Check if volume update should be sent to Hdmi system audio.
if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
}
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
// mHdmiCecSink true => mHdmiPlaybackClient != null
if (mHdmiCecSink
&& streamTypeAlias == AudioSystem.STREAM_MUSIC
// vol change on a full volume device
&& ((device & mFullVolumeDevices) != 0)) {
int keyCode = KeyEvent.KEYCODE_UNKNOWN;
switch (direction) {
case AudioManager.ADJUST_RAISE:
keyCode = KeyEvent.KEYCODE_VOLUME_UP;
break;
case AudioManager.ADJUST_LOWER:
keyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
break;
case AudioManager.ADJUST_TOGGLE_MUTE:
keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
break;
default:
break;
}
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
final long ident = ();
try {
(keyCode, true);
(keyCode, false);
} finally {
(ident);
}
}
}if (mHdmiAudioSystemClient != null &&
mHdmiSystemAudioSupported &&
streamTypeAlias == AudioSystem.STREAM_MUSIC &&
(oldIndex != newIndex || isMuteAdjust)) {
final long identity = ();
(
isMuteAdjust, getStreamVolume(AudioSystem.STREAM_MUSIC),
getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
isStreamMute(AudioSystem.STREAM_MUSIC));
(identity);
}
}
}
}
int index = mStreamStates[streamType].getIndex(device);
sendVolumeUpdate(streamType, oldIndex, index, flags, device);
}
- )mUseFixedVolume:表示使用固定音量,我们无法修改音量
- )mStreamVolumeAlias[streamType]:进行音频流的映射,拿到映射后的音频流
- )mStreamStates[streamTypeAlias]:mStreamStates是一个存储VolumeStreamState类型的数组,保存着每个音频流的状态。VolumeStreamState是AudioService的一个内部类,里面保存单个音频流的所有信息,比如流类型,音量大小,mute状态等。并且相同的流类型,在不同的设备,大小也是不一样的(比如耳机和扬声器,媒体音量大小是不一样的),这也是在VolumeStreamState里面去维护的。)
在以上方法体中 如下条件
} else if (((device & mFullVolumeDevices) == 0)
&& ((direction * step, device, caller)
|| )) {
// Post message to set system volume (it in turn will post a
// message to persist).
执行
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
(以下逻辑在文章android音量调节(四)android10 设置模块音量设置源码实现 中的后半部分的实现是一样的)
在 public void handleMessage(Message msg) { 方法中 有如下
case MSG_SET_DEVICE_VOLUME:
setDeviceVolume((VolumeStreamState) , msg.arg1);
break;
以上方法中
/*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
final boolean isAvrcpAbsVolSupported = ();
synchronized () {
// Apply volume 设置音量
streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
// Apply change to all streams using this one as alias 使用当前别名同步所有音量设置
int numStreamTypes = ();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
if (streamType != &&
mStreamVolumeAlias[streamType] == ) {
// Make sure volume is also maxed out on A2DP device for aliased stream
// that may have a different device selected
int streamDevice = getDeviceForStream(streamType);
if ((device != streamDevice) && isAvrcpAbsVolSupported
&& ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
mStreamStates[streamType].applyDeviceVolume_syncVSS(device,
isAvrcpAbsVolSupported);
}
mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice,
isAvrcpAbsVolSupported);
}
}
}
// Post a persist volume msg
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
PERSIST_DELAY);
}
在方法体中有
// Apply volume 设置音量
streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
以上方法实现如下
// must be called while synchronized
/*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) {
int index;
if (mIsMuted) {
index = 0;
} else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) {
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
} else if ((device & mFullVolumeDevices) != 0) {
index = (mIndexMax + 5)/10;
} else if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
index = (mIndexMax + 5)/10;
} else {
index = (getIndex(device) + 5)/10;
}
setStreamVolumeIndex(index, device);
}
以上方法中setStreamVolumeIndex(index, device)的实现如下
private void setStreamVolumeIndex(int index, int device) {
// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
// This allows RX path muting by the audio HAL only when explicitly muted but not when
// index is just set to 0 to repect BT requirements
if (mStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0 && !mIsMuted) {
index = 1;
}
(mStreamType, index, device);
}
在AudioSystem中 调用如下,
/** Wrapper for native methods called from AudioService */
public static int setStreamVolumeIndexAS(int stream, int index, int device) {
if (DEBUG_VOLUME) {
(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream]
+ " dev=" + (device) + " idx=" + index);
}
return setStreamVolumeIndex(stream, index, device);
}
return中调用jni方法,至此,framework层中的按实体音量键,修改音量逻辑完成
(若有错误,感谢斧正!)