硬核实战 | Cortex-M85平台Speex回声消除与降噪算法移植指南

描述

本文分享基于Titan Mini Board移植speex音频处理前端算法。实现AEC回声消除功能测试,分享应用Demo。

目录

 


 

实现shell和文件传输


 

移植speex,实现降噪和回声消除测试应用


 

测试


 

总结


 

获取同款硬件

1 实现shell和文件传输


 

基于FIFO的串口驱动,移植xprintf进行标准输入输出处理,移植shell,移植xmodem实现文件传输,相关文件如下, 移植参考公众号文章和源码,这里不再赘述

降噪

2 移植speex,实现降噪和回声消除测试应用


 

参考公众号系列文章

基于speex的音频处理系列之一-speex在pc上的移植与回声消除测试

基于speex的音频处理系列之二-降噪效果测试

基于speex的音频处理系列之三-在嵌入式平台的移植

基于speex的音频处理系列之四:VAD功能

基于speex的音频处理系列之五:AGC功能

参考https://github.com/Guineverqiu/speex_test.git

准备代码

git clone https://github.com/Guineverqiu/speex_test.git

添加以下文件

降噪

项目属性中设置头文件包含路径

降噪

添加宏

降噪

设置优化-O3

-fsingle-precision-constant

降噪

其中

os_support_custom.h

speexecho.c需要适配

os_support_custom.h如下

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  



#ifndef OS_SUPPORT_CUSTOM_H#define OS_SUPPORT_CUSTOM_H#include #include "xprintf.h"#define OVERRIDE_SPEEX_ALLOC#define OVERRIDE_SPEEX_ALLOC_SCRATCH#define OVERRIDE_SPEEX_REALLOC#define OVERRIDE_SPEEX_FREE#define OVERRIDE_SPEEX_FREE_SCRATCHstatic size_t s_max_used = 0;static size_t s_cur_used = 0;static inline void *speex_alloc(int size){    void* p = rt_malloc(size+4);    memset(p,0,size);    *(uint32_t*)p = size;  /* 记录分配的大小 用于调试使用*/    s_cur_used += size;    if(s_max_used < s_cur_used){        s_max_used = s_cur_used;    }    //xprintf("malloc%u,cur:%u\r\n",s_max_used,s_cur_used);    return (uint8_t*)p+4;}static inline void *speex_alloc_scratch(int size){   /* Scratch space doesn't need to be cleared */    void* p = rt_malloc(size+4);  /* freertos malloc默认配置是8字节对齐,所以预留的4字节强制类型转换不会有对齐问题 */    *(uint32_t*)p = size;  /* 记录分配的大小 用于调试使用*/    s_cur_used += size;    if(s_max_used < s_cur_used){        s_max_used = s_cur_used;    }    //xprintf("malloc%u,cur:%u\r\n",s_max_used,s_cur_used);    return (uint8_t*)p+4;}static inline void speex_free(void *ptr){    rt_free((uint8_t*)ptr-4);    s_cur_used -= *(uint32_t*)((uint8_t*)ptr-4);    //xprintf("free%u,cur:%u\r\n",s_max_used,s_cur_used);}static inline void *speex_realloc(void *ptr, int size){    uint32_t len;    /* 原来为空直接申请 */    if(ptr == (void*)0){        return speex_alloc(size);    }    /* size为0则直接释放 */    if(size == 0){        speex_free(ptr);        return (void*)0;    }    /* 申请新的大小 */    void* p = rt_malloc(size+4);    *(uint32_t*)p = size;  /* 记录分配的大小 */    s_cur_used += size;    if(s_max_used < s_cur_used){        s_max_used = s_cur_used;    }    len = *(uint32_t*)((uint8_t*)ptr-4);    if(len > (uint32_t)size){        len = (uint32_t)size;  /* 注意可能放大也可能缩小 */    }    /* 复制的数据 */    memcpy((uint8_t*)p+4,ptr,len);    /* 释放原来的 */    rt_free((uint8_t*)ptr-4);    s_cur_used -= len;    //xprintf("realloc%u,cur:%u\r\n",s_max_used,s_cur_used);    return (uint8_t*)p+4;}static inline void speex_free_scratch(void *ptr){    rt_free((uint8_t*)ptr-4);    s_cur_used -= *(uint32_t*)((uint8_t*)ptr-4);    //xprintf("free%u,cur:%u\r\n",s_max_used,s_cur_used);}#define OVERRIDE_SPEEX_COPY#define OVERRIDE_SPEEX_MOVE#define OVERRIDE_SPEEX_MEMSET#define SPEEX_COPY(dst, src, n) (memcpy((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) ))#define SPEEX_MOVE(dst, src, n) (memmove((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) ))#define SPEEX_MEMSET(dst, c, n) (memset((dst), (c), (n)*sizeof(*(dst))))#define OVERRIDE_SPEEX_FATALstatic inline void _speex_fatal(constchar *str, constchar *file, int line){   xprintf("Fatal (internal) error in %s, line %d: %s\n", file, line, str);   while(1);}#define OVERRIDE_SPEEX_WARNINGstatic inline void speex_warning(constchar *str){#ifndef DISABLE_WARNINGS   xprintf ("warning: %s\n", str);#endif}#define OVERRIDE_SPEEX_WARNING_INTstatic inline void speex_warning_int(constchar *str, int val){#ifndef DISABLE_WARNINGS   xprintf ("warning: %s %d\n", str, val);#endif}#define OVERRIDE_SPEEX_NOTIFYstatic inline void speex_notify(constchar *str){#ifndef DISABLE_NOTIFICATIONS   xprintf ("notification: %s\n", str);#endif}#define OVERRIDE_SPEEX_PUTCstatic inline void _speex_putc(int ch, void *file){   (void)file;   xprintf("%c", ch);}#define speex_fatal(str) _speex_fatal(str, __FILE__, __LINE__);#define speex_assert(cond) {if (!(cond)) {speex_fatal("assertion failed: " #cond);}}#ifndef RELEASEstatic inline void print_vec(float *vec, int len, char *name){   int i;   xprintf ("%s ", name);   for (i=0;i      xprintf (" %f", vec[i]);   xprintf ("\n");}#endif#endif

 

