Android原生音量控制实例详解

本文主要涉及AudioService。还是基于5.1.1版本的代码。

AudioService.java文件位于/framework/base/media/java/android/media/下。

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

  1. AudioService音量管理的核心是VolumeStreamState。它保存了一个流类型所有的音量信息。
  2. VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的。所以进行音量设置需要做两件事情:更新VolumeStreamState存储的音量值,设置音量到Audio底层系统。
  3. 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);
  }

接着看看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);// 通知外界音量值发生了变化
  }

总结一下这个函数:

  • 计算按下音量键的音量步进值。这个步进值是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;
        }
      }
    }

可以看出,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原生音量控制实例详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

您可能感兴趣的文章:

  • Android系统音量条实例代码
  • Android编程实现音量按钮添加监听事件的方法
  • android编程获取和设置系统铃声和音量大小的方法
  • Android 自定义SeekBar动态改变硬件音量大小实现和音量键的同步(推荐)
  • Android seekbar(自定义)控制音量同步更新
  • Android使用AudioManager修改系统音量的方法
  • Android 使用Vitamio打造自己的万能播放器(2)—— 手势控制亮度、音量、缩放
  • Android中自定义View实现圆环等待及相关的音量调节效果
(0)

相关推荐

  • android编程获取和设置系统铃声和音量大小的方法

    本文实例讲述了android编程获取和设置系统铃声和音量大小的方法.分享给大家供大家参考,具体如下: 通过程序获取android系统手机的铃声和音量.同样,设置铃声和音量的方法也很简单! 设置音量的方法也很简单,AudioManager提供了方法: public voidsetStreamVolume(intstreamType,intindex,intflags) 其中streamType有内置的常量,去文档里面就可以看到. JAVA代码: AudioManager mAudioManager

  • Android编程实现音量按钮添加监听事件的方法

    本文实例讲述了Android编程实现音量按钮添加监听事件的方法.分享给大家供大家参考,具体如下: 很多Android应用都应用到音量按钮,比如翻页,调整音乐声音大小等,但是如果没有对音量按钮进行监听,则无法达到预期的效果.如下代码,就是监听Android手机的音量按钮,开发者可以在相应的位置添加自己需要实现的功能. @Override public boolean onKeyDown (int keyCode, KeyEvent event) { // 获取手机当前音量值 int i = get

  • Android seekbar(自定义)控制音量同步更新

    Android seekbar控制音量同步更新 作为开发人员来讲,seekbar你一定会碰到,那么怎么自定义seekbar以及seekbar控制的音量怎么与系统的seekbar音量同步更新.今天就下拉菜单中添加SeekBar控制音量为例(Android 5.1系统)   一:自定义Seekbar 先来看一下所自定义的效果图: 首先来来看一下布局文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/androi

  • Android中自定义View实现圆环等待及相关的音量调节效果

    圆环交替.等待效果 效果就这样,分析了一下,大概有这几个属性,两个颜色,一个速度,一个圆环的宽度. 自定View的几个步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 3.重写onMesure  4.重写onDraw 1.自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <attr name="firstColor" f

  • Android系统音量条实例代码

    最近在定制Android系统音量条,发现代码还是蛮多的,下面总结一下. 代码是基于5.1.1版本的. 系统音量条的代码是在/frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java 布局文件是在/frameworks/base/packages/SystemUI/res/layout下. 先看看原生的音量条样式: 在代码中可以发现volume_dialog.xml这个文件,这个文件就是承载

  • Android使用AudioManager修改系统音量的方法

    本文实例讲述了Android使用AudioManager修改系统音量的方法.分享给大家供大家参考,具体如下: 下面介绍几个AudioManager的几个音量调整方面的方法. 首先是得到AudioManager实例: AudioManager am=(AudioManager)getSystemService(Context.AUDIO_SERVICE); 调整音量方法有两种,一种是渐进式,即像手动按音量键一样,一步一步增加或减少,另一种是直接设置音量值. 1.渐进式 public void ad

  • Android 自定义SeekBar动态改变硬件音量大小实现和音量键的同步(推荐)

    1,上图: 2,代码: MainActivity.Java package com.hero.zhaoq.seekbarchangeddemo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObse

  • Android 使用Vitamio打造自己的万能播放器(2)—— 手势控制亮度、音量、缩放

    前言 本章继续完善播放相关播放器的核心功能,为后续扩展打好基础. 系列 1.Android 使用Vitamio打造自己的万能播放器(1)--准备 正文 一.实现目标 1.1 亮度控制 模仿VPlayer界面: 1.2 声音控制 模仿VPlayer界面: 1.3 画面缩放 根据下面API提供画面的拉伸.剪切.100%.全屏     二.Vitamio API 介绍 VideoView 2.1 public void start() 开始播放 2.2 public void pause() 暂停播放

  • Android原生音量控制实例详解

    本文主要涉及AudioService.还是基于5.1.1版本的代码. AudioService.java文件位于/framework/base/media/java/android/media/下. 音量控制是AudioService最重要的功能之一.先总结一下: AudioService音量管理的核心是VolumeStreamState.它保存了一个流类型所有的音量信息. VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的.所以进行音

  • Android 十六进制状态管理实例详解

    目录 背景 示例 实现思路 代码测试 十六进制 总结 背景 最近需要实现一个状态管理类: 在多种场景下,控制一系列的按钮是否可操作. 不同场景下,在按钮不可操作的时候,点击弹出对应的Toast. 随着场景数量的增加,这个管理类的实现,就可能会越来越复杂. 刚好看到大佬的文章,顺便学习和实践一下.参考学习:就算不去火星种土豆,也请务必掌握的 Android 状态管理最佳实践 示例 还是用大佬那个例子. 例如,存在 3 种模式,和 3个按钮,按钮不可用的时候弹出对应的 Toast. 模式 A 下,要

  • Android 帧动画的实例详解

    Android 帧动画的实例详解 对于 Android 帧动画 大体上可以理解成 一张张图片 按一定顺序切换, 这样当连续几张图是一组动画时,就可以连起来了看成是一个小电影,你懂得 好得,比就装到这里,下面开始进入正题,由于产品需求 需要做一个 声音喇叭动态切换的样式,我特么第一就想到是帧动画切换,然后就百度了一些资料,发现 真的, 现在这个网上太多的资料是 copy粘贴过来的, 一错全错,对于这种情况我只想说,made,一群垃圾, 所以今天我将带你们走进Android 正确帧动画地址. 第一步

  • Android的搜索框架实例详解

    基础知识 Android的搜索框架将代您管理的搜索对话框,您不需要自己去开发一个搜索框,不需要担心要把搜索框放什么位置,也不需要担心搜索框影响您当前的界面.所有的这些工作都由SearchManager类来为您处理(以下简称"搜索管理器"),它管理的Android搜索对话框的整个生命周期,并执行您的应用程序将发送的搜索请求,返回相应的搜索关键字. 当用户执行一个搜索,搜索管理器将使用一个专门的Intent把搜索查询的关键字传给您在配置文件中配置的处理搜索结果的Activity.从本质上讲

  • Android Intent封装的实例详解

    Android Intent封装的实例详解 什么是Intent: Intent是协调应用间.组件之间的通讯和交互.通过Intent你可以启动Activity.Service.Broadcasts.更可以跨程序调用第三方组件.例如:启动拨打电话界面.音乐播放等. 组件     启动 Activity startActicity() Service startService(),bindService( ) Broadcasts sendBroadcast() 使用Intent: 栗子:在一个Act

  • Android 完全退出的实例详解

    Android 完全退出的实例详解 首先,在基类BaseActivity里,注册RxBus监听: public class BaseActivity extends AppCompatActivity { Subscription mSubscription; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Utils.intiSySBar(thi

  • Android 中ContentProvider的实例详解

    Android 中ContentProvider的实例详解 Content Provider 的简单介绍: * Android中的Content Provider 机制可支持在多个应用中存储和读取数据.这也是跨应用 共享数据的唯一方式.在Android系统中,没有一个公共的内存区域,供多个应用共享存储数据: * Android 提供了一些主要数据类型的ContentProvider ,比如:音频.视频.图片和私人通讯录等: 在android.provider 包下面找到一些android提供的C

  • Android 中 ActivityLifecycleCallbacks的实例详解

    Android 中 ActivityLifecycleCallbacks的实例详解           以上就是使用ActivityLifecycleCallbacks的实例,代码注释写的很清楚大家可以参考下, MyApplication如下: package com.cc; import java.util.LinkedList; import android.app.Activity; import android.app.Application; import android.os.Bun

  • Android全局获取Context实例详解

    Android全局获取Context实例详解 在弹出Toast 启动活动 发送广播 操作数据库 使用通知等等时都需要Context 如果操作在活动中进行是很简单的,因为活动本身就是一个Context对象 但是当逻辑代码脱离了Activity类,此时使用Context就需要一些技巧了: 我们可以定制一个自己的Application类,以便管理程序内一些全局状态信息,比如全局Context 代码如下: public class MyApplication extends Application{ p

  • Android IPC机制Messenger实例详解

    Android IPC机制Messenger实例详解 前言: Messenger可以翻译成信使,通过它可以在不同进程间传递Message对象有了它就可以轻松实现进程间的数据传递了. Messenger使用的方法相对AIDL比较简单,它对AIDL做了一层封装是的我们不需要像采用AIDL那样去实现进程通信那么麻烦,可以看看他的源码有AIDL的迹象. public final class Messenger implements Parcelable { private final IMessenge

随机推荐