Qualcomm ADSP audio algorithm integration (2) CAPI v2 algorithm module and test unit

        CAPI v2 (Common Audio Processor Interface v2) is a common audio processing interface used in the ADSP framework. Its design purpose is to encapsulate the details of the underlying algorithm and abstract it into a common interface form. Through CAPI v2, the control layer code can easily call the algorithm module to realize audio codec, audio mixing, echo cancellation, noise reduction and other functions. The design of this interface makes it easier for developers to utilize the underlying algorithm modules without having to pay attention to the details of the underlying implementation.

One-channel data exchange

Define a simple algorithm: capi_v2_channelswap, which exchanges the left and right channels of the two-channel audio data.

Scenario: On the left and right channels of the earphones, a speaker is connected after passing through the PA to achieve stereo sound. However, when the hardware is designing the circuit, the DAC output design of the left and right channels is reversed.

As shown in the figure above, the CAPI v2 module is mainly divided into the following three parts:

1. capi_v2_channelswap_t data structure

Each algorithm module needs to define an internal data structure, and the memory space of this structure needs to be applied by the control layer and passed in when the algorithm is initialized. The capi_v2_channelswap data structure is defined as follows:

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

The capi_v2_vtbl_t structure contains the following functions for exposure to control plane calls.

capi_v2_vtbl_t::process() //algorithm processing, core code! !

capi_v2_vtbl_t::end() //End of algorithm processing

//Description: When setting and obtaining parameters, you need to define the ID of the parameter, and this ID will also be used for ACDB and algorithm import later.

capi_v2_vtbl_t::set_param() //Set parameters, algorithm calibration parameters, enable switches, etc.

capi_v2_vtbl_t::get_param()//Get parameters, algorithm calibration parameters, enable switches, etc.

capi_v2_vtbl_t::set_properties()//Set audio characteristics, input and output formats, callback functions, etc.

capi_v2_vtbl_t::get_properties()//Get audio characteristics, input and output formats, callback functions, etc.

  • Module calibration parameters

Algorithm calibration parameters, enable switches, etc.

  • CAPI_V2 Properties

Input and output ports, callback functions passed in by the control layer, etc.

  • Media formats

audio format

2. capi_v2_channelswap_get_static_properties

Before initializing the CAPI v2 module, capi_v2_channelswap_get_static_properties is called by the control layer to obtain the static characteristics of the current module. For example, the size of the memory occupied by the capi_v2_channelswap_t data structure is used to apply for the control layer and pass in the appropriate memory during initialization; the CAPI v2 module processing function consumes the size of the stack space so that ADSP can allocate the stack space when creating the SVC.

The implementation is as follows, and finally the implementation of capi_v2_vtbl_t::get_properties() is called, namely 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

The memory requested by the control layer for the internal data structure of the algorithm is passed in through the capi_v2_t* _pif parameter, namely capi_v2_channelswap_t. After clearing capi_v2_channelswap_t, assign the module’s internal algorithm operation function table to vtbl_ptr, so that the control layer can obtain a capi_v2_t structure, obtain the ability to pass parameters and call algorithm processing, and initialize other data members of capi_v2_channelswap_t.

The characteristics of the current data stream, such as the number of ports and the format of the input data stream, are passed in through the init_set_properties parameter. Finally, the implementation of capi_v2_vtbl_t::set_properties() is called, namely 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
};

The process is implemented as follows, and other functions are similar to the SDK samples.

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;

}

Parameter settings: capi_v2_channelswap only has enable parameters.

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 header files

#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 compiling 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

As above, the final output of capi_v2_channelswap is the algorithm library and algorithm header files.

Two writing test unit

The Hexagon SDK provides an executable test framework for testing the developed algorithm library.

1. Test Audio and Profiles

capi_v2_channelswap/data

.

├── channelswap.cfg

├── test_audio_in.raw // dual voice path 16bit PCM 48k

The content of the channelswap.cfg file is as follows:

######################################################################
# 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. Test the main function entry

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 Open the algorithm library and load the function

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 Call the processing function

test_capi_v2_main is a function provided by the test framework.

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;
}

Three algorithm effect verification

Execute the following test command:

~/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

Generate the test_audio_out.raw file in the data directory.

Open the test_audio_in.raw and test_audio_out.raw files and compare them as follows, the algorithm takes effect! !

Guess you like

Origin blog.csdn.net/Q_Lee/article/details/131219170