Android平台音量调节
本文基于Android 8.0讲述Android平台原生音量控制功能。
流的定义
Android中,音量都是分开控制,各种流定义各种流的音量。在Android8.0中,定义了11种流类型。对每种流类型都定义了最大音量(MAX_STREAM_VOLUME),默认音量(DEFAULT_STREAM_VOLUME)和最小音量(MIN_STREAM_VOLUME)。一个流也可以用另外一个流的音量设置,所以需要一个别名映射。
- 最大音量
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private static int[] MAX_STREAM_VOLUME = new int[] {
5, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
7, // STREAM_RING
15, // STREAM_MUSIC
7, // STREAM_ALARM
7, // STREAM_NOTIFICATION
15, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
15, // STREAM_DTMF
15, // STREAM_TTS
15 // STREAM_ACCESSIBILITY
};
- 最小音量
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private static int[] MIN_STREAM_VOLUME = new int[] {
1, // STREAM_VOICE_CALL
0, // STREAM_SYSTEM
0, // STREAM_RING
0, // STREAM_MUSIC
0, // STREAM_ALARM
0, // STREAM_NOTIFICATION
0, // STREAM_BLUETOOTH_SCO
0, // STREAM_SYSTEM_ENFORCED
0, // STREAM_DTMF
0, // STREAM_TTS
0 // STREAM_ACCESSIBILITY
};
- 默认音量
* frameworks/base/media/java/android/media/AudioSystem.java
public static int[] DEFAULT_STREAM_VOLUME = new int[] {
4, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
5, // STREAM_RING
11, // STREAM_MUSIC
6, // STREAM_ALARM
5, // STREAM_NOTIFICATION
7, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
11, // STREAM_DTMF
11, // STREAM_TTS
11, // STREAM_ACCESSIBILITY
};
- 别名映射
Android中各种设备的映射不尽相同,Android中定义了3中设备DEFAULT,VOICE,TELEVISION对应的别名映射。(STREAM_VOLUME_ALIAS_VOICE)。
STREAM_VOLUME_ALIAS_VOICE对应能处理能处理Voice的设备,比如电话,请映射如下:
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
AudioSystem.STREAM_RING, // STREAM_SYSTEM
AudioSystem.STREAM_RING, // STREAM_RING
AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
AudioSystem.STREAM_ALARM, // STREAM_ALARM
AudioSystem.STREAM_RING, // STREAM_NOTIFICATION
AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_RING, // STREAM_DTMF
AudioSystem.STREAM_MUSIC, // STREAM_TTS
AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
STREAM_VOLUME_ALIAS_TELEVISION对应电视,机顶盒等。
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
AudioSystem.STREAM_MUSIC, // STREAM_VOICE_CALL
AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM
AudioSystem.STREAM_MUSIC, // STREAM_RING
AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
AudioSystem.STREAM_MUSIC, // STREAM_ALARM
AudioSystem.STREAM_MUSIC, // STREAM_NOTIFICATION
AudioSystem.STREAM_MUSIC, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_MUSIC, // STREAM_DTMF
AudioSystem.STREAM_MUSIC, // STREAM_TTS
AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
STREAM_VOLUME_ALIAS_DEFAULT其实和Voice映射是一样的,对应其他的设备,比如平板等。
private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
AudioSystem.STREAM_RING, // STREAM_SYSTEM
AudioSystem.STREAM_RING, // STREAM_RING
AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
AudioSystem.STREAM_ALARM, // STREAM_ALARM
AudioSystem.STREAM_RING, // STREAM_NOTIFICATION
AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_RING, // STREAM_DTMF
AudioSystem.STREAM_MUSIC, // STREAM_TTS
AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
映射关系如下:
流代号 | 流类型 | 默认音量 | 最大音量 | 最小音量 | DEFAULT/Voice映射 | TELEVISION映射 |
---|---|---|---|---|---|---|
0 | STREAM_VOICE_CALL | 4 | 5 | 1 | STREAM_VOICE_CALL | STREAM_MUSIC |
1 | STREAM_SYSTEM | 5 | 7 | 0 | STREAM_RING | STREAM_MUSIC |
2 | STREAM_RING | 5 | 7 | 0 | STREAM_RING | STREAM_MUSIC |
5 | STREAM_NOTIFICATION | 5 | 7 | 0 | STREAM_RING | STREAM_MUSIC |
7 | STREAM_SYSTEM_ENFORCED | 7 | 7 | 0 | STREAM_RING | STREAM_MUSIC |
8 | STREAM_DTMF | 11 | 15 | 0 | STREAM_RING | STREAM_MUSIC |
3 | STREAM_MUSIC | 11 | 15 | 0 | STREAM_MUSIC | STREAM_MUSIC |
9 | STREAM_TTS | 11 | 15 | 0 | STREAM_MUSIC | STREAM_MUSIC |
10 | STREAM_ACCESSIBILITY | 11 | 15 | 0 | STREAM_MUSIC | STREAM_MUSIC |
4 | STREAM_ALARM | 6 | 7 | 0 | STREAM_ALARM | STREAM_MUSIC |
6 | STREAM_BLUETOOTH_SCO | 7 | 15 | 0 | STREAM_BLUETOOTH_SCO | STREAM_MUSIC |
所以在手机设备中,从上表来看,我们能调节的其实就5个音量。当你想调节STREAM_SYSTEM,STREAM_NOTIFICATION等流类型的音量时,实际上是调节了STREAM_RING的音量。当前可控的流类型可以通过下表更直观地显示:
流类型 | 默认音量 | 最大音量 | 意义 |
---|---|---|---|
STREAM_VOICE_CALL | 4 | 5 | 通话音量 |
STREAM_RING | 5 | 7 | 响铃,通知,系统默认音等 |
STREAM_MUSIC | 11 | 15 | 多媒体音量 |
STREAM_ALARM | 6 | 7 | 闹钟音量 |
STREAM_BLUETOOTH_SCO | 7 | 15 | 蓝牙音量 |
音量键的处理流程
音量键是常用的调节音量的方式,调节音量时,是调节音量节,比如媒体音STREAM_MUSIC的最大音量为15,其实是15节,你也可以理解为16节,因为还有0.
音量键的处理
Android设置了3个音量处理键,音量加,音量减和静音。3个音量键通过PhoneWindowManager进行处理。
* frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private void dispatchDirectAudioEvent(KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return;
}
int keyCode = event.getKeyCode();
int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
| AudioManager.FLAG_FROM_KEY;
String pkgName = mContext.getOpPackageName();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
try {
getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
} catch (Exception e) {
Log.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) {
Log.e(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e);
}
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
try {
if (event.getRepeatCount() == 0) {
getAudioService().adjustSuggestedStreamVolume(
AudioManager.ADJUST_TOGGLE_MUTE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
}
} catch (Exception e) {
Log.e(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e);
}
break;
}
}
音量的加减都是一节一节的调节的,比如最大音量为 7,表示有7节,加上0,就是8节。
3音量按键对应如下:
按键 | AudioService 对应的操作 | 实际意义 |
---|---|---|
KEYCODE_VOLUME_UP | ADJUST_RAISE | 音量加 |
KEYCODE_VOLUME_DOWN | ADJUST_LOWER | 音量减 |
KEYCODE_VOLUME_MUTE | ADJUST_TOGGLE_MUTE | 静音按钮 |
ADJUST_TOGGLE_MUTE是静音按钮,如果现在是静音的,那么将切为非静音;如果是非静音的,那么将设置为静音。相关的还有ADJUST_MUTE和ADJUST_UNMUTE。
此外,AudioService相关的还有ADJUST_SAME,这个只是弹出调节音量的框,不去修改音量。
adjustSuggestedStreamVolume通过Binder调到AudioService中。
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller) {
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
caller, Binder.getCallingUid());
}
adjustSuggestedStreamVolume调同名函数adjustSuggestedStreamVolume:
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller, int uid) {
final int streamType;
if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
streamType = mVolumeControlStream;
} else {
final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
final boolean activeForReal;
... ...
}
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;
}
if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) {
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);
}
这里主要做了以下几件事:
- mUserSelectedVolumeControlStream 表示强制控制某一种流类型,这里应该很少走
- getActiveStreamType获取要控制的流类型,从Binder那边传过来的 值为USE_DEFAULT_STREAM_TYPE,getActiveStreamType大概流程如下:
- 获取映射别名 resolvedStream
- 对于通知音或铃声STREAM_RING,先显示调节音量的对话框,这个在AudioService的内部类VolumeController中处理。
- 最后,通过adjustStreamVolume函数设置音量
adjustStreamVolume函数
adjustStreamVolume函数比较长,我们分段来看
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
... ...
ensureValidDirection(direction);
ensureValidStreamType(streamType);
boolean isMuteAdjust = isMuteAdjust(direction);
if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {
return;
}
int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
- 获取需要设置音量类型的映射别名streamTypeAlias
- 获取音量类型对应的音量信息状态 streamState,VolumeStreamState 音量控制的核心,映射后的每种音量都有各自的VolumeStreamState,它保存了一个流类型所有的音量信息。
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
... ...
final int device = getDeviceForStream(streamTypeAlias);
int aliasIndex = streamState.getIndex(device);
boolean adjustVolume = true;
int step;
- 获取流类型对应的设备
流各自的VolumeStreamState通过observeDevicesForStream_syncVSS去获取对应的device,observeDevicesForStream_syncVSS的获取到的是一个device列表,最终是通过AudioSystem去native获取的。
再从获取的device列表中进行二次选择device:
private int getDeviceForStream(int stream) {
int device = getDevicesForStream(stream);
if ((device & (device - 1)) != 0) {
if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {
device = AudioSystem.DEVICE_OUT_SPEAKER;
} else if ((device & AudioSystem.DEVICE_OUT_HDMI_ARC) != 0) {
device = AudioSystem.DEVICE_OUT_HDMI_ARC;
} else if ((device & AudioSystem.DEVICE_OUT_SPDIF) != 0) {
device = AudioSystem.DEVICE_OUT_SPDIF;
} else if ((device & AudioSystem.DEVICE_OUT_AUX_LINE) != 0) {
device = AudioSystem.DEVICE_OUT_AUX_LINE;
} else {
device &= AudioSystem.DEVICE_OUT_ALL_A2DP;
}
}
return device;
}
继续看adjustStreamVolume,这部分我们以注释的形式加在代码中
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
... ...
// 绝对音量控制
if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 &&
(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
return;
}
// user处理
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
}
// 权限处理
if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
// 清掉待处理的音量处理命令
synchronized (mSafeMediaVolumeState) {
mPendingVolumeCommand = null;
}
// 处理是否混音,获取step
flags &= ~AudioManager.FLAG_FIXED_VOLUME;
if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
((device & mFixedVolumeDevices) != 0)) {
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 &&
(device & mSafeMediaVolumeDevices) != 0) {
step = mSafeMediaVolumeIndex;
} else {
step = streamState.getMaxIndex();
}
if (aliasIndex != 0) {
aliasIndex = step;
}
} else {
// convert one UI step (+/-1) into a number of internal units on the stream alias
step = rescaleIndex(10, streamType, streamTypeAlias);
}
// 响铃模式处理
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(streamTypeAlias == getUiSoundsStreamType())) {
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;
}
}
// 如果不设置音量adjustVolume
if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {
adjustVolume = false;
}
int oldIndex = mStreamStates[streamType].getIndex(device);
if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
// 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) {
synchronized (mA2dpAvrcpLock) {
if (mA2dp != null && mAvrcpAbsVolSupported) {
mA2dp.adjustAvrcpAbsoluteVolume(direction);
}
}
}
// 静音设置
if (isMuteAdjust) {
boolean state;
if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {
state = !streamState.mIsMuted;
} else {
state = direction == AudioManager.ADJUST_MUTE;
}
if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
setSystemAudioMute(state);
}
for (int stream = 0; stream < mStreamStates.length; stream++) {
if (streamTypeAlias == mStreamVolumeAlias[stream]) {
if (!(readCameraSoundForced()
&& (mStreamStates[stream].getStreamType()
== AudioSystem.STREAM_SYSTEM_ENFORCED))) {
mStreamStates[stream].mute(state);
}
}
}
} else if ((direction == AudioManager.ADJUST_RAISE) &&
!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
//安全模式,提示音量过高
mVolumeController.postDisplaySafeVolumeWarning(flags);
} else if (streamState.adjustIndex(direction * step, device, caller)
|| streamState.mIsMuted) {
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);
}
}
} // 设置到底层
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
}
// 是否需要设置HDMI的音量
int newIndex = mStreamStates[streamType].getIndex(device);
if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
}
if (mHdmiManager != null) {
synchronized (mHdmiManager) {
// mHdmiCecSink true => mHdmiPlaybackClient != null
if (mHdmiCecSink &&
streamTypeAlias == AudioSystem.STREAM_MUSIC &&
oldIndex != newIndex) {
synchronized (mHdmiPlaybackClient) {
int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :
KeyEvent.KEYCODE_VOLUME_UP;
final long ident = Binder.clearCallingIdentity();
try {
mHdmiPlaybackClient.sendKeyEvent(keyCode, true);
mHdmiPlaybackClient.sendKeyEvent(keyCode, false);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
}
}
}
// 更新音量
int index = mStreamStates[streamType].getIndex(device);
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
在上面这段代码中,需要注意下面几个关键的流程:
- 计算step
如果不是音乐类型,且固定音量,那么音量调节将转换,转换的算法为:
private int rescaleIndex(int index, int srcStream, int dstStream) {
return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex();
}
index为10,所以STREAM_NOTIFICATION的step为10,STREAM_DTMF的step为(10x15 + 15/2) / 15 = 5
- 处理RingMode
Android定义了如下的模式:
public static final int RINGER_MODE_SILENT = 0;
public static final int RINGER_MODE_VIBRATE = 1;
public static final int RINGER_MODE_NORMAL = 2;
public static final int RINGER_MODE_MAX = RINGER_MODE_NORMAL;
- 设置音量
首先处理静音,处理安全音量,最后再处理音量。
音量首先保存在每个流类型的VolumeStreamState中:
public boolean adjustIndex(int deltaIndex, int device, String caller) {
return setIndex(getIndex(device) + deltaIndex, device, caller);
}
在通过 MSG_SET_DEVICE_VOLUME 消息设置到底层。AudioHandler通过setDeviceVolume处理MSG_SET_DEVICE_VOLUME消息。最后是通过AudioSystem的native接口设置到native。
- 通知音量更改
最后,通过sendVolumeUpdate广播整个系统,通知整个系统音量修改。
看完代码,我们来看看Java层的时序吧~