speexecho.c如下

  •  
  •  
  •  
  •  
  •  

#ifdef HAVE_CONFIG_H#include "config.h"#endif#include #include "speex/speex_echo.h"#include "speex/speex_preprocess.h"#include #include #include #include "xprintf.h"static uint32_tget_tm_ms(void){    return rt_tick_get()*1000/RT_TICK_PER_SECOND;}/* WAV解析 */#define CHUNK_RIFF "RIFF"#define CHUNK_WAVE "WAVE"#define CHUNK_FMT "fmt "#define CHUNK_DATA "data"typedef struct{    uint32_t off;    uint32_t chunksize;    uint16_t audioformat;    uint16_t numchannels;    uint32_t samplerate;    uint32_t byterate;    uint16_t blockalign;    uint16_t bitspersample;    uint32_t datasize;}wav_t;static int wav_decode_head(uint8_t* buffer, wav_t* wav){    uint8_t* p = buffer;    uint32_t chunksize;    uint32_t subchunksize;    if(0 != memcmp(p,CHUNK_RIFF,4))    {        return -1;;    }    p += 4;    chunksize = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);    wav->chunksize = chunksize;    p += 4;    if(0 != memcmp(p,CHUNK_WAVE,4))    {        return -2;    }    p += 4;    do    {        if(0 == memcmp(p,CHUNK_FMT,4))        {            p += 4;            subchunksize = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);            p += 4;            /* 解析参数 */            wav->audioformat = (uint16_t)p[0] | ((uint16_t)p[1]<<8);            if((wav->audioformat == 0x0001) || (wav->audioformat == 0xFFFE))            {                p += 2;                wav->numchannels = (uint16_t)p[0] | ((uint16_t)p[1]<<8);                p += 2;                wav->samplerate = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);                p += 4;                wav->byterate = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);                p += 4;                wav->blockalign = (uint16_t)p[0] | ((uint16_t)p[1]<<8);                p += 2;                wav ->bitspersample = (uint16_t)p[0] | ((uint16_t)p[1]<<8);                p += 2;                if(subchunksize >16)                {                    /* 有ext区域 */                    uint16_t cbsize = (uint16_t)p[0] | ((uint16_t)p[1]<<8);                    p += 2;                    if(cbsize > 0)                    {                        /* ext数据 2字节有效bits wValidBitsPerSample ,4字节dwChannelMask 16字节SubFormat */                        p += 2;                        p += 4;                        /* 比对subformat */                        p += 16;                           }                }            }            else            {                p += subchunksize;            }        }        else if(0 == memcmp(p,CHUNK_DATA,4))        {            p += 4;            subchunksize = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);            wav->datasize = subchunksize;            p += 4;            wav->off = (uint32_t)(p- buffer);            return 0;        }        else        {            p += 4;            subchunksize = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);            p += 4;            p += subchunksize;        }    }while((uint32_t)(p - buffer) < (chunksize + 8));    return -3;}/* 填充44字节的wav头 */static void wav_fill_head(uint8_t* buffer, int samples, int chnum, int freq){    /*     * 添加wav头信息     */    uint32_t chunksize = 44-8+samples*chnum*16/8;    uint8_t* p = (uint8_t*)buffer;    uint32_t bps = freq*chnum*16/8;    uint32_t datalen = samples*chnum*16/8;    p[0] = 'R';    p[1] = 'I';    p[2] = 'F';    p[3] = 'F';    p[4] = chunksize & 0xFF;    p[5] = (chunksize>>8) & 0xFF;    p[6] = (chunksize>>16) & 0xFF;    p[7] = (chunksize>>24) & 0xFF;    p[8] = 'W';    p[9] = 'A';    p[10] = 'V';    p[11] = 'E';    p[12] = 'f';    p[13] = 'm';    p[14] = 't';    p[15] = ' ';    p[16] = 16;  /* Subchunk1Size */    p[17] = 0;    p[18] = 0;    p[19] = 0;    p[20] = 1;  /* PCM */    p[21] = 0;    p[22] = chnum; /* 通道数 */    p[23] = 0;    p[24] = freq & 0xFF;    p[25] = (freq>>8) & 0xFF;    p[26] = (freq>>16) & 0xFF;    p[27] = (freq>>24) & 0xFF;     p[28] = bps & 0xFF;      /* ByteRate */    p[29] = (bps>>8) & 0xFF;    p[30] = (bps>>16) & 0xFF;    p[31] = (bps>>24) & 0xFF;     p[32] = chnum*16/8; /* BlockAlign */    p[33] = 0;    p[34] = 16;  /* BitsPerSample */    p[35] = 0;    p[36] = 'd';    p[37] = 'a';    p[38] = 't';    p[39] = 'a';    p[40] = datalen & 0xFF;    p[41] = (datalen>>8) & 0xFF;    p[42] = (datalen>>16) & 0xFF;    p[43] = (datalen>>24) & 0xFF; }void wav_print(wav_t* wav){   xprintf("off:%d\r\n",wav->off);   xprintf("chunksize:%d\r\n",wav->chunksize);   xprintf("audioformat:%d\r\n",wav->audioformat);   xprintf("numchannels:%d\r\n",wav->numchannels);   xprintf("samplerate:%d\r\n",wav->samplerate);   xprintf("byterate:%d\r\n",wav->byterate);   xprintf("blockalign:%d\r\n",wav->blockalign);   xprintf("bitspersample:%d\r\n",wav->bitspersample);   xprintf("datasize:%d\r\n",wav->datasize);}#define NN 128#define TAIL 1024int speexecho(int argc, char **argv){   FILE *spk_fd, *mic_fd, *out_fd;      short spk_buf[NN], mic_buf[NN], out_buf[NN];   uint8_t spk_wav_buf[44]; /* 输入spk wav文件头缓存 */   uint8_t mic_wav_buf[44]; /* 输入mic wav文件头缓存 */   uint8_t out_wav_buf[44]; /* 输出文件wav头缓存 */   wav_t spk_wav;   wav_t mic_wav;   int samps;  /* 采样点数 */   int times;    /* 读取次数 */   SpeexEchoState *st;   SpeexPreprocessState *den;   int sampleRate;   char* mic_fname = argv[1];   char* spk_fname = argv[2];   char* out_fname = argv[3];   int ctl_i;   float ctl_f;   if (argc != 4)   {      xprintf("testecho mic.wav spk.wav out.wav\n");      return -1;   }   spk_fd = fopen(spk_fname, "rb");   if(spk_fd == NULL){      xprintf("open file %s err\n",spk_fname);      return -1;   }else{      xprintf("open file %s ok\n",spk_fname);   }   mic_fd  = fopen(mic_fname,  "rb");   if(mic_fd == NULL){      xprintf("open file %s err\n",mic_fname);      fclose(spk_fd);      return -1;   }else{      xprintf("open file %s ok\n",mic_fname);   }   out_fd    = fopen(out_fname, "wb");   if(out_fd == NULL){      xprintf("open file %s err\n",out_fname);      fclose(spk_fd);      fclose(mic_fd);      return-1;   }else{      xprintf("open file %s ok\n",out_fname);   }   if(44 != fread(mic_wav_buf, 1, 44, mic_fd)){      xprintf("read file %s err\n",mic_fname);      fclose(spk_fd);      fclose(mic_fd);      fclose(out_fd);      return-1;   }else{      xprintf("read file %s ok\n",mic_fname);   }   if(44 != fread(spk_wav_buf, 1, 44, spk_fd)){      xprintf("read file %s err\n",spk_fname);      fclose(spk_fd);      fclose(mic_fd);      fclose(out_fd);      return-1;   }else{      xprintf("read file %s ok\n",spk_fname);   }   if(0 != wav_decode_head(spk_wav_buf, &spk_wav)){      xprintf("decode file %s err\n",spk_fname);      fclose(spk_fd);      fclose(mic_fd);      fclose(out_fd);      return -1;   }else{      xprintf("decode file %s ok\n",spk_fname);   }   xprintf("[spk_wav]\r\n");   wav_print(&spk_wav);   if(0 != wav_decode_head(mic_wav_buf, &mic_wav)){      xprintf("decode file %s err\n",mic_fname);      fclose(spk_fd);      fclose(mic_fd);      fclose(out_fd);      return -1;   }else{      xprintf("decode file %s ok\n",mic_fname);   }   xprintf("[mic_wav]\r\n");   wav_print(&mic_wav);
   samps = spk_wav.datasize > mic_wav.datasize ? mic_wav.datasize : spk_wav.datasize; /* 获取较小的数据大小 */   samps /= spk_wav.blockalign;  /* 采样点数 =  数据大小 除以 blockalign */   xprintf("\r\nsamps:%d\r\n",samps);
   sampleRate = spk_wav.samplerate;
   wav_fill_head(out_wav_buf, samps, 1, sampleRate);  /* 输出文件头 */   if(44 != fwrite(out_wav_buf, 1, 44, out_fd)){      xprintf("write file %s err\n",out_fname);      fclose(spk_fd);      fclose(mic_fd);      fclose(out_fd);      return -1;   }
   st = speex_echo_state_init(NN, TAIL);   den = speex_preprocess_state_init(NN, sampleRate);   speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate);   ctl_i=1;   speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_DENOISE, &ctl_i); /* 打开降噪 ctl_i=1打开 0关闭*/   ctl_i=80;   speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &ctl_i);    ctl_i=80;   speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS, &ctl_i);   times = samps / NN;   /* 一次读取NN个点,读取times次 */   for(int i=0; i   {      if(NN != fread(mic_buf, sizeof(short), NN, mic_fd)){        xprintf("read file %s err\n",mic_fname);        fclose(spk_fd);        fclose(mic_fd);        fclose(out_fd);        return -1;      }      if(NN != fread(spk_buf, sizeof(short), NN, spk_fd)){        xprintf("read file %s err\n",spk_fname);        fclose(spk_fd);        fclose(mic_fd);        fclose(out_fd);        return -1;      }      uint32_t t0;      uint32_t t1;    t0 = get_tm_ms();      speex_echo_cancellation(st, mic_buf, spk_buf, out_buf);      speex_preprocess_run(den, out_buf);      t1 = get_tm_ms();      xprintf("used:%dmS\r\n",(t1-t0));      if(NN != fwrite(out_buf, sizeof(short), NN, out_fd)){        xprintf("write file %s err\n",out_fname);        fclose(spk_fd);        fclose(mic_fd);        fclose(out_fd);        return -1;      }   }   speex_echo_state_destroy(st);   speex_preprocess_state_destroy(den);   fclose(out_fd);   fclose(spk_fd);   fclose(mic_fd);   return0;}

 

