闹中取静——移动端音频降噪实践

0x00 引言

2018到2020年,全球音视频类应用和用户数飞速增长。今年年初的新冠病毒蔓延,直接又导致全球约20~30亿的人口进行居家隔离,一度,这些人的学习、工作和生活都从线下转移到了线上,人类初步完成学习和工作线上化迁移。在这场变革中,音视频技术助力众多产业转型升级,众多的新兴场景与行业借助音视频技术实现了更加丰富炫目高效准确的场景表达与业务落地,与此同时,音视频技术的市场规模也一跃跳至千亿,万亿级,成为了新基建的重要基础技术。

上篇文章数字世界中的声音表征,介绍了音视频领域中音频的基础理论和实践,本篇中我们主要讨论音频处理维度的一个进阶知识点:音频降噪。

音频降噪(噪声抑制,Active Noise Control, ANC)与回声消除(Acoustic Echo Cancelling,AEC)、自动增益(Automatic Gain Control,AGC)并列称为音频处理领域的3A算法。音频降噪技术已广泛应用于微课录制,语音聊天,视频会议,在线教育,直播等领域的音频前处理阶段。

从计算机数字领域到哲学领域,音频降噪更是一种闹中取静的艺术。万物纷杂,极似组成声音的各种不同频率的正弦波,浮世喧嚣,红尘纷扰,我们往往变得浮躁不安,闹中取静(降噪),能让我们看清内心,寻回本心。

0x01 业务背景

业务需求是推动技术和架构进步的根本源动力,这里我先介绍一些对音频降噪研究的业务需求背景:知识胶囊。
知识胶囊是什么?可以先点击下面的链接体验一下。

知识胶囊图片

知识胶囊是一种集成了课件互动功能的新型微课。区别与传统微课,胶囊将课件的行为回放和授课录音巧妙地融合在一起,并以一种时间同步策略进行回放。在胶囊里既可以像传统微课一样观看老师授课精彩过程,也可以让学生参互动,单独操作课件。

这里打个小广告,用希沃白板APP即可方便地录制知识胶囊,欢迎大家下载体验。
全中国已有越来越多的老师和学生使用知识胶囊这种新的具有互动功能的微课来授课和学习。

点击下载希沃白板APP

在知识胶囊的录制过程中,声音的录制是一个关键的部分。移动终端设备的麦克风拾音效果明显好于普通PC电脑上自带的,我们最开始使用移动终端设备录制得到原始的PCM数据,录制的环境是一个略有嘈杂的办公室环境(这里有键盘敲击声,小组问题讨论声,风扇声音,走廊外面人的走路回声),在对PCM进行回放时能听到很明显的背景噪声:键盘敲击声,远处讨论问题的声音等,下面是一个典型的例子。

有噪声的音频示例

显然作为微课,我们并不希望这些噪声太过于突出,甚至掩盖了老师讲授知识点的声音。我们需要寻找一种去除背景噪声的方法。

0x02 移动端音频降噪实践

2.1 目标和原理

依托业务,声音降噪的目标是:在对需要的语音造成最小失真的前提下,尽可能的将噪声从含噪信号中去除。

在上篇文章中,我们知道声音其实也是一种波,是由不同频率的正弦波叠加而成。所以音频降噪本质上就是想办法去除掉代表噪声的某些正弦波。
音频降噪的基本原理:对数字音频信号进行频谱分析,得到噪声的强度和频谱分布,然后根据这个模型就能设计一个滤波器,过滤掉噪音,得到更加突出的主体声。

2.2 降噪实践

音频降噪算法,网上公开的算法不多,资源也比较有限。在iOS和Android的系统API中,也没有提供方便易用的降噪算法实现(回声消除的支持倒是有的,且效果显著)。
Speex是一套主要针对语音的开源免费(BSD授权)的应用集合,其中包含了编解码器、VAD(语音检测)、AEC(回声消除)和NS(降噪)等实用模块,因而我们可以在商业应用中使用Speex进行声音降噪的处理。

Google在2011做了一件好事,开源了WebRTC(目前已经基本成为了W3C规范),随着WebRTC开源出来的,还有一大堆音视频部分的算法实现,其中就包含音频降噪的代码。WebRTC中的音频降噪部分的代码是C语言实现的,因而在不同的平台(arm和x86)具有良好的兼容性。
业务上,声音降噪是跟场景强相关的,例如在安静的会议室和在嘈杂的商场,采取的降噪算法和策略会明显不同,伴随着机器学习和神经网络的发展,也出现另一些对业务场景音频数据进行学习和训练,逐步优化降噪效果的降噪算法,例如RNNoise,也有人利用TensorFlow对音频降噪效果进行优化。

开发Speex的Xiph.org基金会已经宣布废弃Speex,建议改用Opus取代,所以本篇文章不再对基于Speex的降噪做过多的介绍,感兴趣的同学请移步

