##AudioEffect
Android2.3增加了对音频混响的支持,通过AudioEffect可以方便地对AudioTrack和MediaPlayer播放的音乐进行音效控制。AudioEffect是音效控制基类,开发者不应直接使用此类,应该使用它的派生类:

  • Equalizer 均衡器:增加或降低某一频率的声音响度来达到想要的效果
  • Virtualizer 频谱(示波器):音频频谱可视化
  • BassBoost 重低音控制器:增加低音的强度
  • PresetReverb 预设混响(推荐用于音乐):使音乐通过声音在不同路径传播下造成的反射叠加产生的声音特效,比如流行,古典,爵士等。
  • EnvironmentalReverb 环境混响(推荐用于游戏):比如马路,走廊,室内,大厅等

以上音效包含在android.media.audiofx包中,具体使用可以参考 Android实现音乐示波器、均衡器、重低音和音场功能

这里以Equalizer为例,主要使用步骤如下:

//创建MediaPlayer,音频源为/res/raw/audio.mp3
mEqualizer = new Equalizer(0, mMediaPlayer.getAudioSessionId());//创建Equalizer,通过AudioSessionId绑定到MediaPlayer
Equalizer mEqualizer = new Equalizer(0, mMediaPlayer.getAudioSessionId());
//启用、获取/设置参数
mEqualizer.setEnabled(true);
short bands = mEqualizer.getNumberOfBands();
mEqualizer.setBandLevel(band, level);//MP播放,创建Equalizer
mMediaPlayer.start();

AudioEffect的具体效果作用在音频数据上,MediaPlayer只管播放音频,二者通过AudioSessionId关联起来。可以说,音效的处理对MediaPlayer是透明的,具体的处理由Android框架进行。

效果图:
(图中几个滑动条是Equalizer的控制,顶部的示波器是Virtualizer)

##Equalizer.java解析
Equalizer继承AudioEffect,二者的Java代码都十分简单,实际处理是通过AudioEffect构造函数中的JNI接口native_setup,调用了C++的代码实现。

(以下代码有删减)

Equalizer是AudioEffect的子类:

public class Equalizer extends AudioEffect {

**参数序号定义。**这部分定义了相关参数的序号PARAM_XXX,作为C++代码中参数数组的下标进行访问:

//Band level. Parameter ID for OnParameterChangeListener
public static final int PARAM_BAND_LEVEL = 2;

**成员变量定义。**其中特别的一个成员mParamListenerLock用于参数监听器的线程同步的锁定?:

//Lock for access to mParamListener
private final Object mParamListenerLock = new Object();

**构造函数。**将优先级、音频会话ID、音效类型与实现引擎信息传递给父类AudioEffect构造函数进行初始化。其中,音效类型EFFECT_TYPE_EQUALIZER、音效实现引擎EFFECT_TYPE_NULL都由AudioEffect中定义,将在后文介绍。

参数:

