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

    xiaoxiao2021-03-25  8

    AudioEffect构造流程跟踪

    为了编写新的音效实现,需要了解Android底层在AudioEffect的底层实现: 在Java层new Equalizer();后,通过JNI进入底层C/C++的实现过程。在底层,通过层层调用,由音控中枢AudioFlinger.cpp负责音效的管理,在线程中使用音效工厂EffectFactory.c读取.conf配置文件完成音效实例的创建。

    AudioEffect构造流程图

    详细代码跟踪参考: 安卓音效AudioEffect源码剖析1——构造流程 根据上文,我做了张图帮助理解,其中将涉及到的类、头文件的关系列出,并标明了代码路径方便查找。当然,图中只列出了构造流程的关键代码。

    音效工厂EffectsFactory.c中的EffectCreate方法中的三个调用:

    // 从配置文件读取平台支持的音效信息 ret = init(); // 在支持的音效中查找是否有指定的音效type/uuid ret = findEffect(NULL, uuid, &l, &d); // 若有,则创建音效实例 ret = l->desc->create_effect(uuid, sessionId, ioId, &itfe);

    配置文件路径

    init()读取运行环境的音效配置文件,如果vendor/etc/audio_effects.conf存在则使用该配置,若不存在则使用系统的system/etc/audio_effects.conf。

    配置文件的路径定义在 /system/media/audio_effects/include/audio_effects/audio_effects_conf.h,有以下值:

    常量值顺序AUDIO_EFFECT_VENDOR_CONFIG_FILEvendor/etc/audio_effects.conf优先AUDIO_EFFECT_DEFAULT_CONFIG_FILEsystem/etc/audio_effects.conf其次

    配置文件audio_effects.conf

    audio_effects.conf配置文件内声明了平台所支持的音效库,新增音效库时需对该文件进行修改。 AOSP路径:/frameworks/av/media/libeffects/data/audio_effects.conf

    配置内容如下:

    #audio_effects.conf libraries { ... bundle { path /system/lib/soundfx/libbundlewrapper.so } ... } effects { ... bassboost { library bundle uuid 8631f300-72e2-11df-b57e-0002a5d5c51b } equalizer { library bundle uuid ce772f20-847d-11df-bb17-0002a5d5c51b } ... }

    libraries指出了音效库.so文件路径,默认是在平台的/system/lib/soundfx/目录下,新增的音效库so也要放在此处。 effects定义了音效名、音效库、实现引擎uuid的关系。注意到,同一个音效库so可以包含多种音效引擎。

    到此,Java层到底层C/C++层层调用,找到.so库完成对音效的创建。

    AudioEffect音效库实现

    公司导师要求做一个新的AudioEffect音效库实现音频的升降调,目前已经使用SoundTouch实现。 这里解读Android自带的音效库Visualizer实现,因为这个音效实现代码最为简约,模仿这个音效库容易写出新库。

    Visualizer相关

    Visualizer路径音效库.so(运行平台)/system/lib/soundfx/libvisualizer.so编译脚本/frameworks/av/media/libeffects/visualizer/Android.mk实现.cpp/frameworks/av/media/libeffects/visualizer/EffectVisualizer.cpp头文件.h/system/media/audio_effects/include/audio_effects/effect_visualizer.h

    创建新库的过程是编写EffectVisualizer.cpp实现effect_visualizer.h中的接口,并用编译脚本Android.mk编译为音效库libvisualizer.so。前3个对象是要编写的内容。

    头文件effect_visualizer.h

    #ifndef ANDROID_EFFECT_VISUALIZER_H_ #define ANDROID_EFFECT_VISUALIZER_H_ //------包含audio_effect.h,其中定义了音效库接口、音效控制接口 #include <hardware/audio_effect.h> //------C/C++条件编译 #if __cplusplus extern "C" { #endif //...省略... //------参数常量,需要与Java中的定义保持同步(一致) // to keep in sync with frameworks/base/media/java/android/media/audiofx/Visualizer.java #define VISUALIZER_SCALING_MODE_NORMALIZED 0 #define VISUALIZER_SCALING_MODE_AS_PLAYED 1 //------音效参数枚举 //通过该参数进行对应控制,从Java层传进的参数序号正是与此对应 /* enumerated parameters for Visualizer effect */ typedef enum { VISUALIZER_PARAM_CAPTURE_SIZE, // Sets the number PCM samples in the capture. VISUALIZER_PARAM_SCALING_MODE, // Sets the way the captured data is scaled VISUALIZER_PARAM_LATENCY, // Informs the visualizer about the downstream latency } t_visualizer_params; //------音效控制命令 //audio_effect.h中已经预设了EFFECT_CMD_SET_PARAM等命令,此处是Visualizer自增的命令 /* commands */ typedef enum { VISUALIZER_CMD_CAPTURE = EFFECT_CMD_FIRST_PROPRIETARY, // Gets the latest PCM capture. }t_visualizer_cmds; #if __cplusplus } // extern "C" #endif #endif /*ANDROID_EFFECT_VISUALIZER_H_*/

    实现EffectVisualizer.cpp

    #define LOG_TAG "EffectVisualizer" //#define LOG_NDEBUG 0 #include <cutils/log.h> #include <assert.h> #include <stdlib.h> #include <string.h> #include <new> #include <time.h> #include <audio_effects/effect_visualizer.h> //包含头文件 extern "C" { // effect_handle_t interface implementation for visualizer effect extern const struct effect_interface_s gVisualizerInterface; //------音效引擎描述结构体 //定义音效类型Type、实现引擎UUID、版本、连接模式、实现者等信息,与Java中的Descriptor类对应 //注意:在配置文件audio_effects.conf中effects的uuid元素是指实现引擎engine-uuid,而Java层的AudioEffect.java中定义的EFFECT_TYPE_XXX是音效类型effect-type。 //当AudioEffect.java的构造函数中只指定type参数(uuid参数为EFFECT_TYPE_NULL)时,系统自动查找该音效类型可用的实现引擎。若只指定uuid参数(type参数为EFFECT_TYPE_NULL)时,系统直接使用指定的音效实现引擎。 // Google Effect Type : e46b26a0-dddd-11db-8afd-0002a5d5c51b // Google Visualizer UUID: d069d9e0-8329-11df-9168-0002a5d5c51b const effect_descriptor_t gVisualizerDescriptor = { {0xe46b26a0, 0xdddd, 0x11db, 0x8afd, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}, // effect-type 音效类型 {0xd069d9e0, 0x8329, 0x11df, 0x9168, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}}, // engine-uuid 实现引擎 EFFECT_CONTROL_API_VERSION, //版本 (EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST), //连接模式 0, // CPU load 1, // Data memory "Visualizer", // 音效名 "The Android Open Source Project", //实现者 }; //------音效引擎状态 enum visualizer_state_e { VISUALIZER_STATE_UNINITIALIZED,//未初始化 VISUALIZER_STATE_INITIALIZED,//已初始化 VISUALIZER_STATE_ACTIVE,//激活 }; //------包装上下文 //持有一些处理对象 //***持有SoundTouch对象 struct VisualizerContext { const struct effect_interface_s *mItfe; effect_config_t mConfig; uint32_t mCaptureIdx; uint32_t mCaptureSize; uint32_t mScalingMode; uint8_t mState; uint8_t mLastCaptureIdx; uint32_t mLatency; struct timespec mBufferUpdateTime; uint8_t mCaptureBuf[CAPTURE_BUF_SIZE]; }; //------局部方法 //重置上下文 void Visualizer_reset(VisualizerContext *pContext) //设置I/O配置:采样率、声道、格式等 int Visualizer_setConfig(VisualizerContext *pContext, effect_config_t *pConfig) //依据配置初始化音效引擎 int Visualizer_init(VisualizerContext *pContext) //------音效库接口实现 //每个音效库都必须有名为AUDIO_EFFECT_LIBRARY_INFO_SYM的audio_effect_library_t结构体,该结构体的定义位于audio_effect.h,它定义了音效库的5个音效处理函数指针。 //(按C/C++语法,该结构体的赋值应位于使用的5个函数指针定义之后,提前在这是为了为了方便理解) audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = { tag : AUDIO_EFFECT_LIBRARY_TAG, version : EFFECT_LIBRARY_API_VERSION, name : "Visualizer Library", implementor : "The Android Open Source Project", #if EFFECT_API_VERSION_MAJOR(EFFECT_LIBRARY_API_VERSION) <= 2 query_num_effects : VisualizerLib_QueryNumberEffects, query_effect : VisualizerLib_QueryEffect, #endif create_effect : VisualizerLib_Create, release_effect : VisualizerLib_Release, get_descriptor : VisualizerLib_GetDescriptor, }; //该结构体中5个函数的定义分别为: //音效库兼容性检查 //audio_effect_library_t的定义在audio_effect.h中,该结构体在Android4.3+时有发生变化,query_num_effects()和query_effect()被删除。如果希望做库兼容性,需要检测EFFECT_LIBRARY_API_VERSION #if EFFECT_API_VERSION_MAJOR(EFFECT_LIBRARY_API_VERSION) <= 2 //查询音效数量(API版本检查) int VisualizerLib_QueryNumberEffects(uint32_t *pNumEffects) //查询音效(API版本检查) int VisualizerLib_QueryEffect(uint32_t index, effect_descriptor_t *pDescriptor) #endif //创建音效库 int VisualizerLib_Create(const effect_uuid_t *uuid, int32_t sessionId, int32_t ioId, effect_handle_t *pHandle) //释放音效库 int VisualizerLib_Release(effect_handle_t handle) //获取音效描述(gVisualizerDescriptor) int VisualizerLib_GetDescriptor(const effect_uuid_t *uuid, effect_descriptor_t *pDescriptor) //------音效控制接口实现 //音效控制接口effect_interface_s的实现 //effect_handle_s的定义位于audio_effect.h,定义了用于音效控制的3个函数指针 //(按C/C++语法,该结构体的赋值应位于使用的5个函数指针定义之后,提前在这是为了为了方便理解) const struct effect_interface_s gVisualizerInterface = { Visualizer_process,//音效处理 函数指针 Visualizer_command,//命令执行 函数指针 Visualizer_getDescriptor,//获取音效描述 函数指针 NULL,//见定义 }; //该结构体中3个函数的定义分别为: //音效处理:对音频数据进行处理,实现具体效果 //***SoundTouch的处理应放在此处 int Visualizer_process(effect_handle_t self,audio_buffer_t *inBuffer, audio_buffer_t *outBuffer) //执行命令:执行对音效的指定操作命令 int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData) { VisualizerContext * pContext = (VisualizerContext *)self; int retsize; if (pContext == NULL || pContext->mState == VISUALIZER_STATE_UNINITIALIZED) { return -EINVAL; } switch (cmdCode) {//预设音效命令在audio_effect.h中定义 case EFFECT_CMD_INIT://初始化 if (pReplyData == NULL || *replySize != sizeof(int)) { return -EINVAL; } *(int *) pReplyData = Visualizer_init(pContext); break; case EFFECT_CMD_SET_CONFIG://配置 case EFFECT_CMD_GET_CONFIG://读取配置 case EFFECT_CMD_RESET://重置 case EFFECT_CMD_ENABLE://启用 case EFFECT_CMD_DISABLE://禁用 case EFFECT_CMD_GET_PARAM: {//获取参数 //... //根据传入的音效参数枚举,返回对应值 switch (*(uint32_t *)p->data) {//(音效参数枚举在effect_visualizer.h中定义) case VISUALIZER_PARAM_CAPTURE_SIZE://音效参数之一 ALOGV("get mCaptureSize = %d", pContext->mCaptureSize); *((uint32_t *)p->data + 1) = pContext->mCaptureSize; p->vsize = sizeof(uint32_t); *replySize += sizeof(uint32_t); break; case VISUALIZER_PARAM_SCALING_MODE:// default:// } } break; case EFFECT_CMD_SET_PARAM: {//设置参数 //根据传入的音效参数枚举与值,设置参数 switch (*(uint32_t *)p->data) { case VISUALIZER_PARAM_CAPTURE_SIZE://音效参数之一 pContext->mCaptureSize = *((uint32_t *)p->data + 1); ALOGV("set mCaptureSize = %d", pContext->mCaptureSize); break; case VISUALIZER_PARAM_SCALING_MODE:// case VISUALIZER_PARAM_LATENCY:// default:// } } break; case EFFECT_CMD_SET_DEVICE: case EFFECT_CMD_SET_VOLUME: case EFFECT_CMD_SET_AUDIO_MODE: break; //Visualizer自增的命令 case VISUALIZER_CMD_CAPTURE: //... break; default:// } return 0; } //获取音效描述(gVisualizerDescriptor) //与int VisualizerLib_GetDescriptor(const effect_uuid_t *uuid, effect_descriptor_t *pDescriptor) 不同 int Visualizer_getDescriptor(effect_handle_t self, effect_descriptor_t *pDescriptor)

    编译脚本Android.mk

    LOCAL_PATH:= $(call my-dir) # Visualizer library include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ EffectVisualizer.cpp #实现源码.cpp LOCAL_CFLAGS+= -O2 LOCAL_SHARED_LIBRARIES := \ libcutils \ libdl LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx #编译出so的输出位置,默认/system/lib/soundfx/ LOCAL_MODULE:= libvisualizer #so库名:libvisualizer.so LOCAL_C_INCLUDES := \ $(call include-path-for, graphics corecg) \ $(call include-path-for, audio-effects) # 调用宏定义函数批量引入库,audio-effects的定义位于/build/core/pathmap.mk,其值为system/media/audio_effects/include ,该位置包含了effect_visualizer.h include $(BUILD_SHARED_LIBRARY)

    编译音效库.so

    将编写的音效文件EffectVisualizer.cpp、effect_visualizer.h、Android.mk置于AOSP的/external/目录下,使用Android.mk进行编译,成功的话会在/system/lib/soundfx/目录中生成libvisualizer.so。

    P.S. 在NDK中编译音效库是不够的,缺乏audio_effect.h相关库。

    编写中介类Visualizer.java

    完成了底层的音效库实现后,在Java层编写对应的类进行调用。Visualizer.java只是“中介”,代码十分简单,继承音效基类AudioEffect.java,实现相关的调用即可。具体编写可参考: AudioEffect与Equalizer解析(Java侧)中对中介类的解读。

    type和uuid的区别? 对于Android预设的Equalizer等音效,在AudioEffect基类中定义的EFFECT_TYPE_EQUALIZER对应底层音效引擎.cpp中effect_descriptor_t结构体的type字段。因此,在Equalizer等预设音效类的构造函数中,均指定type参数而将uuid参数指定为EFFECT_TYPE_NULL,如:

    public Equalizer(int priority, int audioSession) throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException, RuntimeException { super(EFFECT_TYPE_EQUALIZER, EFFECT_TYPE_NULL, priority, audioSession);

    按我的理解,如果只指定type,系统会自动查找支持该type音效的一个音效引擎进行实现;如果只指定uuid,那么不管是什么类型的音效,系统直接使用指定uuid的引擎进行实现。一开始在这上面弄糊涂了,导致系统一直找不到编写好的音效库。

    如何使用@hide? 在Java层继承AudioEffect实现新的音效中介类时,会遇到AudioEffect的几乎所有的public字段、方法都被@hide 标记导致无法在子类中调用的问题。

    原因与解决方法参考:Android中使用@hide成员

    导入classes.jar包(注意:要勾上System library(addedto the boot class path),否则“Java heap space”爆满)Java反射机制

    修改配置文件audio_effects.conf

    如果只是单独编译了新的音效库用于现有Android平台(手机)的使用,那么需要修改平台的system/etc/audio_effects.conf配置文件,加入新音效库的.so库路径以及新音效的effect uuid。 (猜测)如果是重编译整个Android版本的话,修改/frameworks/av/media/libeffects/data/audio_effects.conf文件后进行编译即可自动修改平台运行配置文件。

    在手机上使用新音效

    假设单独编译了新音效库libnew.so,也正确编写新应用.apk,那么要在手机上让新音效库生效,需要以下步骤:

    将新音效库libnew.so放到/system/lib/soundfx/目录下修改配置文件:system/etc/audio_effects.conf,加入新音效库路径path以及effect uuid重启手机,运行apk

    P.S. 修改手机system/下目录和文件需要root权限

    #手机root后: adb root #进入root模式,$变# adb remount #重新挂载system目录 adb shell cp ... #复制新内容到指定位置

    参考

    安卓音效AudioEffect源码剖析1——构造流程安卓音效AudioEffect源码剖析2——音效库接口Android AudioEffect机制初探Android源码分析:AudioEffectAudioEffect与Equalizer解析(Java侧)Android中使用@hide成员
    转载请注明原文地址: https://ju.6miu.com/read-156187.html

    最新回复(0)