AEC算法使用参考


1. 概述


1.1. 算法说明

回声消除(Acoustic Echo Cancellation,简称AEC),是一种用于抑制远程回声的功能。

回声常见于会议系统、楼宇对讲、安防监控等场景,当远程声音从喇叭播放出来后,麦克风在非常小的延时后,重新采集喇叭播放出来的声音并传输回远程,这个延迟的原始信号将导致远程听到回声。


1.2. 关键词说明

  • Far-end signal (Sin)

    设备扬声器的信号源,远程传来的信号。

  • Near-end signal (Rin)

    设备麦克风录到的信号,此信号可能包含声学回声(Acoustic echo)和近端语者(Near-end talker)的信号

  • Acoustic echo (AE)

    从设备扬声器播放的声音通过直接或间接路径传回麦克风,产生回声;远程语者在说话后会在延迟某段时间后听到自己的回声,此回声称为声学回声。

  • Single talk

    只有设备端的喇叭播放远程信号(Sin = AE + NS当 NS =0)

  • Double talk

    设备端喇叭播放的同时近端语者也对着麦克风说话(Sin = AE + NS当 NS≠0)


1.3. 注意

为方便调试和确认算法效果,需要用户应用自行实现替换算法参数和抓取音频数据的逻辑。


2. API 参考


2.1. 功能模块API

API名 功能
IaaAec_GetBufferSize 获取Aec算法运行需要的内存大小
IaaAec_Init 初始化Aec算法
IaaAec_Config 配置Aec算法
IaaAec_Reset 重新初始化Aec算法
IaaAec_Free 释放Aec算法资源
IaaAec_Run Aec算法处理
IaaAec_GetLibVersion 获取Aec算法的版本信息
IaaAec_SetRecursiveRatio 配置Aec算法recursive ratio参数
IaaAec_ADF_Run 线性Aec算法处理
IaaAec_NLP_Run 非线性Aec算法处理
IaaAec_GetAPIVersion 获取Aec版本
IaaAec_SetHandleId 配置Aec算法id

2.2. IaaAec_GetBufferSize

  • 功能

    获取Aec算法运行所需要的内存大小。

  • 语法

    unsigned int IaaAec_GetBufferSize(void);
    
  • 形参

    参数名称 描述 输入/输出
  • 返回值

    返回值为Aec算法运行所需要的内存大小

  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

  • 注意

    该接口仅返回需要的内存大小,申请和释放内存的动作需应用来处理。

  • 举例

    无。


2.3. IaaAec_Init

  • 功能

    初始化Aec算法需要的内存。

  • 语法

    AEC_HANDLE IaaAec_Init(char* working_buffer_address, AudioAecInit * aec_init);
    
  • 形参

    参数名称 描述 输入/输出
    working_buffer_address Aec算法使用的内存地址 输入
    aec_init Aec算法的初始化结构体指针 输入
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

  • 注意

    • Aec算法仅支持8K/16K采样率。
  • 举例

    无。


2.4. IaaAec_Config

  • 功能

    配置Aec算法。

  • 语法

    ALGO_AEC_RET IaaAec_Config(AEC_HANDLE handle, AudioAecConfig *aec_config);
    
  • 形参

    参数名称 描述 输入/输出
    handle Aec算法handle 输入
    aec_config Aec算法配置参数的结构体指针 输入
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

  • 注意

    无。

  • 举例

    无。


2.5. IaaAec_Reset

  • 功能

    重新初始化Aec算法的内存。

  • 语法

    AEC_HANDLE IaaAec_Reset(char* working_buffer_address, AudioAecInit * aec_init);
    
  • 形参

    参数名称 描述 输入/输出
    working_buffer_address Aec算法所使用的内存地址 输入
    aec_init Aec算法的初始化结构体指针 输入
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

  • 注意

    • 针对同一个算法handle(同一内存地址)重新初始化Aec算法(即重新设置采样率和近远程的通道数)

    • 重新初始化Aec算法后,需要重新配置

  • 举例

    无。


2.6. IaaAec_Free

  • 功能

    释放Aec算法的资源

  • 语法

    ALGO_AEC_RET IaaAec_Free(AEC_HANDLE handle);
    
  • 形参

    参数名称 描述 输入/输出
    handle Aec算法handle 输入
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

  • 注意

    • 必须先调用IaaAec_Free,再释放供Aec算法所使用的内存。
  • 举例

    无。


