通过遥控器调节功放设备的音量和静音状态,以及通过安卓TV设备音量条UI设置静音状态的接口都是 adjustStreamVolume方法,该方法的代码在2-2章节中有写,接下来主要分析一下改方法中与ARC功放设备音量设置相关的代码。
protected void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid, int pid, String attributionTag,
boolean hasModifyAudioSettings, int keyEventMode) {
......
//获取音频流的别名
int streamTypeAlias = mStreamVolumeAlias[streamType];
//通过别名获取streamState对象
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
//传入音量流的别名`streamTypeAlias`,以确定应该将音量调整应用于哪个音频设备
> [!代码1]
> final int device = getDeviceForStream(streamTypeAlias);
//获取音频设备当前的音量值
int aliasIndex = streamState.getIndex(device);
//表示默认情况下音量需要被调整
boolean adjustVolume = true;
......
//当接入HDMI矩阵的时候走的这里
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);
//判断声音是否需要调整//判断是否是满音量设备
> [!代码2]
> if (isMuteAdjust && !mFullVolumeDevices.contains(device)) {
boolean state;
if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {
state = !streamState.mIsMuted;
} else {
state = direction == AudioManager.ADJUST_MUTE;
}
for (int stream = 0; stream < mStreamStates.length; stream++) {
if (streamTypeAlias == mStreamVolumeAlias[stream]) {
if (!(readCameraSoundForced()
&& (mStreamStates[stream].getStreamType()
== AudioSystem.STREAM_SYSTEM_ENFORCED))) {
//这里调用静音方法
> [!代码3]
> mStreamStates[stream].mute(state);
}
}
}
} else if ((direction == AudioManager.ADJUST_RAISE) &&
!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
mVolumeController.postDisplaySafeVolumeWarning(flags);
> [!代码4]
> } else if (!isFullVolumeDevice(device)
//判断是否是满音量设备//调整音量值
&& (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
streamState.mute(false);
} else if (direction == AudioManager.ADJUST_LOWER) {
if (mIsSingleVolume) {
sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
}
}
}
//发送MSG_SET_DEVICE_VOLUME消息更新设备的声音
> [!代码5]
> sendMsg(mAudioHandler,
> MSG_SET_DEVICE_VOLUME,
> SENDMSG_QUEUE,
> device,
> 0,
> streamState,
> 0);
}
......
}
......
if (adjustVolume) {
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
// At most one of mHdmiPlaybackClient and mHdmiTvClient should be non-null
HdmiClient fullVolumeHdmiClient = mHdmiPlaybackClient;
if (mHdmiTvClient != null) {
fullVolumeHdmiClient = mHdmiTvClient;
}
//判断是否是ARC设备//判断音频流别名是否是STREAM_MUSIC
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:
> [!代码6]
> 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);
}
代码1
@VisibleForTesting
public int getDeviceForStream(int stream) {
return selectOneAudioDevice(getDeviceSetForStream(stream));
}
/*
* Must match native apm_extract_one_audio_device() used in getDeviceForVolume() * or the wrong device volume may be adjusted. */
private int selectOneAudioDevice(Set<Integer> deviceSet) {
if (deviceSet.isEmpty()) {
return AudioSystem.DEVICE_NONE;
} else if (deviceSet.size() == 1) {
return deviceSet.iterator().next();
} else {
// Multiple device selection is either:
// - speaker + one other device: give priority to speaker in this case.
// - one A2DP device + another device: happens with duplicated output. In this case
// retain the device on the A2DP output as the other must not correspond to an active
// selection if not the speaker.
// - HDMI-CEC system audio mode only output: give priority to available item in order.
if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER)) {
return AudioSystem.DEVICE_OUT_SPEAKER;
} else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER_SAFE)) {
// Note: DEVICE_OUT_SPEAKER_SAFE not present in getDeviceSetForStreamDirect
return AudioSystem.DEVICE_OUT_SPEAKER_SAFE;
} else if (deviceSet.contains(AudioSystem.DEVICE_OUT_HDMI_ARC)) {
return AudioSystem.DEVICE_OUT_HDMI_ARC;
} else if (deviceSet.contains(AudioSystem.DEVICE_OUT_HDMI_EARC)) {
return AudioSystem.DEVICE_OUT_HDMI_EARC;
} else if (deviceSet.contains(AudioSystem.DEVICE_OUT_AUX_LINE)) {
return AudioSystem.DEVICE_OUT_AUX_LINE;
} else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPDIF)) {
return AudioSystem.DEVICE_OUT_SPDIF;
} else {
// At this point, deviceSet should contain exactly one A2DP device;
// regardless, return the first A2DP device in numeric order.
// If there is no A2DP device, this falls through to log an error.
for (int deviceType : deviceSet) {
if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(deviceType)) {
return deviceType;
}
}
}
}
Log.w(TAG, "selectOneAudioDevice returning DEVICE_NONE from invalid device combination "
+ AudioSystem.deviceSetToString(deviceSet));
return AudioSystem.DEVICE_NONE;
}
代码2
这里可以看到mFullVolumeDevices集合里面包含了DEVICE_OUT_HDMI_ARC和DEVICE_OUT_HDMI_EARC
这俩设备的ID
public static final int DEVICE_OUT_HDMI_ARC = 0x40000; (转为十进制:262144)
public static final int DEVICE_OUT_HDMI_EARC = 0x40001; (转为十进制:262145)
机器内置的扬声器设备ID:public static final int DEVICE_OUT_SPEAKER = 0x2;
// Devices for which the volume is always max, no volume panel
Set<Integer> mFullVolumeDevices = new HashSet<>(Arrays.asList(
AudioSystem.DEVICE_OUT_HDMI_ARC,
AudioSystem.DEVICE_OUT_HDMI_EARC
));
代码3
//更新了静音状态并发送MSG_SET_ALL_VOLUMES消息将所有设备的音量设置为0
public boolean mute(boolean state) {
boolean changed = false;
synchronized (VolumeStreamState.class) {
if (state != mIsMuted) {
changed = true;
mIsMuted = state;
// Set the new mute volume. This propagates the values to
// the audio system, otherwise the volume won't be changed
// at the lower level.
sendMsg(mAudioHandler,
MSG_SET_ALL_VOLUMES,
SENDMSG_QUEUE,
0,
0,
this, 0);
}
}
if (changed) {
// Stream mute changed, fire the intent.
Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state);
sendBroadcastToAll(intent);
}
return changed;
}
case MSG_SET_ALL_VOLUMES:
setAllVolumes((VolumeStreamState) msg.obj);
break;
//调用了mStreamStates[streamType].applyAllVolumes()方法,这个方法在章节2-3中有分析
private void setAllVolumes(VolumeStreamState streamState) {
// Apply volume
streamState.applyAllVolumes();
// Apply change to all streams using this one as alias
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
if (streamType != streamState.mStreamType &&
mStreamVolumeAlias[streamType] == streamState.mStreamType) {
mStreamStates[streamType].applyAllVolumes();
}
}
}
//判断是否静音接口
public boolean isStreamMute(int streamType) {
if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
streamType = getActiveStreamType(streamType);
}
synchronized (VolumeStreamState.class) {
ensureValidStreamType(streamType);
return mStreamStates[streamType].mIsMuted;
}
}
代码4
//这里方法里面返回的是mFullVolumeDevices.contains(deviceType
private boolean isFullVolumeDevice(int deviceType) {
if (deviceType == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
&& mRecordMonitor.isLegacyRemoteSubmixActive()) {
return false;
}
return mFullVolumeDevices.contains(deviceType);
}
代码5
//接收MSG_SET_DEVICE_VOLUME消息更新设备音量
case MSG_SET_DEVICE_VOLUME:
setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
/*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
synchronized (VolumeStreamState.class) {
// Apply volume
> [!代码5-1]
> streamState.applyDeviceVolume_syncVSS(device);
// Apply change to all streams using this one as alias
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
if (streamType != streamState.mStreamType &&
mStreamVolumeAlias[streamType] == streamState.mStreamType) {
// 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)
&& (isAbsoluteVolumeDevice(device)
|| isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device))) {
mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
}
mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
}
}
}
// Post a persist volume msg
> [!代码5-2]
> sendMsg(mAudioHandler,
> MSG_PERSIST_VOLUME,
> SENDMSG_QUEUE,
> device,
> 0,
> streamState,
> PERSIST_DELAY);
}
代码5-1
//这个方法最后也是调用的setStreamVolumeIndex方法去设置设备的音量
//可以通过命令dumpsys audio查看设备音量相关信息
/*package*/ void applyDeviceVolume_syncVSS(int device) {
int index;
if (isFullyMuted()) {
index = 0;
} else if (isAbsoluteVolumeDevice(device)
|| isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device)) {
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
} else if (isFullVolumeDevice(device)) {
index = (mIndexMax + 5)/10;
} else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
index = (mIndexMax + 5)/10;
} else {
index = (getIndex(device) + 5)/10;
}
if (isTablet() && isSyncAjustVolumeDevice(device) && mStreamType == AudioSystem.STREAM_MUSIC) {
for (Integer item : mSyncAjustVolumeDevices) {
index = (getIndex(item) + 5)/10;
setStreamVolumeIndex(index, item);
}
} else {
setStreamVolumeIndex(index, device);
}
}
代码5-2
//MSG_PERSIST_VOLUME消息通过handleMessage进入persistVolume,最终调用System.putIntForUser将用户设置的内容设置到Settings.system中,设备声音初始化的时候通过这个属性去获取音量的初始值
可以通过指令:settings list system 查看该值
或者通过命令: settings get system volume_music_speaker 单独获取某个设备的值
case MSG_PERSIST_VOLUME:
persistVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
private void persistVolume(VolumeStreamState streamState, int device) {
if (mUseFixedVolume) {
return;
}
if (mIsSingleVolume && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
return;
}
if (streamState.hasValidSettingsName()) {
mSettings.putSystemIntForUser(mContentResolver,
streamState.getSettingNameForDevice(device),
(streamState.getIndex(device) + 5)/ 10,
UserHandle.USER_CURRENT);
if(isTablet())
streamState.restoreSyncDevicesIndex();
}
}
代码6
//调用的HdmiControlService.java的sendVolumeKeyEvent方法发送键值更新功放设备的音量,这部分的代码不追下去了
@Override
public void sendVolumeKeyEvent(
final int deviceType, final int keyCode, final boolean isPressed) {
initBinderCall();
runOnServiceThread(new Runnable() {
@Override
public void run() {
if (mCecController == null) {
Slog.w(TAG, "CEC controller not available to send volume key event.");
return; }
HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType);
if (localDevice == null) {
Slog.w(TAG, "Local device " + deviceType
+ " not available to send volume key event.");
return; }
localDevice.sendVolumeKeyEvent(keyCode, isPressed);
}
});
}