基于android 9平台分析。
在Android系统中,默认的设备(phone等)音量都是分开控制的,这些包括媒体、铃声、闹铃、蓝牙、通话通过音频流来区别不同的音量类型。每种流类型都定义最大音量、最小音量及默认音量,Android 9定了了11中音频流类型:
流类型
//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",
"STREAM_ACCESSIBILITY"
};
最大音量(音量等级)
//frameworks/base/services/core/java/com/android/server/audio/AudioService.java
/** Maximum volume index values for audio streams */
protected 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
};
根据音量曲线表,一般情况音量等级最大可以设置为100。但是,有些音频音量调节并不经过音箱曲线表,而是直接调用HAL层的set_volume,而HAL层对音量又做了类似“音量曲线”的转换。所有修改音量级别,可能会有以下问题:
1、调至15时音量已经最大,15级以上的音量等级无效。
比如amlogic T972将MAX_STREAM_VOLUME 调整为30等级,HAL层audio_hw.c对音量调节:out_set_volume()–>volume2Ms12DBGain()–>AmplToDb(),因15等级时DB值已经“够大”,再往上调音量变化不明显,修改如下:
2、调节音量时音量过大导致输出波形失真。
因喇叭性能或功放电路的原因(最好改电路,否则产品声音小),CPU音量输出增益不能太大,否则引起波形失真。比如Mst358在HAL层audio_hw.c对音量调节:
最小音量
//frameworks/base/services/core/java/com/android/server/audio/AudioService.java
/** Minimum volume index values for audio streams */
protected static int[] MIN_STREAM_VOLUME = new int[] {
1, // STREAM_VOICE_CALL
0, // STREAM_SYSTEM
0, // STREAM_RING
0, // STREAM_MUSIC
1, // STREAM_ALARM
0, // STREAM_NOTIFICATION
0, // STREAM_BLUETOOTH_SCO
0, // STREAM_SYSTEM_ENFORCED
0, // STREAM_DTMF
0, // STREAM_TTS
1 // 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
0, // STREAM_RING
5, // STREAM_MUSIC
0, // STREAM_ALARM
5, // STREAM_NOTIFICATION
7, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
5, // STREAM_DTMF
5, // STREAM_TTS
5, // STREAM_ACCESSIBILITY
};
音频流映射StreamAlias
不同设备的映射定义不同,系统中一共定义三种设备的音频流的映射,分别是STREAM_VOLUME_ALIAS_VOICE,STREAM_VOLUME_ALIAS_TELEVISION,STREAM_VOLUME_ALIAS_DEFAULT
StreamAlias存在的意义:流类型别名,某音频流的行为等同于另外一种音频流,可以将它映射为另一种音频流,比如AudioSystem.STREAM_RING用作AudioSystem.STREAM_MUSIC来处理。
StreamAlias实际使用的意义是,Android为了兼容各种设备,定义了尽可能多的音频流。但是,在有些简单的设备中,可能仅有一个喇叭,所以对音频操作没有必要区分音频流。所以通过StreamAlias,在手机和平板上实际上能调节的就是五种音量,在TV和就机顶盒之类设备能调节的仅仅一种音量即music,故如有需求需要统一音量的可以将当前的音频流改为TV类型。
调用AudioSsytem::getPlatformType()可知道系统是手机、平板或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
};
音量按键处理流程
在Android平台上,音量键,主页键(home),都是全局按键,但是主页键是个例外不能被应用所捕获。
下面分析一下音量按键的流程,主要从framework层处理开始,至于EventHub 从驱动的/dev/input/event0获取按键信息到上抛属于Android input 系统方面的流程。
framework层接收音量按键
ViewRootImpl.processKeyEvent 处理Activity 上面收到的按键
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mUnhandledKeyManager.preViewDispatch(event)) {
return FINISH_HANDLED;
}
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
。。。。。
}
从中可以看到mView.dispatchKeyEvent(event),完成将按键发送给Activity处理,由于每个Activity都是view的子类,所有这些按键将dispatchKeyEvent传递给onKeyDown:
/**
* Called to process key events. You can override this to intercept all
* key events before they are dispatched to the window. Be sure to call
* this implementation for key events that should be handled normally.
*
* @param event The key event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
// Let action bars open menus in response to the menu key prioritized over
// the window handling it
final int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
注意这个方法可以被子类覆盖。
win.superDispatchKeyEvent()不处理音量键,调用根View的dispatchKeyEvent,进而调用ViewGroup的dispatchKeyEvent,如果都没处理,则调用View的dispatchKeyEvent:
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
// Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
通过event.dispatch进一步分发
//framework/base/core/java/android/view/KeyEvent.java
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
mFlags &= ~FLAG_START_TRACKING;
if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ ": " + this);
boolean res = receiver.onKeyDown(mKeyCode, this);
if (state != null) {
if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
if (DEBUG) Log.v(TAG, " Start tracking!");
state.startTracking(this, target);
} else if (isLongPress() && state.isTracking(this)) {
try {
if (receiver.onKeyLongPress(mKeyCode, this)) {
if (DEBUG) Log.v(TAG, " Clear from long press!");
state.performedLongPress(this);
res = true;
}
} catch (AbstractMethodError e) {
}
}
}
return res;
}
......
}
上面只关注了ACTION_DOWN的处理。KeyEvent.dispatch通过receiver.onKeyDown将最终的按键消息发送给当前的Activity,而receiver即为KeyEvent.Callback的实现类(View的子类等等),至此如果上面上传应用处理完了就会返回,如果没有处理就会流向mFallbackEventHandler.dispatchKeyEvent(event),其实mFallbackEventHandler就是PhoneFallbackEventHandler,接着看 PhoneFallbackEventHandler.dispatchKeyEvent的处理流程
//framework/base/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
public boolean dispatchKeyEvent(KeyEvent event) {
final int action = event.getAction();
final int keyCode = event.getKeyCode();
if (action == KeyEvent.ACTION_DOWN) {
return onKeyDown(keyCode, event);
} else {
return onKeyUp(keyCode, event);
}
}
进入onKeyDown
boolean onKeyDown(int keyCode, KeyEvent event) {
/* ****************************************************************************
* HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
* See the comment in PhoneWindow.onKeyDown
* ****************************************************************************/
final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
handleVolumeKeyEvent(event);
return true;
}
。。。。。
}
private void handleVolumeKeyEvent(KeyEvent keyEvent) {
getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(keyEvent,
AudioManager.USE_DEFAULT_STREAM_TYPE);
}
调用MeidaSessionManager::dispatchVolumeKeyEventAsSystemService()–>MediaSessionService::dispatchVolumeKeyEvent()
/*将音量按钮事件分派给其中一个已注册的侦听器。
如果有一个音量键长按侦听器,并且没有活动的全局优先级会话,长按将被发送到长按侦听器,而不是调整音量。
如果没有注册长按监听器、没有活动的全局优先级会话,则进行音量调节*/
@Override
public void dispatchVolumeKeyEvent(String packageName, boolean asSystemService,
KeyEvent keyEvent, int stream, boolean musicOnly) {
......
try {
synchronized (mLock) {
//如果没有注册长按监听器,则调用dispatchVolumeKeyEventLocked进行音量调节。
if (isGlobalPriorityActiveLocked()
|| mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
dispatchVolumeKeyEventLocked(packageName, pid, uid, asSystemService,
keyEvent, stream, musicOnly);
} else {
// TODO: Consider the case when both volume up and down keys are pressed
// at the same time.
if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
if (keyEvent.getRepeatCount() == 0) {
// Keeps the copy of the KeyEvent because it can be reused.
mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
KeyEvent.obtain(keyEvent);
mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
mHandler.sendMessageDelayed(
mHandler.obtainMessage(
MessageHandler.MSG_VOLUME_INITIAL_DOWN,
mCurrentFullUserRecord.mFullUserId, 0),
mLongPressTimeout);
}
if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
dispatchVolumeKeyLongPressLocked(
mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
// Mark that the key is already handled.
mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
}
dispatchVolumeKeyLongPressLocked(keyEvent);
}
} else { // if up
......
}
继续dispatchVolumeKeyEventLocked()–>dispatchAdjustVolumeLocked()
private void dispatchAdjustVolumeLocked(String packageName, int pid, int uid,
boolean asSystemService, int suggestedStream, int direction, int flags) {
MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
: mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
boolean preferSuggestedStream = false;
if (isValidLocalStreamType(suggestedStream)
&& AudioSystem.isStreamActive(suggestedStream, 0)) {
preferSuggestedStream = true;
}
if (session == null || preferSuggestedStream) {
// Execute mAudioService.adjustSuggestedStreamVolume() on
// handler thread of MediaSessionService.
// This will release the MediaSessionService.mLock sooner and avoid
// a potential deadlock between MediaSessionService.mLock and
// ActivityManagerService lock.
mHandler.post(new Runnable() {
@Override
public void run() {
try {
String packageName = getContext().getOpPackageName();
mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
flags, packageName, TAG);
}
}
});
} else {
session.adjustVolume(packageName, pid, uid, null, asSystemService,
direction, flags, true);
}
}
两种情况,一种调用mAudioService.adjustSuggestedStreamVolume(),一种调用session.adjustVolume()。这里以adjustSuggestedStreamVolume()为例。
AudioService音量控制流程
adjustSuggestedStreamVolume 过渡到adjustStreamVolume,进入音量设置的主要流程,主要对流类型,设备,声音设备状态,步进大小进行判断处理,另外蓝牙设备音量和主设备音量进行了控制,最后通过mVolumePanel刷新界面音量显示,并且广播通过上层应用。
protected void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
。。。。。
if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
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)) {
Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
mVolumeController.postDisplaySafeVolumeWarning(flags);
} 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 (mIsSingleVolume) {
sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
}
}
}
//发送MSG_SET_DEVICE_VOLUME消息去设置系统音量,在handleMessage()被处理
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) {
synchronized (mA2dpAvrcpLock) {
if (mA2dp != null && mAvrcpAbsVolSupported) {
mA2dp.setAvrcpAbsoluteVolume(newIndex / 10);
}
}
}
// Check if volume update should be send to Hearing Aid
if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
setHearingAidVolume(newIndex, streamType);
}
// Check if volume update should be sent to Hdmi system audio.
//与HDMI输出相关
if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
}
if (mHdmiManager != null) {
......
}
}
int index = mStreamStates[streamType].getIndex(device);
//UI更新系统音量
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
蓝牙音量的控制
由上可知,如果当前连接了蓝牙也将对音量进行控制,mA2dp.adjustAvrcpAbsoluteVolume,暂不分析。
音频处理设置
音频处理由AudioHandler来进行,adjustStreamVolume做完相关处理后,通过sendMsg发送音量变化消息MSG_SET_DEVICE_VOLUME进入AudioHandler.handleMessage调用AudioHandler.setDeviceVolume
private void setDeviceVolume(VolumeStreamState streamState, int device) {
synchronized (VolumeStreamState.class) {
// Apply volume
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
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
PERSIST_DELAY);
}
VolumeStreamState.applyDeviceVolume_syncVSS设置设备音量
// must be called while synchronized VolumeStreamState.class
public void applyDeviceVolume_syncVSS(int device) {
int index;
if (mIsMuted) {
index = 0;
} else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) {
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;
}
AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
}
接着发送MSG_PERSIST_VOLUME消息通过handleMessage进入persistVolume,最终调用System.putIntForUser将用户设置的内容设置到Settings.system中。
AudioSystem调节音量
applyDeviceVolume处理完,AudioSystem就开始接着往下设置setStreamVolumeIndex,通过JNI调用到AudioSystem.cpp中setStreamVolumeIndex()。
status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream,
int index,
audio_devices_t device)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return PERMISSION_DENIED;
return aps->setStreamVolumeIndex(stream, index, device);
}
获取去音频策略服务(AudioPolicyService.cpp),进行设置
AudioPolicyService::setStreamVolumeIndex()–>AudioPolicyManager::setStreamVolumeIndex()。