2.7. IaaAec_Run

  • 功能

    Aec算法处理

  • 语法

    ALGO_AEC_RET IaaAec_Run(AEC_HANDLE handle, short* pss_audio_near_end, short* pss_audio_far_end);
    
  • 形参

    参数名称 描述 输入/输出
    handle 算法handle 输入
    pss_audio_near_end 近端数据指针 输入/输出
    pss_audio_far_end 远程数据指针 输入
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

  • 注意

    • 近远程的数据量必须和调用IaaAec_Init时设定的point_number(一次IaaAec_Run所需要的采样点数)相对应。

    • AEC后结果由pss_audio_near_end输出

  • 举例

    #include <stdio.h>
    #include <string.h>
    #include <sys/time.h>
    #include <stdlib.h>
    
    #include "AudioAecProcess.h"
    
    /*  0:Fixed input file  1:User input file   */
    #define IN_PARAMETER 1
    
    float AVERAGE_RUN(int a)
    {
        static unsigned int num = 0;
        static float avg = 0;
        if(0 == num)
            avg = 0;
        num++;
        avg = avg + ((float)a - avg) / ((float)num);
        return avg;
    }
    
    unsigned int _OsCounterGetMs(void)
    {
        struct timeval t1;
        gettimeofday(&t1, NULL);
        unsigned int T = ((1000000 * t1.tv_sec) + t1.tv_usec) / 1000;
        return T;
    }
    
    int main(int argc, char *argv[])
    {
        short in_output[1024];
        short input_far[1024];
        char src_file1[128] = {0};
        char src_file2[128] = {0};
        char dst_file[128] = {0};
        unsigned int workingBufferSize;
        char *workingBuffer = NULL;
        int counter = 0;
        ALGO_AEC_RET ret;
        int tempSize;
        unsigned int T0, T1;
        float avg;
        FILE *fpInFar, *fpOut, *fpInNear;
        AudioAecInit aec_init;
        AudioAecConfig aec_config;
        AEC_HANDLE handle;
        /*********************User change section start*******************/
        unsigned int supMode_band[6] = {20,40,60,80,100,120};
        unsigned int supMode[7] = {4,4,4,4,4,4,4};
        aec_init.point_number = 128;
        aec_init.nearend_channel = 1;
        aec_init.farend_channel = 1;
        aec_init.sample_rate = IAA_AEC_SAMPLE_RATE_16000;
        aec_config.delay_sample = 0;
        aec_config.comfort_noise_enable = IAA_AEC_FALSE;
        /*********************User change section end*******************/
        memcpy(&(aec_config.suppression_mode_freq[0]), supMode_band, sizeof(supMode_band));
        memcpy(&(aec_config.suppression_mode_intensity[0]), supMode, sizeof(supMode));
        //(1)IaaAec_GetBufferSize
        workingBufferSize = IaaAec_GetBufferSize();
        workingBuffer = (char*)malloc(workingBufferSize);
        if(NULL == workingBuffer)
        {
            printf("workingBuffer malloc failed !\n");
            return -1;
        }
        printf("workingBuffer malloc success !\n");
        //(2)IaaAec_Init
        handle = IaaAec_Init(workingBuffer, &aec_init);
        if (NULL == handle)
        {
            printf("AEC init failed !\r\n");
            return -1;
        }
        printf("AEC init success !\r\n");
        //(3)IaaAec_Config
        ret = IaaAec_Config(handle, &aec_config);
        if(ret)
        {
            printf("IaaAec_Config failed !\n");
            return -1;
        }
        printf("IaaAec_Config succeed !\n");
    
    #if IN_PARAMETER
        if(argc < 4)
        {
            printf("Please enter the correct parameters!\n");
            return -1;
        }
        sscanf(argv[1], "%s", src_file1);
        sscanf(argv[2], "%s", src_file2);
        sscanf(argv[3], "%s", dst_file);
    #else
        sprintf(src_file1, "%s", "./farend.wav");
        sprintf(src_file2, "%s", "./nearend.wav");
        if(argc < 2)
        {
            printf("Please enter the correct parameters!\n");
            return -1;
        }
        sscanf(argv[1], "%s", dst_file);
    #endif
    
        fpInFar = fopen(src_file1, "rb");
        if(NULL == fpInFar)
        {
            printf("src_file1 open failed !\n");
            return -1;
        }
        printf("src_file1 open succeed !\n");
        fpInNear = fopen(src_file2, "rb");
        if(NULL == fpInNear)
        {
            printf("src_file2 open failed !\n");
            return -1;
        }
        printf("src_file2 open succeed !\n");
        fpOut = fopen(dst_file, "wb");
        if(NULL == fpOut)
        {
            printf("dst_file open failed !\n");
            return -1;
        }
        printf("dst_file open succeed !\n");
    #if 1
        fread(in_output, sizeof(char), 44, fpInNear);  // Remove the 44 bytes header
        fwrite(in_output, sizeof(char), 44, fpOut);  // New file add header
        fread(input_far, sizeof(char), 44, fpInFar); // Remove the 44 bytes header
    #endif
        tempSize = aec_init.point_number * aec_init.nearend_channel;
        while(fread(in_output, sizeof(short), tempSize, fpInNear))
        {
            tempSize = aec_init.point_number * aec_init.farend_channel;
            fread(input_far, sizeof(short), tempSize, fpInFar);
            counter++;
            T0  = (long)_OsCounterGetMs();
            //(4)IaaAec_Run
            ret = IaaAec_Run(handle, in_output, input_far);
            T1  = (long)_OsCounterGetMs();
            avg = AVERAGE_RUN(T1 - T0);
            if(0 == counter % 100)
            {
                printf("counter = %d\n", counter);
                printf("current time = %f\n", (float)counter * aec_init.point_number / aec_init.sample_rate);
                printf("process time = %lu(ms)\t", (long)(T1 - T0));
                printf("AVG is %.2f ms\n", avg);
            }
            if(ret < 0)
            {
                printf("IaaAec_Run failed !\n");
                break;
            }
            tempSize = aec_init.point_number * aec_init.nearend_channel;
            fwrite(in_output, sizeof(short), tempSize, fpOut);
        }
        //(5)IaaAec_Free
        IaaAec_Free(handle);
        fclose(fpInFar);
        fclose(fpInNear);
        fclose(fpOut);
        free(workingBuffer);
        printf("AEC end !\n");
    
        return 0;
    }
    

