在前面两个小节小节中,已经讲解了音量调节的最重要部分,该小节讲解一下使用音量键,或者在Setting界面调节滑动条调节音量时,其处理流程是怎么样的。
使用音量键控制音量会涉及两个系统,一个是输入系统,一个是音频系统,我们需要从源头开始分析,我们先来看看输入系统,在以前我们讲解输入系统时,我们曾经说过,对于输入事件,我们会分为很多个stage进行处理: 分为输入法之前的处理以及输入法之后的处理,我们打开ViewRootImpl.java文件,对于音量调节键,我们关心的是在输入法之后的处理:
final class ViewPostImeInputStage extends InputStage {
处理过程,他会调用该类中的onProcess函数,在这之前我们先来梳理一下,他的处理流程:输入系统会把输入事件发送给位于最前面的APP(activity),activity收到输入事件之后,会把他发送给自己wind,wind发送给deocview,deocview再把他发送给输入焦点,所谓的输入焦点就是一个控件,这个控件位于最前面,他接受用户的输入: 如果其上的wind,deocview,以及输入焦点都不能处理这个事件,我们还有一个兜底工作,对于音量键,就是由兜底工作完成的,当然,我们也可以在输入焦点上重写输入函数,对音量键进行处理,这样的话,如果重写的APP位于最前面,我们按下音量键,接不能操控声音了,具体行为按照编写的程序为准。一般来说,我们不会重新音量键程序。也就是说,在兜底的工作中,实现对音量键对音量的控制。下面是调用的时序图:
现在我们查看onProces函数:
protected int onProcess(QueuedInputEvent q) {
/*处理案件事件*/
processKeyEvent(q);
/*该就是我们前面提到的处理流程*/
// Deliver the key to the view hierarchy.
mView.dispatchKeyEvent(event)
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
其上dispatchKeyEvent就是我们的音量处理相关函数,其实现在PhoneFallbackEventHandler.cpp中实现:
public boolean dispatchKeyEvent(KeyEvent event) {
/*如果按下音量键*/
if (action == KeyEvent.ACTION_DOWN) {
return onKeyDown(keyCode, event);
} else {//如果松开音量键
return onKeyUp(keyCode, event);
}
其处理流程如下: 这里就不一步一步对源码进行追踪了,下面是一些文字的总结: a. 音量键处理流程 音量键: 如果APP没有重写它的处理函数, 音量键的处理将交给PhoneFallbackEventHandler来处理, 它会调用AudioService.adjustSuggestedStreamVolume( 调整"推荐的流"的音量):
根据时序,我们打开AudioService.java中的adjustSuggestedStreamVolume函数:
/*
direction:增加还是减少音量
suggestedStreamType:推荐的StreamType(最后的选择)
*/
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,String callingPackage, String caller, int uid) {
/*获得当前活跃的StreamType,如果没有活跃的,则使用推荐值*/
streamType = getActiveStreamType(suggestedStreamType);
/*手机*/
case AudioSystem.PLATFORM_VOICE:
/*如果正在通话中*/
if (isInCommunication()) {
/*并且使用蓝牙设备*/
if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) == AudioSystem.FORCE_BT_SCO) {
/*返回蓝牙的StreamType类型*/
return AudioSystem.STREAM_BLUETOOTH_SCO;
} else {//没有使用蓝牙设备
return AudioSystem.STREAM_VOICE_CALL;
}
/*如果推荐之后为USE_DEFAULT_STREAM_TYPE*/
} else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
/*电视,如机顶盒*/
/*如果最近听过音乐*/
if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) {
/*返回音乐STREAM_MUSIC*/
return AudioSystem.STREAM_MUSIC;
case AudioSystem.PLATFORM_TELEVISION:
/*永远方返回音乐*/
return AudioSystem.STREAM_MUSIC;
default://平板之类的
//和电话处理类似
这样我们就解答了前面的问题,如何获得推荐的StreamType, stream = getActiveStreamType(…)。
为了减少用户的设置难度,我们把Stream归为几类: 我们设置StreamType时,他会导致好几种音量被设置,这就是分组,或者别名alias。我们来看看这别名分组是怎么起作用单的?其调用流程如下:
根据前面getActiveStreamType函数,已经获得了活跃的StreamType,在根据流程,执行到:
打开:
private class AudioHandler extends Handler {
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);
}
注释比较详细,就不进行讲解了,小结如下:
a.2 音量设置的"alias"如何起作用:
set volume for stream; // 设置"推荐的流"的音量
if (mStreamVolumeAlias[other stream] == stream)
set volume for other stream; // 设置同属一个alias的其他流的音量
对于不同的设备(电话、TV、平板), mStreamVolumeAlias指向不同的数组
下面我们看看怎么去设置stream的音量,根据时序图,其最终版会调用到: 我们打开AudioFlinger找到找到:
status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,audio_io_handle_t output)
/*设置mStreamTypes的值*/
mStreamTypes[stream].volume = value;
/*设置其他线程的StreamVolume*/
if (thread == NULL) {
for (size_t i = 0; i setStreamVolume(stream, value);
}
} else {
thread->setStreamVolume(stream, value);
}
小节如下:
a.3 怎么设置流的音量:
设置audioflinger中的数组: mStreamTypes[stream].volume = value;
设置PlaybackThread中的数组: mStreamTypes[stream].volume = value;
下面我们分析音量滑动条的流程。
音量滑动条音量滑动条是一个seekBar控件,打开seekBar.java,
@Override
void onProgressRefresh(float scale, boolean fromUser, int progress) {
super.onProgressRefresh(scale, fromUser, progress);
当我们滑动滑动条的时候onProgressRefresh函数会被调用,我们怎么去设置音量呢?有两种方法: 1.重写onProgressRefresh函数,在里面去设置音量。 2.添加Listener。
为了大家方便,文字描述第二种方法:
b. 音量滑动条处理流程
b.1 通过下面文件定义音量滑动条:
packages/apps/Settings/res/xml/notification_settings.xml
该文件定义了多个VolumeSeekBarPreference
每个VolumeSeekBarPreference要跟一个SeekBar绑定
b.2 在VolumeSeekBarPreference的绑定函数onBindView中,
设置了对应的SeekBar的SeekBarChangeListener (一个SeekBarVolumizer对象)
b.3 当SeekBar被滑动时, 它的onProgressRefresh被调用
该函数会调用 mOnSeekBarChangeListener.onProgressChanged
b.4 mOnSeekBarChangeListener.onProgressChanged去设置音量
b.5 每一个SeekBar对应一个stream,
滑动SeekBar时会设置该stream的音量,
也会去设置同属一个alias(同一分组)的其他stream的音量
(后两分钟无记录)