41

音频降噪在 58 直播中的研究与实现

 5 years ago
source link: https://www.infoq.cn/article/QOp4IOao_DJJ6eNsIOXp?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

背景

在直播时主播经常会受到一些外部环境音、噪音等影响,直播时音频采集会一并采集所有音频推流到观众设备上,从而影响观众收听体验。因此需要在直播主播端主动进行降噪处理,提高观众收听体验。

58 直播为了实现这个功能,通过综合对比调研常见的开源降噪方案 Speex、WebRTC、RNNoise,以及结合降噪之后的处理效果和 58 直播使用体验,最终选择 WebRTC 降噪方案。我们对其进行了优化兼容,将其移植应用到 58 视频直播中,提升直播效果和体验。

降噪方案

常见的开源降噪方案

  • Speex

Speex 是一套主要针对语音的开源免费,无专利保护的应用集合,它不仅包括编解码器,还包括 VAD(语音检测)、DTX(不连续传输)、AEC(回声消除)、NS(去噪) 等实用模块。

  • WebRTC

WebRTC 提供了视频会议的核心技术,包括音视频的采集、编解码、网络传输、显示等功能,并且还支持跨平台:Windows、Linux、Mac、Android。我们这里使用的就是 WebRTC 的音频处理模块 audio_processing。

  • RNNoise

RNNoise 降噪算法是根据纯语音以及噪声通过 GRU 训练来做。包含特征点提取、预料等核心部分。

RNNoise 降噪算法与传统算法对比分析

传统降噪算法大部分是估计噪声 + 维纳滤波,噪声估计的准确性是整个算法效果的核心。根据噪声的不同大部分处理是针对平稳噪声以及瞬时噪声来做。

RNNoise 的优点主要是一个算法通过训练可以解决所有噪声场景以及可以优化传统噪声估计的时延和收敛问题。

RNNoise 的缺点是深度学习算法落地问题。因为相对大部分传统算法,RNNoise 训练要得到一个很好的效果,由于特征点个数、隐藏单元的个数以及神经网络层数的增加,导致模型增大,运行效率。

现在就 WebRTC 和 RNNoise 的降噪集成效果进行对比验证分析。

降噪音频数据对比

音频原始 PCM 数据可通过 Audacity 软件进行分析

下图是 58 公司司庆直播时的截取一段音频数据,音频为双声道、44100 采样率。分别用 RNNoise 和 WebRTC 进行降噪处理得出效果对比图如下:

qMjAbqB.jpg!web

下图是网络下载的一段带有噪音的音频数据,音频为单声道、32000 采样率。分别用 RNNoise 和 WebRTC 进行降噪处理得出效果对比图如下:

qMjAbqB.jpg!web

综合上面两张效果图可以结论出:

  • RNNoise 处理之后的数据更干净些,几乎没有电流音和杂音,但是受限于训练集、特征点问题,在处理一些数据时候会把正常的原声数据一并错误处理掉。

  • WebRTC 处理之后的数据也相对干净,能更好的保持原有声音的数据,数据丢失较少。

降噪方案在直播实现

降噪方案调研过程

RNNoise 过程

  • RNNoise 的代码是基于 C 开源的,集成到 Android 中需要使用 NDK。

  • 开源项目提供的一个测试方法,但是该方法是针对文件处理的,可以把一个带噪音的 PCM 文件处理成无噪音文件。直播 SDK 中的音频数据是分段的 byte 数组数据,所以中间需要添加一些接口来让 RNNoise 来支持分段数据的降噪处理。

  • 根据 RNNoise 的降噪过程和业务接口流程,把接口定义成 init、process、free 三个接口。

  • 在 process 数据时发现 RNNosie 的处理窗口大小是 480,所以传入的数据也必须是 480 的正整数倍。如果不是的话处理之后会有明显的新引入噪音。

复制代码

#defineFRAME_SIZE_SHIFT 2
#defineFRAME_SIZE (120<<FRAME_SIZE_SHIFT)
#defineWINDOW_SIZE (2*FRAME_SIZE)

