本文主要涉及AudioService。还是基于5.1.1版本的代码。
AudioService.java文件位于/framework/base/media/java/android/media/下。
音量控制是AudioService最重要的功能之一。先总结一下:
Andorid5.1在AudioSystem.java定义了有10种流类型。每种流类型的音量都是相互独立的,Android也在AudioService.java定义了几个数组:MAX_STREAM_VOLUME(最大音量),DEFAULT_STREAM_VOLUME(默认音量大小),STREAM_VOLUME_ALIAS_VOICE(映射的流类型)。
虽然Android5.1中拥有10种流类型,但是为了便于使用,android通过判断设备的类型,去映射具体流类型。Android5.1在AudioSystem.java中提供了3个设备(DEFAULT,VOICE,TELEVISION)作为可选择项,分别去映射我们具体的音频流类型。其中,DEFAULT和VOICE类型的音频映射是一致的。
所以,从上表中可以看出,在手机设备当中,我们当前可调控的流类型音量其实只有5个,当你想调节STREAM_SYSTEM,STREAM_NOTIFICATION等流类型的音量时,实际上是调节了STREAM_RING的音量。当前可控的流类型可以通过下表更直观地显示:
音量键处理流程
先看到AudioService的adjustSuggestedStreamVolume()方法。
第一个参数direction指示了音量的调整方向,1为增大,-1为减小;第二个参数suggestedStreamType表示要求调整音量的流类型;第三个参数flags,其实是在AudioManager在handleKeyDown()中设置了两个flags,分别是FLAG_SHOW_UI和FLAG_VIBRATE。前者告诉AudioService需要弹出一个音量控制面板。而在handleKeyUp()里设置了FLAG_PLAY_SOUND,这是为什么在松开音量键后”有时候“(在特定的流类型下,且没有处于锁屏状态)会有一个提示音。
// 1.确定要调整音量的流类型 2.在某些情况下屏蔽FLAG_PLAY_SOUND 3.调用adjustStreamVolume() private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, int uid) { ...... //从这一小段代码中可以看出,在AudioService中还有地方可以强行改变音量键控制的流类型。 //mVolumeControlStream是VolumePanel通过forceVolumeControlStream()设置的, //VolumePanel显示时会调用forceVolumeControlStream强制后续的音量键操作固定为促使它显示的那个流类型, //并在它关闭时取消这个强制设置,设值为-1 if (mVolumeControlStream != -1) { streamType = mVolumeControlStream; } else { //通过getActiveStreamType()函数获取要控制的流类型,这里根据建议的流类型与AudioService的实际情况,返回一个值 streamType = getActiveStreamType(suggestedStreamType); } final int resolvedStream = mStreamVolumeAlias[streamType]; ...... adjustStreamVolume(streamType, direction, flags, callingPackage, uid); }
接着看看adjustStreamVolume()
private void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage, int uid) { ...... ensureValidDirection(direction); //确认一下调整的音量方向 ensureValidStreamType(streamType); //确认一下调整的流类型 int streamTypeAlias = mStreamVolumeAlias[streamType];//获取streamType映射到的流类型 //VolumeStreamState类,保存与一个流类型所有音量相关的信息 VolumeStreamState streamState = mStreamStates[streamTypeAlias]; final int device = getDeviceForStream(streamTypeAlias); int aliasIndex = streamState.getIndex(device);//获取当前音量 ...... //rescaleIndex用于将音量值的变化量从源流类型变换到目标流类型下, //由于不同的流类型的音量调节范围不同,所以这个转换是必需的 step = rescaleIndex(10, streamType, streamTypeAlias); } ...... final int result = checkForRingerModeChange(aliasIndex, direction, step); adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0; //布尔变量,用来表示是否有必要继续设置音量值 ...... int oldIndex = mStreamStates[streamType].getIndex(device);//取出调整前的音量值。这个值会在sendVolumeUpdate()调用 if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) { ...... if ((direction == AudioManager.ADJUST_RAISE) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex); mVolumeController.postDisplaySafeVolumeWarning(flags); //判断streamState.adjustIndex返回值,如果音量值在调整之后并没有发生变化,比如到了最大值,就不需要继续后面的操作了 } else if (streamState.adjustIndex(direction * step, device)) { //这个消息将把音量设置到底层去,并将其存储到Settingsprovider中 sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, device, 0, streamState, 0); } ...... int index = mStreamStates[streamType].getIndex(device); sendVolumeUpdate(streamType, oldIndex, index, flags);// 通知外界音量值发生了变化 }
总结一下这个函数:
下面将分析adjustIndex()、MSG_SET_DEVICE_VOLUME消息的处理和sendVolumeUpdate()。
先看到VolumeStreamState类的adjustIndex()
//更改VolumeStreamState对象中保存的音量值 public boolean adjustIndex(int deltaIndex, int device) { return setIndex(getIndex(device) + deltaIndex, device);// 将现有的音量值加上变化量,然后调用setIndex进行设置 } public boolean setIndex(int index, int device) { ...... mIndex.put(device, index);//保存设置的音量值 if (oldIndex != index) { //同时设置所有映射到当前流类型的其他流的音量 boolean currentDevice = (device == getDeviceForStream(mStreamType)); int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { if (streamType != mStreamType && mStreamVolumeAlias[streamType] == mStreamType) { int scaledIndex = rescaleIndex(index, mStreamType, streamType); mStreamStates[streamType].setIndex(scaledIndex, device); if (currentDevice) { mStreamStates[streamType].setIndex(scaledIndex, getDeviceForStream(streamType)); } } } return true; } else { return false; } } }
可以看出,VolumeStreamState.adjustIndex()除了更新自己所保存的音量值外,没有做其他的事情。接下来看看MSG_SET_DEVICE_VOLUME消息处理做了什么。
case MSG_SET_DEVICE_VOLUME: setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1); break;
private void setDeviceVolume(VolumeStreamState streamState, int device) { synchronized (VolumeStreamState.class) { streamState.applyDeviceVolume_syncVSS(device);//这个函数会调用AudioSystem.setStreamVolumeIndex(), //到这,音量就被设置到底层的AudioFlinger中 // 对所有流应用更改,使用此别名作为别名。处理流音量映射的情况 int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { ...... } //发送消息,其处理函数将会调用persitVolume()函数,这将会把音量的设置信息存储到SettingsProvide中。 //Audioservice在初始化时,将会从SettingsProvide中将音量设置读取出来并进行设置 sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, SENDMSG_QUEUE, device, 0, streamState, PERSIST_DELAY); }
最后看到sendVolumeUpdate()
// UI update and Broadcast Intent private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) { //判断设备是否拥有通话功能。对没有通话能力的设备来说,RING流类型自然也就没有意义了。这句话应该算是一种从语义操作上进行的保护 if (!isPlatformVoice() && (streamType == AudioSystem.STREAM_RING)) { streamType = AudioSystem.STREAM_NOTIFICATION; } if (streamType == AudioSystem.STREAM_MUSIC) { flags = updateFlagsForSystemAudio(flags); } mVolumeController.postVolumeChanged(streamType, flags);//最后将显示系统音量条的提示框 if ((flags & AudioManager.FLAG_FIXED_VOLUME) == 0) { oldIndex = (oldIndex + 5) / 10; //+5的意义是实现四舍五入;除以10是因为存储时先乘了10,转换过程中保留小数点后一位的精度 index = (index + 5) / 10; Intent intent = new Intent(AudioManager.VOLUME_CHANGED_ACTION); intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType); intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); intent.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); sendBroadcastToAll(intent); } }
mVolumeController.postVolumeChanged()方法将会调用到mController.volumeChanged()方法,通过AIDL将调用到VolumeUI.java文件中的VolumeController.volumeChanged()方法,最后将会调用mPanel.postVolumeChanged更新系统音量条的UI,这里就是VolumePanel的内容啦,具体可看上一篇文章系统音量条
通过音量设置函数setStreamVolume()
除了音量键调节音量以外,还可以通过系统设置中进行调节。
控件会根据当初的音量和模式去调用AudioManager的adjustStreamVolume(静音或震动模式)或setStreamVolume(普通模式)去调整相对应的音量。
AudioManager.setStreamVolume()是系统设置界面中调整音量所使用的接口。
private void setStreamVolume(int streamType, int index, int flags, String callingPackage, int uid) { ...... ensureValidStreamType(streamType);//先判断一下流类型这个参数的有效性 int streamTypeAlias = mStreamVolumeAlias[streamType];//对这个数组进行流类型的转换 VolumeStreamState streamState = mStreamStates[streamTypeAlias]; final int device = getDeviceForStream(streamType);//获取当前流将使用哪一个音频设备进行播放。最终会被调用到AudioPolicyService中 ...... oldIndex = streamState.getIndex(device);//获取当前流的音量 index = rescaleIndex(index * 10, streamType, streamTypeAlias);//将原流类型下的音量值映射到目标流类型下的音量值 ...... if (!checkSafeMediaVolume(streamTypeAlias, index, device)) { mVolumeController.postDisplaySafeVolumeWarning(flags); mPendingVolumeCommand = new StreamVolumeCommand( streamType, index, flags, device); } else { onSetStreamVolume(streamType, index, flags, device);//将调用setStreamVolumeInt()方法 index = mStreamStates[streamType].getIndex(device);//获取设置结果 } } sendVolumeUpdate(streamType, oldIndex, index, flags);//通知外界音量发生了变化 }
onSetStreamVolume()方法主要就是调用了setStreamVolumeInt()方法,下面看下setStreamVolumeInt()
private void setStreamVolumeInt(int streamType, int index, int device, boolean force) { VolumeStreamState streamState = mStreamStates[streamType]; if (streamState.setIndex(index, device) || force) { //调用streamState.setIndex(),更改VolumeStreamState对象中保存的音量值 //这个消息将把音量设置到底层去,并将其存储到Settingsprovider中 sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, device, 0, streamState, 0); } }
仔细一看,会发现这与上面音量键控制音量的adjustStreamVolume()函数的代码很类似,主要都是调用了那几个方法。
以上就是本文关于Android原生音量控制实例详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#nhooo.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。