AudioEffect与Equalizer解析(Java侧)

来源:互联网 发布:结伴编程 编辑:程序博客网 时间:2024/04/28 17:52

AudioEffect

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

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

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

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

//创建MediaPlayer,音频源为/res/raw/audio.mp3mEqualizer = new Equalizer(0, mMediaPlayer.getAudioSessionId());//创建Equalizer,通过AudioSessionId绑定到MediaPlayerEqualizer mEqualizer = new Equalizer(0, mMediaPlayer.getAudioSessionId());//启用、获取/设置参数mEqualizer.setEnabled(true);short bands = mEqualizer.getNumberOfBands();mEqualizer.setBandLevel(band, level);//MP播放,创建EqualizermMediaPlayer.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 OnParameterChangeListenerpublic static final int PARAM_BAND_LEVEL = 2;

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

//Lock for access to mParamListenerprivate 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;//第二个参数为参数值band        checkStatus(getParameter(param, result));//获取gain        return 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.OnParameterChangeListener            l.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 effectpublic static final UUID EFFECT_TYPE_EQUALIZER = UUID        .fromString("0bed4300-ddd6-11db-8f34-0002a5d5c51b");//Null effect UUID. Used when the UUID for effect type ofpublic 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 creationpublic static final int STATE_UNINITIALIZED = 0;

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

//The effect descriptor contains information on a particular effect implemented in the audio frameworkpublic 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 initialization    int 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)
1 0