* 通过测试发现这个窗口大小是可以进行微调的,为了方便音频数据的处理尝试大小修改长 512,虽然通过 Audacity 分析频谱发现会有一些噪音波出现,但是在实际感观中效果还是可以接受的。这个方案可以临时解决非 480 正整数倍数的问题。

复制代码

// 强制修改 FRAME_SIZE 大小
#defineFRAME_SIZE (128<<FRAME_SIZE_SHIFT)
  • 开源代码中的 rnn_data.c 和 rnn_data.h 是通过机器学习训练出来的,不是通用的。在处理一个噪音数据时发现有些数据中的原声也会一并处理掉,这个效果如果不通过新的数据集训练那么降噪之后的数据是不可用的。

复制代码

/*This file is automatically generated from a Keras model*/
#ifndefRNN_DATA_H
#defineRNN_DATA_H
#include"rnn.h"
#defineINPUT_DENSE_SIZE 24
externconstDenseLayer input_dense;
#defineVAD_GRU_SIZE 24
externconstGRULayer vad_gru;
#defineNOISE_GRU_SIZE 48
externconstGRULayer noise_gru;
#defineDENOISE_GRU_SIZE 96
externconstGRULayer denoise_gru;
#defineDENOISE_OUTPUT_SIZE 22
externconstDenseLayer denoise_output;
#defineVAD_OUTPUT_SIZE 1
externconstDenseLayer vad_output;

structRNNState{
floatvad_gru_state[VAD_GRU_SIZE];
floatnoise_gru_state[NOISE_GRU_SIZE];
floatdenoise_gru_state[DENOISE_GRU_SIZE];
};
#endif
  • 机器学习和训练是 RNNoise 的灵魂,需要业务接入方根据自身的使用场景通过大量的数据集来找出最合适的处理集。

WebRTC 过程

  • WebRTC 的代码是基于 C++ 开源的,集成到 Android 中需要使用 NDK。

  • WebRTC 官方没有提供降噪增益的测试代码,需要查找相关资料找到其中的降噪、增益模块,通过资料去熟悉其中的处理逻辑。

  • WebRTC 只能处理特定的采样率数据,这个是其代码内部是写死的,需要自己实现音频重采样来满足 WebRTC 的降噪采样率需求。音频的重采样算法有很多,在项目集成中都尝试使用过,效果都是差不多的。

复制代码

// WebRTC 处理支持的采样率
// Initialization of struct.
if(fs ==8000|| fs ==16000|| fs ==32000|| fs ==44100|| fs ==48000) {
self->fs = fs;
}else{
return-1;
}
  • 根据 WebRTC 的降噪过程和业务接口流程,把接口定义成 init、process、free 三个接口。区别 RNNoise 的是需要在 process 中做增益处理,WebRTC 降噪会降低数据的声音大小,通过增益用来补充声音大小。

  • 在 process 数据时发现 WebRTC 的处理窗口大小必须是 160 或是 320 个 byte,根据采样率不同窗口大小不同。测试发现这个和处理 RNNoise 是一致都只能传正整数倍数据,要不还是会新引入噪音数据。

复制代码

if(fs ==8000) {
self->blockLen =80;
self->anaLen =128;
self->window = kBlocks80w128;
}else{
self->blockLen =160;
self->anaLen =256;
self->window = kBlocks160w256;
}

  • WebRTC 在 process 时有两种处理数据的方法:一种是需要把原始数据分成高频数据和低频数据给底层逻辑;一种是不用区分高低频数据直接把数据给底层逻辑。资料上的解释是 32k 以上需要分高低频处理。但是在实际测试中发现分高低频的处理效果不如不分高低频的效果好。

  • WebRTC 的降噪 NS 模块和增益 AGC 模块是独立的,为了一次数据完成两个过程需要组合数据,边降噪边增益,减少处理耗时。

  • WebRTC_NS 在处理数据时不应该选择高低频分开采样处理,应直接把数据给你 WebRTC_NS 处理就可以。经过测试发现通过高低频处理之后的音频降噪效果不如不区分高低频的,高低频处理之后会有明显的人声破音出现,且处理的降噪效果不纯净。这个地方走了一些弯路,在发现降噪效果不理想时没有怀疑是 api 使用的问题,这个高低频操作是很多资料都推荐的使用方法,但是在运用到实际场景时发现效果不如不使用的。

