AI 降噪:端侧实时语音降噪与视频降噪

本文处理常见的刚需——降噪。通话里的键盘声、直播里的风扇声、低光视频的噪点,都是信号淹没在噪声里。端侧降噪的难点是实时性:音频一帧 20ms 内要出结果,视频一帧 33ms 内要出结果。

1、你遇到的场景

“用户在地铁里开语音通话,对面只听到轰隆隆。视频聊天在暗光环境,画面全是雪花点。想加个降噪,传统算法(谱减法、维纳滤波)降完人声发闷、金属音严重;上深度学习模型,手机跑不动实时……”

降噪的两难:

  1. 降得多 ≠ 听感好 — 降噪太狠,人声失真、音乐变成”电话音”
  2. 实时预算 — 音频 48kHz 采样,20ms 一帧,模型只能算 5ms 左右
  3. 场景泛化 — 地铁、咖啡馆、办公室的噪声谱完全不同

2、两条降噪路线

维度语音降噪视频降噪
输入单/多声道 PCMYUV/RGB 帧序列
主流模型RNNoise / 频域掩码 DNN时域+空域 CNN(如 FastDVDnet)
帧预算20ms/帧33ms/帧
关键指标信噪比 SNR + 主观 MOSPSNR + 主观观感

核心洞察: 语音降噪在频域做最划算(噪声和语音在频谱上可分),视频降噪要时域+空域一起做(噪点随时间抖、空间上散)。两者共性:都用「掩码」——估计一个 0~1 的增益,乘回原信号,而不是直接生成干净信号。

语音降噪管线(频域掩码):

PCM → 分帧加窗 → STFT(FFT) → DNN估计掩码 → 掩码×频谱 → iSTFT → 干净PCM
                            (噪声大处≈0,语音处≈1)

3、语音降噪:RNNoise 风格频域掩码

# 训练侧:频域掩码网络(Claude Code 生成)
# 输入:一帧的幅度谱(161 个频点);输出:每个频点的增益 mask
import torch
import torch.nn as nn

class MaskNet(nn.Module):
    """输入 (batch, 161),输出 (batch, 161) 的增益,0~1"""
    def __init__(self):
        super().__init__()
        self.fc = nn.Sequential(
            nn.Linear(161, 256), nn.ReLU(),
            nn.Linear(256, 256), nn.ReLU(),
            nn.Linear(256, 161), nn.Sigmoid(),
        )

    def forward(self, mag):        # mag: 帧幅度谱
        return self.fc(mag)        # mask: 0~1 增益

# 参数量 ~150K,量化后不到 200KB,实时毫无压力

4、端侧实时语音降噪

// AudioDenoiser.swift (iOS)
// 实时语音降噪:AVAudioEngine 回调里逐帧处理

final class AudioDenoiser {
    private let engine: MLInferenceEngine
    private let fft: FFTProcessor          // 自实现 STFT/iSTFT
    private let window: [Float]            // 汉宁窗

    init() throws {
        engine = try CoreMLEngine(bundle: "masknet")
        fft = FFTProcessor(fftSize: 320)   // 20ms @16kHz
        window = Self.hann(320)
    }

    /// 输入一帧 PCM(320 采样点),输出降噪后 PCM
    func denoise(_ frame: [Float]) -> [Float] {
// 1. 分帧加窗 → FFT → 幅度谱
        let mag = fft.magnitude(applyWindow: frame, window: window)
        // 2. DNN 估计 mask
        let mask = try! engine.predict(MLTensor(shape: [1, 161], data: bytes(mag)))
        // 3. mask × 幅度谱(相位保持不变)→ iSTFT → 干净帧
        let cleanMag = zip(mag, maskFloats).map(*)
        return fft.inverse(cleanMag, phase: fft.phase)
    }
}
// AudioDenoiser.kt (Android)
// AudioRecord 读到的 PCM 实时降噪

class AudioDenoiser(private val engine: MLInferenceEngine) {

    fun denoise(frame: ShortArray, out: ShortArray) {
        // ShortArray → Float 幅度谱 → mask → 干净 PCM
        val mag = stft.magnitude(frame)
        val mask = engine.predict(MLTensor(intArrayOf(1, 161), floatToBytes(mag)))
        val clean = FloatArray(161) { i -> mag[i] * mask[i] }
        stft.inverse(clean, out)
    }
}

5、视频降噪:时域 + 空域

视频降噪用相邻帧的时域信息,噪点随机分布,多帧平均能压掉噪声又保住细节。

帧 t-2  帧 t-1  帧 t        ← 输入多帧
  │       │       │
  └───┬───┴───┬───┘
      ▼       ▼
  时域对齐(运动补偿)→ 空域 CNN 去噪 → 干净帧 t
# 训练侧:3 帧输入的视频降噪网络(FastDVDnet 思路)
# 输入 (batch, 3帧, H, W),输出干净帧 t
class VideoDenoiseNet(nn.Module):
    def __init__(self):
        super().__init__()
        # 时域融合:3 帧在通道维拼接后过卷积
        self.enc = nn.Sequential(
            nn.Conv2d(9, 32, 3, padding=1), nn.ReLU(),
            nn.Conv2d(32, 32, 3, padding=1), nn.ReLU(),
            nn.Conv2d(32, 3, 3, padding=1),
        )
    def forward(self, frames):   # frames: 3帧×3通道
        return self.enc(frames)

💡 实测取舍:视频降噪在低光场景效果最明显,但 3 帧输入意味着要缓存 2 帧历史,且算量翻倍。低端机可退化成单帧空域降噪,保住实时性。

6、踩坑记录

#问题现象根因修复
1人声发闷降噪后声音”堵”mask 过狠,高频语音被压mask 下限设 0.1,保留残留
2金属音/水声背景有”叮叮”声相位不连续 + 帧间不重叠帧间 50% 重叠 + overlap-add
3延迟累积通话延迟越来越高帧缓冲没有对齐处理帧长 = 采集帧长,别攒帧
4音乐被吃掉放音乐时全变噪降噪把音乐当噪声检测到音乐类信号时旁路降噪
5视频降噪拖影运动物体后面有”鬼影”时域对齐不准运动补偿 + 运动大时减时域权重
6第一秒有噪声刚开始没降噪模型加载慢预加载 + 首帧 warmup

学习和提升音视频开发技术,欢迎你加入我们的知识星球

AI 降噪:端侧实时语音降噪与视频降噪

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

赞 (0)

相关推荐