You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python统计WAV音频可听/非可听频率时域占比实现问题

核心问题说明

你当前用全局FFT统计频率bin数量的计算方式完全不可行,得到的结果和你需要的时域占比没有关联。全局FFT是把整段音频的所有时域信息混合后转换到频域,返回的频率数组长度仅由FFT点数、采样率决定,无法对应到具体时域片段的持续时长,你当前写法算出的结果只是「频率轴上非可听频段的频率点数量占总频率点的比例」,完全无法反映"5秒音频里2秒是可听内容"这类时域维度的统计结果。

正确实现思路

要得到时域维度的频段占比,必须基于短时傅里叶变换(STFT)做分帧检测,核心逻辑是把整段音频切分为多个短时间片段,逐片段判断频段属性后累计时长,步骤如下:

  • 音频预处理:如果输入是多声道WAV,先按通道取均值转为单声道,同时做振幅归一化消除录音电平的影响
  • 分帧加窗:将音频切为长度20ms-50ms的短帧(这个长度是音频检测的常用取值,符合人耳对声音的稳态感知区间),帧之间设置50%重叠减少截断误差,每帧加汉明窗抑制频谱泄漏
  • 逐帧频段判定:对每一帧单独做FFT得到对应频谱,分别计算次声段(<20Hz)、可听段(20Hz-20kHz)、超声段(>20kHz)的能量占比;注意不要直接以"是否存在对应频率点"作为判定标准——正常可听音频的FFT结果也会有少量量化噪声落在非可听段,需要设置判定阈值,比如某类频段能量占该帧总能量的90%以上,就把该帧归属到对应类别
  • 时长累计:帧移对应每帧的实际时间步长(帧移=帧长*(1-重叠率)),统计归属到不同类别的总帧数,乘以帧移对应的时长就是该类别的总持续时间,除以音频总时长即可得到占比
参考实现代码
from scipy.io import wavfile
from scipy.signal import stft
import numpy as np

wav_path = '/path/to/wav/file'
# 可调参数
frame_len_ms = 30  # 单帧时长30ms
overlap_rate = 0.5  # 帧间50%重叠
energy_threshold = 0.9  # 频段能量占比判定阈值

# 读取音频
rate, wav_data = wavfile.read(wav_path, 'rb')
# 多声道转单声道
if len(wav_data.shape) > 1:
    wav_data = np.mean(wav_data, axis=1)
# 振幅归一化
wav_data = wav_data.astype(np.float32) / np.iinfo(wav_data.dtype).max
total_duration = len(wav_data) / rate

# 计算STFT得到时频谱
f, t, Zxx = stft(
    wav_data,
    fs=rate,
    nperseg=int(rate * frame_len_ms / 1000),
    noverlap=int(rate * frame_len_ms / 1000 * overlap_rate),
    window='hamming'
)
# 计算每个时频点的能量
frame_energy = np.abs(Zxx) ** 2
total_energy_per_frame = np.sum(frame_energy, axis=0)

# 标记不同频段的频率索引
infrasonic_mask = f < 20
audible_mask = (f >= 20) & (f <= 20e3)
ultrasonic_mask = f > 20e3

# 逐帧计算各频段能量占比,加1e-8避免除零错误
infrasonic_ratio = np.sum(frame_energy[infrasonic_mask, :], axis=0) / (total_energy_per_frame + 1e-8)
audible_ratio = np.sum(frame_energy[audible_mask, :], axis=0) / (total_energy_per_frame + 1e-8)
ultrasonic_ratio = np.sum(frame_energy[ultrasonic_mask, :], axis=0) / (total_energy_per_frame + 1e-8)

# 统计各类型有效帧数
infrasonic_frames = np.sum(infrasonic_ratio >= energy_threshold)
audible_frames = np.sum(audible_ratio >= energy_threshold)
ultrasonic_frames = np.sum(ultrasonic_ratio >= energy_threshold)
# 单帧对应的实际时长(帧移步长)
frame_step = frame_len_ms / 1000 * (1 - overlap_rate)

# 输出统计结果
print(f"音频总时长: {total_duration:.2f}s")
print(f"次声段时长: {infrasonic_frames*frame_step:.2f}s, 占比: {infrasonic_frames*frame_step/total_duration*100:.1f}%")
print(f"可听段时长: {audible_frames*frame_step:.2f}s, 占比: {audible_frames*frame_step/total_duration*100:.1f}%")
print(f"超声段时长: {ultrasonic_frames*frame_step:.2f}s, 占比: {ultrasonic_frames*frame_step/total_duration*100:.1f}%")
注意事项
  • 如果音频采样率低于40kHz,根据奈奎斯特采样定理,音频本身不会包含20kHz以上的频率分量,此时超声段占比恒为0,不需要额外计算
  • 能量阈值可以根据实际场景调整,如果音频本身底噪较高,可以适当降低阈值,避免把有效内容误判为混合片段
  • 后续开发语音/非语音占比统计函数时可以复用这套分帧逻辑,只需要把频段判定规则替换为语音对应的特征规则即可——比如语音核心能量集中在300Hz-3400Hz,也可以搭配过零率等特征提升判定准确率,按同样的帧统计逻辑就能得到时域占比结果。

内容的提问来源于stack exchange,提问作者Jana.k

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 11:45:31