本文分享基于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回采数据,实践中可以实时获取对应的音频采集数据进行实时处理。
全部0条评论
快来发表一下你的评论吧 !