rk3568上解析webrtc音频降噪算法处理流程

前言:

大家好,在上一篇文章里面,我们已经把webrtc的apm降噪工程代码已经移植到rk3568上,今天就开始从最简单的音频降噪NS工程代码来学习音频降噪的原理。

噪声的分类:

在讲解代码实践之前,首先我们需要了解噪声的分类;虽然我们都知道噪声是啥,但是在不同场景的噪声,处理的手段会有差异。

噪声分两大类:

  • 1、稳态噪声(Stationary Noise)

  • 2、非稳态噪声(Non-stationary Noise)

稳态噪声:

稳态噪声就是“长期存在、变化缓慢的噪声”,它的能量谱(频谱)随时间变化很小。常见例子,比如说:

噪声类型

是否稳态

解释

空调噪声

稳态

“呼呼”持续不变

风扇噪声

稳态

基本恒定频率

电脑电流声

稳态

高频持续“滋滋”

车内发动机怠速

稳态

频谱变化缓慢

服务器机房噪声

稳态

持续背景噪声

这些噪声通常:

频谱平滑

能量分布稳定

变化极小

非稳态噪声

指短时突发、变化快、频谱不稳定的噪声,它的能量和频谱随时间剧烈变化。 比如说:

噪声类型

是否非稳态

原因

键盘敲击声

非稳态

高瞬态能量

门关上的“砰”

非稳态

爆发式冲击

撞击、敲桌子

非稳态

极短时冲击波

塑料袋、纸张摩擦

非稳态

随机频率变化

讲话中的爆破音(P/K)

瞬态

高频瞬态声

儿童尖叫声

变化快

非周期不稳定

特点:

瞬态(transient)

不可预测

持续时间极短(几毫秒)

能量突发式增大

对于这种非稳态噪声处理,是比较难处理的,在后续的文章里面,我们再详细介绍这块。

常见降噪处理算法和处理流程介绍

噪声处理,一句话来说,就是把语音信号里面的噪声去除掉,留下纯净的语音信号即可。

常见的语音降噪算法有,我这里介绍的是单通道的处理算法,而且是大概概括一下,本篇文章暂时不做详细的介绍:

  • 1、谱减法

  • 2、维纳滤波法

  • 3、基于最大似然(ML)、最大后验(MAP)、最小均方估计(MMSE)的统计模型法

  • 4、贝叶斯估计法

  • 5、基于特征值和奇异值分解的子空间法

  • 6、音频机器学习模型,代表算法:

    RNNoise(实用、轻量级)
    
     DTLN(高质量)
    
     DeepFilterNet
    
     SEGAN
    
     DeepConv-TasNet
    
     Demucs

RNNoise(基于RNN,实时降噪)开源地址:

https://github.com/xiph/rnnoise?utm_source=chatgpt.com

DTLN(基于双信号转换 LSTM 网络),开源地址:

https://github.com/breizhn/DTLN?utm_source=chatgpt.com

DeepFilterNet(深度多帧过滤器,实时优化),开源地址:

https://github.com/Rikorose/DeepFilterNet?utm_source=chatgpt.com

webrtc降噪处理流程:

在讲解webrtc中的降噪代码之前,我们要先了解一下webrtc中降噪的总体流程:

  • 1、帧分割(10 ms)

  • 2、Hann 窗函数

  • 3、FFT(分为几个 sub-band)

  • 4、噪声能量估计(Min-statistics 或 MCRA)

  • 5、计算 SNR(信噪比)

  • 6、计算 Wiener 增益:gain = SNR / (SNR + 1)

  • 7、应用增益: 低 SNR(吵噪)→ 小增益 高 SNR(有人声)→ 保留语音

  • 8、iFFT

  • 9、重建时域信号

10ms PCM → FFT → 噪声估计 → Wiener 增益 → 降噪频谱 → iFFT → 重建 PCM

帧分割(10ms)Frame Splitting:

为什么要分帧?这个肯定是第一眼看到你很疑惑;原因如下:

音频信号是连续的(流),但降噪算法(尤其频域算法)必须一次处理一小段。

为什么 10ms?