2.8. IaaAec_GetLibVersion

  • 功能

    获取Aec算法的版本信息

  • 语法

    ALGO_AEC_RET IaaAec_GetLibVersion(unsigned short *ver_year,
                        unsigned short *ver_date,
                        unsigned short *ver_time);
    
  • 形参

    参数名称 描述 输入/输出
    ver_year Aec算法库文件编译时的年份 输出
    ver_date Aec算法库文件编译时的日期 输出
    ver_time Aec算法库文件编译时的时间 输出
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

  • 注意

    无。

  • 举例

    无。


2.9. IaaAec_SetRecursiveRatio

  • 功能

    配置Aec算法recursive ratio参数。

  • 语法

    ALGO_AEC_RET IaaAec_SetRecursiveRatio(AEC_HANDLE handle, unsigned int recursive_ratio);
    
  • 形参

    参数名称 描述 输入/输出
    handle Aec算法handle 输入
    recursive_ratio 递归参数输入。参数设置越大,递归更新讯号速度越快,在Double talk切换到Single talk后,减少回声过度消除近端讯号情形,相对回声消除平顺度降低,背景残留回声不连续性提高。
    取值范围:1 ~ 10
    输入
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a


2.10. IaaAec_ADF_Run

  • 功能

    线性Aec算法处理

  • 语法

    ALGO_AEC_RET IaaAec_ADF_Run(AEC_HANDLE handle, short* pss_audio_near_end, short* pss_audio_far_end);
    
  • 形参

    参数名称 描述 输入/输出
    handle 算法handle 输入
    pss_audio_near_end 近端数据指针 输入/输出
    pss_audio_far_end 远程数据指针 输入
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

  • 注意

    • 近远程的数据量必须和调用IaaAec_Init时设定的point_number(一次IaaAec_ADF_Run所需要的采样点数)相对应。

    • AEC后结果由pss_audio_near_end输出

    • 若输入为双声道,请自行将其依据左右左右排列分割成左声道与右声道两个数组。

  • 举例

    无。


