背景

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

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进行降噪处理得出效果对比图如下:

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

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

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

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

降噪方案在直播实现

降噪方案调研过程

RNNoise过程

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

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

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

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

#define FRAME_SIZE_SHIFT 2#define FRAME_SIZE (120\u0026lt;\u0026lt;FRAME_SIZE_SHIFT)#define WINDOW_SIZE (2*FRAME_SIZE)

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

//强制修改FRAME_SIZE大小#define FRAME_SIZE (128\u0026lt;\u0026lt;FRAME_SIZE_SHIFT)
  • 开源代码中的rnn_data.c和rnn_data.h是通过机器学习训练出来的,不是通用的。在处理一个噪音数据时发现有些数据中的原声也会一并处理掉,这个效果如果不通过新的数据集训练那么降噪之后的数据是不可用的。
/*This file is automatically generated from a Keras model*/#ifndef RNN_DATA_H#define RNN_DATA_H#include \u0026quot;rnn.h\u0026quot;#define INPUT_DENSE_SIZE 24extern const DenseLayer input_dense;#define VAD_GRU_SIZE 24extern const GRULayer vad_gru;#define NOISE_GRU_SIZE 48extern const GRULayer noise_gru;#define DENOISE_GRU_SIZE 96extern const GRULayer denoise_gru;#define DENOISE_OUTPUT_SIZE 22extern const DenseLayer denoise_output;#define VAD_OUTPUT_SIZE 1extern const DenseLayer vad_output;  struct RNNState {  float vad_gru_state[VAD_GRU_SIZE];  float noise_gru_state[NOISE_GRU_SIZE];  float denoise_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-\u0026gt;fs = fs;} else {   return -1;}
  • 根据WebRTC的降噪过程和业务接口流程,把接口定义成init、process、free三个接口。区别RNNoise的是需要在process中做增益处理,WebRTC降噪会降低数据的声音大小,通过增益用来补充声音大小。

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

