1:按键事件
当用户按下音量上键和音量下键时,对应的keyEvent是:
public class KeyEvent extends InputEvent implements Parcelable {
...
/** Key code constant: Volume Up key.
* Adjusts the speaker volume up. */
public static final int KEYCODE_VOLUME_UP = 24;
/** Key code constant: Volume Down key.
* Adjusts the speaker volume down. */
public static final int KEYCODE_VOLUME_DOWN = 25;
系统的多个地方都可能会处理这一事件,比如下面这两种情况。
(1)AudioManager
省去中间的传递过程,我们直接看AudioManager.java对这一事件的处理。
public void handleKeyDown(KeyEvent event, int stream) {
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
…
if (mUseMasterVolume) {
…
} else {
adjustSuggestedStreamVolume(keyCode == KeyEvent.KEYCODE_VOLUME_UP? ADJUST_RAISE: ADJUST_LOWER, stream, flags);
}
break;
…
这个函数很简单,它根据“音量+” 或“音量-”进一步调用下面这个函数。
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags){
IAudioService service = getService();
…
ervice.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags,
mContext.getOpPackageName());
…
}
其中getService()将得到名为AUDIO_SERVICE = "audio"的binder服务,具体对应SystemServer.java 中添加的AudioService。因此上面的adjustSuggestedStreamVolume的sever端实现如下:
/* frameworks\base\media\java\android\media\AudioService.java*/
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage) {
int streamType;
if (mVolumeControlStream != -1) {
…
} else {
streamType = getActiveStreamType(suggestedStreamType);
}
…
if (streamType == STREAM_REMOTE_MUSIC) {
…
} else {
adjustStreamVolume(streamType, direction, flags, callingPackage);
}
}
Android中将声音分为STREAM_ _MUSIC, STREAM_VOICE_CALL等多种流类型,那么当用户按下音量键(+/-)后,具体针对的是哪一种?这就是getActiveStreamType所要做的工作。从方法名可以知道,这个方法是用于获取当前活跃的Stream类型——它首先判断当前是不是在通话中。如果是在进一步判断是蓝牙还是听筒或其他设备进行通话;又或者当前不在通话,则判断是否在播放音乐等。借助getActiveStreamType,系统可以很方便的设置不同场合下的音量大小。
adjustStreamVolume方法的源码实现我们后面讲解。
(2)[email protected]
对于部分重要的按键(比如HOME和音量调节键),系统会先判断它们是否需要做预处理,即在interceptKeyBeforeQueueing方法会首先根据当前的具体情况(是不是在通话状态、是不是在播放音乐)来决定需要调整的Stream类型,而且最后它会调用AudioService 的adjustStreamVolume方法,接下来的处理流程就和上面AudioManager中情况一致了。
2:adjustStreamVolume说明
/* frameworks\base\media\java\android\media\AudioService.java*/
public void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage) {
…
/*(1)为各streamType寻找Alias归类*/
int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
/*(2)为stream Alias寻找匹配的设备*/
final int device = getDeviceForStream(streamTypeAlias);
/*(3)获取对应device 的Index*/
int aliasIndex = streamState.getIndex(device);
boolean adjustVolume = true;
int step;
…
/*(4)调节Index*/
step = rescaleIndex(10, streamType, streamTypeAlias);
/*(5)音量调节对音量模式的影响*/
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(streamTypeAlias == getMasterStreamType())) {
…
}
/*(6)将音量调节事件发送给下一个处理者*/
int oldIndex = mStreamStates[streamType].getIndex(device);
if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
…
if ((direction == AudioManager.ADJUST_RAISE) &&
!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
mVolumePanel.postDisplaySafeVolumeWarning(flags);
} else if (streamState.adjustIndex(direction * step, device)) {
sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE, device, 0, streamState, 0);
}
}
int index = mStreamStates[streamType].getIndex(device);
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
这个方法看起来很长,但概括起来只有两个重点:
1:计算oldIndex(之前的音量值),index(要调整的音量值)和flags。
2:调用sendVolumeUpdate和sendMsg把上一步的计算结果发出去。
下来分步看看是如果计算出oldIndex,index的:
在上边代码位置(1)获取当前的streamType对应的Alias。虽然Stream被划分为多种不同类型,但是它们在某些方面的行为却是一样的。换句话说,就是可以对它们进行二次分类,这就是mStreamVolumeAlias数组存在的意义。
默认情况下,各类型Stream的分组如下(在支持Voice时)
private final int[] STREAM_VOLUME_ALIAS = 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
};
右半部分的注释表示Stream的类型,左边的值是它们在Alias数组中的组编号。比如STREAM_SYSTEM,STREAM_SYSTEM_ENFORCED,STREAM_DTMF和STREAM_RING属于同一小组。同一小组的stream State是一样的,由mStreamStates[]数组记录。
(2)根据stream Alias通过getDeviceForStream方法寻找匹配的设备。
(3)通过VolumeStreamState获得音量值(Index),看下面的方法:
public synchronized int getIndex(int device) {
Integer index = mIndex.get(device);
if (index == null) {
// there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
index = mIndex.get(AudioSystem.DEVICE_OUT_DEFAULT);
}
return index.intValue();
}
VolumeStreamState内部维护了一个mIndex数组来记录device对应的index值。
(4)我们应该都注意到了,调整音量时系统会有一个音量条出现,同时还有提示音——这样用户才能知道系统是否已经正确执行我们的音量调节命令。这一步就是在为UI显示做前期准备。
(5)音量调节可能和手机的铃声模式(RingerMode)有关。
原生系统有如下几种模式:
public static final int RINGER_MODE_SILENT = 0; //静音模式
public static final int RINGER_MODE_VIBRATE = 1; //震动模式
public static final int RINGER_MODE_NORMAL = 2; //震动模式
private static final int RINGER_MODE_MAX = RINGER_MODE_NORMAL;
比如当前是是震动模式,那么当用户调整音量后,这一模式很可能被改变。
(6)adjustStreamVolume方法末尾分别使用了两种方式来把音量调节命令进一步传递给后续的处理者——其中sendVolumeUpdate用于产生音量调节提示音并显示系统的音量条;而sendMsg则会把命令投递到消息队列中,再有AudioHandler做进一步处理。AudioHandler除了将音量值保存到系统设置文件中外,还会调用AudioPolicyService的相关接口来真正调节音频设备的音量。
3:AudioFlinger部分的音量控制说明
status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,
audio_io_handle_t output)
{ …
AutoMutex lock(mLock);
PlaybackThread *thread = NULL;
if (output) {
thread = checkPlaybackThread_l(output);
…
}
mStreamTypes[stream].volume = value;
if (thread == NULL) {
…
} else {
thread->setStreamVolume(stream, value);
}
return NO_ERROR;
}
首先根据output找到对应的PlaybackThread,然后交由这个线程具体处理流的音量,同时把新值记录到mStreamTypes中(这里注意AudioFlinger和PlaybackThread各有一个mStreamTypes数组,不能搞混了)。
void AudioFlinger::PlaybackThread::setStreamVolume(audio_stream_type_t stream,
float value)
{
Mutex::Autolock _l(mLock);
mStreamTypes[stream].volume = value;
}
可以看到,PlaybackThread只是对音量做了简单的记录,并没有实际的动作。音量变化什么时候能生效?在PlaybackThread::threadLoop()中,它对音频数据进行处理前需要调用MixerThread::prepareTracks_l来做准备工作,看下面代码:
AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(
Vector< sp
mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, (void *)vl);
mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, (void *)vr);
…
}
之前setStreamVolume设置的音量值在这里会被提取出来,并和主音量等其它因素进行综合运算——这样才能得到最终的音量值。紧接着这个值还会通过setParameter
传给AudioMixer,以保证后者能根据当前音量做出正确的混音处理。
4:补充:公司添加的音量控制功能
(1)会议/静音模式在耳机插入后是可以听音乐和使用FM广播的。
情景模式的静音是通过设置主音量为0来实现的,耳机设备接入时MasterVolume设置为1,断开设置MasterVolume为0。关键代码如下:
/frameworks\base\media\java\android\media\AudioService.java
private void onSetWiredDeviceConnectionState(int device, int state, String name)
{//耳机、耳麦等设备的连接/断开处理函数
synchronized (mConnectedDevices) {
……
/*yulong add for set master volume when headset plug,yangyitao,20120604 */
initSystemManager();
int mode_cur = getCurrentModel();//得到当前情景模式
if(mode_cur == c_quietmode || mode_cur == c_meetingmode){//会议或者静音模式
if (state == 1) {//耳机连接
Log.d(TAG, "In quiet or meeting mode,headset plugin");
AudioSystem.setMasterVolume(1.0f);
} else if (state == 0&& mConnectedDevices.containsKey(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) {//耳机断开,但蓝牙耳机已接入
Log.d(TAG, "In quiet or meeting mode,headset plugout,but bluetooth is connect");
AudioSystem.setMasterVolume(1.0f);
} else {//耳机断开
Log.d(TAG, "In quiet or meeting mode,headset plugout");
AudioSystem.setMasterVolume(0.0f);
} /* yulong end */
…….
}
蓝牙音频设备的情景模式的静音处理方式个耳机基本相同,这里不再赘述。
(2)允许耳机在会议和静音模式下调节音乐和收音机的音量
公司还有个需求,允许耳机或蓝牙耳机在会议或静音模式下收听音乐或FM调节音量。
其修改点在AudioService 的adjustStreamVolume方法里,这和前面讲的音量控制流相符。
public void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage) {
…
initSystemManager();
boolean scenemodelquiet = (getCurrentModel() == c_quietmode);//静音模式
boolean scenemodelmetting = (getCurrentModel() == c_meetingmode);//会议模式
if (streamType != AudioManager.STREAM_VOICE_CALL &&
streamType != AudioManager.STREAM_BLUETOOTH_SCO &&
(scenemodelquiet || scenemodelmetting|| AudioManager.speaker_mute_state)) {
boolean isAnyConnected = (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADSET)|| mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE)||
|| mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
/*当是耳机或蓝牙A2DP设备时,主音量为1,切当前要设置音量的流类型是音乐或FM时,允许继续调节音量,否则返回 */
if(isAnyConnected
&& (int) AudioSystem.getMasterVolume() == 1
&&((AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
&& streamType == AudioSystem.STREAM_MUSIC)
|| (AudioSystem.isStreamActive(AudioSystem.STREAM_FM, 0)
&& streamType == AudioSystem.STREAM_FM))) { ){
Log.i(TAG, "Even in quiet or meeting mode,but headset is connected!");
}else{
…
Log.w(TAG, "can not adjust stream volume in quiet or meeting mode!");
return;
}
}
根据上边的代码,可以看调节音量时,在adjustStreamVolume方法先获取当前的情景模式,如果是会议/静音模式,使用的是有麦耳机(HEADSET)、无麦耳机(HEADPHONE)、蓝牙A2DP音频设备(蓝牙耳机)中的一个,并且当前主音量是1,调整的是音乐或FM的音量,满足这几个条件是允许调节音量的,否则返回不调整音量。