音量调节
TV/STB等产品的音量调节主要是通过遥控器上的音量键实现。具体到AudioService则是adjustSuggestedStreamVolume函数。
adjustSuggestedStreamVolume
java
/** All callers come from platform apps/system server, so no attribution tag is needed */
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller, int uid, int pid, boolean hasModifyAudioSettings,
int keyEventMode) {
//判断是否有外部的音量控制器。这个功能没用过,但猜测是可以bypass Android的音量处理,有厂商自己定制音量调节的实现。
boolean hasExternalVolumeController = notifyExternalVolumeController(direction);
if (hasExternalVolumeController) {
return;
}
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 = mAudioSystem.isStreamActive(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
if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)
&& !mIsSingleVolume) {
direction = 0;
flags &= ~AudioManager.FLAG_PLAY_SOUND;
flags &= ~AudioManager.FLAG_VIBRATE;
if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
}
adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid, pid,
null, hasModifyAudioSettings, keyEventMode);
}
adjustSuggestedStreamVolume针对各种音量调节过程中涉及的场景做完相应的处理后(这部分代码有个大概的印象就可以了,如此琐碎很难记住),最后调用到adjustStreamVolume。
adjustStreamVolume
audioservice的代码是真的长,只讲解一些比较常见的逻辑。受限于开发经验,如果遗漏还请见谅。重点逻辑已使用中文做注释。
java
protected void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid, int pid, String attributionTag,
boolean hasModifyAudioSettings, int keyEventMode) {
if (mUseFixedVolume) {
return;
}
if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction
+ ", flags=" + flags + ", caller=" + caller);
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.
if (isMuteAdjust &&
(streamType == AudioSystem.STREAM_VOICE_CALL ||
streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
return;
}
// If the stream is STREAM_ASSISTANT,
// make sure that the calling app have the MODIFY_AUDIO_ROUTING permission.
if (streamType == AudioSystem.STREAM_ASSISTANT &&
mContext.checkPermission(
android.Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
return;
}
// use stream type alias here so that streams with same alias have the same behavior,
// including with regard to silent mode control (e.g the use of STREAM_RING below and in
// checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
//获得streamtype对应的别名,所谓别名就是可以让一些streamtype共享音量一套音量调节
int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
//获得streamtype当前正在输出的设备类型
final int device = getDeviceForStream(streamTypeAlias);
int aliasIndex = streamState.getIndex(device);
boolean adjustVolume = true;
int step;
//所谓蓝牙的绝对音量就是音量算法由蓝牙设备执行。而非绝对音量则是由audiofinger的audiomixer执行。
// skip a2dp absolute volume control request when the device
// is neither an a2dp device nor BLE device
if ((!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
&& !AudioSystem.DEVICE_OUT_ALL_BLE_SET.contains(device))
&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
return;
}
// If we are being called by the system (e.g. hardware keys) check for current user
// so we handle user restrictions correctly.
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
}
// validate calling package and app op
if (!checkNoteAppOp(
STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage, attributionTag)) {
return;
}
// reset any pending volume command
synchronized (mSafeMediaVolumeStateLock) {
mPendingVolumeCommand = null;
}
flags &= ~AudioManager.FLAG_FIXED_VOLUME;
if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
flags |= AudioManager.FLAG_FIXED_VOLUME;
// Always toggle between max safe volume and 0 for fixed volume devices where safe
// volume is enforced, and max and 0 for the others.
// This is simulated by stepping by the full allowed volume range
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
mSafeMediaVolumeDevices.contains(device)) {
step = safeMediaVolumeIndex(device);
} else {
step = streamState.getMaxIndex();
}
if (aliasIndex != 0) {
aliasIndex = step;
}
} else {
//计算音量调节步幅(按键每次步进是1)
// convert one UI step (+/-1) into a number of internal units on the stream alias
step = rescaleStep(10, streamType, streamTypeAlias);
}
// If either the client forces allowing ringer modes for this adjustment,
// or the stream type is one that is affected by ringer modes
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(isUiSoundsStreamType(streamTypeAlias))) {
int ringerMode = getRingerModeInternal();
// do not vibrate if already in vibrate mode
if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
flags &= ~AudioManager.FLAG_VIBRATE;
}
// Check if the ringer mode handles this adjustment. If it does we don't
// need to adjust the volume further.
final int result = checkForRingerModeChange(aliasIndex, direction, step,
streamState.mIsMuted, callingPackage, flags);
adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
// If suppressing a volume adjustment in silent mode, display the UI hint
if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
flags |= AudioManager.FLAG_SHOW_SILENT_HINT;
}
// If suppressing a volume down adjustment in vibrate mode, display the UI hint
if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
}
}
// If the ringer mode or zen is muting the stream, do not change stream unless
// it'll cause us to exit dnd
if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {
adjustVolume = false;
}
int oldIndex = mStreamStates[streamType].getIndex(device);
// Check if the volume adjustment should be handled by an absolute volume controller instead
if (isAbsoluteVolumeDevice(device)
&& (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) {
AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
if (info.mHandlesVolumeAdjustment) {
dispatchAbsoluteVolumeAdjusted(streamType, info, oldIndex, direction,
keyEventMode);
return;
}
}
if (adjustVolume && (direction != AudioManager.ADJUST_SAME)
&& (keyEventMode != AudioDeviceVolumeManager.ADJUST_MODE_END)) {
mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
if (isMuteAdjust && !mFullVolumeDevices.contains(device)) {
boolean state;
if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {
state = !streamState.mIsMuted;
} else {
state = direction == AudioManager.ADJUST_MUTE;
}
muteAliasStreams(streamTypeAlias, state);
} else if ((direction == AudioManager.ADJUST_RAISE) &&
!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
mVolumeController.postDisplaySafeVolumeWarning(flags);
} else if (!isFullVolumeDevice(device)
//在此处修改stream对应的index
&& (streamState.adjustIndex(direction * step, device, caller,
hasModifyAudioSettings)
|| streamState.mIsMuted)) {
// Post message to set system volume (it in turn will post a
// message to persist).
if (streamState.mIsMuted) {
// Unmute the stream if it was previously muted
if (direction == AudioManager.ADJUST_RAISE) {
// unmute immediately for volume up
muteAliasStreams(streamTypeAlias, false);
} else if (direction == AudioManager.ADJUST_LOWER) {
if (mIsSingleVolume) {
sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
}
}
}
//重点来了,发送设置设备音量的消息,这个调用会使得音量修改到达底层audioflinger并真正生效
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
&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
if (DEBUG_VOL) {
Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index="
+ newIndex + "stream=" + streamType);
}
mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10);
} else if (isAbsoluteVolumeDevice(device)
&& (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) {
AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
dispatchAbsoluteVolumeChanged(streamType, info, newIndex);
}
if (AudioSystem.isLeAudioDeviceType(device)
&& streamType == getBluetoothContextualVolumeStream()
&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
if (DEBUG_VOL) {
Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+ newIndex + " stream=" + streamType);
}
mDeviceBroker.postSetLeAudioVolumeIndex(newIndex,
mStreamStates[streamType].getMaxIndex(), streamType);
}
// Check if volume update should be send to Hearing Aid
if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
// only modify the hearing aid attenuation when the stream to modify matches
// the one expected by the hearing aid
if (streamType == getBluetoothContextualVolumeStream()) {
if (DEBUG_VOL) {
Log.d(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index="
+ newIndex + " stream=" + streamType);
}
mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
}
}
}
final int newIndex = mStreamStates[streamType].getIndex(device);
if (adjustVolume) {
//针对接有HDMI设备并且适配了HDMI相关接口的产品
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
// At most one of mHdmiPlaybackClient and mHdmiTvClient should be non-null
HdmiClient fullVolumeHdmiClient = mHdmiPlaybackClient;
if (mHdmiTvClient != null) {
fullVolumeHdmiClient = mHdmiTvClient;
}
if (fullVolumeHdmiClient != null
&& mHdmiCecVolumeControlEnabled
&& streamTypeAlias == AudioSystem.STREAM_MUSIC
// vol change on a full volume device
&& isFullVolumeDevice(device)) {
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:
case AudioManager.ADJUST_MUTE:
case AudioManager.ADJUST_UNMUTE:
// Many CEC devices only support toggle mute. Therefore, we send the
// same keycode for all three mute options.
keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
break;
default:
break;
}
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
final long ident = Binder.clearCallingIdentity();
try {
switch (keyEventMode) {
case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, true);
fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, false);
break;
case AudioDeviceVolumeManager.ADJUST_MODE_START:
fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, true);
break;
case AudioDeviceVolumeManager.ADJUST_MODE_END:
fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, false);
break;
default:
Log.e(TAG, "Invalid keyEventMode " + keyEventMode);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
if (streamTypeAlias == AudioSystem.STREAM_MUSIC
&& (oldIndex != newIndex || isMuteAdjust)) {
maybeSendSystemAudioStatusCommand(isMuteAdjust);
}
}
}
}
//发送音量改变的通知
sendVolumeUpdate(streamType, oldIndex, newIndex, flags, device);
}
adjustStreamVolume里面的代码同样很长,涉及到很多不同的场景要处理。主脉络是
1.修改streamtype的index。我们在此处加log可以判断音量值(index)是否调节符合预期。
java
streamState.adjustIndex(direction * step, device, caller,
hasModifyAudioSettings)
2.发送消息(message)将刷新后的音量值(index)设置到底层。就是这行代码。
java
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
MSG_SET_DEVICE_VOLUME消息处理
Audioservice内部有个handler负责将client对server的调用全部顺序执行。
java
public void handleMessage(Message msg) {
5350 switch (msg.what) {
5351
5352 case MSG_SET_DEVICE_VOLUME:
5353 setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);
5354 break;
5355
java
/*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
5033
5034 final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
5035
5036 synchronized (VolumeStreamState.class) {
5037 // Apply volume
//将VolumeStreamState的index值设置到底层
5038 streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
5039
5040 // Apply change to all streams using this one as alias
//将使用当前streamtype作为别名的streamtype的index也同步设置到底层
5041 int numStreamTypes = AudioSystem.getNumStreamTypes();
5042 for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
5043 if (streamType != streamState.mStreamType &&
5044 mStreamVolumeAlias[streamType] == streamState.mStreamType) {
5045 // Make sure volume is also maxed out on A2DP device for aliased stream
5046 // that may have a different device selected
5047 int streamDevice = getDeviceForStream(streamType);
5048 if ((device != streamDevice) && isAvrcpAbsVolSupported
5049 && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
5050 mStreamStates[streamType].applyDeviceVolume_syncVSS(device,
5051 isAvrcpAbsVolSupported);
5052 }
5053 mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice,
5054 isAvrcpAbsVolSupported);
5055 }
5056 }
5057 }
5058 // Post a persist volume msg
//将VolumeStreamState的index保存到settings进行持久化
5059 sendMsg(mAudioHandler,
5060 MSG_PERSIST_VOLUME,
5061 SENDMSG_QUEUE,
5062 device,
5063 0,
5064 streamState,
5065 PERSIST_DELAY);
5066
5067 }
由setDeviceVolume函数负责处理MSG_SET_DEVICE_VOLUME消息。主要逻辑如下:
1.通过applyDeviceVolume_syncVSS函数将VolumeStreamState的index值同步设置到底层。VSS就是VolumeStreamState的缩写。
2.将使用当前streamtype作为别名的streamtype的index也同步设置到底层。
3.将VolumeStreamState的index保存到settings进行持久化
applyDeviceVolume_syncVSS
函数很简单,比较重要的一点是通过将index设置为0来实现mute。接着调用setStreamVolumeIndex函数。
java
void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) {
4656 int index;
4657 if (mIsMuted) {
4658 index = 0;
4659 } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) {
4660 index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
4661 } else if ((device & mFullVolumeDevices) != 0) {
4662 index = (mIndexMax + 5)/10;
4663 } else if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
4664 index = (mIndexMax + 5)/10;
4665 } else {
4666 index = (getIndex(device) + 5)/10;
4667 }
4668 setStreamVolumeIndex(index, device);
4669 }
setStreamVolumeIndex
setStreamVolumeIndex函数最终将VolumeStreamState的mStreamType,index和device通过AudioSystem接口设置到底层。
java
private void setStreamVolumeIndex(int index, int device) {
4645 // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
4646 // This allows RX path muting by the audio HAL only when explicitly muted but not when
4647 // index is just set to 0 to repect BT requirements
4648 if (mStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0 && !mIsMuted) {
4649 index = 1;
4650 }
4651 AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
4652 }
4653
MSG_PERSIST_VOLUME消息处理
通过调用persistVolume函数处理MSG_PERSIST_VOLUME消息。
java
5360 case MSG_PERSIST_VOLUME:
5361 persistVolume((VolumeStreamState) msg.obj, msg.arg1);
5362 break;
persistVolume持久化音量index
通过Android系统的ContentResolver机制对音量的index进行持久化。对ContentResolver机制感兴趣的同学可以搜集资料了解一下。根据AudioService持久化音量的方式,我们可以在自己的应用里面监听音量index的变化以实现某些定制需求。
java
private void persistVolume(VolumeStreamState streamState, int device) {
5088 if (mUseFixedVolume) {
5089 return;
5090 }
5091 if (mIsSingleVolume && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
5092 return;
5093 }
5094 if (streamState.hasValidSettingsName()) {
5095 System.putIntForUser(mContentResolver,
5096 streamState.getSettingNameForDevice(device),
5097 (streamState.getIndex(device) + 5)/ 10,
5098 UserHandle.USER_CURRENT);
5099 }
5100 }
至此,调节音量在AudioService中的实现已经分析完了。接下来的逻辑我们在后面继续讨论,现在做一下总结。
总结
1.AudioService通过VolumeStreamState维护每一种streamtype对应的index。
2.每个index由streamtype和device共同决定。
3.index设置到底层生效最终是通过AudioSystem的接口。(后续我们将会看到底层设置音量做了哪些工作)
4.Audioservice涉及和底层交互的逻辑都会通过消息机制(handler)进行异步序列化处理。