[STM32N6] 【STM32N6570-DK测评】基于RNNoise的降噪技术

[复制链接]
 楼主| xhackerustc 发表于 2025-5-3 16:09 | 显示全部楼层 |阅读模式
<
本帖最后由 xhackerustc 于 2025-5-3 16:33 编辑

#申请原创# @21小跑堂

STM32N6系列集成有MDF(Multi-function digital filter),然后阅读原理图发现STM32N6570-DK又板载了一颗MEMS芯片:MP23DB01HP,
2.jpg 3.jpg
这款MEMS芯片是ST家的传感器产品之一,两者配合就可以做到录音功能了。

事实上STM32Cube_FW_N6中的一个测试用例(Projects/STM32N6570-DK/Examples/MDF/MDF_AudioRecorder)展示了录音的效果。其中MDF的滤波器配置结构体MdfFilterconfig可配置各种参数比如放大系数、各种滤波参数等等,也就说STM32N6硬件上已经做了大部分的降噪处理,可见MDF非常强大,如果对去噪声要求不是极高,当下的代码已经直接能用了。

笔者使用这个测试用例测试语音时发现如果同时有人规则地敲桌子、拍球,或者把朝大马路上的窗户开启汽车鸣笛,这些规则的背景噪声还能听到,如果此时要求更高把这些规则背景噪声去除,这时候就要上一些软件去噪方案了。这块开源的方案不多,以前是用speex中自带去噪功能,现在自从2018年Jean-Marc Valin大牛独创性的基于递归神经网络(RNN)的ML模型来抑制音频源中的噪声并开源了RNNoise方案后,RNNoise方案也得到大量的应用。RNNoise原理请阅读Jean-Marc的论文https://jmvalin.ca/demo/rnnoise/  以及 https://arxiv.org/pdf/1709.08243.pdf  本文只从工程实践性角度移植RNNoise到STM32N6上并做基本测试。

RNNoise的移植
clone RNNoise的源码
  1. git clone  https://gitlab.xiph.org/xiph/rnnoise.git


PC机上编译
  1. ./autogen.sh
注:这一步会下载训练好的数据并自动解压
  1. ./configure
  2. make


rnnoise源码拷贝
然后我们需要把如下文件加进STM32N6的测试工程:
  1. src/celt_lpc.c  src/kiss_fft.c  src/rnn.c
  2. src/denoise.c   src/pitch.c     src/rnn_data.c
  3. src/arch.h            src/opus_types.h
  4. src/celt_lpc.h        src/pitch.h
  5. src/common.h          src/rnn_data.h
  6. src/_kiss_fft_guts.h  src/rnn.h
  7. src/kiss_fft.h        src/tansig_table.h
  8. src/denoise.h

rnnoise源码修改
虽然STM32N6的CM55开了双精度浮点支持,但是对rnnoise去噪来说双精度其实没必要,float的精度就够啦,所以在CFLAGS中加入两个参数
  1. -fsingle-precision-constant --Wdouble-promotion -Werror
意思是只用单精度float运算,然后源码中任何float->double promotion的地方都会有编译器警告,再配合-Werror CFLAGS编译器碰上就可以报错,一行一行的改,改动基本上是如下这两大类改动:
改1: 任意浮点数常量加f后缀,比如代码中有大量的.5这样浮点常量,改成.5f即可
改2: 任意math中的函数调用加f后缀,比如sqrt()->sqrtf()

这样把所有rnnoise代码中的双精度浮点的使用就可以全去掉啦。

stm32n6的rnnoise处理能力测试
rnnoise默认是48KHZ采样语音,但Projects/STM32N6570-DK/Examples/MDF/MDF_AudioRecorder是16KHZ采样,我们先看看48KHZ的采样,以STM32N6的CPU能否做到实时线性去燥。这里实时线性去噪意思是采样得到的数据能否及时处理,为此笔者仿rnnoise的example写了个小测试用例,代码片段如下:
  1.   float x[FRAME_SIZE];
  2.   int i, loops = 100;

  3.   DenoiseState* st = rnnoise_create(NULL);

  4.   /* Enable the cycle counter */
  5.   CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
  6.   DWT->CTRL = 0;
  7.   DWT->CYCCNT = 0;
  8.   DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;

  9.   int cycles = DWT->CYCCNT;
  10.   for (j = 0; j < loops; j++) {
  11.     rnnoise_process_frame(st, x, x);
  12.   }
  13.   cycles = (DWT->CYCCNT - cycles);

  14.   printf("%u cycles\n", cycles / loops);
这部分代码在STM32N6上运行结果是2435154 cycles,因cpu是600MHZ,那么就是0.00405859s,FRAME_SIZE沿用rnnoise定义是480个采样数据,那么STM32N6 cpu每秒可做480/0.00405859 ~= 118268个采样>数据的RNN去噪,所以即使48KHZ采样即每秒48000个采样数据,STM32N6的CPU也能cover住。