  • int priority:优先级,多个应用可以共享同一Equalizer引擎,该参数指出控制优先权,默认为0.
  • int audioSession:音频会话ID,系统范围内唯一,Equalizer将被附加在拥有相同音频会话ID的MediaPlayer或AudioTrack上生效。
public Equalizer(int priority, int audioSession)throws IllegalStateException, IllegalArgumentException,UnsupportedOperationException, RuntimeException {super(EFFECT_TYPE_EQUALIZER, EFFECT_TYPE_NULL, priority, audioSession);if (audioSession == 0) {Log.w(TAG, "WARNING: attaching an Equalizer to global output mix is deprecated!");}//...

如果要应用全局音频输出的混响效果必须指定audioSession=0,并且要求有MODIFY_AUDIO_SETTINGS权限。但是,注意到api的提示:

attaching an Equalizer to the global audio output mix by use of session 0 is deprecated.

使用session=0进行全局音频混响,已经被废弃

**参数get/set。**传入参数,包装为参数数组后调用父类AudioEffect的getParameter/setParameter进行参数设置,其中参数数组中第一个数是改变参数的序号,如之前设置的PARAM_BAND_LEVELgetParameter/setParameter返回值是操作的结果,传入父类AudioEffect的checkStatus进行状态检查,若不成功则抛出异常。

/*** Gets the gain set for the given equalizer band.* @param band frequency band whose gain is requested.* @return the gain in millibels of the given band.*/public short getBandLevel(short band)throws IllegalStateException, IllegalArgumentException,UnsupportedOperationException {int[] param = new int[2];//参数数组short[] result = new short[1];//返回值数组param[0] = PARAM_BAND_LEVEL;//第一个参数为参数序号param[1] = (int)band;//第二个参数为参数值bandcheckStatus(getParameter(param, result));//获取gainreturn result[0];}

**定义音效参数监听器:**面向Equalizer的使用者的监听器Equalizer.OnParameterChangeListener

//The OnParameterChangeListener interface defines a method called by the Equalizer when a parameter value has changed.
public interface OnParameterChangeListener  {void onParameterChange(Equalizer effect, int status, int param1, int param2, int value);
}

**实现基类参数监听:**继承AudioEffect.OnParameterChangeListener,实现具体的监听方法,对基类传来的参数变化的原始数据进行包装,并传递给Equalizer.OnParameterChangeListener

//Listener used internally to receive unformatted parameter change events from AudioEffect super class.
private class BaseParameterListener implements AudioEffect.OnParameterChangeListener
{private BaseParameterListener() {}public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {OnParameterChangeListener l = null;//线程同步:锁机制synchronized(mParamListenerLock) {if (mParamListener != null){l = mParamListener;}}if (l != null){int p1 = -1;int p2 = -1;int v = -1;// 按字节长度判断参数、值个数if (param.length >= 4){p1 = byteArrayToInt(param, 0);if (param.length >= 8){p2 = byteArrayToInt(param, 4);}}if (value.length == 2){v = (int)byteArrayToShort(value, 0); ;}else if (value.length == 4){v = byteArrayToInt(value, 0);}if (p1 != -1 && v != -1){//包装参数传递给Equalizer.OnParameterChangeListenerl.onParameterChange(Equalizer.this, status, p1, p2, v);}}
}

**定义参数包装类Settings:**将音效器的各种参数包装为类,方便访问

public static class Settings {//...
};
public Equalizer.Settings getProperties()
public void setProperties(Equalizer.Settings settings)

##AudioEffect.java解析

  • AudioEffect是由Android音频框架提供的音频效果控制的基类,开发者不应直接使用该类,而是该使用该类的派生类,如Equalizer。
  • 将AudioEffect应用于特定AudioTrack或MediaPlayer实例时,需要在创建AudioEffect时指定播放器实例的AudioSessionId。共用一个AudioSessionId的AudioTrack和MediaPlayer会共用一个AudioEffect。
  • 通过指定session=0对全局音频输出的混响效果已经废弃。
  • 创建AudioEffect对象时将会在Android框架中创建对应的音效引擎。当指定audio session中不存在该音效实例时创建,存在时复用。
  • 多个应用共享同一个音效引擎。若A正在使用音效引擎,而B用比A更高的优先级创建音效,则B将从A获得该音效引擎的控制权;若B的优先级比A低,则控制权还在A上,B将被告知音效引擎状态或者控制权的变更信息。

**载入so库。**AudioEffect的具体实现是通过JNI调用Android框架so库中C++的代码实现。

public class AudioEffect {static {System.loadLibrary("audioeffect_jni");native_init();
}

**定义音效类型Type。**指定了由Android音频框架实现的音效类型Type的128位标识符,与框架中的C++实现挂钩(识别作用)。

//UUID for equalizer effect
public static final UUID EFFECT_TYPE_EQUALIZER = UUID.fromString("0bed4300-ddd6-11db-8f34-0002a5d5c51b");
//Null effect UUID. Used when the UUID for effect type of
public static final UUID EFFECT_TYPE_NULL = UUID.fromString("ec7178ec-e5e1-4432-a3f4-4657e6795210");

在框架源码中,音效的C++代码实际上指定了两个UUID类型的属性:Effect_Type、Effect_UUID。Effect_Type指定音效的类型,每种类型可以有多种实现,而Effect_UUID指定某类音效的具体实现引擎。
上述代码中常量名为EFFECT_TYPE,而注释却是“UUID”有点误导,实际上就是指音效类型(而不是音效引擎)。
需要留意一个特别的UUID:UUID EFFECT_TYPE_NULL

UUID(Universally Unique Identifier,通用唯一识别码),这是一个128位的唯一识别码,其生成涉及网卡地址、纳秒级时间、芯片ID等信息。标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12),其中每个x是一个十六进制数字(0-9 a-f)。

Java中的uuid生成:
import java.util.UUID;
UUID uuid = UUID.randomUUID();

在线生成网站:https://www.uuidgenerator.net/

**状态值定义:**指定操作的状态,与底层C++代码对应

//State of an AudioEffect object that was not successfully initialized upon creation
public static final int STATE_UNINITIALIZED = 0;

**音效描述类定义:**该类是对音效引擎的描述,在底层C++实现中也有该类型结构体。

//The effect descriptor contains information on a particular effect implemented in the audio framework
public static class Descriptor
{public Descriptor(String type, String uuid, String connectMode,String name, String implementor){//...};

连接模式connectMode的说明:

待补充:

EFFECT_INSERT = “Insert”
EFFECT_AUXILIARY = “Auxiliary”
EFFECT_PRE_PROCESSING = “Pre Processing”

**成员变量定义。**其中特别的一个成员mParamListenerLock用于参数监听器的线程同步的锁定?

OnEnableStatusChangeListener //启用状态
OnControlStatusChangeListener //控制权
OnParameterChangeListener //参数变化
Object mListenerLock //同步锁

构造函数。将优先级、音频会话ID、音效类型与实现引擎信息传递给本地方法native_setup,并根据返回值判断初始化操作的结果。

参数:

  • UUID type:内置音效类型,若指定了不支持的音效类型将抛出IllegalArgumentException异常。扩展的音效类型可以通过新的UUID指定,并且音效需在平台上可用(引入so库)。将该参数设置为EFFECT_TYPE_NULL可以只使用uuid来指定音效。
  • UUID uuid:音效类型的特定实现,将该参数设置为EFFECT_TYPE_NULL可以只使用type来指定音效。
  • int priority:优先级,多个应用可以共享同一音效引擎,该参数指出控制优先权,默认为0.
  • int audioSession:音频会话ID,系统范围内唯一,音效将被附加在拥有相同音频会话ID的MediaPlayer或AudioTrack上生效。
public AudioEffect(UUID type, UUID uuid, int priority, int audioSession)throws IllegalArgumentException, UnsupportedOperationException,RuntimeException {//...// native initializationint initResult = native_setup(new WeakReference<AudioEffect>(this), type.toString(), uuid.toString(), priority, audioSession, id, desc);if (initResult != SUCCESS && initResult != ALREADY_EXISTS) {switch (initResult) {case ERROR_BAD_VALUE://...case ERROR_INVALID_OPERATION://...default://...}}//...
}

如果要应用全局音频输出的混响效果必须指定audioSession=0,并且要求有MODIFY_AUDIO_SETTINGS权限。但是,注意到api的提示:

attaching an Equalizer to the global audio output mix by use of session 0 is deprecated.

使用session=0进行全局音频混响,已经被废弃

**参数get/set。**传入参数数组给本地方法native_setParameter。param数组中包含修改参数的序号(修改哪些音效参数),value数组是修改的音效参数值。其他get/set是对setParameter(byte[], byte[])的重载。

public int setParameter(byte[] param, byte[] value)throws IllegalStateException {checkState("setParameter()");return native_setParameter(param.length, param, value.length, value);
}
public int getParameter(byte[] param, byte[] value)throws IllegalStateException {checkState("getParameter()");return native_getParameter(param.length, param, value.length, value);
}

**引擎命令:**向音效引擎发送命令,与底层C++实现对接。

public int command(int cmdCode, byte[] command, byte[] reply)throws IllegalStateException {checkState("command()");return native_command(cmdCode, command.length, command, reply.length, reply);
}

**其他:**监听定义、广播定义、本地方法与Java方法互相调用的声明、以及checkState、byteArrayToInt等工具方法的定义。

##AudioEffect的底层实现(native侧)
业务要求实现新的AudioEffect引擎,进行音频升降调处理。从Equalizer、AudioEffect的Java代码可以知道,具体的实现其实都是在JNI本地方法中,所以要实现新的音效引擎,**需编写底层引擎代码(c++)**并在Android框架下编译。因此,得了解AudioEffect在框架中的具体实现。

本文主要记录AudioEffect的Java层实现,底层框架C++的实现请移步:
AudioEffect底层框架代码跟踪(native侧)

##参考

  • Android实现音乐示波器、均衡器、重低音和音场功能
  • Android新增API之AudioEffect中文API与应用实例(Equalizer)
  • Android音频进阶(Virtualizer)

AudioEffect与Equalizer解析(Java侧)相关推荐

  1. 深入解析Java AtomicInteger 原子类型

    深入解析Java AtomicInteger原子类型 在进行并发编程的时候我们需要确保程序在被多个线程并发访问时可以得到正确的结果,也就是实现线程安全.线程安全的定义如下: 当多个线程访问某个类时,不 ...

  2. java jni 数据类型_【Android JNI】Native层解析Java复杂数据类型HashMap

    前提 Java HashMap 是基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.HashMap是存放引用类型数据的容器,只能存放引用数据类型,不能存 ...

  3. 转 : 深入解析Java锁机制

    深入解析Java锁机制 https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&mid=2247485524&idx=1&s ...

  4. java 获取spring对象数组_解析Java中如何获取Spring中配置的bean

    解析Java中如何获取Spring中配置的bean Java中如何获取Spring中配置的bean?下面是由百分网小编为大家整理的解析Java中如何获取Spring中配置的bean,喜欢的可以收藏一下 ...

  5. 聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁

    上一篇聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁 讲了可重入读写锁的基本情况和主要的方法,显示了如何 ...

  6. 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore

    前几篇分析了一下AQS的原理和实现,这篇拿Semaphore信号量做例子看看AQS实际是如何使用的. Semaphore表示了一种可以同时有多个线程进入临界区的同步器,它维护了一个状态表示可用的票据, ...

  7. 聊聊高并发(二十二)解析java.util.concurrent各个组件(四) 深入理解AQS(二)

    上一篇介绍了AQS的基本设计思路以及两个内部类Node和ConditionObject的实现 聊聊高并发(二十一)解析java.util.concurrent各个组件(三) 深入理解AQS(一) 这篇 ...

  8. 聊聊高并发(二十一)解析java.util.concurrent各个组件(三) 深入理解AQS(一)

    AQS是AbstractQueuedSynchronizer的缩写,AQS是Java并包里大部分同步器的基础构件,利用AQS可以很方便的创建锁和同步器.它封装了一个状态,提供了一系列的获取和释放操作, ...

  9. java 日期 解析_Java日期解析(Java DATE Parsing)

    Java日期解析(Java DATE Parsing) 我有一个java.util.Date的愚蠢问题. 我有这一行代码,但我不明白为什么这个日期是用这种格式无法解析的. public class T ...

最新文章

  1. Spring Cloud Gateway 之请求坑位[微服务IP不同请求会失败]
  2. 三天没有更新我的BLOG
  3. django之允许外部机器访问
  4. Redis缓存穿透、缓存击穿和缓存雪崩
  5. 在线网站原型设计工具Cacoo
  6. Data intensive Application (1)
  7. Bootstrap元素居中
  8. [BZOJ]1042 硬币购物(HAOI2008)
  9. iPad和iPhone上的应用程序图标
  10. android 按键上报流程,input子系统详解,按键如何上报
  11. EDA工具对芯片产业的重要性知识科普
  12. VMware Workstation -- 破解密码
  13. 爬虫 - 股票爬虫实例之雪球网
  14. 2021年最新3d材质贴图素材大合集来咯
  15. 查询商品分类id为10或11的商品对应的商品信息
  16. html制作日期计划表,学习计划每日时间安排表参考
  17. 正点原子STM32F103学习笔记(六)——时钟系统
  18. 人脸验证与二分类(Face verification and binary classification)
  19. 目标跟踪之Lukas-Kanade光流法
  20. windows下编译以及运行cryptominisat 求解器(sat求解器)

热门文章

  1. 华为服务器 国产化 操作系统,华为设备开始使用国产操作系统,国产操作系统的春天,百万人建议强制使用...
  2. 江苏统考计算机英语作文,高考江苏英语作文范文
  3. 手机android的文件怎么恢复,安卓手机怎样恢复删除的文件
  4. android vector 圆形,Android中矢量图形的那些事 - SVG or Vector
  5. 【渝粤题库】陕西师范大学202901小学生心理辅导作业(高起专 、专升本)
  6. TFT-LCD制程之CF制程
  7. 2.JAVA自带的序列化反序列化机制
  8. 嵌入式系统设计---实时系统与嵌入式操作系统
  9. shmget函数与shmat
  10. 有哪些在线尺子测量工具?这个工具值得试试