跳转至

VPA (Voice Process Algorithm)移植指南*

1.概述*

本文主要讲解如何部署自己的算法在 SDK 上,适用于第三方算法公司阅读。

  • vpa 是 GX830X芯片SDK 专门用来处理音频算法的子系统。
  • 可以在 vpa 中部署各种音频算法,包括前处理和神经网络模型。

2.vpa 目录结构*

  • vpa 目录结构如下
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    .
    ├── algorithm_package
    │   └── bypass
    │       ├── bypass.mk
    │       └── vpa_process.c
    ├── base
    │   ├── base.mk
    │   ├── vpa_ae.c
    │   ├── vpa_context.c
    │   └── vpa_helper.c
    ├── include
    │   ├── vpa_ae.h
    │   ├── vpa_context.h
    │   ├── vpa_helper.h
    │   └── vpa_process.h
    └── vpa.mk
    
    4 directories, 11 files
    

2.1.algorithm_package 介绍*

  • algorithm_package 下面的每一个目录都是一个独立的算法包,各算法包之间不允许相互调用。

    重要

    algorithm_package 下的每一个子目录是一个独立的算法包,方便用户进行算法版本管理,不推荐直接基于 algorithm_package 进行算法开发。

2.1.2.bypass 算法包介绍*

  • 该算法包实现了对 mic 、 ref 拷贝到 output 通道中,并根据应用层的配置来决定是否交织output通道中的数据

    重要

    该算法包仅仅用于示范,不推荐直接基于 bypass 目录进行算法开发。

2.2.base 目录介绍*

  • 该目录包含了 3 个c源码文件:
    • vpa_ae.c: 实现了 FFT、IFFT 相关 API 接口。
    • vpa_context.c:根据 app 的参数完成 context 的初始化。
    • vpa_helper.c:提供了与 context 相关的 api 接口供开发者使用。

3.如何构建自己的算法包*

algorithm_package 下面的每一个目录都是一个独立的算法包,各算法包之间不允许相互调用。本节我们参考 bypass 来构建属于自己的算法包。

  • 拷贝 bypass 算法目录,重命名为需要移植的算法, 例如改为 gx_lib,并将 bypass.mk 重命名为 gx_lib.mk,且将 gx_lib.mk 中的 BYPASS 改为 GX_LIB,
  • 在 vpa.mk 中增加如下代码:

    gx_lib.mk
    1
    2
    3
    include $(VPA_DIR)algorithm_package/gx_lib/gx_lib.mk
    VPA_OBJS += $(GX_LIB_OBJS)
    VPA_INCS += $(GX_LIB_INCS)
    
  • 之后就可以在 gx_lib 中进行算法移植开发了。

  • 构建完 gx_lib 后,在 apps 对应的 app 中 app.mk 加上下面两句话,就可以编译到 algorithm_package/gx_lib 算法包了

    app.mk
    1
    2
    BUILD_VPA_ENABLE := 1
    BUILD_VPA_GX_LIB_ENABLE := 1
    

4.如何移植自己的算法*

  • 供 apps 调用的几个算法 api 接口见 vpa_process.h 三个接口,这三个接口均在 vpa_porcess.c 中实现。
  • vpa_process.c 中必须包含如下三个接口的实现:

    vpa_process.c
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    int32_t vpa_init(VPA_CONTEXT_HEADER *context_header, void *priv)
    {
        ... // 省略1一亿行代码
    }
    int32_t vpa_done(void)
    {
        ... // 省略1一亿行代码
    }
    int32_t vpa_process_active(VPA_CONTEXT *context)
    {
        ... // 省略1一亿行代码
    }
    

4.1.算法初始化:vpa_init*

  • 算法需要初始化的代码放在 vpa_init 接口中,这个接口会在上电后调用一次
  • 示例代码如下:
    vpa_process.c -->> vpa_init(VPA_CONTEXT_HEADER *context_header, void *priv)
    1
    2
    3
    4
    5
    6
    7
    8
    int32_t vpa_init(VPA_CONTEXT_HEADER *context_header, void *priv)
    {
        printf(LOG_TAG" [%s]\n", __func__);
    
        vpa_top_init(context_header);
        gx_snpu_init();
        return 0;
    }
    

