Skip to content

Latest commit

 

History

History
216 lines (186 loc) · 9.13 KB

File metadata and controls

216 lines (186 loc) · 9.13 KB

完整代码方案:WebRTC AEC + RNNoise NS + AGC 音频前端

目标:把传统 DSP 链路里的 NS 这格替换为神经网络(RNNoise),AEC/AGC 仍用 WebRTC,各取所长。 现状:三段已分别跑通并验证;合成场景下 AEC3 双讲过度抑制近端(见末尾"已知限制")。


1. 架构总览

基线(纯 WebRTC,优化前)

mic ──► [WebRTC APM: AEC3 → NS → AGC2] ──► 上行
        (一个库,内部串联,NS 是传统 DSP)

问题:NS 这格(WebRTC 自带)过度抑制近端语音、去噪一般。

优化后(AEC/AGC 用 WebRTC,NS 换 RNNoise)

                ┌─────────────────────┐
mic ──► Stage1  │ WebRTC APM (AEC only)│ ──► aec_out(消回声,保近端)
        ┌──────┘  echo=on, ns=off,    ┘
        │
        ▼  resample 32k→48k
        │
Stage2  │ RNNoise NS (神经降噪) ──► ns_out(去噪,少削近端)
        │  rnnoise_process_frame, 480样本/帧@48k
        ▼  resample 48k→32k
        │
Stage3  │ WebRTC APM (AGC only) ──► 最终输出(增益归一)
        └  echo=off, ns=off, gc2=on

关键改动:NS 从 WebRTC 内部移出,换成 RNNoise 独立一级,AEC 和 AGC 仍由 WebRTC 担任。


2. 代码逻辑在哪

Stage 1 — AEC(WebRTC,NS 关)

文件:webrtc-audio-processing/examples/run-offline.cpp 配置(关键:关 NS、关 AGC,只留 AEC):

webrtc::AudioProcessing::Config config;
config.echo_canceller.enabled = true;
config.echo_canceller.mobile_mode = false;   // AEC3(嵌入式可用 true=AECM)
config.gain_controller1.enabled = false;     // 模拟AGC,离线关
config.gain_controller2.enabled = false;     // Stage1 不做AGC
config.noise_suppression.enabled = false;    // ★NS 关掉,交给 RNNoise
config.high_pass_filter.enabled = true;
apm->ApplyConfig(config);

帧循环(10ms 一帧,32kHz,int16):

// play = 扬声器参考(far),rec = 麦克风(mic)
while (!play_file.eof() && !rec_file.eof()) {
    play_file.read(...); rec_file.read(...);      // 各读 320 样本(10ms@32k)
    apm->ProcessReverseStream(play_frame, ...);   // 喂远端参考(AEC 用)
    apm->ProcessStream(rec_frame, ...);           // 处理 mic(AEC 消回声)
    aec_file.write(...);                          // 输出 aec_out
}

Stage 2 — NS(RNNoise,关键路径)

文件:rnnoise/examples/rnnoise_demo.c(核心推理在 rnnoise/src/rnnoise.crnnoise_process_frame) API(rnnoise/include/rnnoise.h):

int              rnnoise_get_frame_size(void);            // = 480
DenoiseState*    rnnoise_create(RNNModel *model);         // model=NULL 用内置权重
float            rnnoise_process_frame(DenoiseState *st, float *out, const float *in);  // ★一帧进一帧出
void             rnnoise_destroy(DenoiseState *st);

帧循环(10ms 一帧,48kHz,int16):

#define FRAME_SIZE 480   // 48k 下 10ms
DenoiseState *st = rnnoise_create(NULL);
while (1) {
    short tmp[FRAME_SIZE];
    fread(tmp, sizeof(short), FRAME_SIZE, f1);   // ★s16le, 不是 float
    if (feof(f1)) break;
    for (i=0;i<FRAME_SIZE;i++) x[i] = tmp[i];    // int16→float
    rnnoise_process_frame(st, x, x);             // ★神经降噪
    for (i=0;i<FRAME_SIZE;i++) tmp[i] = x[i];
    fwrite(tmp, sizeof(short), FRAME_SIZE, fout);
}
rnnoise_destroy(st);

数据流:PCM 进 → STFT → GRU 网络 → 增益 mask → iSTFT → PCM 出。权重烤在 src/rnnoise_data.c(78MB,由 download_model.sh 下)。

Stage1→Stage2 的衔接(因为采样率不同,32k↔48k):

# aec_out(32k wav) → 48k s16 raw → RNNoise → 32k wav
ffmpeg -i aec_out.wav -f s16le -ac 1 -ar 48000 aec_out_48k.raw
rnnoise_demo aec_out_48k.raw ns_out_48k.raw
ffmpeg -f s16le -ar 48000 -ac 1 -i ns_out_48k.raw -ar 32000 ns_out.wav

Stage 3 — AGC(WebRTC,NS/AEC 关)

文件:webrtc-audio-processing/examples/run-offline.cpp(同一程序,换配置) 配置:

config.echo_canceller.enabled = false;   // AEC 已在 Stage1 做完
config.gain_controller2.enabled = true;  // ★只做 AGC2(固定数字增益+limiter)
config.noise_suppression.enabled = false;// NS 已在 Stage2 做完
config.high_pass_filter.enabled = true;

循环同 Stage1,只是 AEC 关、AGC2 开;输入是 ns_out,输出是最终信号。


3. 头文件

头文件 提供 来自
webrtc/api/audio/audio_processing.h AudioProcessingAudioProcessingBuilderConfig(AEC/NS/AGC 全在这里开关) WebRTC APM
rnnoise/include/rnnoise.h rnnoise_create/process_frame/destroyDenoiseStateRNNModel RNNoise