去噪效果
因从MP23DB01HP全向数字麦克风采集数据并DMA搬运到sram/psram非本文重点,事实上ST官方的Projects/STM32N6570-DK/Examples/MDF/MDF_AudioRecorder照着改改就可以用的,即使调节也无非改改MDF的参数,所以这里着重测试rnnoise的去噪效果,所以笔者测试过程如下:
初始化psram=》openocd加载一段裸pcm数据到psram=》运行rnnoise去噪,并保存到psram中=》openocd把此去噪后的pcm数据dump到PC机,然后用audacity软件比较去噪前和去噪后的pcm数据得到结论。

首先psram初始化
STM32Cube_FW_N6其实唷psram初始化例子,在Projects/STM32N6570-DK/Examples/XSPI/XSPI_PSRAM_MemoryMapped/,我们拿过来改改即可,核心代码片段如下:
  1.   MX_XSPI1_Init();
  2.   Configure_APMemory();

  3.   /*Configure Memory Mapped mode*/
  4.   sCommand.OperationType      = HAL_XSPI_OPTYPE_WRITE_CFG;
  5.   sCommand.InstructionMode    = HAL_XSPI_INSTRUCTION_8_LINES;
  6.   sCommand.InstructionWidth   = HAL_XSPI_INSTRUCTION_8_BITS;
  7.   sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
  8.   sCommand.Instruction        = WRITE_CMD;
  9.   sCommand.AddressMode        = HAL_XSPI_ADDRESS_8_LINES;
  10.   sCommand.AddressWidth       = HAL_XSPI_ADDRESS_32_BITS;
  11.   sCommand.AddressDTRMode     = HAL_XSPI_ADDRESS_DTR_ENABLE;
  12.   sCommand.Address            = 0x0;
  13.   sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
  14.   sCommand.DataMode           = HAL_XSPI_DATA_16_LINES;
  15.   sCommand.DataDTRMode        = HAL_XSPI_DATA_DTR_ENABLE;
  16.   sCommand.DataLength         = BUFFERSIZE;
  17.   sCommand.DummyCycles        = DUMMY_CLOCK_CYCLES_WRITE;
  18.   sCommand.DQSMode            = HAL_XSPI_DQS_ENABLE;


  19.   if (HAL_XSPI_Command(&hxspi1, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  20.   {
  21.     Error_Handler();
  22.   }

  23.   sCommand.OperationType = HAL_XSPI_OPTYPE_READ_CFG;
  24.   sCommand.Instruction = READ_CMD;
  25.   sCommand.DummyCycles = DUMMY_CLOCK_CYCLES_READ;
  26.   sCommand.DQSMode     = HAL_XSPI_DQS_ENABLE;

  27.   if (HAL_XSPI_Command(&hxspi1, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  28.   {
  29.     Error_Handler();
  30.   }

  31.   sMemMappedCfg.TimeOutActivation = HAL_XSPI_TIMEOUT_COUNTER_ENABLE;
  32.   sMemMappedCfg.TimeoutPeriodClock      = 0x34;

  33.   if (HAL_XSPI_MemoryMapped(&hxspi1, &sMemMappedCfg) != HAL_OK)
  34.   {
  35.     Error_Handler();
  36.   }

  





其次rnnoise处理psram中的原始pcm数据
  1.   float x[FRAME_SIZE];
  2.   short *srctmp = 0x90000000;
  3.   short *dsttmp = 0x90000000 + 4*1024*1024;

  4.   __BKPT(0); //此处breakpoint,openocd会收到这个信息,然后openocd即可加载原始PCM数据

  5.   while(srctmp < (short *)(0x90000000 + PCM_LEN)) {
  6.     for (i=0;i<FRAME_SIZE;i++) x[i] = *srctmp++;
  7.     rnnoise_process_frame(st, x, x);
  8.     for (i=0;i<FRAME_SIZE;i++) *dsttmp++ = x[i];
  9.   }

  10.   for (;;) __WFI();

openocd加载pcm数据到psram
  1. load_image /tmp/output.raw 0x90000000


openocd把解噪后的pcm数据dump到pc机
  1. dump_image /tmp/my.raw 0x90400000 3563520


audacity对比rnnoise解噪效果
1.jpg
如图所示上面的是解噪前,下面的解噪后,可以看到rnnoise效果还是很不错的,很适合用于语音降噪。


后续工作:
Cortex-M55 MVE优化rnnoise的代码
能否利用STM32N6自带的NPU来优化rnnoise的代码

powerantone 发表于 2025-6-5 17:22 | 显示全部楼层
相比传统方法,RNNoise在复杂噪声环境下能够显著提高语音清晰度,同时减少语音失真。
AdaMaYun 发表于 2025-6-8 22:29 | 显示全部楼层
降噪技术学习一下
gejigeji521 发表于 2025-6-11 12:16 | 显示全部楼层
AI降噪技术吗
Ketose 发表于 2025-7-8 15:57 | 显示全部楼层
收一块STM32N6开发板。
szt1993 发表于 2025-7-8 19:37 | 显示全部楼层
stm32n6的rnnoise处理能力非常不错
您需要登录后才可以回帖 登录 | 注册

本版积分规则

42

主题

165

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部