1. https://www.speex.org/ 
2. https://www.shuzhiduo.com/A/Gkz14A9gdR/。

本篇我会侧重介绍基于WebRTC的音频降噪实现,对于RNNoise,我会分享一段实际调用和验证的代码。

WebRTC NS
噪声抑制在WebRTC中有两个版本,一个是浮点数版本,一个是定点数版本。需要根据业务场景采集的原始PCM数据的格式来确定使用哪个版本。大部分业务场景,包括我们的胶囊录制场景,用定点数版本足矣,所以本篇我只会介绍定点数版本的移植和使用。

第一步是将NS的代码从WebRTC的庞大源码里抽离出来,并解除对其他文件的依赖,保证可以单独编译和使用。

最新的WebRTC源码中,NS相关的代码位于下面的目录中:

https://webrtc.googlesource.com/src/+/refs/heads/master/modules/audio_processing/ns/

NS源码图片

下面是我从WebRTC源码中抽离出来的NS代码,精简为两个文件,无其他依赖,可以单独编译和使用。

https://github.com/guoxiucai/AudioAndVideo/NoiseSuppression/sdk/src

第二步是熟悉API使用说明,这里直接与代码一起解释说明

NsHandle *nsHandle = WebRtcNs_Create();
WebRtcNs_Init(nsHandle, 16000);  // 初始化
WebRtcNs_set_policy(nsHandle, 2); // 降噪等级,0~3

int num_bands = 1;
int16_t *nsIn[1] = {pcm_buffer};        // 原始音频数据
int16_t *nsOut[1] = {denoised_buffer};  // 降噪之后的音频数据

WebRtcNs_Analyze(nsHandle, nsIn[0]);    // 音频分析

WebRtcNs_Process(nsHandle, (const int16_t *const *) nsIn, num_bands, nsOut); // 实际的降噪处理

......

WebRtcNs_Free(nsHandle);

需要注意的一点,音频处理时WebRTC一次仅能处理10ms数据, 小于10ms的数据不要传入,因为即使是传入小于10ms的数据最后传入也是按照10ms的数据传出,此时会出现问题。另外支持的采样率也只有8K,16K,32K, 48K四种,不论是降噪模块,或者是回声消除增益等等均是如此。对于8000采样率,16bit的音频数据,10ms的时间采样点就是80个,一个采样点16bit也就是两个字节,那么需要传入WebRtcNsx_Process的数据就是160字节。

采样得到的PCM音频数据,需要自己采用队列模型控制,保证每次处理时处理10ms的数据。

第三步分享一个iOS项目的实际使用范例,由于是C源码,Android中使用需要借助于JNI。

- (void)noiseSuppression {
    self.configuration.audioSampleRate = CVTAudioSampleRate16000Hz;
    self.configuration.numberOfChannels = CVTAudioChannalMono;
    self.configuration.audioBitDepth = CVTAudioBitDepth16;
    
    self.pcmPath = [self getSourcePath];    //原始音频文件
    self.dstPath = [self getOutPath];       //降噪后文件 
    
    NSLog(@"pcm file path:%@, dst file path:%@", self.pcmPath, self.dstPath);
    
    [self openFile]; //打开原始WAV文件

    dispatch_async(self.taskQueue, ^{
        @try {
            int read;
            
            FILE *pcm = fopen([self.pcmPath cStringUsingEncoding:1], "rb");
            fseek(pcm, 44, SEEK_CUR);
            
            if (pcm == NULL ) {
                NSLog(@"open pcm file failed!");
                return;
            }
            
            int read_sample_count = self.configuration.audioSampleRate / 1000 * 10;
            read_sample_count = read_sample_count > 160 ? 160 : read_sample_count; //最大支持160
            
            int num_bands = 1;
            
            short int pcm_buffer[read_sample_count];
            short int nsed_buffer[read_sample_count];
            
            
            NsHandle *nsHandle = WebRtcNs_Create();
            int status = WebRtcNs_Init(nsHandle, self.configuration.audioSampleRate);
            if (status != 0) {
                NSLog(@"WebRtcNs_Init fail!");
                return;
            }
            
            status = WebRtcNs_set_policy(nsHandle, 2);
            if (status != 0) {
                printf("WebRtcNs_set_policy fail\n");
                return;
            }

            do {
                
                // in LPCM  frames == samples
                int bytesPerSample = self.configuration.audioBitDepth * self.configuration.numberOfChannels / 8; // = 2
                
                read = (int)fread(pcm_buffer, bytesPerSample, read_sample_count, pcm);
                
                NSLog(@"read=%@, read_sample_count=%@", @(read), @(read_sample_count));
                
                if (read != read_sample_count) {
                    // 不做处理,直接丢弃(也可以不降噪直接写到文件中去)
                    NSLog(@"read != read_sample_count 丢弃处理");
                    
                } else {
                    //读了read个
                    int16_t *nsIn[1] = {pcm_buffer};   //ns input[band][data]
                    int16_t *nsOut[1] = {nsed_buffer};  //ns output[band][data]
                    WebRtcNs_Analyze(nsHandle, nsIn[0]);
                    WebRtcNs_Process(nsHandle, (const int16_t *const *) nsIn, num_bands, nsOut);
                    
                    NSData *data = [NSData dataWithBytes:nsed_buffer length:read * bytesPerSample];
                    [self.fileHandle writeData:data];
                }
                
            } while (read != 0);
            
            [self closeFile]; //关闭原始WAV文件
            fclose(pcm);
            WebRtcNs_Free(nsHandle);
        } @catch (NSException *exception) {
            NSLog(@"出现错误%@",[exception description]);
            
        } @finally {
            NSLog(@"finally ns 结束");
        }
    });
}

