本文转载自: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. 微信小程序UI组件库 iView Weapp快速上手

    概述 今天在网上突然看到iView新出了一个微信小程序的组件库iView Weapp,自己就上手试了一下,发现用起来还是不错的,把自己使用的过程与大家分享下. 一 预览iView组件 1.可以在手机上 ...

  2. iPhoneX 适配H5页面的解决方案

    由于在iPhonex在状态栏增加了24px的高度,对于通栏banner规范的内容区域会有遮挡情况. 解决方案:在页面通栏banner顶部增加一层高度44px的黑色适配层,整个页面往下挪44px,这种做 ...

  3. Python入门之类(class)

    面向对象三大特性 面向对象的三大特性是指:封装.继承和多态. 一.封装 封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容. 所以,在使用面向对象的封装特性时,需要: 将内容封装到 ...

  4. 样例GeoQuiz应用开发 第2章

    先介绍一下MVC,Model View Controller,是软件架构中最常见的一种框架. 简单来说就是通过 controller 的控制去操作 model 层的数据,并且返回给 view 层展示, ...

  5. 零基础入门学习Python(15)--格式化

    前言 上节课我们介绍了字符串N多种奇葩方法的用法,但是我们唯独漏掉了format方法,那为何不把format方法和上节课的内容一起讲呢? 因为小甲鱼觉得format方法,跟今天的主题是如出一辙的,都是 ...

  6. 关于latch: cache buffers chains的sql优化

    前段时间,优化了一些耗buffer比较多的sql,但是CPU使用率还是没下来 . 查看操作系统CPU使用率 查看awr,发现又有一条超级耗性能的sql冒出来了. 该SQL每次执行耗费3e多个buffe ...

  7. PHP实现微信第三方登录的方法

    本文实例讲述了PHP版微信第三方实现一键登录及获取用户信息的方法.分享给大家供大家参考,具体如下: 注意,要使用微信在第三方网页登录是需要“服务号”才可以哦,所以必须到官方申请 一开始你需要进入微信公 ...

  8. UVA 227 周期串

    题意: 给一个字符串,寻找最短的循环节 如abcabcabcabc以3为周期,也按6和12为周期. 分析: 因为循环节肯定是相等的,所以枚举串长度的所有约数的循环节再判断是否相等即可. 我的方法是枚举 ...

  9. 【bzoj3505】[Cqoi2014]数三角形

    [bzoj3505][Cqoi2014]数三角形 2014年5月15日3,5230 Description 给定一个nxm的网格,请计算三点都在格点上的三角形共有多少个.下图为4×4的网格上的一个三角 ...

  10. Intent使用Parcelable传递对象

    package com.pingyijinren.test; import android.os.Parcel; import android.os.Parcelable; import java.i ...