高通ADSP音频算法集成(二)CAPI v2算法模块和测试单元

        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文件对比如下,算法生效!!

猜你喜欢

转载自blog.csdn.net/Q_Lee/article/details/131219170