[STM32N6]

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

[复制链接]
174|0
手机看帖
扫描二维码
随时随地手机跟帖
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的源码
git clone  https://gitlab.xiph.org/xiph/rnnoise.git


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


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

rnnoise源码修改
虽然STM32N6的CM55开了双精度浮点支持,但是对rnnoise去噪来说双精度其实没必要,float的精度就够啦,所以在CFLAGS中加入两个参数
-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写了个小测试用例,代码片段如下:
  float x[FRAME_SIZE];
  int i, loops = 100;

  DenoiseState* st = rnnoise_create(NULL);

  /* Enable the cycle counter */
  CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
  DWT->CTRL = 0;
  DWT->CYCCNT = 0;
  DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;

  int cycles = DWT->CYCCNT;
  for (j = 0; j < loops; j++) {
    rnnoise_process_frame(st, x, x);
  }
  cycles = (DWT->CYCCNT - cycles);

  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/,我们拿过来改改即可,核心代码片段如下:
  MX_XSPI1_Init();
  Configure_APMemory();

  /*Configure Memory Mapped mode*/
  sCommand.OperationType      = HAL_XSPI_OPTYPE_WRITE_CFG;
  sCommand.InstructionMode    = HAL_XSPI_INSTRUCTION_8_LINES;
  sCommand.InstructionWidth   = HAL_XSPI_INSTRUCTION_8_BITS;
  sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
  sCommand.Instruction        = WRITE_CMD;
  sCommand.AddressMode        = HAL_XSPI_ADDRESS_8_LINES;
  sCommand.AddressWidth       = HAL_XSPI_ADDRESS_32_BITS;
  sCommand.AddressDTRMode     = HAL_XSPI_ADDRESS_DTR_ENABLE;
  sCommand.Address            = 0x0;
  sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
  sCommand.DataMode           = HAL_XSPI_DATA_16_LINES;
  sCommand.DataDTRMode        = HAL_XSPI_DATA_DTR_ENABLE;
  sCommand.DataLength         = BUFFERSIZE;
  sCommand.DummyCycles        = DUMMY_CLOCK_CYCLES_WRITE;
  sCommand.DQSMode            = HAL_XSPI_DQS_ENABLE;


  if (HAL_XSPI_Command(&hxspi1, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }

  sCommand.OperationType = HAL_XSPI_OPTYPE_READ_CFG;
  sCommand.Instruction = READ_CMD;
  sCommand.DummyCycles = DUMMY_CLOCK_CYCLES_READ;
  sCommand.DQSMode     = HAL_XSPI_DQS_ENABLE;

  if (HAL_XSPI_Command(&hxspi1, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }

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

  if (HAL_XSPI_MemoryMapped(&hxspi1, &sMemMappedCfg) != HAL_OK)
  {
    Error_Handler();
  }

  





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

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

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

  for (;;) __WFI();

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


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


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


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

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

36

主题

144

帖子

1

粉丝