因为:语音信号 10ms 内基本视为“平稳”(stationary)

  • FFT 需要固定窗口长度

  • 10ms 延迟几乎听不出来(实时处理用)

  • 分帧就是把“长波形”切成一页页的小段来处理。

WebRTC 规定,这个是原文,:

// APM processes audio in chunks of about 10 ms. See GetFrameSize() for
  // details.
  static constexpr int kChunkSizeMs = 10;

  // Returns floor(sample_rate_hz/100): the number of samples per channel used
  // as input and output to the audio processing module in calls to
  // ProcessStream, ProcessReverseStream, AnalyzeReverseStream, and
  // GetLinearAecOutput.
  //
  // This is exactly 10 ms for sample rates divisible by 100. For example:
  //  - 48000 Hz (480 samples per channel),
  //  - 44100 Hz (441 samples per channel),
  //  - 16000 Hz (160 samples per channel).
  //
  // Sample rates not divisible by 100 are received/produced in frames of
  // approximately 10 ms. For example:
  //  - 22050 Hz (220 samples per channel, or ~9.98 ms per frame),
  //  - 11025 Hz (110 samples per channel, or ~9.98 ms per frame).
  // These nondivisible sample rates yield lower audio quality compared to
  // multiples of 100. Internal resampling to 10 ms frames causes a simulated
  // clock drift effect which impacts the performance of (for example) echo
  // cancellation.
  static int GetFrameSize(int sample_rate_hz) { return sample_rate_hz / 100; }

APM(Audio Processing Module,音频处理模块)以约 10 毫秒为一个音频块来进行处理。具体细节请参阅 GetFrameSize() 函数。kChunkSizeMs = 10:每个处理块的固定时长为 10 毫秒。GetFrameSize() 的作用: 返回 floor(sample_rate_hz / 100) 的结果。 这个值表示:在调用以下 APM 函数时,每个声道要输入 / 输出的采样点数量:

ProcessStream()

ProcessReverseStream()

AnalyzeReverseStream()

GetLinearAecOutput()

如果采样率能够被 100 整除,那么计算出来的值就与 10ms 的音频长度完全一致。比如:

48000 Hz → 每声道 480 个采样点(等于 10ms)

44100 Hz → 每声道 441 个采样点(等于 10ms)

16000 Hz → 每声道 160 个采样点(等于 10ms)

如果采样率不能被 100 整除,那么得到的帧长度只是“接近” 10ms,而不是严格 10ms。比如:

22050 Hz → 每声道 220 个采样点(≈ 9.98ms)

11025 Hz → 每声道 110 个采样点(≈ 9.98ms)

这些不能整除 100 的采样率,相比那些整百采样率,会导致更低的音频处理质量。 因为 APM 内部必须将其重采样为严格 10ms 的帧长度,这会产生一种类似“时钟漂移(clock drift)”的效应,并且会影响某些算法的性能(例如回声消除 Echo Cancellation)。

GetFrameSize() 的具体实现非常简单: 只返回 sample_rate_hz / 100,也即采样率除以 100。这个结果就是 10 毫秒音频所包含的采样点数量。

static int GetFrameSize(int sample_rate_hz) { 
    return sample_rate_hz / 100; 
}

其他环节我们详细来看源代码来解读,这里暂时介绍第一个。

webrtc降噪代码工程:

下面是一个最简单的降噪代码工程:

// Build AudioProcessing
    rtc::scoped_refptr<webrtc::AudioProcessing> apm = webrtc::AudioProcessingBuilder().Create();
    webrtc::AudioProcessing::Config cfg = apm->GetConfig();
    cfg.high_pass_filter.enabled = opt.hpf;
    cfg.noise_suppression.enabled = true;
    // Map NS level
    if (opt.ns == "low") cfg.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
    elseif (opt.ns == "moderate") cfg.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;
    else cfg.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh;
    // AGC2 (recommended over AGC1 for most apps)
    cfg.gain_controller2.enabled = opt.agc2;
    cfg.gain_controller2.adaptive_digital.enabled = opt.agc2;
    // VAD
    //cfg.voice_detection.enabled = opt.vad;
    // AEC (disabled in this file-only demo; left here for reference)
    cfg.echo_canceller.enabled = false;

    // 瞬态抑制
    cfg.transient_suppression.enabled = true;

    // 语音检测
    //cfg.voice_detection.enabled = true;

    apm->ApplyConfig(cfg);

    // 1) 创建并配置 VAD
      VadInst* vad = nullptr;
      WebRtcVad_Create();
      WebRtcVad_Init(vad);
      // 模式:0~3,越大越“激进/敏感”
      WebRtcVad_set_mode(vad, 2);



    const int sample_rate = (int)in.sample_rate;
    const size_t channels = in.channels;
    const size_t frame_samples = static_cast<size_t>(sample_rate / 100); // 10 ms
    const size_t total_samples = in.samples.size();

    webrtc::StreamConfig input_cfg(sample_rate, channels);
    webrtc::StreamConfig output_cfg(sample_rate, channels);

    std::vector<int16_t> out;
    out.reserve(total_samples);

    size_t frames_with_voice = 0, total_frames = 0;

    for (size_t pos = 0; pos < total_samples; pos += frame_samples * channels) {
      size_t remaining = total_samples - pos;
      size_t this_frame = std::min(frame_samples * channels, remaining);
      std::vector<int16_t> frame(frame_samples * channels, 0);
      std::memcpy(frame.data(), in.samples.data() + pos, this_frame * sizeof(int16_t));

      // In-place process
      int err = apm->ProcessStream(frame.data(), input_cfg, output_cfg, frame.data());
      if (err != 0) throw std::runtime_error("AudioProcessing::ProcessStream failed");

      if (opt.vad) {
        auto stats = apm->GetStatistics();
        // voice_detected is optional in stats
        if (stats.voice_detected && *stats.voice_detected) frames_with_voice++;
        total_frames++;
      }
      out.insert(out.end(), frame.begin(), frame.end());
    }

官方也有给一个demo学习,但是他不是降噪的工程,我上面是代码基于这个改造得到的:

在讲解代码之前,我们先简单来看AudioProcessing降噪结构定义:

// Enables background noise suppression.
    struct NoiseSuppression {
      bool enabled = false;
      enum Level { kLow, kModerate, kHigh, kVeryHigh };
      Level level = kModerate;
      bool analyze_linear_aec_output_when_available = false;
    } noise_suppression;

有了这个基础之后,我们开始介绍一下上面的代码,整个代码处理流程:

原始 PCM
   ↓
分帧(10ms)
   ↓
APM 加工(包含:高通 + 降噪 + AGC2 + 瞬态抑制)
   ↓
WebRTC Spectral Noise Suppression(维纳滤波降噪)
   ↓
可选:VAD 检测是否有人声
   ↓
输出降噪后的 PCM
  • 1、AudioProcessing(APM)是 WebRTC 音频处理的总控模块

rtc::scoped_refptr<webrtc::AudioProcessing> apm =
    webrtc::AudioProcessingBuilder().Create();

AudioProcessing内部包括模块有:

模块

功能

HPF

高通,去低频噪声(风噪、低频嗡声)

NS

噪声抑制(频域维纳滤波)

AEC

回声消除(你这里关闭了)

AGC1/AGC2

自动增益控制(响度一致)

VAD

语音检测

TS

瞬态噪声抑制(键盘敲击声)

目前代码开通的模块:

高通滤波

降噪(NS)

AGC2

瞬态抑制(击键/碰撞噪声处理)

  • 2、开启高通滤波器

cfg.high_pass_filter.enabled = opt.hpf;//这里赋值为ture

高通滤波器的定义:

/*
    高通滤波器在 APM 中是非常重要的基础模块,主要用来:
    去除低频轰鸣声(风声、空调声、道路噪声)
    去掉麦克风/电源带来的 50/60Hz 嗡声
    降低 DC offset(直流偏置)
    提升 Voice Activity Detection (VAD) 的准确性;WebRTC 的高通滤波器通常是 90 Hz(左右)截止频率。
    Input PCM
        ↓
    [HighPassFilter]  ← 你这段配置控制的就是这一步
        ↓
    Echo Cancellation (AEC)
        ↓
    Noise Suppression (NS)
        ↓
    Automatic Gain Control (AGC)
        ↓
    Output PCM
    */
    struct HighPassFilter {
      bool enabled = false;//是否启用高通滤波器
      /*
      决定高通滤波器作用在哪个频带。如果为 true(默认):高通滤波器对整段信号进行处理(full-band);也就是说:
      所有频率范围的输入信号都经过 HPF → 再进入 APM 其它模块。
      如果为 false:高通滤波只应用在音频的“底层带宽”(即低带),通常是 窄带(8kHz) 或 16kHz 的部分段
      apply_in_full_band 的含义是:
          true:在整个 full-band 上应用 HPF(即对原始全频率信号做一次高通,然后再做分带)
          false:只对某一个子带(通常是低频/主带)应用 HPF,其他高频带不单独再滤
      */
      bool apply_in_full_band = true;
    } high_pass_filter;
  • 3、开启降噪(Noise Suppression)

cfg.noise_suppression.enabled = true;
  • 4、设置降噪强度(low / moderate / high)

if (opt.ns == "low") cfg.noise_suppression.level = kLow;
else if (opt.ns == "moderate") cfg.noise_suppression.level = kModerate;
else cfg.noise_suppression.level = kHigh;

level

降噪力度

声音效果

low

最轻

保真度最高

moderate

中等(默认)

会议最佳

high

强降噪

有人声变“干”风险

very high

暴力降噪

强噪环境用

  • 5、开启 AGC2(自动增益控制 2)

cfg.gain_controller2.enabled = opt.agc2;
cfg.gain_controller2.adaptive_digital.enabled = opt.agc2;

AGC2 特点:

完全数字化

不需要硬件支持

自动让音量保持稳定

适合嵌入式系统、录音文件处理

作用:

防止声音忽大忽小

增强较小的语音

提高降噪后音质

AGC2 与 NS 配合可以达到 专业会议级降噪音质。

  • 6、开启瞬态噪声抑制(键盘/撞击噪声)

cfg.transient_suppression.enabled = true;

TS 模块专门处理:

键盘敲击

物件掉落

咔哒声

爆音

鼠标点按

它能去除这种 “瞬间,高能量” 的噪声。

不过这个未来可能是被抛弃,官方后期不会维护这个模块,估计对于处理非稳态噪声,处理效果不是很好,暂时没有测试,后面有测试再和大家说。

  • 7、ApplyConfig — 把设置应用到 APM 内部

apm->ApplyConfig(cfg);

执行这个之后:

HPF、NS、AGC2、TS 都被开启

APM 内部会创建对应模块

初始化 FFT、噪声估计器、滤波器等

这是开始降噪前必要的一步。

  • 8、准备分帧(WebRTC 统一使用 10ms)

const size_t frame_samples = sample_rate / 100; // 10ms
  • 9、10ms 循环:核心处理过程

for (pos=0; pos < total_samples; pos += frame_samples * channels)
{
    ...
    apm->ProcessStream(frame.data(), input_cfg, output_cfg, frame.data());
    ...
}

ProcessStream 内部执行哪些降噪算法?

Raw PCM
 ↓
High Pass Filter(高通)
 ↓
Noise Suppression(频域降噪)
 ↓
Transient Suppression(瞬态噪声抑制)
 ↓
AGC2(自适应增益 + 限幅器)
 ↓
Output PCM

-10、可选:统计 VAD(语音检测)

auto stats = apm->GetStatistics();
if (stats.voice_detected && *stats.voice_detected) frames_with_voice++;

WebRTC 内部会用:

能量判断

频谱判断

多带语音概率

来判断是否为语音帧。

可用于:

静音检测

录音分析

语音触发

自动剪辑无声片段

总结:

上面每一个流程会比较复杂,暂时我们没有看到里面具体是怎么实现,所以后期我们后面深入去追webrtc APM的源码来学习,下期内容,我们来放到板端进行测试一下效果是怎样的!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值