AudioEffect与Equalizer解析(Java侧)

Posted wkw1125

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AudioEffect与Equalizer解析(Java侧)相关的知识,希望对你有一定的参考价值。

##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;//第二个参数为参数值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 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 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侧)

##参考

以上是关于AudioEffect与Equalizer解析(Java侧)的主要内容,如果未能解决你的问题,请参考以下文章

Android AudioEffect 音效方案

Android 音效方案

AudioEffect构造流程跟踪 & 音效库实现(native侧)

将 AudioEffect 附加到全局混音的首选方式?

UVa12545 Bits Equalizer (贪心)

UVa 12545 - Bits Equalizer