编译错误 error: 'M_PI' undeclared (first use in this function)

src\speex_test\libspeexdsp\math_approx.h下添加

  •  
  •  
  •  
  •  

#ifndef M_PI#define M_PI 3.14159265358979323846#endif#define spx_cos_norm(x) (spx_cos((.5f*M_PI)*(x)))

shell_func.c中添加命令行

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

static void speexechofunc(uint8_t* param);{ (uint8_t*)"speexecho", speexechofunc, (uint8_t*)"speexecho mic spk out"},extern int speexecho(int argc, char **argv);static void speexechofunc(uint8_t * cmdbuf){int len;char mic[32];char spk[32];char out[32];char *argv[4];argv[0]="speexecho";argv[1]=mic;argv[2]=spk;argv[3]=out;memset((void *)mic, 0, sizeof(mic));memset((void *)spk, 0, sizeof(spk));memset((void *)out, 0, sizeof(out));len = sscanf((charconst *)cmdbuf, "%*s %31s %31s %31s", mic,spk,out);if (len == 3){speexecho(4, argv);}else{}}

3 测试


 

将mic3.wav spk3.wav通过我们的shell,xmodem传输到开发板

核心代码如下

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

初始化配置,降噪,回声消除st = speex_echo_state_init(NN, TAIL);den = speex_preprocess_state_init(NN, sampleRate);speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate);ctl_i=1;speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_DENOISE, &ctl_i); /* 打开降噪 ctl_i=1打开 0关闭*/ctl_i=80;speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &ctl_i);ctl_i=80;speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS, &ctl_i);处理speex_echo_cancellation(st, mic_buf, spk_buf, out_buf);speex_preprocess_run(den, out_buf);

降噪

执行异常,设置栈大一点

rtconfig.h中#define RT_MAIN_THREAD_STACK_SIZE 4096

测试speexecho /sdcard/mic3.wav /sdcard/spk3.wav /sdcard/out3.wav

打印信息和执行时间如下

降噪降噪

查看生成的文件out3.wav

降噪

导出out3.wav

降噪

使用Audacity,查看波形,查看回声消除和降噪效果

降噪

4 总结


 

以上基于TitanMiniBoard移植speex音频处理前端算法。实现AEC回声消除功能测试,分享应用Demo。以上测试是从文件获取mic和spk回采数据,实践中可以实时获取对应的音频采集数据进行实时处理。

 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分