RNNoise

RNNoise将经典信号处理与深度学习相结合来创造一个又小又快的实时噪声抑制算法。

https://github.com/cpuimage/rnnoise。

RNNoise对GPU的要求不高,可在Raspberry Pi上轻松运行。结果十分简单,并且比传统噪声抑制系统听起来要强很多(参考第3部分的频域对比分析),支持针对场景进行训练。

注意:RNNoise目前只支持48K采样率。

RNNoise将降噪接口定义成rnnoise_init、rnnoise_create、rnnoise_process_frame、rnnoise_destroy四个重要接口。

API代码范例

#include 
#include "rnnoise.h"

#define FRAME_SIZE 480

int main(int argc, char **argv) {
  int i;
  int first = 1;
  float x[FRAME_SIZE];
  FILE *f1, *fout;
  DenoiseState *st;
  st = rnnoise_create();
  if (argc!=3) {
    fprintf(stderr, "usage: %s  \n", argv[0]);
    return 1;
  }
  f1 = fopen(argv[1], "r");
  fout = fopen(argv[2], "w");
  while (1) {
    short tmp[FRAME_SIZE];
    fread(tmp, sizeof(short), FRAME_SIZE, f1);
    if (feof(f1)) break;
    for (i=0;i

0x03 降噪效果分析

上面介绍的几种对音频降噪的方法实践,降噪的效果到底如何呢? 我们需要一些对降噪效果进行有效评价的方法。对于典型的业务场景,如在线直播,视频会议等,直接用人耳就要可以听出降噪前后的明显差异;结合上一篇文章中,音频有时域和频域维度的表征,所以也可以通过观察对比降噪前后的波形图或频谱图来进行更加可视化的对比评价。

3.1 主观感受

在同样的办公室噪音环境下通过开启和关闭降噪功能,然后对录制得到的WAV文件进行播放,对比结果如下

16kHz_origin.wav

16kHz_denoise_level1.wav

16kHz_denoise_level2.wav

48KHz_rnn_origin.wav

48KHz_rnn_denoise.wav

通过我们的主观听觉感受,未开启降噪功能时可以明显听到背景杂音(键盘敲击声,远处人的讨论说话声),开启降噪功能之后背景噪音明显减弱或没有,对应的录音者的声音明显凸显出来。

3.2 时域对比

用Audacity分析原声音、webrtc_level1和webrtc_level2的波形图得到的时域对比如下

webrtc时域对比图片

上图中蓝色部分是原始声音的音频波形图,红色部分是采用webrtc_level1进行降噪处理之后的波形图, 绿色部分是采用了webrtc_level2进行降噪处理之后的波形图。从时域波形图对比上可以看到开启降噪逻辑之后波形更加清晰了,噪音部分的振幅明显表变小了很多,降噪效果比较明显。

3.3 频域对比

用Audacity分析原声音、webrtc_level1和webrtc_level2的频谱图得到的频域对比如下


webrtc频域图片

上图中第一行是原始音频的频谱图,第二行是采用webrtc_level1进行降噪处理之后的频谱图, 第三行是采用了webrtc_level2进行降噪处理之后的频谱图。

作为对比RNNoise降噪前后的频谱图如下


RNN频域图片

从上面的频谱对比图中我们可以看出降噪后,噪音部分的亮度(能量值)明显表变小了很多,噪音的频谱被去除掉,音频数据的原始数据更加清晰突出,降噪效果比较明显。

3.4 PESQ

PESQ是更加量化的音频质量的评价参考,对于给出的音频,可以综合给出一个具体的分数值表征音频的质量,感兴趣的参考。

  1. ITU-T P.862
  2. https://en.wikipedia.org/wiki/Perceptual_Evaluation_of_Audio_Quality

0x04 小结

从业务问题背景出发,本文主要介绍移动端音频降噪方案实践,重点介绍了基于WebRTC中降噪模块实现的降噪方案,最后给出了几种降噪效果分析参考方法。

你可能感兴趣的:(闹中取静——移动端音频降噪实践)