WebRTC 的其他头(modules/audio_processing/...)是私有实现,不直接用;RNNoise 的 src/*.h 也是私有,只 include/rnnoise.h 对外。


4. 产物(成果物)

产物 路径 作用
libwebrtc-audio-processing-2.1.dylib / .a webrtc-audio-processing/builddir/webrtc/modules/audio_processing/ AEC3/AECM + AGC2 + NS(传统DSP)
librnnoise.0.dylib / .a rnnoise/.libs/ 神经 NS(权重内置)
run-offline webrtc-audio-processing/builddir/examples/ AEC / AGC 级(改配置切换)
rnnoise_demo rnnoise/examples/ NS 级
score.py versions/ 客观评测(DNSMOS+AECMOS+ERLE+电平)
aec_out.wav / ns_out.wav / final.wav versions/v3-ns-comparison/ 三级中间产物 + 最终输出

5. 构建 & 运行

构建

# RNNoise(autotools)
cd rnnoise && ./autogen.sh && ./configure --enable-examples && make -j4
# → librnnoise.0.dylib + examples/rnnoise_demo

# WebRTC APM(meson)
cd webrtc-audio-processing && meson setup builddir && ninja -C builddir
# → libwebrtc-audio-processing-2.1.dylib + examples/run-offline

运行完整链路(shell 拼接三段)

# Stage1: AEC (run-offline 配置=AEC-only)
run-offline play.raw rec.raw aec_out.raw          # 32k s16
# Stage2: RNNoise NS (需 48k)
ffmpeg -i aec_out.wav -f s16le -ar 48000 a48.raw
rnnoise_demo a48.raw ns48.raw
ffmpeg -f s16le -ar 48000 -i ns48.raw -ar 32000 ns_out.wav
# Stage3: AGC (run-offline 配置=AGC-only)
ffmpeg -i ns_out.wav -f s16le -ar 32000 ns.raw
run-offline play.raw ns.raw final.raw             # play 作 dummy reverse(AEC关)
# 评测
python versions/score.py rec.wav final.wav play.wav

6. 已知限制 & 真实部署要做的事

当前限制(诚实)

  1. 三段是 shell+ffmpeg 拼接,不是单二进制:Stage1/3 共用 run-offline.cpp(改配置重编切换),Stage2 是 rnnoise_demo。中间有 32k↔48k 重采样。
  2. 合成场景 AEC3 双讲过度抑制近端:冷启动 + 无 render_delay 配置,AEC3 延迟估计锁不住,残差抑制器误消近端(近端相关 0.83→0.12)。AECM 则直通不收敛。→ AEC 客观评测必须用真实数据(8295 录音或 AEC Challenge),合成不可信。
  3. 之前 v3 的 NS 对比 SIG 结论受此污染,BAK(去噪)结论相对可信。

真实部署(8295)要做的集成

把三段合成一个 C++ 程序,直接链两个库,在内存里按帧流转(不再走 ffmpeg):

// 伪代码:单程序集成
auto apm_aec = AudioProcessingBuilder().Create();  // AEC-only 配置
auto apm_agc = AudioProcessingBuilder().Create();  // AGC-only 配置
DenoiseState *ns = rnnoise_create(NULL);

while (有 10ms 帧) {
    apm_aec->ProcessReverseStream(far_frame, ...);  // AEC: 喂参考
    apm_aec->ProcessStream(mic_frame, ...);          // AEC: 消回声
    // 32k→48k 重采样(SRC,48k 的 480 样本 = 32k 的 320 样本时长)
    rnnoise_process_frame(ns, ns_out, aec_out_48k);  // NS: 神经降噪
    // 48k→32k 重采样
    apm_agc->ProcessStream(ns_out_32k, ...);         // AGC: 增益归一
    // 输出
}

要点:

  • 帧对齐:AEC/AGC 是 10ms@32k=320 样本;RNNoise 是 10ms@48k=480 样本;两者时长相同,中间用 SRC 转采样率。
  • AEC 的 render_delay 要按真实通路配(8295 上扬声器→mic 的真实延时),否则 AEC3 锁不住。
  • AEC 在 8295 上持续运行(不冷启动),双讲抑制会轻很多。
  • RNNoise 权重 rnnoise_data.c 编进固件(78MB,可考虑量化压缩)。

7. 目录地图

~/Documents/ai-audio/
├── rnnoise/                        # 神经 NS
│   ├── include/rnnoise.h           # ★唯一对外头
│   ├── src/rnnoise.c               # rnnoise_process_frame 实现
│   ├── .libs/librnnoise.0.dylib    # ★产物
│   └── examples/rnnoise_demo.c     # NS 级 demo
├── webrtc-audio-processing/        # AEC + AGC
│   ├── webrtc/api/audio/audio_processing.h  # ★APM 对外头
│   ├── webrtc/modules/audio_processing/{aec3,aecm,agc2,ns}/
│   ├── builddir/.../libwebrtc-audio-processing-2.1.dylib  # ★产物
│   └── examples/run-offline.cpp    # AEC/AGC 级(改配置切换)
├── versions/
│   ├── score.py                    # ★客观评测
│   ├── v1-rnnoise-ns/              # RNNoise 单独 NS
│   ├── v2-webrtc-aec-12s/          # WebRTC 全链路
│   ├── v3-ns-comparison/           # NS 对比 + 最终优化链路
│   └── archive/                    # 早期实验
└── SOLUTION.md                     # 本文档