跳到主要内容

2 篇博文 含有标签「音频」

查看所有标签

DshanPI-A1 音频录制播放与噪声分析

· 阅读需 14 分钟
世玉轩
100askTeam yuxuan.

音频播放

扬声器设备

先看以下这个扬声器如何使用,先列出音频播放设备

aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: rockchipes8388 [rockchip-es8388], device 0: dailink-multicodecs ES8323 HiFi-0 [dailink-multicodecs ES8323 HiFi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: rockchiphdmiin [rockchip,hdmiin], device 0: 2a640000.sai-dummy_codec dummy_codec-0 [2a640000.sai-dummy_codec dummy_codec-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 2: rockchipdp0 [rockchip-dp0], device 0: rockchip-dp0 spdif-hifi-0 [rockchip-dp0 spdif-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 3: rockchiphdmi [rockchip-hdmi], device 0: rockchip-hdmi i2s-hifi-0 [rockchip-hdmi i2s-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0

可以看出,总共检测到 4张音频卡(card 0 ~ card 3)

  • 播放音频到扬声器/耳机:使用 card 0, device 0
  • HDMI音频输出:使用 card 3, device 0
  • DisplayPort音频:使用 card 2, device 0
  • 捕获HDMI输入音频:使用 card 1, device 0

扬声器是card0:subdevice#0,对应alsa设备就是hw:0,0

**** List of PLAYBACK Hardware Devices ****
card 0: rockchipes8388 [rockchip-es8388], device 0: dailink-multicodecs ES8323 HiFi-0 [dailink-multicodecs ES8323 HiFi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0

创建并播放简单测试音

# 创建8kHz采样率的1kHz正弦波WAV文件(5秒)
ffmpeg -f lavfi -i "sine=frequency=1000:duration=5" -c:a pcm_s16le -ar 8000 test_tone.wav
# 播放该文件
aplay test_tone.wav
Playing WAVE 'test_tone.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Mono

测试的命令都成功了,但是没有听到声音,需要查看这个扬声器的属性,很可能是音频路由问题,首先需要确定排查思路:

音频芯片(ES8388)设计是:

  • 软件控制层Speaker Switch - 控制音频流是否发送到芯片
  • 硬件控制层OUT1/OUT2 Switch - 控制芯片物理引脚输出

音频播放调试

先打开音频可视化工具看一下

alsamixer -c 0

image-20251205094259863

可以看出playback的状态不正常: MM 00,需要进一步确认是哪个配置的问题,使用 PulseAudio控制工具

pactl list short sinks
0 alsa_output.0.HiFi__hw_rockchipes8388__sink module-alsa-card.c s16le 2ch 44100Hz SUSPENDED
1 alsa_output.1.stereo-fallback module-alsa-card.c s16le 2ch 44100Hz SUSPENDED

作一些解释:

  • Sink(接收器):音频输出的终点
  • PulseAudio架构
应用程序 → 音频流 → PulseAudio服务器 → Sink → 硬件
(播放器) (混音器) (输出设备) (声卡)

状态说明

  • RUNNING:正在播放音频
  • IDLE:空闲,准备就绪
  • SUSPENDED:挂起,节能模式
  • UNLINKED:未连接

设备详情

  • sink 0alsa_output.0.HiFi__hw_rockchipes8388__sink
    • 对应ALSA声卡0(ES8388音频芯片)
    • 高保真(HiFi)输出
  • sink 1alsa_output.1.stereo-fallback
    • 备用/回退输出设备
    • 当主设备不可用时使用

两个音频sink都处于SUSPENDED状态

0 ... SUSPENDED
1 ... SUSPENDED

SUSPENDED状态意味着

  • PulseAudio认为没有音频流需要播放
  • 为了节能,自动挂起了音频输出
  • 系统准备好接收音频,但当前没有活跃的音频流

根本原因链条

  1. 系统启动时 → PulseAudio加载音频设备

  2. 没有活跃音频流 → PulseAudio挂起设备(SUSPENDED)

  3. 硬件路由未激活 → OUT1/OUT2开关默认关闭

  4. 开始播放音频时

    • PulseAudio唤醒设备
    • 硬件开关(OUT/OUT2)还是关闭状态
    • 需要手动 amixer -c 0 sset 'OUT1' on

    经过我测试,在打开

    amixer -c 0 sset 'OUT2' on

    之后,扬声器音频就可以正常播放了,这里作为记录,列出我调试过程中使用到的一些有用的命令

    #1. 查看更详细的sink信息
    pactl list sinks
    Sink #0
    ...
    Sink #1
    ...
    # 2. 查看当前音频流
    pactl list sink-inputs
    #3.alsa内容控制
    amixer -c 0 scontents
    ...
    amixer -c 0 get 'Speaker'
    amixer -c 0 get 'Master'
    amixer -c 0 get 'Headphone'

对于这个问题有多个解决方案,下面列出

方案1:阻止自动挂起

# 编辑PulseAudio配置
vi /etc/pulse/default.pa
# 防止自动挂起
load-module module-suspend-on-idle timeout=0 # 0表示永不挂起
# 增加超时时间
load-module module-suspend-on-idle timeout=3600 # 1小时
# 重启PulseAudio
pulseaudio -k
pulseaudio --start

方案2:启动时自动激活硬件

# 创建启动脚本 /etc/pulse/audio-init.sh
#!/bin/bash
# 等待PulseAudio启动
sleep 3
# 激活硬件输出
amixer -c 0 sset 'OUT2' on
amixer -c 0 sset 'Speaker' on
# 设置合适音量
amixer -c 0 sset 'Output 2' 90%

方案3:使用udev规则

# 创建 /etc/udev/rules.d/90-audio.rules
ACTION=="add", SUBSYSTEM=="sound", KERNEL=="card0", \
RUN+="/usr/bin/amixer -c 0 sset 'OUT2' on"
#重启后生效

方案4:最简单的临时测试

# 先激活设备再播放
amixer -c 0 sset 'Speaker' on
amixer -c 0 sset 'OUT2' on
amixer -c 0 sset 'Output 2' 90%
#也可以永久保存设置(并未生效)
alsactl store

总结:问题实际上是:

  • 软件层:PulseAudio正常
  • 驱动层:ALSA正常识别设备
  • 硬件层:OUT2物理开关需要手动激活

tips:音频路由

# 假设有多个音频设备:
# 0 - 内置扬声器
# 1 - USB耳机
# 2 - HDMI输出

# 将Chrome音频发送到耳机
pactl move-sink-input $(pactl list short sink-inputs | grep chrome | awk '{print $1}') 1
# 将音乐播放器发送到HDMI
pactl move-sink-input $(pactl list short sink-inputs | grep spotify | awk '{print $1}') 2

音频录制

使用得硬件是百问网200w usb摄像头+音频mems一体化模块,如下图所示

7de0c186abbafe3783a045089397308b

设备信息获取

首先需要获得整个mems得信息,它是通过usb与rk3576通信得,有几个方法能看出它得信息

v4l2-sysfs-path
Video device: video36
video: video37
sound card: hw:4
pcm capture: hw:4,0
mixer: hw:4
Video device: video37
sound card: hw:4
pcm capture: hw:4,0
mixer: hw:4
.....
alsactl info
......
- card: 4
id: Camera
name: USB 2.0 Camera
longname: lihappe8 Corp. USB 2.0 Camera at usb-xhci-hcd.8.auto-1.2.2, high speed
driver_name: USB-Audio
mixer_name: USB Mixer
components: USB038f:0541
controls_count: 4
pcm:
- stream: CAPTURE
devices:
- device: 0
id: USB Audio
name: USB Audio
subdevices:
- subdevice: 0
name: subdevice #0
.....

可以看出在alsa里这个设备得name是hw:4,0

音频录制测试

#录制一段背景噪声
arecord -D hw:4,0 -f S16_LE -r 8000 -c 2 -d 10 noise
Recording WAVE 'noise.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo
aplay noise.wav
Playing WAVE 'noise.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo

可以听到明显得”吱吱“声,这个mems的底噪还是很大的,后续需要对其进行降噪,才可以进行正常的语音通讯。

噪声分析

用sox生成频谱图

sox noise.wav -n spectrogram -o noise_spectrogram.pn

noise_spectrogram

#全局频谱
sox noise.wav -n spectrogram -d 10 -x 1200 -z 80 -o noise_full.png

noise_full

sox noise.wav -r 2000 -c 1 noise_2k.wav
sox noise_2k.wav -n spectrogram -d 10 -x 1200 -z 80 -o noise_low.png

noise_low

整体观察背景噪声属于「近似白噪声 + 低频强峰 + 中频轻微纹理噪声」

1. 0~200Hz 附近有非常明显的低频能量团(尤其 <100Hz)

这一般意味着:

  • 机械/电源相关噪声
  • 风扇、震动、机箱共振
  • 电源纹波(50Hz / 60Hz + 基波)
  • 麦克风指向性/箱体耦合放大超低频噪声

2. 1kHz~4kHz 范围是随机噪声(近似白噪声),能量较低

说明麦克风底噪属于典型:

  • MEMS 麦克风本底噪声
  • ADC 自噪声
  • 放大器输入噪声

这部分是系统底噪

3. 高频(>6kHz)完全没有奇怪峰值

这非常好 → 没有明显:

  • 数字 EMI
  • 时钟泄漏
  • 采样抖动噪声

4. 没有明显「啸叫模式」出现

三张图都没有看到典型啸叫特征:

  • 啸叫通常是固定频率一条亮线持续不变
  • 图中只有起始瞬间的脉冲(可能是开始录音时的点击声)

没有稳定峰随时间持续。

逐图分析


(A) 第一张图:全频谱(到 4kHz)

看起来特点是:

整体偏红/紫,噪声密度高但均匀 50Hz 左右有明显竖线 100Hz、150Hz 也有轻微能量

➜ 这几乎肯定是:

工频噪声(50/60Hz)+ 谐波(100/150Hz)

原因:

  • USB 供电带来的大量 50/60Hz hum
  • 声卡或麦克风的模拟前端隔离不好
  • 地线不干净(如 USB 共地回路)

(B) 第二张图:动态范围压窄的版本(-80dBFS)

这一张另外暴露出:

在 1.8kHz ~ 2.2kHz 有一条非常窄的横线

非常淡,但稳定存在。

这表示:

系统时钟/PLL 干扰泄漏

常见于:

  • I2S/MCLK 泄漏
  • PCB 上麦克风的时钟耦合
  • 数字电源噪声叠加到麦克风模拟部分

这部分不会导致啸叫,但会降低 SNR。


(C) 第三张图:低频到 1000Hz

非常典型:

<150Hz 区域噪声比其他频段高很多

像一个大“灯泡”形状,非常明显。

这说明:

低频振动 + 电源噪声是主要底噪来源

包括:

  • 机箱震动、风扇、台面共振
  • 电源 50/60Hz + 谐波
  • 麦克风本身的 LF roll-off 不够

综合判断:背景噪声构成比例

噪声类型占比特征
低频机械/电源噪声 (<200Hz)50%最大来源,来自电源、机箱、振动
工频泄漏 50/60Hz + 谐波25%图中最强的固定峰
麦克风本底白噪声20%散布在 1k~4kHz 随机噪声
数字时钟泄漏(2kHz 附近)5%很弱但可见的一条细线

PSD噪声模型分析

使用python生成PSD噪声模型

import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
from scipy.signal import welch, find_peaks, spectrogram, butter, sosfilt
import IPython.display as ipd
import os

rate, data = wavfile.read("/mnt/data/noise.wav")
if data.ndim>1:
data = data.mean(axis=1)
data = data.astype(np.float32)
N = len(data)
duration = N / rate

# Calculate overall RMS and dBFS
# Assuming 16-bit PCM if dtype was int16; determine scale
# infer max possible value from original dtype by reloading header:
import struct
# Determine dtype max
# but we'll normalize by max of int16 if dtype came as int16, else use max(abs(data))
max_possible = 32768.0
rms = np.sqrt(np.mean(data**2))
dbfs_rms = 20*np.log10(rms / max_possible) if rms>0 else -np.inf

# Welch PSD
f, Pxx = welch(data, fs=rate, nperseg=4096, scaling='density')
# find peaks in PSD (in linear)
peaks, props = find_peaks(Pxx, height=np.max(Pxx)*0.15, distance=5)
peak_freqs = f[peaks]
peak_heights = props['peak_heights']

# Find dominant low-frequency peak under 500Hz
low_idx = np.where(f<=500)[0]
low_f = f[low_idx]
low_P = Pxx[low_idx]
lp_peaks, lp_props = find_peaks(low_P, height=np.max(low_P)*0.2)
lp_freqs = low_f[lp_peaks]
lp_heights = lp_props['peak_heights']

# Short-time energy to find transient (e.g., first second pulse)
frame_ms = 20
frame_len = int(rate * frame_ms/1000)
hop = frame_len//2
frames = []
for start in range(0, N-frame_len, hop):
frames.append(np.sum(data[start:start+frame_len]**2))
frames = np.array(frames)
frame_times = (np.arange(len(frames))*hop)/rate

# detect where energy spikes relative to median
median_e = np.median(frames)
spikes = np.where(frames > median_e*8)[0] # 8x median
spike_times = frame_times[spikes]

# Spectrogram
f_s, t_s, Sxx = spectrogram(data, fs=rate, nperseg=2048, noverlap=1024, scaling='density', mode='magnitude')

# Plot PSD with peaks marked
plt.figure(figsize=(10,5))
plt.semilogy(f, Pxx, color='tab:orange')
plt.scatter(peak_freqs, peak_heights, color='k', zorder=5)
for pf, ph in zip(peak_freqs, peak_heights):
plt.text(pf, ph*1.1, f"{pf:.0f} Hz", fontsize=8, ha='center')
plt.xlim(0, rate/2)
plt.xlabel("Frequency (Hz)")
plt.ylabel("PSD")
plt.title("Welch PSD with detected peaks")
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("/mnt/data/psd_peaks.png")

# Plot spectrogram (dB)
Sxx_db = 20*np.log10(Sxx + 1e-12)
plt.figure(figsize=(10,5))
plt.pcolormesh(t_s, f_s, Sxx_db, shading='gouraud')
plt.colorbar(label='dB')
plt.ylim(0, 4000)
plt.xlabel("Time (s)")
plt.ylabel("Frequency (Hz)")
plt.title("Spectrogram (dB)")
plt.tight_layout()
plt.savefig("/mnt/data/spectrogram_db.png")

# Prepare summary
summary = {
"sampling_rate": rate,
"duration_s": duration,
"rms": float(rms),
"dbfs_rms": float(dbfs_rms),
"dominant_peaks_hz": [float(p) for p in peak_freqs[:8]],
"dominant_peaks_vals": [float(p) for p in peak_heights[:8]],
"low_freq_peaks_hz": [float(p) for p in lp_freqs],
"low_freq_peaks_vals": [float(p) for p in lp_heights],
"spike_times_s": [float(s) for s in spike_times[:10]],
"spectrogram_image": "/mnt/data/spectrogram_db.png",
"psd_image": "/mnt/data/psd_peaks.png"
}

import json
with open("/mnt/data/noise_analysis_summary.json","w") as f:
json.dump(summary, f, indent=2)

# Display small tables and figures
from caas_jupyter_tools import display_dataframe_to_user
import pandas as pd

df_peaks = pd.DataFrame({
"freq_hz": peak_freqs,
"psd_val": peak_heights
})
display_dataframe_to_user("Detected PSD Peaks", df_peaks.head(20))

plt.figure(figsize=(10,3))
plt.plot(frame_times, 10*np.log10(frames+1e-12))
plt.xlabel("Time (s)")
plt.ylabel("Frame energy (dB)")
plt.title("Short-time frame energy (20ms frames)")
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("/mnt/data/frame_energy.png")
plt.show()

# Show audio player
ipd.display(ipd.Audio("/mnt/data/noise.wav"))

summary

image-20251205153910716

image-20251205153954232

image-20251205154029359

image-20251205154110367

得出关键量化结果:

  • 采样率:8000 Hz
  • 时长:10.0 s
  • 全段 RMS = 223.11 (样点),换算为 -43.34 dBFS(以 16-bit 满量程 32768 作归一): 说明底噪中等偏高
  • 检测到的显著频率峰(Welch PSD peak,按强度顺序,列出前几项):
    • 74.2 Hz, 85.94 Hz, 103.52 Hz, 113.28 Hz, 146.48 Hz, 167.97 Hz, 以及较小的 396 Hz 等。
  • 低频段(≤500 Hz)主要峰:85.94, 95.70, 103.52, 113.28, 146.48 Hz —— 多个近频段峰,像是工频/开关电源谐波或机械谐振的集合。
  • 瞬态能量峰(短时能量帧,20 ms)检测到在:约 0.36–0.38 s 有几个短脉冲(可能是启动点击/人为触碰/瞬态事件)。

结论:噪声由明显的低频“hum/纹波/振动”组成 + 宽带白噪声(中高频),低频是主要能量所在。

降噪方案

1.在音频链路上加 2 阶高通 (fc = 80 Hz)

  • 直接把大部分机械/供电低频去掉,不损伤语音带宽(语音主要 >100Hz)。
  • 实现:用 DSP 库里的 butter/sos 或自行实现biquad(Direct Form I 或 II)。

2 针对性陷波(notch)

  • 若 HPF 后仍留有几个极窄强峰(如 103Hz),加一两个 Q=20~40 的陷波。Q 越高带宽越窄,越不伤及邻近频段,但系数更接近 1。
  • 考虑实时性需要串联 hp -> notch(强峰1) -> notch(强峰2)

3 本质还是硬件

  • 更换或改善模拟供电(低噪 LDO、更多旁路电容、星形接地)或使用SNR更好的数字 MEMS 麦克风。
  • 在嵌入式板子上,尽量远离开关电源走线与麦克风差分/模拟线。

对实时音质的考虑

  • 如果需要非常低延迟(例如回声取消 / 实时 loopback),得使用 IIR(biquad)单向实时实现(延迟极低),但注意相位会改变,若不允许相位改变(比如做更精确的定位),用 FIR + zero-phase 会有延迟代价。

参考滤波系数

说明:下面给出的系数是标准二阶节(biquad)格式,按 [b0, b1, b2, a0, a1, a2](a0 已正规化为 1 或者给出时需要归一化)排列。实现时通常把 a0 规一化为 1,然后按 Direct Form I/II 实现。

高通:2nd-order Butterworth(fc = 80 Hz, fs = 8000 Hz)

sos_hp 第一节系数(已归一化 a0=1):

b0 = 0.9565432255568767
b1 = -1.9130864511137533
b2 = 0.9565432255568767
a0 = 1.0
a1 = -1.911197067426073
a2 = 0.9149758348014336

若干陷波(notch, Q=30)

(示例三条,取自检测到的峰位)格式同上(b0,b1,b2,a0,a1,a2),a0 已正规化为 1 在下面输出中:

  • notch @ 103.515625 Hz
b0 = 0.998648305437691
b1 = -1.99069933085876
b2 = 0.998648305437691
a0 = 1.0
a1 = -1.99069933085876
a2 = 0.9972966108753819
  • notch @ 146.484375 Hz
b0 = 0.9980904047539934
b1 = -1.9829844796660758
b2 = 0.9980904047539934
a0 = 1.0
a1 = -1.9829844796660758
a2 = 0.9961808095079867
  • notch @ 74.21875 Hz
b0 = 0.9990299707952924
b1 = -1.9946663265604048
b2 = 0.9990299707952924
a0 = 1.0
a1 = -1.9946663265604048
a2 = 0.9980599415905849

这些噪声分析是作传统频减法音频滤波必要的素材,采用标准的rnnoise AI降噪则不需要,但如果自己改进rnnoise,做模型微调则需要参考,以获得更好的降噪效果。

RK3576 实现 sox 降噪和 rnnoise 降噪

· 阅读需 12 分钟
世玉轩
100askTeam yuxuan.

概述

音频降噪技术的发展,经历了从对抗物理噪声到智能识别分离的演变。

模拟时代(20世纪中期)

  • 杜比A型(1965年):开创性的动态降噪技术,采用“压缩-扩张”原理降低磁带本底噪声。
  • 杜比B型(1968年):A型的消费简化版,让卡式磁带走入千家万户。
  • dbx(1971年):更激进的压缩-扩张系统,动态范围更大。
  • 后续发展:Dolby C、SR等更先进的模拟系统相继问世。

数字时代早期(1980-1990年代)

  • 数字信号处理芯片的出现,让实时数字滤波和谱减法成为可能
  • 算法开始从时域转向频域处理
  • 自适应滤波理论成熟并应用于通信领域

数字算法普及期(1990-2010年代)

  • 个人电脑性能提升,专业音频软件(如Audition、iZotope RX)普及了数字降噪工具
  • 心理声学模型的应用提升了降噪音质
  • 主动降噪耳机开始商业化(Bose等厂商推动)

人工智能时代(2010年代至今)

  • 深度学习彻底改变了降噪方式,能够处理复杂的非平稳噪声
  • 技术广泛应用于视频会议、语音助手、音乐流媒体等领域
  • 研究方向从单纯降噪扩展到语音分离、人声提取等精细化任务

主要降噪方法

基于频谱的降噪 核心思路是在频域上区分噪声和信号:

  • 谱减法:从音频中减去噪声频谱
  • 维纳滤波:更优的统计降噪方法
  • 掩蔽效应法:利用人耳特性保留音质

机器学习降噪

  • 使用深度学习模型(RNN、CNN、Transformer等)从数据中学习降噪
  • 能够有效处理复杂场景和非平稳噪声
  • 支持端到端的波形或频谱处理

滤波降噪

  • 自适应滤波:需要参考噪声信号,用于电话和主动降噪耳机
  • 固定滤波:去除特定频率噪声,如50Hz工频干扰

多麦克风技术

  • 通过麦克风阵列形成指向性波束
  • 增强目标方向声音,抑制环境噪声
  • 常见于手机、会议设备和智能音箱

传统处理方法

  • 噪声门:通过阈值静音低电平信号
  • 简单有效,常用于音乐制作和直播场景

下面使用两种方法来进行音频降噪的处理:sox降噪rnnoise降噪

sox降噪

sox的 noisered 是一个经典的、基于噪声采样和谱减法的非AI降噪工具,原理直观,实现相对简单,对稳态噪声(如风扇声、空调声、恒定电流声)效果显著,noisered 效果器是其降噪的核心,核心原理步骤如下:

sox
  1. 分析/训练阶段:核心是建立噪声指纹。SoX需要先“学习”噪声是什么样的。可以提供一段纯噪声片段(例如录音前的环境底噪),或者让它自动检测音频中能量较低的“静音”部分。它会分析这些片段,计算出一个平均噪声频谱,并保存为一个.prof文件。这个文件就是后续降噪的参考基准。
  2. 降噪处理阶段:核心是频谱减法
    • 分帧与变换:将连续的音频信号切成短时重叠的帧,并通过FFT转换到频域。在频域,信号表现为不同频率的能量(幅度)和相位。
    • 关键操作 - 谱减法:这是最核心的一步。算法会比较当前帧的频谱和之前学到的噪声样本频谱。基本思想非常简单:干净信号频谱 ≈ 带噪信号频谱 - 噪声频谱
      • 能量相减:主要操作是在能量/幅度谱上进行减法。当前帧某个频率的能量如果低于或接近噪声样本在该频率的能量,就会被大幅抑制;如果远高于,则会被保留。
      • 相位保留:相位信息对于重建声音波形至关重要。谱减法通常不改变原始信号的相位,直接用降噪后的幅度谱和原始相位谱合成新信号。
      • 抑制因子:SoX的 amount 参数(0.0 到 1.0)控制减法力度。0.5意味着只减去一半的噪声能量,更为保守,能减少失真。
    • 合成与输出:处理后的频域数据通过IFFT变回时域波形,再通过重叠相加方法将短时帧合成连续的音频信号,最终得到降噪后的音频。

看起来很复杂,但操作起来很简单,现在使用sox命令操纵一下就明白了。

sox命令降噪

#noise.wav是背景噪声,在安静状态下录制的一段10s的音频
ls
noise.wav speech.wav
#生成噪声profile(手动截取纯噪声5-10秒)
sox noise.wav -n noiseprof noise.prof
#降噪,0.21是经验值,绝大部分素材不会出现水声
sox speech.wav noisered_speech.wav noisered noise.prof 0.21
#对比播放,可以看出效果明显
aplay noise.wav
Playing WAVE 'noise.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo
aplay speech.wav
Playing WAVE 'speech.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo
aplay noisered_speech.wav
Playing WAVE 'noisered_speech.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo

只是使用sox命令降噪,无法集成到嵌入式系统中,下面采用C语言,使用sox的库和API来进行音频降噪。

使用sox的库和API来进行音频降噪

代码结构如下:

(base) ubuntu@ubuntu-2204:~/baiwen/sox_noise_reduction$ tree -L 2
.
├── CMakeLists.txt
├── deps
│   ├── include
│   └── lib
├── include
│   ├── custom_effects.h
│   └── sox_noise_reduction.h
├── Makefile
└── src
├── custom_effects.c
├── main.c
└── sox_noise_reduction.c

5 directories, 7 files

编译

要编译这个demo,需要设置cmakelist.txt中得工具链

# 指定交叉编译器
set(CMAKE_C_COMPILER /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin/aarch64-buildroot-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin/aarch64-buildroot-linux-gnu-g++)

# 设置sysroot
set(CMAKE_SYSROOT /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/staging)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})

# 添加链接库
target_link_libraries(sox_noise_reduction
sox
)
#ubuntu22.04编译
(base) ubuntu@ubuntu-2204:~/baiwen/sox_noise_reduction$ make
Configuring CMake...
-- The C compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ubuntu/baiwen/sox_noise_reduction/build
Building with 16 jobs...
make[1]: Entering directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[2]: Entering directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[3]: Entering directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[3]: Leaving directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[3]: Entering directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
[ 75%] Building C object CMakeFiles/sox_noise_reduction.dir/src/sox_noise_reduction.c.o
[ 75%] Building C object CMakeFiles/sox_noise_reduction.dir/src/custom_effects.c.o
[ 75%] Building C object CMakeFiles/sox_noise_reduction.dir/src/main.c.o
[100%] Linking C executable sox_noise_reduction
make[3]: Leaving directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
[100%] Built target sox_noise_reduction
make[2]: Leaving directory '/home/ubuntu/baiwen/sox_noise_reduction/build'
make[1]: Leaving directory '/home/ubuntu/baiwen/sox_noise_reduction/build'

#make push会通过adb 将sox_noise_reduction可执行文件推送到buildroot的/develo
(base) ubuntu@ubuntu-2204:~/baiwen/sox_noise_reduction$ make push
Running build/sox_noise_reduction...
build/sox_noise_reduction: 1 file pushed. 10.6 MB/s (23840 bytes in 0.002s)

运行

在rk3576上

root@rk3576-buildroot:/develop# ls
live_life.mp3 noise.wav sox_noise_reduction speech.wav

先在安静环境下录制一段 noise.wav的背景噪声,再对着麦克风说一段话speech.wav

root@rk3576-buildroot:/develop# ./sox_noise_reduction
SoX音频噪声降低处理开始
=======================
噪声文件: noise.wav
输入文件: speech.wav
输出文件: noisered_output.wav
降噪敏感度: 0.21
-----------------------
Processing...
[1/2] Creating noise profile...
Creating noise profile from: noise.wav
Processing noise profile...
Noise profile created: /tmp/noise_profile_NFk1uN.prof
[2/2] Applying noise reduction...
Applying noise reduction to: speech.wav
Processing audio with noise reduction...
Noise reduction applied. Output: noisered_output.wav
Done!
处理成功! 输出文件: noisered_output.wav
#使用aplay播放可以对比
aplay noise.wav
aplay speech.wav
aplay noisered_output.wav

可以听到 noisered_output.wav明显改善,但还有一些微弱的**“音乐噪声”,这是采用sox谱减法降噪固有的副作用,要完全去除,需要再设计一个维纳滤波**效果插件,进一步去除。

sox_noise_reduction的使用可以看-h的帮助信息

root@rk3576-buildroot:/develop# ./sox_noise_reduction -h
SoX音频噪声降低工具
====================

使用方法: ./sox_noise_reduction [选项]

选项:
-n 文件 噪声样本文件 (默认: noise.wav)
-i 文件 输入语音文件 (默认: speech.wav)
-o 文件 输出文件 (默认: noisered_output.wav)
-s 数值 降噪敏感度 0.0-1.0 (默认: 0.21)
-h 显示此帮助信息

参数说明:
敏感度: 0.0表示最强降噪(可能损伤语音),1.0表示最弱降噪
推荐值: 0.21 (经验值,适用于大多数音频素材)

使用示例:
./sox_noise_reduction # 使用所有默认设置
./sox_noise_reduction -n mynoise.wav # 指定噪声文件
./sox_noise_reduction -i myspeech.wav # 指定输入文件
./sox_noise_reduction -s 0.3 # 调整降噪强度
./sox_noise_reduction -o clean.wav # 指定输出文件

rnnoise降噪

RNNoise 是一个优秀的开源音频降噪工具,非常适合入门学习、快速集成和中等性能需求的场景。

  1. 技术原理
    • 结合传统信号处理(谱分析)与深度学习(RNN神经网络)。
    • 输入音频分帧→提取特征(如频带能量、基音)→RNN预测每个频带的增益掩码(VAD也集成在其中)→输出降噪后的频谱→重建波形。
  2. 轻量化设计
    • 模型仅约86KB,适合嵌入式或实时处理。
    • 单核CPU即可实时处理。

优点

  • 开源且易用:代码清晰,提供C语言API,易于集成到各类项目。
  • 低延迟:帧处理延迟约10ms(默认帧长20ms),适合实时通信。
  • 兼容性强:无第三方深度学习框架依赖,纯C实现。
  • 语音保护较好:在抑制稳态噪声(如风扇声)的同时,对语音损伤较小。

局限性

  • 非通用降噪:主要针对语音通信优化,对音乐、突发噪声(如键盘声)效果有限。
  • 参数固定:模型为通用场景训练,难以针对特定噪声定制。
  • 残留“音乐噪声”:某些场景下可能引入类似水波的残留噪声。
  • 不支持高采样率:默认仅支持48kHz/16kHz单声道,音乐降噪需调整。

编译

## 设置交叉编译工具链路径
export TOOLCHAIN_DIR="/home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin"
export CC="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-gcc"
export CXX="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-g++"
export AR="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-ar"
export LD="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-ld"
export RANLIB="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-ranlib"
export STRIP="${TOOLCHAIN_DIR}/aarch64-buildroot-linux-gnu-strip"

# 设置目标架构
export ARCH="aarch64"
export CROSS_COMPILE="aarch64-buildroot-linux-gnu-"

# 设置sysroot(重要!)
export SYSROOT="/home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/staging"
export CFLAGS="--sysroot=${SYSROOT} -O2"
export LDFLAGS="--sysroot=${SYSROOT}"

echo "工具链设置完成"
echo "CC = $CC"

# 克隆项目并编译
git clone https://github.com/xiph/rnnoise.git
cd rnnoise
./autogen.sh
./configure \
--host=aarch64-buildroot-linux-gnu \
--build=x86_64-pc-linux-gnu \
--prefix=$(pwd)/tmp \
--enable-static \
--enable-shared \
CFLAGS="--sysroot=${SYSROOT} -O2" \
LDFLAGS="--sysroot=${SYSROOT}" \
CC="${CC}" \
CXX="${CXX}" \
AR="${AR}" \
LD="${LD}"
#编译
make
#安装到./tmp
make install
(base) ubuntu@ubuntu-2204:~/rnnoise$ tree -L 2 tmp
tmp
├── include
│   └── rnnoise.h
├── lib
│   ├── librnnoise.a
│   ├── librnnoise.la
│   ├── librnnoise.so -> librnnoise.so.0.4.1
│   ├── librnnoise.so.0 -> librnnoise.so.0.4.1
│   ├── librnnoise.so.0.4.1
│   └── pkgconfig
└── share
└── doc
(base) ubuntu@ubuntu-2204:~/rnnoise$ ls examples/
rnnoise_demo rnnoise_demo.c rnnoise_demo.o

./tmp目录下的文件就是需要使用的编译出来的rnnoise的头文件和库,用来做集成,examples下的rnnoise_demo就是参考实现,还有.c文件可以参考

adb push examples/.libs/rnnoise_demo /usr/bin/rnnoise_demo
adb push tmp/lib/librnnoise.so* /usr/lib/

现在可以到开发板进行简单的测试了

测试

# 先检查原始WAV文件信息
soxi speech.wav
# 如果采样率不是48000,先转换到48000
sox speech.wav -r 48000 speech_48k.wav
# 调整输入音量到合适范围(-3dB到-6dB)
# RNNoise要求:48000Hz,单声道,16位有符号整数PCM
sox speech_48k.wav -r 48000 -c 1 -e signed-integer -b 16 -t raw speech.pcm gain -n -3
# 运行RNNoise降噪
rnnoise_demo speech.pcm rnnoise.pcm
#转换回WAV格式
sox -r 48000 -c 1 -e signed-integer -b 16 -t raw rnnoise.pcm rnnoise.wav

使用rnnoise的库和API来进行音频降噪

代码结构

(base) ubuntu@ubuntu-2204:~/baiwen/rnnoise_reduction$ tree -L 3
.
├── CMakeLists.txt
├── deps
│   ├── include
│   │   └── rnnoise.h
│   └── lib
│   └── librnnoise.so
├── include
├── Makefile
└── src
└── main.c

5 directories, 5 files

其中 rnnnoise.h librnnoise.s0来自rnnoise的编译产物,直接复制就行,CMakeLists.txt需要更改工具链路径

# 指定交叉编译器
set(CMAKE_C_COMPILER /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin/aarch64-buildroot-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/host/bin/aarch64-buildroot-linux-gnu-g++)

# 设置sysroot
set(CMAKE_SYSROOT /home/ubuntu/rk3576_AI/buildroot/output/rockchip_rk3576/staging)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})

编译

make clean
make
#push adb到开发板的develop目录
make push

在开发板测试

root@rk3576-buildroot:/develop# ./rnnoise_reduction speech.wav rnnoise.wav -3
=== RNNoise音频降噪处理 ===
1. 读取WAV文件: speech.wav
采样率: 8000Hz, 声道: 2, 采样点数: 40000 (5.00秒)
2. 应用增益: -3.0dB
3. 重采样到48kHz
重采样后: 240000采样点 (5.00秒)
4. RNNoise降噪处理
处理帧数: 500, 每帧480个样本
第一帧前5个原始样本值: -34 -28 -23 -17 -11
处理后: 240000采样点 (5.00秒)
5. 保存为WAV文件: rnnoise.wav
=== 处理完成 ===
root@rk3576-buildroot:/develop# aplay speech.wav
Playing WAVE 'speech.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo
root@rk3576-buildroot:/develop# aplay rnnoise.wav
Playing WAVE 'rnnoise.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono

可以听出明显的降噪效果