if (fs == 8000) {    self-\u0026gt;blockLen = 80;    self-\u0026gt;anaLen = 128;    self-\u0026gt;window = kBlocks80w128;} else {    self-\u0026gt;blockLen = 160;    self-\u0026gt;anaLen = 256;    self-\u0026gt;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     重采样之后的数据长度 */void resampleData(const int16_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 \u0026lt; dstSize; idx++) {        float index = ((float) idx * sampleRate) / (newSampleRate);        uint32_t p1 = (uint32_t) index;        float coef = 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个。
//部分逻辑代码如下所示://这个数据拆分和缓冲区数据逻辑可以由业务方自行出/** * 降噪处理方法,数据异步处理之后通过回调方法通知给调用方。 * * @param bytes             音频数据 * @param nsProcessListener 异步方法回调 */public void webRtcNsProcess(byte[] bytes, INsProcessListener nsProcessListener) {    if (isAudioNsInitOk) {        synchronized (TAG) {            if (null == bytes || bytes.length == 0) {                return;            }                        ...            int byteLen = bytes.length;            if (inByteLen != byteLen) {                inByteLen = byteLen;                webRtcNsInit(byteLen);            }            int frames = byteLen / FRAME_SIZE;            int lastFrame = byteLen % FRAME_SIZE;            int frameBufferLen = frames * FRAME_SIZE;            byte[] buf = new byte[frameBufferLen];            Log.d(TAG, \u0026quot;webRtcNsProcess inBufferSize:\u0026quot; + inBufferSize);            if (inBufferSize \u0026gt;= frameBufferLen) {                Log.d(TAG, \u0026quot;webRtcNsProcess mInByteBuffer full\u0026quot;);                nsProcessInner(buf, nsProcessListener);            }            ...                        nsProcessInner(buf, nsProcessListener);        }    } else {        if (null != nsProcessListener) {            nsProcessListener.onProcess(bytes);        }    }}private void nsProcessInner(byte[] buf, INsProcessListener nsProcessListener) {    mInByteBuffer.rewind();    mInByteBuffer.get(buf, 0, buf.length);    byte[] inBufferLeft = new byte[inBufferSize - mInByteBuffer.position()];    ...        byte[] nsProcessData = AudioNoiseUtils.webRtcNsProcess(buf);    byte[] outBuf = new byte[inByteLen];        ...        if (outBufferSize \u0026gt;= inByteLen) {        ...        byte[] outBufferLeft = new byte[outBufferSize - mOutByteBuffer.position()];        ...                mOutByteBuffer.put(outBufferLeft);        outBufferSize += outBufferLeft.length;        if (null != nsProcessListener) {            nsProcessListener.onProcess(outBuf);        }    }}

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

  • 时域对比

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

  • 频谱图对比

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

  • 主观感觉对比

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

总结

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

本文作者:金开龙,来自58集团TEG多媒体部,安卓高级工程师,专注音视频开发。

音频降噪在58直播中的研究与实现相关推荐

  1. python音频 降噪_从视频中提取音频数据,然后应用傅里叶对音频降噪(python)...

    视频准备 QQ有热键 然后随便打开一个视频网站进行录屏 我选择B站 从视频中提取音频 需要安装包moviepy pip install moviepy 提取代码 from moviepy.editor ...

  2. 音频降噪 java_流音频中的降噪和压缩

    希望你能帮忙 . 我正在录制麦克风的音频并通过网络直播 . 样品质量为11025hz,8位,单声道 . 虽然有一点延迟(1秒),但效果很好 . 我需要帮助的是我现在尝试实现降噪和压缩,使音频更安静并使 ...

  3. 模态识别在计算机视觉应用,相关性学习在计算机视觉任务中的研究与应用

    人类视觉具备多种多样的能力,而计算机视觉则基于此定义了许多不同的视觉任务.如今,计算机视觉已解决大多数视觉任务,但现有方法大多采取将视觉任务逐一击破,而忽略了大部分的数据和任务都是存在相关性的.相关性 ...

  4. android降噪算法,面向Android设备的音频降噪系统设计

    摘要: 在语音信号采集过程中,由于系统工作环境及电路设计等原因,采集到的语音信号中无可避免地引入了噪声信号,降低了语音的信噪比.由于Android2.3.1原生音频系统未提供针对语音信号进行降噪处理的 ...

  5. 连麦互动直播中混屏技术设计

    连麦互动直播中混屏技术设计 目录 一. 混屏原理 二. 实现混屏关键技术 1. 并发解码 2. 图像合成 3. 视频同步 4. 编码输出 5. 内存管理 三. 具体实现流程 1.混屏流程 2.混屏算法 ...

  6. C#封装C++编写的Speex实现wav音频降噪(字节数组)

    在上一篇博文https://blog.csdn.net/zxy13826134783/article/details/105882490的基础上进一步研究,上一篇博客后面使用C#调用Speex是以文件 ...

  7. 开源直播工具OBS研究

    项目简介 OBS - Free and open source software for live streaming and screen recording(OBS是一款开源的用于录屏直播的工具软 ...

  8. 神奇,声网Web SDK还能这么实现直播中美颜功能

    前言 本篇文章是通过使用声网Web SDK来实现直播中美颜效果的深度体验文章,其中发现了屏幕共享并本地合图多个视频.图片,声网美颜插件等功能特性十分强大和专业,特为此做一个技术+体验的分享,毕竟好技术 ...

  9. 详解音视频直播中的低延时

    高泽华,声网 Agora 音频工匠,先后在中磊电子.士兰微电子.虹软科技主导音频项目.任职 YY 期间负责语音音频技术工作.在音乐.语音编解码方面有超过十年的研发经验. 音视频实时通讯的应用场景已经随 ...

最新文章

  1. 心得体悟帖---开解语录2
  2. python从字符串中提取数字_filter
  3. 【LeetCode】687. 最长同值路径
  4. redis java根据value排序_Redis高级特性——排序
  5. 支付宝第三方授权登陆
  6. leetcode1047. 删除字符串中的所有相邻重复项(栈的日常应用)
  7. 微信小程序轮播图中间变大_微信小程序实现带放大效果的轮播图
  8. 一文搞懂 PyTorch 内部机制
  9. 微软 azure_Microsoft Azure,我们迁移数据的第一步
  10. css3中的background的新特性background-origin,background-clip,background-size详解
  11. 使用json解决 《三级联动》(省市县) 含地区编号
  12. 对Proteus与Keil联调过程中遇到的怪异现象解决方法
  13. Node.js web框架Clouda初接触
  14. webserver的使用
  15. rufus安装centos8(旧电脑玩Linux)
  16. Leveraging Long-Range Temporal Relationships Between Proposals for Video Object Detection论文详读
  17. psql: FATAL: the database system is in recovery mode - 问题定位方法与解决
  18. 使用python调整图片大小
  19. 网吧计算机配置特点,揭秘:网吧电脑配置很低,却怎么用也不卡顿,这是为什么呢?...
  20. 怎么用ai做出适量插画_AI怎么画矢量插画? ai手绘插画的教程

热门文章

  1. leetcode算法题--Russian Doll Envelopes
  2. leetcode算法题--Surrounded Regions
  3. leetcode算法题--剪绳子 II
  4. python做动态折线图_Python数据可视化 pyecharts实现各种统计图表过程详解
  5. linux 列出目录结构6,Linux系统目录结构及文件管理命令
  6. 基于前后端分离的Nginx+Tomcat动静分离
  7. 在GitHub上使用Hexo 搭建自己的博客
  8. ASP.NET 安全认证(一)
  9. Ubuntu批量修改文件名后缀
  10. iOS序列化与反序列化