2.11. IaaAec_NLP_Run

  • 功能

    非线性Aec算法处理

  • 语法

    ALGO_AEC_RET IaaAec_NLP_Run(AEC_HANDLE handle, short* pss_audio_near_end);
    
  • 形参

    参数名称 描述 输入/输出
    handle 算法handle 输入
    pss_audio_near_end 近端数据指针 输入/输出
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

  • 注意

    • 近远程的数据量必须和调用IaaAec_Init时设定的point_number(一次IaaAec_NLP_Run所需要的采样点数)相对应。

    • AEC后结果由pss_audio_near_end输出

    • 若输入为双声道,请自行将其依据左右左右排列分割成左声道与右声道两个数组。

  • 举例

    无。


2.12. IaaAec_GetAPIVersion

  • 功能

    获取AEC版本

  • 语法

    ALGO_AEC_RET IaaAec_GetAPIVersion(unsigned short* major, unsigned short* minor);
    
  • 形参

    参数名称 描述 输入/输出
    major 算法版本 输出
    minor 算法版本 输出
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参照错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    - 库文件: libAEC_LINUX.so/ libAEC_LINUX.a

    2.13. IaaAec_SetHandleId

  • 功能

    配置Aec算法id

  • 语法

    ALGO_AEC_RET IaaAec_SetHandleId(AEC_HANDLE handle, int id);
    
  • 形参

    参数名称 描述 输入/输出
    handle Aec算法handle 输入
    id Aec算法handle id
    数值范围:[0,100]
    输入
  • 返回值

    返回值 结果
    0 成功
    非0 失败,参考错误码
  • 依赖

    • 头文件: AudioAecProcess.h

    • 库文件: libAEC_LINUX.so/ libAEC_LINUX.a


3. AEC 数据类型


3.1. AEC模块相关数据类型定义

数据类型 定义
AudioAecInit Aec算法初始化参数数据结构体类型
AudioAecConfig Aec算法配置参数数据结构体类型
IAA_AEC_SAMPLE_RATE Aec算法支持的采样率类型
IAA_AEC_BOOL Aec算法的布尔类型
AEC_HANDLE Aec算法句柄类型

3.2. AudioAecInit

  • 说明

    定义Aec算法的初始化参数结构体。

  • 定义

    typedef struct
    
    {
    
        unsigned int point_number;
    
        unsigned int nearend_channel;
    
        unsigned int farend_channel;
    
        IAA_AEC_SAMPLE_RATE sample_rate;
    
    }AudioAecInit;
    
  • 成员

    成员名称 描述
    point_number Aec算法处理一次的采样点数,预设可支持为128亦可透过下面指令确认可支持的处理点数
    strings libAEC_LINUX.so | grep PN
    nearend_channel 近端的信道数(即麦克风的信道数),可设定单声道或双声道
    范围:[1, 2]
    farend_channel 远端的信道数(即喇叭的通道数),可设定单声道或双声道
    范围:[1, 2]
    sample_rate Aec算法处理的采样率
  • 注意事项

    • 若使用的输入为双声道,请依照左右声道交错排列的方式给讯号,例如:左/右/左/右...

    • IaaAec_Run读取近端数据指针的长度为 $point_number \times nearend_channel \times 2 $ bytes

      读取远端数据指针的长度为

      $point_number \times farend_channel \times 2 $ bytes

    • 若设定为双声道,AEC会把两个声道平均为一个声道处理,并将处理后结果复制到两个声道,如需两声道分开,请初始化两个handle,分开执行IaaApc_Run

  • 相关数据类型及接口

    IaaAec_InitIaaAec_Reset


