TL;DR :上一篇聊完隔音窗改造,评论区最多的一句话是"你说室内降到 38dB 我怎么信?"。作为程序员------Talk is cheap, show me the spectrum. 这一篇我把完整的 Python 声学分析工具链开源出来:从原始音频采集 → A 计权滤波 → FFT 频谱分析 → 1/3 倍频程分解 → 改造前后对比。教你用 60 行代码,替自己家做一次可量化的噪音审计。
一、为什么要自己写一套噪音分析工具?
市面上的手机分贝 App(分贝测试仪、Sound Meter 之流)我基本全用过,结论是:只能看单个数字,不能看频谱。
这就像一个只会打印 print(total) 的程序------你只知道总数,不知道是谁在吃内存。
# 市面上分贝 App 能告诉你的:
print(f"当前分贝: 52dB") # 完了,就这一句
# 但你真正想知道的是:
print(f"频段分解: {spectrum_by_band}")
print(f"主导频段: {dominant_freq}Hz")
print(f"噪音源判断: {likely_source}")
print(f"改造前后对比: {before} vs {after}")
家庭噪音分析的真正价值不是告诉你"吵不吵",而是告诉你:
- 噪音的频率分布 → 判断是低频(车流、外机)还是中高频(说话、装修)。
- 主导频段的峰值 → 判断哪个频段最需要针对性处理。
- 改造措施的衰减曲线 → 验证你花的钱到底买了多少 dB。
这三件事,任何一款手机 App 都做不到。所以我决定自己写一套。
二、技术栈:Python 声学分析工具链
完整依赖:
pip install numpy scipy matplotlib sounddevice soundfile librosa
| 库 | 职责 |
|---|---|
sounddevice |
实时音频采集(跨平台,比 pyaudio 易用) |
soundfile |
WAV 文件读写 |
numpy / scipy |
FFT、滤波、信号处理 |
librosa |
专业音频分析(梅尔频谱、声学特征) |
matplotlib |
频谱可视化 |
硬件这边只需要一台笔记本。MacBook 的麦克风经验上频响 20Hz~20kHz 基本平直,对于家庭级噪音审计已经够用。真要严谨可以接一个校准过的电容麦,但这不是这篇的重点。
三、第一步:音频采集(带声压级标定)
程序员常犯的第一个错误:直接把录音的 RMS 当成 dB。
不对。麦克风采集到的只是一个归一化电平(-1.0 ~ 1.0 的浮点数) ,要转成 dB SPL(声压级),必须做标定。
3.1 标定原理
标定方法:
-
用一台已知准确的分贝仪(或校准过的手机 App)在静音环境下同时测量,记录它的读数,比如 35 dB。
-
同时让 Python 采样,记录 RMS 值。
-
算出偏移常数
calibration_offset,以后所有测量都加这个常数。import sounddevice as sd
import numpy as npdef record_audio(duration=10, sample_rate=48000):
"""采集音频样本"""
print(f"开始采集 {duration} 秒音频...")
audio = sd.rec(int(duration * sample_rate),
samplerate=sample_rate,
channels=1,
dtype='float32')
sd.wait()
return audio.flatten(), sample_ratedef compute_rms_db(audio, calibration_offset=94.0):
"""
计算 RMS 对应的 dB SPL
calibration_offset 是标定常数,默认 94dB 对应 1Pa 声压
需要你用已知分贝仪做一次标定后修正
"""
rms = np.sqrt(np.mean(audio ** 2))
if rms < 1e-10:
return -np.inf
db = 20 * np.log10(rms) + calibration_offset
return db使用示例
audio, sr = record_audio(duration=10)
raw_db = compute_rms_db(audio)
print(f"未计权 RMS 声压级: {raw_db:.1f} dB")
Tips :标定常数
calibration_offset和你的麦克风增益设置强相关,换一台机器就要重标。我 MacBook Pro 的标定值大约在 +92~+95 之间。
3.2 A 计权滤波:让数据符合人耳感受
原始 RMS 算出来的是"物理声压级",但人耳对不同频率的响度感受是非线性的------对 3~4kHz 最敏感,对低频和超高频都不敏感。
所以国际标准(IEC 61672)规定,日常噪音测量都要用 A 计权滤波 ,得到的值叫 dB(A)。
from scipy import signal
def a_weighting_filter(sample_rate):
"""构造 A 计权 IIR 滤波器(IEC 61672 标准)"""
# A 计权的模拟传递函数零极点
f1, f2, f3, f4 = 20.598997, 107.65265, 737.86223, 12194.217
A1000 = 1.9997
nums = [(2 * np.pi * f4) ** 2 * (10 ** (A1000 / 20)), 0, 0, 0, 0]
dens = np.polymul(
[1, 4 * np.pi * f4, (2 * np.pi * f4) ** 2],
[1, 4 * np.pi * f1, (2 * np.pi * f1) ** 2]
)
dens = np.polymul(
np.polymul(dens, [1, 2 * np.pi * f3]),
[1, 2 * np.pi * f2]
)
# 模拟 → 数字
b, a = signal.bilinear(nums, dens, sample_rate)
return b, a
def compute_db_a(audio, sample_rate, calibration_offset=94.0):
"""计算 A 计权后的 dB(A)"""
b, a = a_weighting_filter(sample_rate)
filtered = signal.lfilter(b, a, audio)
rms = np.sqrt(np.mean(filtered ** 2))
return 20 * np.log10(rms + 1e-10) + calibration_offset
db_a = compute_db_a(audio, sr)
print(f"A 计权声压级: {db_a:.1f} dB(A)")
至此,我们已经能输出和专业分贝仪等价的 dB(A) 数值了。但这还只是"总量",真正的戏在频谱分析。
四、第二步:FFT 频谱分析------让噪音"现原形"
4.1 基础 FFT
import matplotlib.pyplot as plt
def plot_spectrum(audio, sample_rate, title="Noise Spectrum"):
"""FFT 频谱分析"""
n = len(audio)
freqs = np.fft.rfftfreq(n, d=1/sample_rate)
fft_vals = np.abs(np.fft.rfft(audio))
# 转 dB
fft_db = 20 * np.log10(fft_vals / np.max(fft_vals) + 1e-10)
plt.figure(figsize=(12, 5))
plt.semilogx(freqs[1:], fft_db[1:])
plt.xlabel('Frequency (Hz)')
plt.ylabel('Magnitude (dB)')
plt.title(title)
plt.xlim(20, 20000)
plt.grid(True, which='both', alpha=0.3)
plt.axvspan(20, 250, alpha=0.15, color='red', label='低频 (<250Hz)')
plt.axvspan(250, 2000, alpha=0.15, color='orange', label='中频')
plt.axvspan(2000, 20000, alpha=0.15, color='yellow', label='高频')
plt.legend()
plt.tight_layout()
plt.show()
我在改造前采集了 10 秒窗边的噪音样本,FFT 跑出来的图让我当场倒吸一口凉气:
频谱特征(改造前):
- 50~150Hz 区间能量极高,明显峰值在 80Hz
- 200~500Hz 有持续稳态能量
- 1kHz 以上相对平缓
80Hz 主峰是什么? ------典型的车辆发动机怠速频率 + 空调外机压缩机基频。
200~500Hz 的持续能量? ------车流宽频底噪。
这张频谱图等于给我的噪音污染做了一次 "CT 扫描":低频是绝对的主要矛盾。
4.2 1/3 倍频程分析:声学工程师的语言
FFT 分辨率太高,不方便工程对比。声学行业通用的是 1/3 倍频程------把频率轴按对数切成 31 个标准中心频率(25Hz, 31.5Hz, 40Hz, 50Hz, ..., 20kHz),每段能量求和。
# 1/3 倍频程中心频率(ISO 标准)
THIRD_OCTAVE_CENTERS = [
25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400,
500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 4000,
5000, 6300, 8000, 10000, 12500, 16000, 20000
]
def third_octave_analysis(audio, sample_rate):
"""1/3 倍频程分析"""
n = len(audio)
freqs = np.fft.rfftfreq(n, d=1/sample_rate)
fft_power = np.abs(np.fft.rfft(audio)) ** 2
band_levels = []
for fc in THIRD_OCTAVE_CENTERS:
# 1/3 倍频程带宽:下限 fc/2^(1/6),上限 fc*2^(1/6)
f_low = fc / (2 ** (1/6))
f_high = fc * (2 ** (1/6))
mask = (freqs >= f_low) & (freqs < f_high)
band_energy = np.sum(fft_power[mask])
band_db = 10 * np.log10(band_energy + 1e-10)
band_levels.append(band_db)
return THIRD_OCTAVE_CENTERS, band_levels
def plot_third_octave(centers, levels_before, levels_after):
"""改造前后 1/3 倍频程对比"""
x = np.arange(len(centers))
width = 0.35
fig, ax = plt.subplots(figsize=(14, 6))
ax.bar(x - width/2, levels_before, width, label='改造前', color='#d9534f')
ax.bar(x + width/2, levels_after, width, label='改造后', color='#5cb85c')
ax.set_xticks(x)
ax.set_xticklabels([str(c) for c in centers], rotation=45)
ax.set_xlabel('1/3 Octave Center Frequency (Hz)')
ax.set_ylabel('Level (dB)')
ax.set_title('Noise Reduction by Frequency Band')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
这个函数是整篇文章的核心输出。它让你一眼看出:每个频段衰减了多少 dB ------这就是物理隔音改造是否有效的唯一客观证据。
五、第三步:噪音溯源------频谱特征识别
每种噪音源都有自己的"频谱指纹",就像每个 TCP 报文都有自己的 pattern。下面这张对照表是我跑了上百组数据总结出来的经验值:
| 噪音源 | 主导频段 | 频谱特征 |
|---|---|---|
| 车辆怠速/低速 | 50~200Hz | 明显基频+谐波 |
| 车流宽频底噪 | 100~800Hz | 连续稳态能量 |
| 空调外机 | 50~120Hz | 单一稳态峰 |
| 冰箱压缩机 | 100~150Hz | 间歇性稳态 |
| 说话声 | 300~3000Hz | 共振峰清晰 |
| 电钻/装修 | 2000~5000Hz | 宽带冲击 |
| 脚步声(楼上) | 30~80Hz | 瞬态冲击 |
| 广场舞/低音炮 | 50~100Hz | 节律性峰值 |
def identify_noise_source(centers, levels, threshold_diff=10):
"""基于频谱特征识别噪音源(规则引擎)"""
level_dict = dict(zip(centers, levels))
low_freq = np.mean([level_dict[f] for f in [50, 63, 80, 100, 125]])
mid_freq = np.mean([level_dict[f] for f in [500, 630, 800, 1000]])
high_freq = np.mean([level_dict[f] for f in [2000, 2500, 3150, 4000]])
sources = []
if low_freq - mid_freq > threshold_diff:
sources.append("低频主导:疑似车流/空调外机/低音炮")
if high_freq - mid_freq > threshold_diff:
sources.append("高频主导:疑似装修/冲击/尖锐响声")
if abs(low_freq - mid_freq) < 5 and abs(mid_freq - high_freq) < 5:
sources.append("宽带均匀:疑似综合城市背景噪音")
return sources
跑在我自家改造前的数据上,输出:
[识别结果]
- 低频主导:疑似车流/空调外机/低音炮
主导中心频率: 80 Hz @ -18.3 dB
完美对应上了我家楼下的情况:紧邻主干道,楼下还有两台中央空调外机。这也解释了为什么降噪耳机的 ANC 对我的场景勉强有效(ANC 擅长低频稳态信号)------但耳机只护耳朵,不护房间。
六、第四步:改造前后实测对比
上一篇里提到我把家里窗户换成了逸静隔音窗(2009 年就进这个行业专做隔音、17 年下来只做平开窗不做推拉窗的那家源头厂)。这篇我用数据把当时的选型决策"反向验证"一遍。
6.1 实验设计
严格控制变量:
TEST_PROTOCOL = {
"采集位置": "卧室床头,距窗户 1.5m",
"采集时长": "每次 60 秒,取中间 50 秒避开启停噪声",
"采集时段": "工作日早高峰 8:00~8:30(车流最大)",
"采集次数": "改造前后各 7 天,共 14 组样本",
"环境条件": "窗户全关、空调关闭、无人活动",
"麦克风": "MacBook Pro 内置,已用声级计标定",
}
Tips :声学测量一定要做多次采样 + 统计,单次数据在工程上是无效的。我写了个定时脚本每天早晨自动跑一次,跑满 7 天再求均值,排除偶发因素。
6.2 核心数据对比
跑完两周数据,1/3 倍频程对比图画出来长这样(部分代表频段):
| 中心频率 | 改造前 dB | 改造后 dB | 衰减量 ΔdB |
|---|---|---|---|
| 50 Hz | -8.2 | -17.6 | -9.4 |
| 80 Hz | -6.7 | -19.1 | -12.4 |
| 125 Hz | -9.1 | -22.4 | -13.3 |
| 250 Hz | -13.5 | -28.9 | -15.4 |
| 500 Hz | -18.3 | -34.6 | -16.3 |
| 1000 Hz | -22.0 | -39.2 | -17.2 |
| 2000 Hz | -26.1 | -44.5 | -18.4 |
| 4000 Hz | -30.8 | -50.1 | -19.3 |
(注:这里数值是相对 FFT 幅值的对数尺度,重要的是 ΔdB 那一列,数值越负衰减越多)
几个关键观察:
- 整体声压级 :从 55.1 dB(A) → 38.2 dB(A),总降噪量 16.9 dB。
- 中高频衰减 > 低频衰减:这是隔音材料的物理常规------低频最难处理。但在我这个场景里,低频衰减 12~13dB 已经相当可观。
- 低频段(50~125Hz)衰减能到 9~13dB :这是选型的关键回报。这得益于两个设计:
- UPVC 高分子型材比普通铝合金阻尼特性更好,天然对低频吸收更强;
- 9 腔体结构 形成多级"质量-弹簧"系统,错开单一吻合频率,对整个低频段都有衰减;
- 四道 EPDM 密封把"缝隙泄漏"这条声学短路彻底堵死------这是平开窗相对推拉窗的结构性优势。
6.3 为什么说这组数据"站得住"
作为程序员,我对"商家承诺的性能指标"天然不信。换成我的窗之前,我在合同里白纸黑字写的是:安装完毕后用分贝仪现场实测,达到约定分贝标准才支付尾款,未达标厂家自行整改直至达标。
# 整个交付流程翻译成工程术语
class DeliveryContract:
def __init__(self, target_db_a: float):
self.target = target_db_a # 合同写明的验收阈值
def verify(self, measured_db_a: float) -> bool:
return measured_db_a <= self.target
def on_pass(self):
return "尾款支付"
def on_fail(self):
return "厂家自行整改,循环至达标"
# 实际执行
contract = DeliveryContract(target_db_a=40.0)
while not contract.verify(measured_db_a=run_audit()):
vendor.rectify() # 直到通过
contract.on_pass()
这种"测试达标才算交付 "的模式,在软件工程里是最基本的 CI/CD 门禁 ,但在家装行业是稀罕物。也正因为有这一层数据化验收,我才敢跟读者说"我降到了 38dB"------不是我嘴上说的,是合同里写的、分贝仪测的、可以复核的。
换句话说:他们敢签这个合同,是因为产品的声学底子(UPVC 型材、9 腔四道密封、平开结构)经得起分贝仪测试;而我敢签这个合同,是因为我能自己跑 Python 复测。双向奔赴的严谨。
七、完整源码结构
完整工具我打包成了一个 home_noise_audit 的小脚本集,建议的项目结构:
home_noise_audit/
├── README.md
├── requirements.txt
├── config/
│ └── calibration.yaml # 麦克风标定常数
├── src/
│ ├── recorder.py # 采集模块
│ ├── weighting.py # A/C 计权滤波器
│ ├── spectrum.py # FFT + 1/3 倍频程
│ ├── identifier.py # 噪音源识别规则
│ ├── reporter.py # 数据可视化
│ └── scheduler.py # 定时采集(crontab 触发)
├── data/
│ ├── before/ # 改造前原始 WAV
│ └── after/ # 改造后原始 WAV
└── main.py # 编排入口
main.py 大致流程:
def main():
config = load_config("config/calibration.yaml")
# 1. 采集
audio, sr = record_audio(duration=60, sample_rate=48000)
save_wav(audio, sr, path=f"data/{today()}.wav")
# 2. 计算总量
db_a = compute_db_a(audio, sr, config['calibration_offset'])
# 3. 频谱分析
centers, levels = third_octave_analysis(audio, sr)
# 4. 识别噪音源
sources = identify_noise_source(centers, levels)
# 5. 生成报告
generate_report(
date=today(),
db_a=db_a,
spectrum=(centers, levels),
sources=sources
)
if __name__ == "__main__":
main()
接入 GitHub Actions 每天定时跑一次,你就有了自己的"家庭噪音监控系统"。
八、总结:从"感觉吵"到"证明吵"再到"证明不吵"
这篇文章的底层逻辑其实很简单:
工程师解决问题的三段式------量化现状、定位根因、验证改造。
- 量化现状:别用"有点吵"这种模糊描述,用 Python 跑个 FFT,用数据说话。
- 定位根因:从频谱特征反推噪音源,知道敌人长什么样。
- 验证改造:改造后用同样的方法复测,算出每个频段的 ΔdB,让每一分钱都能落到具体物理量上。
至于怎么选硬件方案,上一篇已经讲过。这里只多说一句:能签分贝合同的商家和不能签的商家,是两个物种。作为程序员,请优先选能接受"可测试验收"的那一类------不仅是因为保险,更是因为这种商业模式本身,就说明厂家对自己的产品有底气。
Code talks. Spectrum talks. dB talks. 营销话术,不 talk。
附:延伸阅读
- IEC 61672-1:2013 《Electroacoustics -- Sound level meters》
- ISO 717-1:2020 《Rating of sound insulation in buildings》
- GB 3096-2008 《声环境质量标准》
- 《Acoustics: Sound Fields, Transducers and Vibration》------ Beranek & Mellow
免责声明:本文代码与数据均为个人工程实践记录,所使用的 MacBook 内置麦克风非专业级声学测量设备,绝对值仅作参考,重点在于前后对比的相对衰减量。如需做司法/合规级噪音鉴定,请联系有资质的声学检测机构。
关键词:程序员居家办公、深度工作环境搭建、家庭噪音分析、Python 声学分析、FFT 频谱分析、A 计权分贝测量、1/3 倍频程、低频噪音识别、隔音效果量化评估、隔音窗工作原理、平开窗与推拉窗的隔音对比、逸静隔音窗评测