两种降噪方案集成优缺点对比

  • 目前 WebRTC 最新代码只支持采样率为 8000、16000、32000、44100、48000 的音频进行降噪,针对其余的采样率需要进行数据重采样到上述采样率之后进行降噪,处理完毕之后需要再次恢复原采样率;RNNoise 对采样率没有要求,可以适配常见的采样率。

  • WebRTC 在降噪之后还需要对数据进行增益处理,但是增益会增大电流音,效果会稍差些。

  • WebRTC 处理数据的 buffer 目前代码是 320 的整数倍;RNNoise 处理数据的 buffer 目前代码是 480 的整数倍。输入的 buffer 需是固定大小的,如果不是正整数倍,需要外部在传入时处理下。

  • 从代码复杂度看,WebRTC 的代码是多于 RNNoise 代码的。RNNoise 支持机器学习,通过机器学习生成 rnn_data.h 和 rnn_data.c 文件来匹配不同的降噪效果。

  • 降噪耗时对比,RNNoise 处理 3840 字节的 buffer 数据耗时大概在 6ms 左右,但在开始时耗时在 30ms 左右,递减到 6ms 并稳定;WebRTC 处理 3840 字节的 buffer 数据耗时大概在 2ms 左右,但在开始时耗时在 10ms 左右,递减到 2ms 并稳定。对比发现 WebRTC 处理效率更好些。

  • 从处理流程上看都是需要 init、process、free 操作的,对接入方接入成本是一致的。

安卓端 58 直播 SDK 接入降噪方案

通过上章节的优缺点对比以及 58 直播中已经在使用了 WebRTC 相关代码逻辑,综合调研和处理结果验证工作之后,最终选择了 WebRTC 降噪方案。

APM 模块集成 WebRTC 降噪功能

  • 在 58 多媒体整体架构上选择把降噪模块单独解耦提取一个 APM module,方便 58 视频编辑、58 直播等需要降噪业务统一调用。对外暴露工具类 AudioNoiseHelp 方便业务接入。APM module 的规划以后会接入更多的音频处理模块,现在已经接入降噪、增益模块。

  • 由于 58 直播 SDK 支持音频采样率种类大于 WebRTC 支持的种类,因此需要对数据进行最优音频重采样处理。

复制代码

/**
* 音频重采样
*
* @param sourceData 原始数据
* @param sampleRate 原始采样率
* @param srcSize 原始数据长度
* @param destinationData 重采样之后的数据
* @param newSampleRate 重采样之后的数据长度
*/
voidresampleData(constint16_t *sourceData,int32_t sampleRate,uint32_t srcSize,
int16_t *destinationData,int32_t newSampleRate){
if(sampleRate == newSampleRate) {
memcpy(destinationData, sourceData, srcSize * sizeof(int16_t));
return;
}

uint32_t last_pos = srcSize -1;
uint32_t dstSize = (uint32_t) (srcSize * ((float) newSampleRate / sampleRate));
for(uint32_t idx =0; idx < dstSize; idx++) {
floatindex = ((float) idx * sampleRate) / (newSampleRate);
uint32_t p1 = (uint32_t) index;
floatcoef = index - p1;
uint32_t p2 = (p1 == last_pos) ? last_pos : p1 +1;
destinationData[idx] = (int16_t) ((1.0f- coef) * sourceData[p1] + coef * sourceData[p2]);
}
}
  • 由于 WebRTC 只能处理 320byte 长度正整数倍的数据,但是 58 直播的音频采集数据在不同手机、不同采样率上得到的音频数据长度是不固定的,需要对数据进行切分处理。录音采集数据如果是 byte 格式的,假如长度是 4096,那么直接把 4096 数据传入到 WebRTC_NS 里处理会出现杂音出现,所以在交给 WebRTC_NS 模块之前需要用个缓冲区来处理下,一次最多可以传入 (4096/320)*320=3840 长度数据,并且在数据处理完毕之后还需要用另外一个缓冲区来保证处理之后的长度仍然是 4096 个。

