在Android系统中,默认的设备(phone等)音量都是分开控制的,这些包括媒体、铃声、闹铃、蓝牙、通话通过音频流来区别不同的音量类型。每种流类型都定义最大音量、最小音量及默认音量,AndroidN及之前是十种流类型,Android8.0之后添加了STREAM_ACCESSIBILITY。
-frameworks/base/media/java/android/media/AudioSystem.java
public static final String[] STREAM_NAMES = new String[] {
"STREAM_VOICE_CALL",
"STREAM_SYSTEM",
"STREAM_RING",
"STREAM_MUSIC",
"STREAM_ALARM",
"STREAM_NOTIFICATION",
"STREAM_BLUETOOTH_SCO",
"STREAM_SYSTEM_ENFORCED",
"STREAM_DTMF",
"STREAM_TTS"
};
-frameworks/base/services/core/java/com/android/server/audio/AudioService.java
/** Maximum volume index values for audio streams */
private static int[] MAX_STREAM_VOLUME = new int[] {
5, // STREAM_VOICE_CALL
15, // STREAM_SYSTEM
15, // STREAM_RING
15, // STREAM_MUSIC
15, // STREAM_ALARM
7, // STREAM_NOTIFICATION
15, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
15, // STREAM_DTMF
15 // STREAM_TTS
};
-frameworks/base/services/core/java/com/android/server/audio/AudioService.java
/** Minimum volume index values for audio streams */
private static int[] MIN_STREAM_VOLUME = new int[] {
1, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
7, // STREAM_RING
0, // STREAM_MUSIC
7, // STREAM_ALARM
0, // STREAM_NOTIFICATION
0, // STREAM_BLUETOOTH_SCO
0, // STREAM_SYSTEM_ENFORCED
0, // STREAM_DTMF
0 // STREAM_TTS
};
若有需求需要客制化相关的默认音量在此处修改即可
-frameworks/base/media/java/android/media/AudioSystem.java
public static int[] DEFAULT_STREAM_VOLUME = new int[] {
4, // STREAM_VOICE_CALL
15, // STREAM_SYSTEM
15, // STREAM_RING
11, // STREAM_MUSIC
15, // STREAM_ALARM
5, // STREAM_NOTIFICATION
7, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
11, // STREAM_DTMF
11 // STREAM_TTS
};
不同设备的映射定义不同,系统中一共定义三种设备的音频流的映射,分别是STREAM_VOLUME_ALIAS_VOICE,STREAM_VOLUME_ALIAS_TELEVISION,STREAM_VOLUME_ALIAS_DEFAULT
StreamAlias存在的意义:流类型别名,虽然stream有多种类型,但某些方面的行为一致
在手机和平板上实际上能调节的就是五种音量,在TV和就机顶盒之类设备能调节的仅仅一种音量即music,故如有需求需要统一音量的可以将当前的音频流改为TV类型
-frameworks/base/services/core/java/com/android/server/audio/AudioService.java
1.voice--具有语音功能的设备,电话等
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
};
2. television--电视机顶盒或投影设备
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
};
3. default--平板之类的设备
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
};
先判断音量调整的UI是否还存在,若存在在当前流类型尚进行调整音量,不存在通过MediaSerssion创建UI并调整音量
MediaSession在Android5.0之后开始用于对音量控制,通过其相关的类,最终在MediasessionService中调用AudioService的adjustSuggestedStreamVolume()进行真正的音量设置初步处理
- frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
/**
* A key was pressed down and not handled by anything else in the window.
*
* @see #onKeyUp
* @see android.view.KeyEvent
*/
protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
...
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
int direction = 0;
switch (keyCode) {
// 音量上键
case KeyEvent.KEYCODE_VOLUME_UP:
direction = AudioManager.ADJUST_RAISE;
break;
// 音量下键
case KeyEvent.KEYCODE_VOLUME_DOWN:
direction = AudioManager.ADJUST_LOWER;
break;
// 静音操作
case KeyEvent.KEYCODE_VOLUME_MUTE:
direction = AudioManager.ADJUST_TOGGLE_MUTE;
break;
}
// If we have a session send it the volume command, otherwise
// use the suggested stream.
// 此处判断mMediaController是否为空(显示的音量调整UI是否还存在),若存在直接进行调节音量
if (mMediaController != null) {
// 通过MediaController实现调节调节音量
mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
} else {
MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
mVolumeControlStreamType, direction,
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE
| AudioManager.FLAG_FROM_KEY);
}
return true;
}
...
}
}
通过ISessionManager的Binder client
MediaSessionLegacyHelper.java
- frameworks/base/media/java/android/media/session/MediaSessionLegacyHelper.java
public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);
if (DEBUG) {
Log.d(TAG, "dispatched volume adjustment");
}
}
MediaSessionManager.java
- frameworks/base/media/java/android/media/session/MediaSessionManager.java
/**
* Dispatch an adjust volume request to the system. It will be sent to the
* most relevant audio stream or media session. The direction must be one of
* {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
* {@link AudioManager#ADJUST_SAME}.
*
* @param suggestedStream The stream to fall back to if there isn't a
* relevant stream
* @param direction The direction to adjust volume in.
* @param flags Any flags to include with the volume change.
* @hide
*/
public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
try {
mService.dispatchAdjustVolume(suggestedStream, direction, flags);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send adjust volume.", e);
}
}
进入MediaSessionService.java实现dispatchAdjustVolume方法
- frameworks/base/services/core/java/com/android/server/media/MediaSessionService.java
1. dispatchAdjustVolume
@Override
public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
MediaSessionRecord session = mPriorityStack
.getDefaultVolumeSession(mCurrentUserIdList);
dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
2. dispatchAdjustVolumeLocked
private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
MediaSessionRecord session) {
boolean preferSuggestedStream = false;
if (isValidLocalStreamType(suggestedStream)
&& AudioSystem.isStreamActive(suggestedStream, 0)) {
preferSuggestedStream = true;
}
if (DEBUG) {
Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
+ flags + ", suggestedStream=" + suggestedStream
+ ", preferSuggestedStream=" + preferSuggestedStream);
}
if (session == null || preferSuggestedStream) {
if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
&& !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
if (DEBUG) {
Log.d(TAG, "No active session to adjust, skipping media only volume event");
}
return;
}
try {
String packageName = getContext().getOpPackageName();
mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
flags, packageName, TAG);
} catch (RemoteException e) {
Log.e(TAG, "Error adjusting default volume.", e);
}
} else {
session.adjustVolume(direction, flags, getContext().getPackageName(),
Process.SYSTEM_UID, true);
}
}
AudioService的adjustSuggestedStreamVolume(),负责接收MediaSessionService传入的信息,然后针对要修改流类型获取相应的映射,更改是否显示ui的flag,之后进入adjustStreamVolume()
方法中:
mVolumeControlStream: 用户是否已通过单击音量进度条选择音量流由音量键控制的音量,即激活的音量流类型
suppressAdjustment: 抑制调整。当没有显示音量UI进度条时,不管是加/减音量(mute/unmute除外)
///////////////////////////////////////////////////////////////////////////
// IPC methods
///////////////////////////////////////////////////////////////////////////
/** @see AudioManager#adjustVolume(int, int) */
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller) {
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
caller, Binder.getCallingUid());
}
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller, int uid) {
if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
+ ", flags=" + flags + ", caller=" + caller);
int streamType;
boolean isMute = isMuteAdjust(direction);
// 此处可以更改需要修改的流类型
// mVolumeControlStream 判断该值是否为-1,只要不是-1,则要调整音量的流类型则为该对象。
// 该对象是VolumePanel通过forceVolumeControlStream()设置,方法名显而易见force即为强制设置
// VolumePanel就是按下音量键后显示的音量提示框
if (mVolumeControlStream != -1) {
streamType = mVolumeControlStream;
} else {
// 通过getActiveStreamType()获取需要控制的流类型
streamType = getActiveStreamType(suggestedStreamType);
}
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
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);
}
VolumePanel在显示时会调用forceVolumeControlStream方法强制后续的音量键操作固定为促使它选择的流类型,在其关闭后取消该强制设置,即设置mVolumeControlStream为-1
/** @see AudioManager#forceVolumeControlStream(int) */
public void forceVolumeControlStream(int streamType, IBinder cb) {
synchronized(mForceControlStreamLock) {
mVolumeControlStream = streamType;
if (mVolumeControlStream == -1) {
if (mForceControlStreamClient != null) {
mForceControlStreamClient.release();
mForceControlStreamClient = null;
}
} else {
mForceControlStreamClient = new ForceControlStreamClient(cb);
}
}
}
private class ForceControlStreamClient implements IBinder.DeathRecipient {
private IBinder mCb; // To be notified of client's death
ForceControlStreamClient(IBinder cb) {
if (cb != null) {
try {
cb.linkToDeath(this, 0);
} catch (RemoteException e) {
// Client has died!
Log.w(TAG, "ForceControlStreamClient() could not link to "+cb+" binder death");
cb = null;
}
}
mCb = cb;
}
public void binderDied() {
synchronized(mForceControlStreamLock) {
Log.w(TAG, "SCO client died");
if (mForceControlStreamClient != this) {
Log.w(TAG, "unregistered control stream client died");
} else {
mForceControlStreamClient = null;
mVolumeControlStream = -1;
}
}
}
public void release() {
if (mCb != null) {
mCb.unlinkToDeath(this, 0);
mCb = null;
}
}
}
执行的操作:
- 计算音量键的音量步进值,主要通过rescaleIndex()方法实现返回step值,由于不同的流类型的音量调节范围不同,所以转换是必需的。该步进值是10而不是1.在VolumeStreamState中保存的音量值是实际的10倍,这是为了在不同流类型之间进行音量转化时能够保证一定精度的一种实现即在转化过程中保留小数点后一位的精度
- 检查是否需要更改情景模式,通过checkForRingerModeChange方法判断,调用adjustIndex()更改VolumeStreamState对象中保存的音量值。在切换情景模式(震动到响铃除外)时是没有去调整音量的,通过adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0体现
- 通过sendMsg()发送消息MSG_SET_DEVICE_VOLUME至AudioHandler
- 调用sendVolumeUpdate(),通知音量变化
- 勿扰模式的处理:进入音量调节后先处理静音
private void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
// 判断是否使用固定音量,若是无法修改音量
if (mUseFixedVolume) {
return;
}
if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction
+ ", flags=" + flags + ", caller=" + caller);
// 确认调整的音量方向
ensureValidDirection(direction);
// 确认调整的流类型
ensureValidStreamType(streamType);
// 通过传入的direction值判断是否为静音:direction-int最小值 返回true
boolean isMuteAdjust = isMuteAdjust(direction);
if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {
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映射的流类型
int streamTypeAlias = mStreamVolumeAlias[streamType];
// mStreamStates: 存储VolumeStreamState类型的数组,保存着每个音频流的状态。
// VolumeStreamState是AudioService的一个内部类,保存单个音频流的所有信息,如流类型、音量大小、mute状态等,相同的流类型,在不同设备,大小也不一样(如耳机和扬声器)
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
// 为streamTypeAlias寻找匹配的device
final int device = getDeviceForStream(streamTypeAlias);
// 获取该StreamType的当前音量
int aliasIndex = streamState.getIndex(device);
boolean adjustVolume = true;
int step;
// skip a2dp absolute volume control request when the device
// is not an a2dp device
// 若不是蓝牙设备,则跳过音量绝对控制请求
if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 &&
(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.
// uid判断,方便做用户权限处理
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;
}
// reset any pending volume command
// 清除任何待处理的音乐指令
synchronized (mSafeMediaVolumeState) {
mPendingVolumeCommand = null;
}
// 非固定音量
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
// 若不是多媒体音量或是多媒体音量但非固定音量的设备时
// 调节index,把UI上的+1/-1转成streamTypeAlias内部单元的数字
step = rescaleIndex(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) ||
(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.
// 判断是否应该改变情景模式,如:从震动转响铃时无需改变音量
// adjustVolume--控制量,控制是否需要更改音量
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 ringermode is suppressing media, prevent changes
// 勿扰模式
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))) {
// 获取当前流对应的VolumeStreamState实例然后调用mute方法
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);
// 调用adjustIndex()方法更改VolumeStreamState对象中保存的音量值
} else if (streamState.adjustIndex(direction * step, device, caller)
|| 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 (mPlatformType == AudioSystem.PLATFORM_TELEVISION) {
sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
}
}
}
// 发消息给AudioHandler进行更改音量,设置音量至底层
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
}
// Check if volume update should be sent to Hdmi system audio.
// 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);
}
}
}
}
}
}
// 通过sendVolumeUpdate方法完成音量已变化完成的通知操作
int index = mStreamStates[streamType].getIndex(device);
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
-----------------------------------------------------------------------------------
private int getDeviceForStream(int stream) {
// 此处最终会调用VolumeStreamState中的observeDevicesForStream_syncVSS方法
int device = getDevicesForStream(stream);
if ((device & (device - 1)) != 0) {
// 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 ((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;
}
private int getDevicesForStream(int stream, boolean checkOthers) {
ensureValidStreamType(stream);
synchronized (VolumeStreamState.class) {
return mStreamStates[stream].observeDevicesForStream_syncVSS(checkOthers);
}
}
public int observeDevicesForStream_syncVSS(boolean checkOthers) {
// 开始调用native方法
final int devices = AudioSystem.getDevicesForStream(mStreamType);
if (devices == mObservedDevices) {
return devices;
}
final int prevDevices = mObservedDevices;
mObservedDevices = devices;
if (checkOthers) {
// one stream's devices have changed, check the others
observeDevicesForStreams(mStreamType);
}
// log base stream changes to the event log
if (mStreamVolumeAlias[mStreamType] == mStreamType) {
EventLogTags.writeStreamDevicesChanged(mStreamType, prevDevices, devices);
}
sendBroadcastToAll(mStreamDevicesChanged
.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, prevDevices)
.putExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, devices));
return devices;
}
--------------------------------------------------------------------------------------
// 音量的调整
public boolean adjustIndex(int deltaIndex, int device, String caller) {
return setIndex(getIndex(device) + deltaIndex, device, caller);
}
public boolean setIndex(int index, int device, String caller) {
boolean changed = false;
int oldIndex;
synchronized (VolumeStreamState.class) {
oldIndex = getIndex(device);
index = getValidIndex(index);
synchronized (mCameraSoundForced) {
if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
index = mIndexMax;
}
}
mIndexMap.put(device, index);
// 旧的音量值与新的音量值不一致时需要调整
changed = oldIndex != index;
// Apply change to all streams using this one as alias if:
// - the index actually changed OR
// - there is no volume index stored for this device on alias stream.
// If changing volume of current device, also change volume of current
// device on aliased stream
final boolean currentDevice = (device == getDeviceForStream(mStreamType));
final int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
final VolumeStreamState aliasStreamState = mStreamStates[streamType];
if (streamType != mStreamType &&
mStreamVolumeAlias[streamType] == mStreamType &&
(changed || !aliasStreamState.hasIndexForDevice(device))) {
final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
aliasStreamState.setIndex(scaledIndex, device, caller);
if (currentDevice) {
aliasStreamState.setIndex(scaledIndex,
getDeviceForStream(streamType), caller);
}
}
}
}
if (changed) {
// 开始相关的计算
oldIndex = (oldIndex + 5) / 10;
index = (index + 5) / 10;
// log base stream changes to the event log
if (mStreamVolumeAlias[mStreamType] == mStreamType) {
if (caller == null) {
Log.w(TAG, "No caller for volume_changed event", new Throwable());
}
EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10,
caller);
}
// fire changed intents for all streams
// 发送音量变化的广播
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
sendBroadcastToAll(mVolumeChanged);
}
return changed;
}
首先发送MSG_SET_DEVICE_VOLUME消息,通过VolumeStreamState调用AudioSystem的setStreamVolumeIndex()设置音量到底层的AudioFlinger中,实现–streamState.applyDeviceVolume_syncVSS(device);其次给AudioHandler发送MSG_PERSIST_VOLUME消息,调用persistVolume(),通过System.putIntForUser()将目标音量保存在Settings数据库中,将音量值保存至系统设置文件中
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_DEVICE_VOLUME:
setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);
...
}
}
/** Handles internal volume messages in separate volume thread. */
private class AudioHandler extends Handler {
private void setDeviceVolume(VolumeStreamState streamState, int device) {
// 锁定操作,避免出错
synchronized (VolumeStreamState.class) {
// Apply volume
// 通过VolumeStreamState调用AudioSystem的setStreamVolumeIndex()设置音量到底层的AudioFlinger中
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) && mAvrcpAbsVolSupported &&
((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
}
mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
}
}
}
// Post a persist volume msg
// 给AudioHandler发送MSG_PERSIST_VOLUME消息,调用persistVolume(),通过System.putIntForUser()将目标音量保存在Settings数据库中
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
PERSIST_DELAY);
}
...
}
private void persistVolume(VolumeStreamState streamState, int device) {
if (mUseFixedVolume) {
return;
}
if (isPlatformTelevision() && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
return;
}
System.putIntForUser(mContentResolver,
streamState.getSettingNameForDevice(device),
(streamState.getIndex(device) + 5)/ 10,
UserHandle.USER_CURRENT);
}
在方法applyDeviceVolume_syncVSS中,setStreamVolumeIndex是native的方法,最终实现调用AudioPolicyManagerBase.cpp中的接口。
系统中有多个output,每个output又支持多个device,故进行checkAndSetVolume方法,该方法主要实现的是mpClientInterface->setStreamVolume(AudioSystem::VOICE_CALL, volume, output, delayMs),先进行音量的计算,最终调用AudiFlinger的接口完成音量的设置
public void applyDeviceVolume_syncVSS(int device)@ VolumeStreamState $ AudioService.java {
AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
status_t AudioPolicyManagerBase::setStreamVolumeIndex(AudioSystem::stream_type stream,
int index,
audio_devices_t device)
{
if ((index < mStreams[stream].mIndexMin) || (index > mStreams[stream].mIndexMax)) {
return BAD_VALUE;
}
if (!audio_is_output_device(device)) {
return BAD_VALUE;
}
// Force max volume if stream cannot be muted
if (!mStreams[stream].mCanBeMuted) index = mStreams[stream].mIndexMax;
ALOGV("setStreamVolumeIndex() stream %d, device %04x, index %d",
stream, device, index);
// if device is AUDIO_DEVICE_OUT_DEFAULT set default value and
// clear all device specific values
if (device == AUDIO_DEVICE_OUT_DEFAULT) {
mStreams[stream].mIndexCur.clear();
}
mStreams[stream].mIndexCur.add(device, index);
// compute and apply stream volume on all outputs according to connected device
status_t status = NO_ERROR;
// 根据连接的设备,在所有的输出上计算,应用流音量
for (size_t i = 0; i < mOutputs.size(); i++) {
audio_devices_t curDevice =
getDeviceForVolume(mOutputs.valueAt(i)->device());
if ((device == AUDIO_DEVICE_OUT_DEFAULT) || (device == curDevice)) {
status_t volStatus = checkAndSetVolume(stream, index, mOutputs.keyAt(i), curDevice);
if (volStatus != NO_ERROR) {
status = volStatus;
}
}
}
return status;
}
status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,
audio_io_handle_t output)
{
// check calling permissions
if (!settingsAllowed()) {
return PERMISSION_DENIED;
}
status_t status = checkStreamType(stream);
if (status != NO_ERROR) {
return status;
}
ALOG_ASSERT(stream != AUDIO_STREAM_PATCH, "attempt to change AUDIO_STREAM_PATCH volume");
AutoMutex lock(mLock);
PlaybackThread *thread = NULL;
if (output != AUDIO_IO_HANDLE_NONE) {
// 根据output找到对应的playbackthread
thread = checkPlaybackThread_l(output);
if (thread == NULL) {
return BAD_VALUE;
}
}
// 把新的值记录到对应的StreamType中
mStreamTypes[stream].volume = value;
// playbackThread对音量值做下记录,在混音线程准备音频数据时会把playbackthread保存的值提取出来
if (thread == NULL) {
for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
mPlaybackThreads.valueAt(i)->setStreamVolume(stream, value);
}
} else {
thread->setStreamVolume(stream, value);
}
return NO_ERROR;
}
sendVolumeUpdate()通过AIDL调用VolumeDialogController.java的onVolumeChangedW()方法,从而显示界面调整
// UI update and Broadcast Intent
private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
streamType = mStreamVolumeAlias[streamType];
if (streamType == AudioSystem.STREAM_MUSIC) {
flags = updateFlagsForSystemAudio(flags);
}
mVolumeController.postVolumeChanged(streamType, flags);
}
public static class VolumeController {
private static final String TAG = "VolumeController";
...
public void postVolumeChanged(int streamType, int flags) {
if (mController == null)
return;
try {
mController.volumeChanged(streamType, flags);
} catch (RemoteException e) {
Log.w(TAG, "Error calling volumeChanged", e);
}
}
...
}