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_FILE vendor/etc/audio_effects.conf 优先
AUDIO_EFFECT_DEFAULT_CONFIG_FILE system/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 bundleuuid 8631f300-72e2-11df-b57e-0002a5d5c51b}equalizer {library bundleuuid 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 scaledVISUALIZER_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 load1, // 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) <= 2query_num_effects : VisualizerLib_QueryNumberEffects,query_effect : VisualizerLib_QueryEffect,
#endifcreate_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 #实现源码.cppLOCAL_CFLAGS+= -O2LOCAL_SHARED_LIBRARIES := \libcutils \libdl LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx #编译出so的输出位置,默认/system/lib/soundfx/
LOCAL_MODULE:= libvisualizer #so库名:libvisualizer.soLOCAL_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.hinclude $(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,那么要在手机上让新音效库生效,需要以下步骤:

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

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

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

参考

  1. 安卓音效AudioEffect源码剖析1——构造流程
  2. 安卓音效AudioEffect源码剖析2——音效库接口
  3. Android AudioEffect机制初探
  4. Android源码分析:AudioEffect
  5. AudioEffect与Equalizer解析(Java侧)
  6. Android中使用@hide成员

AudioEffect构造流程跟踪 音效库实现(native侧)相关推荐

  1. I.MX6 Linux Qt 启动流程跟踪

    /*************************************************************************** I.MX6 Linux Qt 启动流程跟踪* ...

  2. Buildroot 打包文件系统流程跟踪

    /********************************************************************************** Buildroot 打包文件系统 ...

  3. 【Android 逆向】函数拦截实例 ( 函数拦截流程 | ① 定位动态库及函数位置 )

    文章目录 一.函数拦截流程 二.定位动态库及函数位置 一.函数拦截流程 函数拦截流程 : 定位动态库及函数位置 : 获取该动态库在内存中的位置 , 以便于 查找函数位置 ; 插桩 : 在函数的入口处插 ...

  4. activiti流程跟踪图简单详解

    我用的是spring+springmvc+mybatis,前端使用的是jsp ===========前台jsp中的写法========== <img  id="imgObj" ...

  5. 一文解锁华为云新技能-AIOT开发全流程【设备接入-ESP端侧数据收集[MQTT]-实时数据分析】(步步截图较详细)

    一文解锁华为云新技能-AIOT开发全流程[设备接入-ESP端侧数据收集[MQTT]-实时数据分析](步步截图较详细) 在这篇你将会接触到:从物联网工程师从硬件层-通信层到应用层全流程:开发华为云最基本 ...

  6. 前端工程化实践 - 代码规范 提交规范 构建流程 Monorepo(附 React Native 案例)

    前端工程化实践 - 代码规范 & 提交规范 & 构建流程 & Monorepo 前言 仓库策略 Multirepo 什么是 Multirepo? Multirepo 的优点 M ...

  7. 【Android 高性能音频】Oboe 开发流程 ( 导入 Oboe 库 | 使用预构建的二进制库和头文件 | 编译 Oboe 源码 )

    文章目录 一.导入 Oboe 库 二.使用预构建的二进制库和头文件 三.编译 Oboe 源代码 Oboe GitHub 主页 : GitHub/Oboe ① 简单使用 : Getting Starte ...

  8. 我的内核学习笔记13:x86平台linux系统重启流程跟踪

    一直以来,笔者只知道重启Linux系统性使用reboot,但对其过程却无所知,涉及到哪些知识点也无概念.本文就跟踪一下重启的流程,平台为Intel x86,Linux内核版本为3.17.行文中&quo ...

  9. coreboot学习4:启动流程跟踪之romstage阶段

    romstage是coreboot的第二个执行阶段.本文分别介绍基于qemu模拟环境的x86的跟踪,以及基于Intel baytrail平台的跟踪. 在romstage阶段,由于内存还未初始化好,所以 ...

  10. ethtool的内核流程跟踪

    这些天开始下决心写写Linux网络方面的文章.由于能力和时间有限,当前还没有对Linux的网络有深入的了解.我一开始打算从网卡基本知识到PHY寄存器,到MAC控制器,到以太网协议栈,一步一步地学习.但 ...

最新文章

  1. pthread调度策略,优先级和竞争范围
  2. Django 之母板
  3. [安卓基础] 008.Android中的显示单位
  4. MySQL Binlog Mixed模式记录成Row格式
  5. 如何优雅地实现 C 编译期静态反射
  6. es6中class类的全方面理解
  7. zynq文档学习之GPIO寄存器基本操作
  8. 博文视点读书节第五日丨IT大咖私房书单继续放送,超级会员返场来袭!
  9. python lambda表达式及用法_Python中lambda表达式的常见用法
  10. “十四五”国家应急体系规划发布 智慧应急成为总体建设目标之一
  11. linux修改dns不生效,Linux 临时修改和永久修改DNS的方法
  12. pdfmake支持html,pdfMake前端导出pdf
  13. 【布局优化】基于人工蜂群算法的无线传感器网(WSN)覆盖优化matlab源码
  14. 怎么做网站?网站用什么服务器好?
  15. ping协议(ICMP)的原理
  16. 2012年9月8日参加中国软件开发者大会学习笔记
  17. 2021-01-07关于Linux段错误的原因和解决办法(初学者)
  18. MAC OS X下批量转换png为pvr.ccz 简易脚本
  19. Echarts主题更换
  20. Python金额大写转换

热门文章

  1. activemq中怎么知道推送消息是否成功_ActiveMQ入门
  2. 前端基础语言HTML、CSS 和 JavaScript 学习指南
  3. ntko php,NTKO OFFICE文档控件
  4. Ubuntu之最好用程序员计算器:qalculate
  5. 自动驾驶的“天眼”!聊一聊高精地图领域中所有主流的制作方案
  6. SOA 普元EOS 工作流开发
  7. Eprime Feedback state激活错误 其他报错
  8. Linux教程系列-命令大全
  9. 绑定流详解——网络测试仪实操
  10. 安卓帧数监测软件_手机帧数测试软件-手机fps帧数显示软件1.6 免root版-东坡下载...