简介

使用开源音频处理库speexDSP里的3A算法,在STM32上实现实时的ANS(音频噪声抑制);
该文章用于记录本人在调试过程中踩过的坑,和一些对于性能有限的嵌入式系统算法优化的方案

优化内容

站内有speexDSP库移植stm32的教程,这里就不重复了

1 初始化

    SpeexPreprocessState *st;   
    st = speex_preprocess_state_init(REC_SAI_RX_DMA_BUF_SIZE / 4, REC_SAMPLERATE);

初始化函数第一个参数为一次输入的数据量,第二个参数为采样率

static inline void *speex_alloc (int size)
{
   /* WARNING: this is not equivalent to malloc(). If you want to use malloc()
      or your own allocator, YOU NEED TO CLEAR THE MEMORY ALLOCATED. Otherwise
      you will experience strange bugs */
   // return calloc(size,1);
   return mymalloc(SRAMIN, size);
}

初始化部分会申请很多内存,这里可以修改为你自己的malloc函数,我这里用的正点原子的

2 修改滤波器参数

    i=-40;
    speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &i);

这段代码仅演示修改降噪深度的方法,还有很多其他参数可以定位到函数第二个参数的宏定义处

3 修改线性刻度到bark刻度

这个的目的是牺牲一点降噪效果换取稍快一点的处理速度,如果你的系统资源并不吃紧则没必要做

   /* Compute a posteriori SNR */
   for (i=N;i<N+M;i++)           //修改了起始位置,从i=0改成i=N,直接改成了只计算bark域的值

   /* Recursive average of the a priori SNR. A bit smoothed for the psd components */
   /*屏蔽了线性频域的更新,只更新bark域的值*/
   // st->zeta[0] = PSHR32(ADD32(MULT16_16(QCONST16(.7f,15),st->zeta[0]), MULT16_16(QCONST16(.3f,15),st->prior[0])),15);
   // for (i=1;i<N-1;i++)
   //    st->zeta[i] = PSHR32(ADD32(ADD32(ADD32(MULT16_16(QCONST16(.7f,15),st->zeta[i]), MULT16_16(QCONST16(.15f,15),st->prior[i])),
   //                         MULT16_16(QCONST16(.075f,15),st->prior[i-1])), MULT16_16(QCONST16(.075f,15),st->prior[i+1])),15);
   for (i=N-1;i<N+M;i++)
      st->zeta[i] = PSHR32(ADD32(MULT16_16(QCONST16(.7f,15),st->zeta[i]), MULT16_16(QCONST16(.3f,15),st->prior[i])),15);

   /* Convert the EM gains and speech prob to linear frequency */
   // filterbank_compute_psd16(st->bank,st->gain2+N, st->gain2);
   // filterbank_compute_psd16(st->bank,st->gain+N, st->gain);

   /* Use 1 for linear gain resolution (best) or 0 for Bark gain resolution (faster) */
   /*默认使用线性域计算增益,本人手动改成bark域*/
   if (0)

以上使用//注释掉的为原来的代码,注释掉这些部分意为屏蔽线性刻度的部分运算,能在一定程度提高速率

4 运算优化

从结果上来说,这部分几乎没啥用处,但还是记录一下

   // for (i=0;i<N+M;i++)
   //    st->echo_noise[i] = 0;
   memset(&st->echo_noise[0], 0, (N+M)*sizeof(float));

   for (i=0;i<2*N;i+=4){
      st->frame[i]     = MULT16_16_Q15(st->frame[i]    , st->window[i]);
      st->frame[i + 1] = MULT16_16_Q15(st->frame[i + 1], st->window[i + 1]);
      st->frame[i + 2] = MULT16_16_Q15(st->frame[i + 2], st->window[i + 2]);
      st->frame[i + 3] = MULT16_16_Q15(st->frame[i + 3], st->window[i + 3]);
   }

快速赋值和循环展开,理论上确实比一个一个处理效率要高,但如果你CPU资源并不吃紧也没必要这么大费周章改代码(但听说arm_math做运算喜欢这么搞)

5 替换speex数学运算函数为CMSIS-DSP库函数

这部分从博主使用的硬件来说对性能提升也几乎没有,反而导致了语音信号的失真,个人推测与博主本人前面做的基于CMSIS-DSP库的频域滤波出现的问题是差不多的
【STM32学习笔记】基于频域FFT滤波的音频均衡器方案验证与效果展示

这里还是贴上代码,其中st->ft的内存需要加大一倍,因为arm_cfft输入要求实部0、虚部0、实部1、虚部1这样存储嘛

   st->frame = (spx_word16_t*)speex_alloc(2*N*sizeof(spx_word16_t));
   st->window = (spx_word16_t*)speex_alloc(2*N*sizeof(spx_word16_t));
   st->ft = (spx_word16_t*)speex_alloc(4*N*sizeof(spx_word16_t));
   
   /* Perform FFT */
   spx_fft(st->fft_lookup, st->frame, st->ft);
   // for(i=0;i<2*N;i++)
   // {
   //    st->ft[2*i]   = st->frame[i] / N / 2;
   //    st->ft[2*i+1] = 0.0f;
   // }
   // arm_cfft_f32(&arm_cfft_sR_f32_len1024, st->ft, 0, 1); //单次输入音频数据量*2作为FFT点数

6 malloc内存管理

这里博主踩了个坑,特此记录一下

   return mymalloc(SRAMIN, size); 
   // return mymalloc(SRAMEX, size);

因为一开始设的buffer长度太大,导致片内的内存不够,于是博主为speex的数据申请了外部SDRAM的内存,结果运算速度慢的出奇,才有了上面一系列的优化方案

所以,尽量不要给你需要频繁存取的数据申请片外的存储空间,速度会很慢

资源共享

代码 https://gitee.com/creator-len/study_speex
效果展示

古法语音去噪ANS,speexDSP+stm32实现

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