3.3. AudioAecConfig

  • 说明

    定义Aec算法的配置参数结构体。

  • 定义

    typedef struct
    
    {
    
        IAA_AEC_BOOL comfort_noise_enable;
    
        short delay_sample;
    
        unsigned int suppression_mode_freq[6];
    
        unsigned int suppression_mode_intensity[7];
    
    }AudioAecConfig;
    
  • 成员

    成员名称 描述
    comfort_noise_enable 在AEC后加入舒适噪声,避免回声消除后部分段落静音,让用户有断线的感觉。 若AEC后有开启Noise reduction,建议开启comfort noise,帮助NR收敛噪声。
    delay_sample 左右声道间的回声延迟样本 当Stereo cost down 开启时,可以根据麦克风摆放位置先行设定样本延迟数,减少双声道所需运算量。 默认为0,单位是sample数 取值范围:-9-9
    suppression_mode_freq AEC处理的频段划分,将不同取样率所能解析的最高频率等分成128等分,设定六个值可切出七个段落设定不同AEC强度。 取值范围:1-127
    suppression_mode_intensity 配合suppression_mode_freq所切割出来的七个段落分别设定七种强度。 取值范围:0-25
  • 注意事项

    • comfort_noise_enable

      使能该参数后,AEC算法会在没有声音时添加一些舒适噪声。当客户对稳定的底噪有要求或AEC消得太干净导致NR收敛时间太长时,可使能该功能。

    • delay_sample

      由于麦克风和喇叭的放置位置、麦克风间的距离等会造成左右声道接收到回声的时间点不一致,两个声道之间回声延迟有差异。此值表示左声道比右声道提早多少个采样点收到回声。可以调整此值来对齐左右声道的资料,设置时,请以左声道作为参考。例如左声道的回声比右声道的回声快4个样本,则s16DelaySample设置为4,反之设置为-4。

    • suppression_mode_freq

      该数组将当前采样率的最高频率分成7个频段来处理。以下数组元素和频率范围的转换公式:

      residual\ Echo\ Frequency\ Range/(sampleRate/2) \times pointNumber(128)

      数组要求每一个元素都必须比前一个元素大。

    • suppression_mode_intensity

      该数组代表各个频段的回声消除强度,各个元素与u32AecSupfreq划分出来的7个频段一一对应。强度越大,消除的细节越多,声音越不自然。

  • 相关数据类型及接口

    IaaAec_Config


3.4. IAA_AEC_SAMPLE_RATE

  • 说明

    AEC算法支持的采样率类型。

  • 定义

    typedef enum
    
    {
    
        IAA_AEC_SAMPLE_RATE_8000 = 8000 ,
    
        IAA_AEC_SAMPLE_RATE_16000 = 16000 ,
    
        IAA_AEC_SAMPLE_RATE_32000 = 32000 ,
    
        IAA_AEC_SAMPLE_RATE_48000 = 48000 ,
    
    }IAA_AEC_SAMPLE_RATE;
    
  • 成员

    成员名称 描述
    IAA_AEC_SAMPLE_RATE_8000 8K采样率
    IAA_AEC_SAMPLE_RATE_16000 16K采样率
    IAA_AEC_SAMPLE_RATE_32000 32K采样率
    IAA_AEC_SAMPLE_RATE_48000 48K采样率
  • 注意事项

  • 相关数据类型及接口

    AudioAecInit


3.5. IAA_AEC_BOOL

  • 说明

    定义Aec算法内部使用的布尔类型。

  • 定义

    typedef enum
    
    {
    
        IAA_AEC_TRUE = 1,
    
        IAA_AEC_FALSE = 0,
    
    }IAA_AEC_BOOL;
    
  • 成员

    成员名称 描述
    IAA_AEC_TRUE True
    IAA_AEC_FALSE False
  • 注意事项

  • 相关数据类型及接口

    AudioAecConfig


3.6. AEC_HANDLE


4. 错误码

AEC API 错误码如表下所示:

错误码 宏定义 描述
0x00000000 ALGO_AEC_RET_SUCCESS AEC运行成功
0x10000401 ALGO_AEC_RET_INIT_ERROR AEC尚未初始化
0x10000402 ALGO_AEC_RET_INVALID_HANDLE HANDLE无效
0x10000403 ALGO_AEC_RET_INVALID_SAMPLE_RATE 取样频率不支持
0x10000404 ALGO_AEC_RET_INVALID_POINT_NUMBER 每帧点数不支持
0x10000405 ALGO_AEC_RET_INVALID_CHANNEL 通道数不支持
0x10000406 ALGO_AEC_RET_INVALID_ENABLE 呼叫不支持
0x10000407 ALGO_AEC_RET_INVALID_SUP_BAND AEC抑制频带参数设置无效
0x10000408 ALGO_AEC_RET_INVALID_SUP_MODE AEC抑制强度参数设置无效
0x10000409 ALGO_AEC_RET_INVALID_COMFORT_NOISE AEC舒适噪声参数设置无效
0x10000410 ALGO_AEC_RET_INVALID_DELAY_SAMPLE AEC延迟样本数设置无效
0x10000411 ALGO_AEC_RET_INVALID_RECURSIVE_RATIO AEC递归参数设置无效
0x10000412 ALGO_AEC_RET_API_CONFLICT 其他API正在运行
0x10000413 ALGO_AEC_RET_INVALID_CALLING 呼叫API顺序错误