一、音频数据
声音的本质是空气的振动,是一种模拟信号(声波 → 麦克风 → 电信号(连续变化) → 数字信号(离散采样))。
数字音频有三个关键参数:采样率、位深度以及声道数。
- 采样率:每秒采样多少次,16kHz = 每秒16k个点
- 位深度:每个样本用多少位表示。16位表示范围是
(也就是-32768 到 32767)
- 声道数:单声道、双声道(立体声)以及多声道。单声道表示一个声道
数字音频数组:
- 单声道是一维数组,该数组包含时间点和值两个参数,其中时间点是通过采样率决定的(如:16kHz 也就是说0-1秒中的第一个采样点为1/16000),时间点对应的值是通过采样点的振幅 * 位深度的最大值得到的。
python
# 创建一段简单的正弦波(模拟人的声音)
import matplotlib.pyplot as plt
import numpy as np
# 生成一个 440Hz 的正弦波(标准A音)
sample_rate = 16000
duration = 0.1 # 100毫秒
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
frequency = 440 # 440Hz(音高A)
audio_wave = 0.5 * np.sin(2 * np.pi * frequency * t)
# 可视化
plt.figure(figsize=(10, 4))
plt.plot(t[:100], audio_wave[:100]) # 只看前100个点
plt.title("音频波形(正弦波)")
plt.xlabel("时间 (秒)")
plt.ylabel("振幅")
plt.grid(True)
plt.show()
- 多声道至少是二维数组,第一维度表示时间,第二维度表示声道,其中的值就是该时间点不同声道的值。
python
import numpy as np
import matplotlib.pyplot as plt
import soundfile as sf
# 参数设置
sample_rate = 16000
duration = 0.1 # 100毫秒
num_samples = int(sample_rate * duration)
# 生成时间序列
t = np.linspace(0, duration, num_samples, endpoint=False)
# 创建双声道音频
# 左声道:440Hz 正弦波
frequency_left = 440 # A音
left_channel = 0.5 * np.sin(2 * np.pi * frequency_left * t)
# 右声道:523.25Hz 正弦波(C音,比左声道高)
frequency_right = 523.25
right_channel = 0.3 * np.sin(2 * np.pi * frequency_right * t)
# 组合成双声道音频(形状:[样本数, 2])
stereo_audio = np.column_stack((left_channel, right_channel))
print("双声道音频形状:", stereo_audio.shape) # 应该是 (1600, 2)
print(f"总样本数: {num_samples}")
print(f"左声道数据类型: {left_channel.dtype}")
print(f"右声道数据类型: {right_channel.dtype}")
print(f"立体声音频数据类型: {stereo_audio.dtype}")
# 可视化双声道音频
fig, axes = plt.subplots(3, 1, figsize=(12, 8))
# 左声道波形
axes[0].plot(t[:200], left_channel[:200], 'b-', linewidth=1.5)
axes[0].set_title(f'左声道 (左耳) - {frequency_left}Hz 正弦波', fontsize=12)
axes[0].set_xlabel('时间 (秒)')
axes[0].set_ylabel('振幅')
axes[0].grid(True, alpha=0.3)
axes[0].set_xlim([0, 0.0125]) # 只看前12.5ms
# 右声道波形
axes[1].plot(t[:200], right_channel[:200], 'r-', linewidth=1.5)
axes[1].set_title(f'右声道 (右耳) - {frequency_right}Hz 正弦波', fontsize=12)
axes[1].set_xlabel('时间 (秒)')
axes[1].set_ylabel('振幅')
axes[1].grid(True, alpha=0.3)
axes[1].set_xlim([0, 0.0125])
# 双声道对比
axes[2].plot(t[:200], left_channel[:200], 'b-', label='左声道', alpha=0.7)
axes[2].plot(t[:200], right_channel[:200], 'r-', label='右声道', alpha=0.7)
axes[2].set_title('双声道对比', fontsize=12)
axes[2].set_xlabel('时间 (秒)')
axes[2].set_ylabel('振幅')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
axes[2].set_xlim([0, 0.0125])
plt.tight_layout()
plt.show()
二、VAD
VAD本质上是模式识别器:输入:480个数字(30ms音频)-> 特征提取(频谱分析)-> 分类器(机器学习模型)-> 输出:True(语音)或 False(非语音)
| WebRTC VAD (最简模式) | SpeechBrain VAD (深度学习范式) | Silero VAD (现代化接口) | |
|---|---|---|---|
| 核心方法数量 | 1个 is_speech() | 10+个 | 5-8个 |
| 复杂度 | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 学习曲线 | ⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
| 准确性 | 中等(85-92%) | 高(95%+) | 很高(98%+) |
| 速度 | ⭐⭐⭐⭐⭐ 初始化就直接创建检测器对象 | ⭐⭐ 初始化的时候需要下载模型、加载权重、构建网络 | ⭐⭐⭐⭐ 初始化需要下载并加载模型 |
| 资源占用 | ⭐⭐⭐⭐⭐ <1MB | ⭐⭐ 几百MB | ⭐⭐⭐ 10-50MB |
| 部署难度 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 长音频处理 | ⭐⭐⭐⭐⭐ 最稳定 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 噪声鲁棒性 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 多语言支持 | 语言无关 | 语言无关 | 多语言 |
| 可解释性 | 高 参数明确,方便调试 | 低(黑盒) | 中等 |
| 社区生态 | ⭐⭐⭐⭐⭐ 最成熟 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
这三个VAD的使用流程分别为:
- WebRTC VAD:初始化 → 手动预处理 → 手动分帧 → 逐帧检测 → 手动后处理
python
import webrtcvad
# 初始化
vad = webrtcvad.Vad(mode=2) # 一行初始化
# 检测(最基础)
frame_bytes = audio_frame.tobytes() # 必须是16位PCM
is_speech = vad.is_speech(frame_bytes, sample_rate=16000)
# 但实际上需要更多步骤:
def full_webrtcvad_process(audio_data, sample_rate=16000):
"""完整的WebRTC VAD处理流程"""
# 1. 初始化
vad = webrtcvad.Vad(mode=2)
# 2. 音频预处理(必要步骤)
if audio_data.dtype != np.int16:
audio_data = (audio_data * 32767).astype(np.int16)
# 3. 分帧处理(必须)
frame_ms = 30
frame_len = int(sample_rate * frame_ms / 1000)
frames = [audio_data[i:i+frame_len]
for i in range(0, len(audio_data), frame_len)]
# 4. 逐帧检测
results = []
for frame in frames:
if len(frame) == frame_len: # WebRTC严格要求帧长度
is_speech = vad.is_speech(frame.tobytes(), sample_rate)
results.append(is_speech)
# 5. 后处理(通常需要),平滑处理、合并相邻片段、过滤短段、增加边界扩展等
speech_segments = smooth_results(results, frame_ms)
return speech_segments
- SpeechBrain:下载模型 → 初始化 → 一键检测(内置前后处理)
python
from speechbrain.pretrained import VAD
# SpeechBrain有更复杂的流程
def speechbrain_vad_process(audio_path):
"""SpeechBrain VAD处理流程"""
# 1. 初始化(加载模型,可能很重)
# 方式一:使用预训练模型
vad_model = VAD.from_hparams(
source="speechbrain/vad-crdnn-libriparty",
savedir="pretrained_models/vad-crdnn"
)
# 或者方式二:自定义模型
# 需要定义:compute_features()、Encoder()、Decoder()等
# 2. 音频加载和预处理(内置处理)
# SpeechBrain有完整的音频处理管道
audio = vad_model.load_audio(audio_path)
# 3. 检测(单次调用处理整个音频)
boundaries = vad_model.get_speech_segments(
audio_file=audio_path,
large_chunk_size=30, # 分块大小(秒)
small_chunk_size=10, # 内部处理块
overlap_small_chunk=True
)
# 4. 后处理(模型可能内置)
# boundaries已经是处理好的时间区间
return boundaries
# 更细粒度的控制
def advanced_speechbrain_vad():
# 可以访问内部概率
probs = vad_model.get_speech_prob_file(audio_file)
# 然后自己应用阈值
decisions = vad_model.apply_threshold(
probs,
activation_th=0.5, # 阈值
deactivation_th=0.25
).float()
# 再进行边界检测
boundaries = vad_model.get_boundaries(decisions)
return boundaries
- Silero VAD:下载模型 → 初始化 → 灵活检测(部分自动化)
python
import torch
import numpy as np
# Silero VAD的使用方式
def silero_vad_process(audio_data, sample_rate=16000):
"""Silero VAD处理流程"""
# 1. 初始化(需要下载模型)
model, utils = torch.hub.load(
repo_or_dir='snakers4/silero-vad',
model='silero_vad',
force_reload=False
)
# 获取辅助函数
(get_speech_timestamps,
save_audio,
read_audio,
VADIterator,
collect_chunks) = utils
# 2. 音频准备(Silero有专用加载函数)
# 如果从文件开始
wav = read_audio(audio_path, sampling_rate=sample_rate)
# 3. 检测(有多种使用方式)
# 方式一:一次性获取所有时间戳(最简单)
speech_timestamps = get_speech_timestamps(
wav,
model,
sampling_rate=sample_rate,
threshold=0.5, # 检测阈值
min_speech_duration_ms=250,
max_speech_duration_s=float('inf'),
min_silence_duration_ms=100,
window_size_samples=512,
speech_pad_ms=200
)
# 方式二:流式处理(适合实时)
vad_iterator = VADIterator(model, threshold=0.5)
window_size_samples = 512
for i in range(0, len(wav), window_size_samples):
chunk = wav[i: i + window_size_samples]
if len(chunk) < window_size_samples:
break
speech_dict = vad_iterator(chunk, return_seconds=True)
if speech_dict:
print(f"Speech detected: {speech_dict}")
vad_iterator.reset_states() # 重置状态
# 方式三:提取语音片段
speech_chunks = collect_chunks(speech_timestamps, wav)
return speech_timestamps, speech_chunks