4.2.算法的处理:vpa_process_active*

  • 算法处理的入口函数在 vpa_process.c 中的 vpa_process_active,所有的算法都是在这个函数中进行处理,这个函数会在初始化后,根据配置的context的时长回调。
  • 示例代码如下:
    vpa_process.c -->> vpa_process_active(VPA_CONTEXT *context)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    int32_t vpa_process_active(VPA_CONTEXT *context)
    {
        uint64_t start_us = gx_get_time_us();
    
        // 刷 cache,使 cache 内数据无效
        vpa_process_invalidate_mic_buffer(context);
        vpa_process_invalidate_ref_buffer(context);
    
        // 将 mic、ref 拷贝到 output
        vpa_copy_mic_to_output(context, 0, CIB_MIC0, CIB_OUT0);
        vpa_copy_mic_to_output(context, 0, CIB_MIC0, CIB_OUT0 + 1);
        if (context->ctx_header->audio_io_config.ref_num > 0) vpa_copy_ref_to_output(context, 0, CIB_REF0, CIB_OUT0 + 2);
    
        // 对 output 进行交织
        vpa_interleave_output_channels(context);
    
        // 刷 cache,将 cache 数据写回 内存
        vpa_process_writeback_out_buffer(context);
    
        uint64_t end_us = gx_get_time_us();
        vpa_top_run(start_us, end_us);
    
        return 0;
    }
    

5.context*

vpa 使用 context(处理上下文)来存放每个处理过程的输入、输出和中间过程数据,这个数据结构在 vpa_context.h 中定义.

5.1.context 结构介绍*

  • context 有两部分内容:VPA_CONTEXT 和 VPA_CONTEXT_HEADER
    • VPA_CONTEXT_HEADER 包含了一些不常变化的信息
      vpa_context.h
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      typedef struct {
          uint32_t        mic_num;                // 1 - 2
          uint32_t        ref_num;                // 0 - 1
          uint32_t        out_num;                // 0 - 1
          uint32_t        out_interlaced_num;     // 0: non-interlace,  >0 Interweave the previous out_interlaced_num
      
          uint32_t        frame_length;           // 10ms, 16ms
      
          uint32_t        sample_rate;            // 16000
          uint32_t        sample_width;           // 16, 32
      
          uint32_t        pcm_frame_num_per_context;              // the FRAME num in a CONTEXT
          uint32_t        context_num_per_channel_for_mic_ref;    // the pcm_frame_num_per_context * context_num_per_channel_for_mic_ref frame num in a mic、ref CHANNEL
          uint32_t        context_num_per_channel_for_out;        // the pcm_frame_num_per_context * context_num_per_channel_for_out frame num in a output CHANNEL
      } AUDIO_IO_CONFIG;
      
      typedef struct {
          uint32_t        version;
      
          AUDIO_IO_CONFIG audio_io_config;
      
          // CTX buffer for GLOBAL
          uint32_t        ctx_num;                // Context number
          void           *ctx_buffer;             // Context Buffer header point
          uint32_t        ctx_size;               // Context size
      
          // OUT buffer for CLOBAL
          void           *out_buffer;
          uint32_t        out_buffer_size;        // Bytes
      
          // MIC buffer for GLOBAL
          void           *mic_buffer;
          uint32_t        mic_buffer_size;        // Bytes
      
          // REF buffer for GLOBAL
          void           *ref_buffer;
          uint32_t        ref_buffer_size;        // Bytes
      
          // TX buffer for GLOBAL
          void           *tx_buffer;
          uint32_t        tx_buffer_size;        // Bytes
      } VPA_CONTEXT_HEADER;
      
    • VSP_CONTEXT 则包含每一个单元数据的内容,其定义如下:
      vpa_context.h
      1
      2
      3
      4
      5
      6
      7
      8
      9
      typedef struct {
          VPA_CONTEXT_HEADER *ctx_header;
          uint32_t        frame_index;            // FRAME index of the first frame in CONTEXT
          uint32_t        ctx_index;              // CONTEXT index from 0 - (2^32 - 1)
          uint32_t        vad:3;
          uint32_t        kws:13;
          uint32_t        reserve1:16;
          uint32_t        reserve2;
      } VPA_CONTEXT;
      

    5.1.context 使用注意事项*

    • 为了减少运算开销,context 采用了非常原始和直接的方式存放数据。而 context 中存放的内容与实际的算法实现和产品需求紧密相关。这一定程度上增加了 context 与各模块的耦合性,提高了 vpa 的开发难度,各模块的开发者必须了解其他模块如何处理Context的,并且根据整体的需求添加或删减字段。
    • context 都存放在内存中,而 AE 和 CPU 都是通过 cache 访问内存的,所以在处理新的 context 前要清除 cache ,而处理完 Context 后要将 cache 写回到内存中

6.算力查看*

  • 我们提供了实时查看算力的功能

    • 首先需要在 vpa_init 接口中调用 vpa_top_init(context_header) 初始化 top 功能
    • 其次在需要关注的算法接口前后调用相关接口接口,如下示例,最终 vpa_top_run 会输出对应的算力百分比。
    uint64_t start_us = gx_get_time_us();
    
    ...//关注的算法接口
    
    uint64_t end_us = gx_get_time_us();
    vpa_top_run(start_us, end_us);
    

7.内存分配接口使用*

  • 建议开发者首先使用 vpa_malloc 和 vpa_free 接口,该接口申请的内存是从 psram 中申请。