CAPI v2 (Common Audio Processor Interface v2) 是 ADSP 框架中使用的通用音频处理接口。它的设计目的是将底层算法细节进行封装,并抽象成通用的接口形式。通过 CAPI v2, 控制层代码能够方便地调用算法模块,实现音频编解码、混音、回声消除、降噪等功能。这种接口的设计使得开发人员可以更轻松地利用底层算法模块,而无需关注底层实现的细节。
一 双声道数据交换
定义一个简单的算法:capi_v2_channelswap,将双声道音频数据进行左右声道交换。
场景:在耳机的左右声道上,通过PA后分别接一个喇叭,从而实现立体声。但是硬件在电路进行设计的时候将左右声道DAC输出设计反了。
如上图所示,CAPI v2 module主要分为下面的三个部分:
1. capi_v2_channelswap_t 数据结构
每个算法模块都需要定义一个内部的数据结构体,并且这个结构体的内存空间需要控制层申请,在算法初始化的时候传入。capi_v2_channelswap数据结构定义如下:
typedef struct _capi_v2_channelswap_t
{
// CAPI_V2 interface
const capi_v2_vtbl_t* vtbl_ptr; //必须结构体的第一个成员
// Module calibration parameters
uint32_t channelswap_enable;
// CAPI_V2 Properties
int heap_id;
capi_v2_event_callback_info_t event_callback;
capi_v2_port_num_info_t ports;
// Media formats:
media_fmt_t input_media_format;
media_fmt_t output_media_format;
} capi_v2_channelswap_t;
- CAPI_V2 interface
capi_v2_vtbl_t结构包含以下函数,用于暴露给控制层调用。
capi_v2_vtbl_t::process() //算法处理,核心代码!!
capi_v2_vtbl_t::end() //算法处理结束
//说明:在设置和获取参数需要定义参数的ID,同时这个ID后面还将用于ACDB,算法导入。
capi_v2_vtbl_t::set_param() //设置参数,算法校准参数,使能开关等
capi_v2_vtbl_t::get_param()//获取参数,算法校准参数,使能开关等
capi_v2_vtbl_t::set_properties()//设置音频特性,输入输出格式、回调函数等
capi_v2_vtbl_t::get_properties()//获取音频特性,输入输出格式、回调函数等
- Module calibration parameters
算法校准参数,使能开关等
- CAPI_V2 Properties
输入和输出的ports,控制层传入的回调函数等
- Media formats
音频格式
2. capi_v2_channelswap_get_static_properties
在初始化CAPI v2 module之前,capi_v2_channelswap_get_static_properties被控制层先调用,以获取当前模块的静态特征。例如 capi_v2_channelswap_t数据结构占用内存的大小,用于控制层申请并在初始化传入合适的内存;CAPI v2 module处理函数消耗栈空间的大小,以便ADSP 在创建SVC时,分配好栈空间。
实现如下,最后调用了capi_v2_vtbl_t::get_properties()的实现,即capi_v2_utils_props_process_properties。
capi_v2_err_t capi_v2_channelswap_get_static_properties(
capi_v2_proplist_t* init_set_properties,
capi_v2_proplist_t* static_properties)
{
if (NULL == static_properties) {
FARF(ERROR, "static_properties is NULL");
return CAPI_V2_EFAILED;
}
return capi_v2_utils_props_process_properties(static_properties,
capi_v2_channelswap_set_props, 0);
return CAPI_V2_EOK;
}
3. capi_v2_channelswap_init
通过capi_v2_t* _pif参数传入了控制层为算法内部数据结构申请的内存,即capi_v2_channelswap_t。对capi_v2_channelswap_t清零后,将模块内部算法操作函数表赋值给vtbl_ptr,使控制层获得一个capi_v2_t结构,获得参数传递和调用算法处理的能力,对capi_v2_channelswap_t其他数据成员进行初始化。
通过init_set_properties参数传递进来处理当前数据流的特性,如端口数、输入数据流格式等。最后调用了capi_v2_vtbl_t::set_properties()的实现,即capi_v2_channelswap_set_properties。
capi_v2_err_t capi_v2_channelswap_init(capi_v2_t* _pif,
capi_v2_proplist_t* init_set_properties)
{
capi_v2_channelswap_t *me = (capi_v2_channelswap_t *)_pif;
capi_v2_prop_t *props;
uint32_t i;
capi_v2_err_t result = CAPI_V2_EOK;
if (NULL == _pif) {
FARF(ERROR, "_pif is NULL.");
return CAPI_V2_EBADPARAM;
}
memset(me, 0, sizeof(capi_v2_channelswap_t));
me->vtbl_ptr = &vtbl;
me->ports.num_input_ports =1;
me->ports.num_output_ports=1;
//disable default
me->channelswap_enable = 0;
me->event_callback.event_cb =NULL;
me->event_callback.event_context = NULL
;
props = init_set_properties->prop_ptr;
for(i = 0; i < init_set_properties->props_num; i++){
switch (props->id)
{
case CAPI_V2_INPUT_MEDIA_FORMAT:
case CAPI_V2_EVENT_CALLBACK_INFO:
case CAPI_V2_HEAP_ID:
case CAPI_V2_PORT_NUM_INFO:
break;
default:
FARF(HIGH, "This property with id <%d> is not supported at initialization of module", props->id);
break;
}
props++;
}
FARF(HIGH, "num of init set props %ld", init_set_properties->props_num);
// Setting parameters now
if(init_set_properties->props_num) {
result = capi_v2_channelswap_set_properties(_pif, init_set_properties);
}
return result;
}
4. capi_v2_vtbl_t vtbl
static const capi_v2_vtbl_t vtbl =
{
// add function pointers to CAPI_V2 functions
capi_v2_channelswap_process,
capi_v2_channelswap_end,
capi_v2_channelswap_set_param,
capi_v2_channelswap_get_param,
capi_v2_channelswap_set_properties,
capi_v2_channelswap_get_properties
};
process实现如下,其他函数与SDK样例类似。
static capi_v2_err_t capi_v2_channelswap_process(
capi_v2_t* _pif,
capi_v2_stream_data_t* input[],
capi_v2_stream_data_t* output[])
{
capi_v2_err_t result = CAPI_V2_EOK;
capi_v2_channelswap_t* me = (capi_v2_channelswap_t*)_pif;
capi_v2_buf_t *in_buf_ptr, *out_buf_ptr;
uint32_t i, k;
FARF(ERROR,"process");
for(k=0; k<me->ports.num_input_ports; k++)
{
in_buf_ptr = input[k]->buf_ptr;
out_buf_ptr = output[k]->buf_ptr;
if(input[k]->bufs_num == 2 && me->channelswap_enable == 1) //swap left and right channel
{
capi_v2_buf_t *in_buf_ptr_r = in_buf_ptr++;
capi_v2_buf_t *out_buf_ptr_r = out_buf_ptr++;
memcpy(out_buf_ptr->data_ptr, in_buf_ptr_r->data_ptr, in_buf_ptr_r->actual_data_len);
out_buf_ptr->actual_data_len = in_buf_ptr_r->actual_data_len;
memcpy(out_buf_ptr_r->data_ptr, in_buf_ptr->data_ptr, in_buf_ptr->actual_data_len);
out_buf_ptr_r->actual_data_len = in_buf_ptr->actual_data_len;
}else{ //pass through
for(i=0; i<input[k]->bufs_num; i++)
{
memcpy(out_buf_ptr->data_ptr, in_buf_ptr->data_ptr, in_buf_ptr->actual_data_len);
out_buf_ptr->actual_data_len = in_buf_ptr->actual_data_len;
in_buf_ptr++;
out_buf_ptr++;
}
}
}
return result;
}
参数设置:capi_v2_channelswap只有使能参数。
static capi_v2_err_t capi_v2_channelswap_set_param(
capi_v2_t* _pif,
uint32_t param_id,
const capi_v2_port_info_t* port_info_ptr,
capi_v2_buf_t* params_ptr)
{
capi_v2_err_t result = CAPI_V2_EOK;
capi_v2_channelswap_t* me = (capi_v2_channelswap_t*)_pif;
//Validate pointers
if (NULL == _pif || NULL == params_ptr) {
FARF(ERROR, "CAPI V2 CHANNELSWAP: set param received NULL pointer");
return CAPI_V2_EBADPARAM;
}
switch(param_id)
{
case CAPI_V2_PARAM_CHANNELSWAP_MODULE_ENABLE:
...
break;
default:
FARF(HIGH, "CAPI V2 CHANNELSWAP: set_param received unsupported parameter(0x%x)", param_id);
result = CAPI_V2_EBADPARAM;
break;
}
return result;
}
5 头文件
#ifndef __CHANNELSWAP_V2_CALIB_H__
#define __CHANNELSWAP_V2_CALIB_H__
#define CAPI_V2_CHANNELSWAP_STACK_SIZE 2048
// Passthru module calibration parameters
#define CAPI_V2_PARAM_CHANNELSWAP_MODULE_ENABLE 0x1111FF01
#ifdef __cplusplus
extern "C"
{
#endif
capi_v2_err_t capi_v2_channelswap_get_static_properties(
capi_v2_proplist_t* init_set_properties,
capi_v2_proplist_t* static_properties);
capi_v2_err_t capi_v2_channelswap_init(capi_v2_t* _pif,
capi_v2_proplist_t* init_set_properties);
#ifdef __cplusplus
}
#endif
#endif
6 capi_v2_channelswap编译
~/Qualcomm/Hexagon_SDK/3.5.4/examples/audio/capi_v2_channelswap
make tree V=hexagon_Debug_dynamic_toolv83_v66
cd hexagon_Debug_dynamic_toolv83_v66/ship/
tree
.
├── capi_v2_channelswap.a
├── capi_v2_channelswap.h
└── capi_v2_channelswap.so
0 directories, 3 files
如上,capi_v2_channelswap最终的产出是算法库和算法头文件。
二 编写测试单元
Hexagon SDK 提供了一个可执行测试框架,用于测试开发的算法库。
1. 测试音频和配置文件
capi_v2_channelswap/data
.
├── channelswap.cfg
├── test_audio_in.raw //双声道16bit PCM 48k
channelswap.cfg文件内容如下:
######################################################################
# Configuration file for Hexagon Access Program Example Passthru unit test
#
# This config file contains the commands that will be run sequentially by the
# test framework.
#
# Specifically it:
# - Sets media format, sampling rate, num channels, bytes per sample
# - Sets Enable module parameters
# - Processes buffers as specified by NumBuffers
######################################################################
# Set Media Format
SetMediaFormat
SetBitstreamFormat 69029
SetDataFormat 0
SetNumChannelsAndMapping 2 0 1
SetBitsPerSample 16
QFactor 0
SetSamplingRate 48000
SetIsSigned 1
SetInterleaving 2
SetOutputMediaFormat
SetBitstreamFormat 69029
SetDataFormat 0
SetNumChannelsAndMapping 2 0 1
SetBitsPerSample 16
QFactor 0
SetSamplingRate 48000
SetIsSigned 1
SetInterleaving 2
# Enable Passthru module
SetParamInband
PayloadSizeInBytes 32
00 00 00 00 # Data Payload address <msw>
00 00 00 00 # Data Payload address <lsw>
00 00 00 00 # mem_map_handle
18 00 00 00 # Data payload size
00 FF 11 11 # module_id 0x11111200 PASSTHRU_V2_MODULE
01 FF 11 11 # param_id 0x11111201 PASSTHRU_PARAM_MOD_ENABLE
04 00 00 00 # param_size
01 00 00 00 # payload (Enable/Disable)
ProcessData
NumBuffers 300
2. 测试主函数入口
int test_main_start(int argc, char* argv[])
{
int err = TEST_SUCCESS;
args_t* input_args = 0;
FARF(HIGH, "test_main_start \n");
#ifdef ENABLE_COMMAND_LINE_PARAMS
if (NULL == (input_args = (args_t*)malloc(sizeof(args_t)))) {
FARF(HIGH, "%s: ERROR CODE 1 - Cannot malloc args\n", argv[0]);
exit(-1);
}
get_eargs(argc, argv, input_args);
#endif
//1. 定义capi_v2_proplist_t和capi_v2_prop_t
capi_v2_proplist_t init_set_properties;
capi_v2_proplist_t static_properties;
capi_v2_prop_t props[5];
capi_v2_init_memory_requirement_t init_memory_requirement;
capi_v2_stack_size_t stack_size;
capi_v2_max_metadata_size_t max_metadata_size;
capi_v2_is_inplace_t is_inplace;
capi_v2_requires_data_buffering_t requires_data_buffering;
init_set_properties.props_num = 0;
static_properties.props_num = 5;
static_properties.prop_ptr = props;
//2. 对props初始化,用于获取算法模块的特征
props[0].id = CAPI_V2_INIT_MEMORY_REQUIREMENT;
props[0].payload.data_ptr = (int8_t*)&init_memory_requirement;
props[0].payload.max_data_len = sizeof(init_memory_requirement);
props[1].id = CAPI_V2_STACK_SIZE;
props[1].payload.data_ptr = (int8_t*)&stack_size;
props[1].payload.max_data_len = sizeof(stack_size);
props[2].id = CAPI_V2_MAX_METADATA_SIZE;
props[2].payload.data_ptr = (int8_t*)&max_metadata_size;
props[2].payload.max_data_len = sizeof(max_metadata_size);
props[3].id = CAPI_V2_IS_INPLACE;
props[3].payload.data_ptr = (int8_t*)&is_inplace;
props[3].payload.max_data_len = sizeof(is_inplace);
props[4].id = CAPI_V2_REQUIRES_DATA_BUFFERING;
props[4].payload.data_ptr = (int8_t*)&requires_data_buffering;
props[4].payload.max_data_len = sizeof(requires_data_buffering);
#ifdef __V_DYNAMIC__
TRY(err, dll_test(input_args, &init_set_properties, &static_properties));
#else
TRY(err, lib_test(input_args, &init_set_properties, &static_properties));
#endif
if (0 == input_args) {
free(input_args);
}
CATCH(err){};
return err;
}
3 打开算法库、加载函数
int dll_test(args_t* input_args, capi_v2_proplist_t* init_set_properties,
capi_v2_proplist_t* static_properties)
{
void* h = 0;
const char* cpszmodname = "capi_v2_channelswap.so";
const char* cpszget_static_properties = "capi_v2_channelswap_get_static_properties";
const char* cpszinit = "capi_v2_channelswap_init";
capi_v2_get_static_properties_f get_static_properties_f = 0;
capi_v2_init_f init_f = 0;
int err = TEST_SUCCESS;
FARF(HIGH, "-- start dll test -- ");
//1. 打开动态库capi_v2_channelswap.so
FARF(HIGH, "attempt to load %s ", cpszmodname);
h = dlopen(cpszmodname, RTLD_NOW);
if (0 == h) {
FARF(HIGH, "dlopen %s failed %s ", cpszmodname, dlerror());
THROW(err, TEST_FAILURE);
}
//2.获得动态库中get_static_properties_f函数
get_static_properties_f = (capi_v2_get_static_properties_f)dlsym(h, cpszget_static_properties);
if (0 == get_static_properties_f) {
FARF(HIGH, "dlsym %s failed %s ", cpszget_static_properties, dlerror());
THROW(err, TEST_FAILURE);
}
//3.获得动态库中capi_v2_init_f函数
init_f = (capi_v2_init_f)dlsym(h, cpszinit);
if (0 == init_f) {
FARF(HIGH, "dlsym %s failed %s ", cpszinit, dlerror());
THROW(err, TEST_FAILURE);
}
TRY(err, test_capi_v2_main(get_static_properties_f,
init_f,
init_set_properties,
static_properties,
"../data/test_audio_in.raw", //输入音频文件
"../data/test_audio_out.raw", //输出音频文件
"../data/channelswap.cfg")); //配置文件
.........
}
4 调用处理函数
test_capi_v2_main是测试框架提供的函数。
capi_v2_err_t test_capi_v2_main(capi_v2_get_static_properties_f get_static_properties_f,
capi_v2_init_f init_f,
capi_v2_proplist_t* init_set_properties,
capi_v2_proplist_t* static_properties,
const char* filename_in, const char* filename_out,
const char* filename_config)
{
module_info_t module;
uint32_t malloc_size = 0;
uint8_t* ptr = 0;
capi_v2_err_t result = CAPI_V2_EOK;
// ------------------------
// Initializations
// ------------------------
// TODO init_profiling();
memset(&module, 0, sizeof(module));
if (filename_in != NULL) {
if ((module.finp = fopen(filename_in, "rb")) == NULL) {
FARF(ERROR, "test_capi_v2: Cannot open input file ");
THROW(result, CAPI_V2_EFAILED);
}
}
if (filename_out != NULL) {
if ((module.fout = fopen(filename_out, "wb")) == NULL) {
FARF(ERROR, "test_capi_v2: Cannot open output file ");
THROW(result, CAPI_V2_EFAILED);
}
}
if ((module.fCfg = fopen(filename_config, "rb")) == NULL) {
FARF(ERROR, "test_capi_v2: Cannot open config file ");
THROW(result, CAPI_V2_EFAILED);
}
// ------------------------
// STEP 1: Get size requirements of CAPI_V2
// ------------------------
FARF(HIGH, "MAIN: ---------------- ");
FARF(HIGH, "MAIN: Initialize module ");
FARF(HIGH, "MAIN: ---------------- ");
result = get_static_properties_f(init_set_properties, static_properties);
if (CAPI_V2_FAILED(result)) {
FARF(ERROR, "MAIN: get_static_properties error ");
THROW(result, result);
}
capi_v2_utils_props_process_properties(static_properties,
test_capi_v2_get_props, &malloc_size);
malloc_size = align_to_8_byte(malloc_size);
// ------------------------
// STEP 2: Allocate memory
// ------------------------
ptr = (uint8_t*)malloc(malloc_size);
if (!ptr) {
FARF(ERROR, "MAIN: Memory allocation error ");
THROW(result, CAPI_V2_ENOMEMORY);
}
module.module_ptr = (capi_v2_t*)ptr;
FARF(HIGH, "allocated %6lu bytes of memory at location 0x%08p ",
malloc_size, ptr);
module.out_format.max_data_len = sizeof(module.out_format_buf);
module.out_format.data_ptr = module.out_format_buf;
// ------------------------
// STEP 3: Initialize module
// ------------------------
result = init_f((capi_v2_t*)(ptr), init_set_properties);
if (CAPI_V2_FAILED(result)) {
FARF(ERROR, "MAIN: Initialization error ");
THROW(result, result);
}
{
// Set event callback information
capi_v2_event_callback_info_t event_cb_info;
event_cb_info = capi_v2_tst_get_cb_info(&module);
capi_v2_proplist_t proplist;
capi_v2_prop_t props[1];
proplist.props_num = 1;
proplist.prop_ptr = props;
props[0].id = CAPI_V2_EVENT_CALLBACK_INFO;
props[0].payload.data_ptr = (int8_t*)(&event_cb_info);
props[0].payload.actual_data_len = sizeof(event_cb_info);
props[0].payload.max_data_len = sizeof(event_cb_info);
module.module_ptr->vtbl_ptr->set_properties(module.module_ptr, &proplist);
}
module.requires_data_buffering = (bool_t)*(static_properties->prop_ptr[CAPI_V2_REQUIRES_DATA_BUFFERING].payload.data_ptr);
// ------------------------
// Run config file
// ------------------------
FARF(HIGH, "MAIN: ---------------- ");
FARF(HIGH, "MAIN: Run config file ");
FARF(HIGH, "MAIN: ---------------- ");
// Added new Input/Output commands for test script support
testCommand inputCmd[2];
strncpy(inputCmd[0].opCode, "Input", 6);
strncpy(inputCmd[1].opCode, "Output", 7);
inputCmd[0].pFunction = &Inputfile;
inputCmd[1].pFunction = &Outputfile;
result = RunTest(&module, inputCmd, 2);
if (CAPI_V2_FAILED(result)) {
FARF(ERROR, "MAIN: Error in RunTest ");
THROW(result, result);
}
// ------------------------
// Destroy CAPI V2 and free memory
// ------------------------
result = module.module_ptr->vtbl_ptr->end(module.module_ptr);
if (CAPI_V2_FAILED(result)) {
FARF(ERROR, "MAIN: Error in call to end ");
THROW(result, result);
}
CATCH(result){};
if (ptr) {
free(ptr);
}
// TODO deinit_profiling();
if (module.finp) {
fclose(module.finp);
}
if (module.fout) {
fclose(module.fout);
}
if (module.fCfg) {
fclose(module.fCfg);
}
FARF(HIGH, "MAIN: Done ");
return result;
}
三 算法效果验证
执行如下测试命令:
~/Qualcomm/Hexagon_SDK/3.5.4/tools/HEXAGON_Tools/8.3.07/Tools/bin/hexagon-sim -mv66g_1024 --simulated_returnval --usefs hexagon_Debug_dynamic_toolv83_v66 hexagon_Debug_dynamic_toolv83_v66/capi_v2_channelswap_q
在data目录生成 test_audio_out.raw文件。
打开test_audio_in.raw和test_audio_out.raw文件对比如下,算法生效!!