本文转载自:http://blog.csdn.net/u013082948/article/details/65630085

本文主要涉及AudioService。还是基于5.1.1版本的代码。 
AudioService.java文件位于/framework/base/media/java/android/media/下。


音量控制是AudioService最重要的功能之一。先总结一下:

  • AudioService音量管理的核心是VolumeStreamState。它保存了一个流类型所有的音量信息。
  • VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的。所以进行音量设置需要做两件事情:更新VolumeStreamState存储的音量值,设置音量到Audio底层系统。
  • VolumeDeathHandler是VolumeStreamState的一个内部类。它的实例对应在一个流类型上执行静音操作的一个客户端,是实现静音功能的核心对象。

音量定义

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的音量。当前可控的流类型可以通过下表更直观地显示:


音量键处理流程

  • 音量键处理流程的发起者是PhoneWindow。
  • AudioManager仅仅起到代理的作用。
  • AudioService接受AudioManager的调用请求,操作VolumeStreamState的实例进行音量的设置。
  • VolumeStreamState负责保存音量设置,并且提供了将音量设置到底层的方法。
  • AudioService负责将设置结果以广播的形式通知外界。

先看到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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

接着看看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);// 通知外界音量值发生了变化
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

总结一下这个函数:

  • 计算按下音量键的音量步进值。这个步进值是10而不是1。在VolumeStreamState中保存的音量值是其实际值的10倍,这是为了在不同流类型之间进行音量转化时能够保证一定精度的一种实现。可以理解为在转化过程中保留了小数点后一位的精度。
  • 检查是否需要改变情景模式。checkForRingerModeChange()和情景模式有关。
  • 调用adjustIndex()更改VolumeStreamState对象中保存的音量值。
  • 通过sendMsg()发送消息MSG_SET_DEVICE_VOLUME到mAudioHandler。
  • 调用sendVolumeUpdate()函数,通知外界音量值发生了变化。

下面将分析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;
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

可以看出,VolumeStreamState.adjustIndex()除了更新自己所保存的音量值外,没有做其他的事情。接下来看看MSG_SET_DEVICE_VOLUME消息处理做了什么。

case MSG_SET_DEVICE_VOLUME:
setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
  • 1
  • 2
  • 3
        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); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

最后看到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);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

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);//通知外界音量发生了变化
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

仔细一看,会发现这与上面音量键控制音量的adjustStreamVolume()函数的代码很类似,主要都是调用了那几个方法。

Android原生音量控制【转】的更多相关文章

  1. Android之声音管理器《AudioManager》的使用以及音量控制

    以下为网上下载然后拼接-- Android声音管理AudioManager使用 手机都有声音模式,声音.静音还有震动,甚至震动加声音兼备,这些都是手机的基本功能.在Android手机中,我们同样可以通 ...

  2. Android应用--简、美音乐播放器增加音量控制

    Android应用--简.美音乐播放器增加音量控制 2013年6月26日简.美音乐播放器继续完善中.. 题外话:上一篇博客是在6月11号发的,那篇博客似乎有点问题,可能是因为代码结构有点乱的原因,很难 ...

  3. Android:Mstar Android8.0平台音量控制流程

    一.Speaker 音量.静音流程分析 java层音量设置首先调用到的是AudioManager.java中的方法,在这里有两种方法可以设置音量 setStreamVolume 和 adjustStr ...

  4. android原生系统裁剪

    Andriod 4.0.4系统包 Andriod 4.1.1系统包 说明   ApplicationsProvider.apk ApplicationsProvider.apk 应用程序存储. 程序管 ...

  5. Android原生游戏开发:使用JustWeEngine开发微信打飞机

    使用JustWeEngine开发微信打飞机: 作者博客: 博客园 引擎地址:JustWeEngine 示例代码:EngineDemo JustWeEngine? JustWeEngine是托管在Git ...

  6. Android进阶(二十七)Android原生扰人烦的布局

    Android原生扰人烦的布局 在开发Android应用时,UI布局是一件令人烦恼的事情.下面主要讲解一下Android中的界面布局. 一.线性布局(LinearLayout) 线性布局分为: (1) ...

  7. android调节音量——AudioManager的应用

    Android中可以通过程序获取系统手机的铃声和音量.同样,也可以设置铃声和音量.android中给出了AudioManager类来实现音量获取.音量控制. 本篇基于 Android API 中的 A ...

  8. EasyPlayerPro windows播放器本地音频播放音量控制实现

    背景描述 作为一个播放器, 除了能播放视频和声音外,音量控制是绝对不能缺少的功能; 本文在音视频播放的基础上,增加对音量的控制: 实现流程 调用mixerGetDevCaps获取音频输出设备列表; 打 ...

  9. Android原生编解码接口 MediaCodec 之——踩坑

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/gb702250823/article/d ...

随机推荐

  1. [Luogu] P4838 P哥破解密码

    题目背景 P哥是一个经常丢密码条的男孩子. 在ION 8102赛场上,P哥又弄丢了密码条,笔试满分的他当然知道这可是要扣5分作为惩罚的,于是他开始破解ION Xunil系统的密码. 题目描述 定义一个 ...

  2. Ztree节点前加上两个自定义按钮

    前言: 在我的权限管理模块遇到了给某些角色加权限的问题,这时就需要实现将每个模块做成树,在每个节点前加上预览和编辑的按钮,这样可以根据数据库的某个字段给每个角色赋权限. 必须必须吐槽的是,这部分的功能 ...

  3. Python 1-3区分Python文件的两种用途和模块的搜索路径

    区分Python文件的两种用途 run.py文件: import m1 m1.py文件: def f1(): print('f1') def f2(): print('f2') #当文件被执行时__n ...

  4. solr-5.3.1配置(win7 x64)

    下载solr,下载地址http://www.eu.apache.org/dist/lucene/solr/5.3.1/solr-5.3.1.zip 解压到某个目录下,这里是解压到了d盘目录下,路径:D ...

  5. 测试Mysql悲观锁

  6. 【转】jquery 注册事件的方法

    原文链接:http://outofmemory.cn/code-snippet/2123/jquery-zhuce-event-method 1.使用事件名来绑定,可用的事件名有 change,cli ...

  7. FIRST集合、FOLLOW集合、SELECT集合以及预测分析表地构造

    FIRST集合.FOLLOW集合.SELECT集合以及预测分析表地构造 FIRST集合的简单理解就是推导出的字符串的开头终结符的集合. FOLLOW集合简单的理解就对于非终结符后面接的第一个终结符. ...

  8. 数据库 SQL SQL转义

    SQL转义 @author ixenos 前言 类似文件分隔符在不同系统的实现不同,我们需要一个中间的转义字符来作为接口,各厂商再具体实现 而SQL的转义语法主要为了支持各种数据库普遍支持的特性,但各 ...

  9. 【计算几何+极角排序+爆ll】E. Convex

    https://www.bnuoj.com/v3/contest_show.php?cid=9147#problem/E [题意] 给定n个点的坐标,可以选择其中的四个点构造凸四边形,问最多能构造多少 ...

  10. 在fragment中获取activity的组件

    在fragment中使用getActivity()即可获取activity的引用