当前位置: 首页 > news >正文

<span class=“js_title_inner“>rk3568上解析webrtc音频降噪算法处理流程</span>

前言:

大家好,在上一篇文章里面,我们已经把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的源码来学习,下期内容,我们来放到板端进行测试一下效果是怎样的!

http://www.jsqmd.com/news/335919/

相关文章:

  • 咸宁管道疏通服务现状与可靠机构盘点 - 2026年企业推荐榜
  • <span class=“js_title_inner“>rk3568移植WebRTC AudioProcessing</span>
  • 2026年咸宁管道疏通服务公司综合实力与口碑盘点 - 2026年企业推荐榜
  • 2026年咸宁管道疏通服务商选择指南:6家优质公司深度解析 - 2026年企业推荐榜
  • <span class=“js_title_inner“>Python+tkinter程序中ttk.Progressbar进度条组件用法演示</span>
  • 2026年国内兰州轻钢龙骨厂家采购参考指南 - 行业平台推荐
  • VPX架构军工级SSD选型指南:板级定制与国产化解决方案(2026)
  • 2026年初粮油批发定制厂商竞争格局深度分析 - 2026年企业推荐榜
  • 2026年热门的兰州吊顶石膏板/青海石膏板值得信赖厂家推荐(精选) - 行业平台推荐
  • <span class=“js_title_inner“>真正的AI手机永远不会有了吗?</span>
  • 腾讯宣布:发10亿现金红包!
  • <span class=“js_title_inner“>2033年将破40亿美元!区块链+AI 撞出“信任+智能”的超级风口</span>
  • 最后一天。
  • <span class=“js_title_inner“>Siri 终于要“死”了?苹果 iOS 27 惊天曝光:这次不仅是更新,而是换脑!</span>
  • 【硬件测试】基于FPGA的8PSK+卷积编码Viterbi译码硬件片内测试,包含帧同步,信道,误码统计,可设置SNR
  • 前后端分离人事管理系统系统|SpringBoot+Vue+MyBatis+MySQL完整源码+部署教程
  • <span class=“js_title_inner“>让 AI 也能当“反洗钱专家“——一个通俗易懂的模型训练故事</span>
  • 通过 DeepFlow 查询函数在 CPU 上消耗的时间(CPU 性能剖析)
  • 2026年靠谱的防火石膏板/纸面石膏板新厂实力推荐(更新) - 行业平台推荐
  • 2026年口碑好的青甘大环线旅游/西北旅游客户认可榜 - 行业平台推荐
  • 2026硝化菌厂家甄选指南:三大维度与顶尖服务商解析 - 2026年企业推荐榜
  • Vue-day6 路由!
  • Linux命令--echo~反引号符~重定向符(>>)~tail命令
  • which命令
  • C++_竞态_底层原理解释
  • 基于FPGA的8PSK+卷积编码Viterbi译码通信系统,包含帧同步,信道,误码统计,可设置SNR
  • 2026年健康无添加石榴汁供货商实力盘点 - 2026年企业推荐榜
  • 2026年靠谱的私人定制旅行社/1V1旅行社口碑推荐榜 - 行业平台推荐
  • 2026石榴汁批发选谁?口碑Top5服务商深度解析 - 2026年企业推荐榜
  • GitLab基本设置