复制代码

// 部分逻辑代码如下所示:
// 这个数据拆分和缓冲区数据逻辑可以由业务方自行出
/**
* 降噪处理方法,数据异步处理之后通过回调方法通知给调用方。
*
*@parambytes 音频数据
*@paramnsProcessListener 异步方法回调
*/
publicvoidwebRtcNsProcess(byte[] bytes, INsProcessListener nsProcessListener){
if(isAudioNsInitOk) {
synchronized(TAG) {
if(null== bytes || bytes.length ==0) {
return;
}

...

intbyteLen = bytes.length;
if(inByteLen != byteLen) {
inByteLen = byteLen;
webRtcNsInit(byteLen);
}

intframes = byteLen / FRAME_SIZE;
intlastFrame = byteLen % FRAME_SIZE;
intframeBufferLen = frames * FRAME_SIZE;
byte[] buf =newbyte[frameBufferLen];
Log.d(TAG,"webRtcNsProcess inBufferSize:"+ inBufferSize);
if(inBufferSize >= frameBufferLen) {
Log.d(TAG,"webRtcNsProcess mInByteBuffer full");
nsProcessInner(buf, nsProcessListener);
}

...

nsProcessInner(buf, nsProcessListener);
}
}else{
if(null!= nsProcessListener) {
nsProcessListener.onProcess(bytes);
}
}
}

privatevoidnsProcessInner(byte[] buf, INsProcessListener nsProcessListener){
mInByteBuffer.rewind();
mInByteBuffer.get(buf,0, buf.length);
byte[] inBufferLeft =newbyte[inBufferSize - mInByteBuffer.position()];

...

byte[] nsProcessData = AudioNoiseUtils.webRtcNsProcess(buf);
byte[] outBuf =newbyte[inByteLen];

...

if(outBufferSize >= inByteLen) {

...

byte[] outBufferLeft =newbyte[outBufferSize - mOutByteBuffer.position()];

...

mOutByteBuffer.put(outBufferLeft);
outBufferSize += outBufferLeft.length;
if(null!= nsProcessListener) {
nsProcessListener.onProcess(outBuf);
}
}
}

58 直播接入降噪之后的效果对比

  • 时域对比

下图中蓝色部分是 58 直播时截取的一段未开启降噪逻辑的音频波形 dB 图,绿色部分是 58 直播时截取的一段开启降噪逻辑的音频波形 dB 图。从时域波形图对比上可以看到开启降噪逻辑之后波形更加清晰了,降噪效果比较明显。

E3Uvyqj.jpg!web

  • 频谱图对比

下图中上半部分是 58 直播时截取的一段未开启降噪逻辑音频的频谱图,下半部分是 58 直播时截取的一段开启降噪逻辑音频的频谱图。从频谱图对比上可以看到开启降噪逻辑之后噪音的频谱被去除掉,音频数据的原始数据更加清晰突出。

6bYVRfz.jpg!web

  • 主观感觉对比

在同样的噪音环境下通过开启和关闭降噪功能,在观众端体验收听效果。未开启降噪功能时观众端可以明显的听到沙沙的杂音,开启降噪功能之后沙沙声音明显减少或没有,对应的主播的声音凸显出来。

总结

本文分享了 58 直播在降噪方面所做的一些调研实践经验,重点阐述了其中的一些痛点和难点问题以及我们的解决方案。由于 RNNoise 降噪方案的优势是存在的,在后续研究中会对 RNNoise 的深度学习继续进行深入了解,期望能更好的解决噪音问题,更好的提升直播体验。也希望能有更多朋友一起来探讨更优的解决方案。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK