说话人分离
识别多说话人录音中"谁在什么时间说话"。提供两种说话人分离引擎:两阶段的 Pyannote 流水线(分段 + 基于活动的说话人串联,再进行事后 embedding 提取)和端到端的 Sortformer 模型(CoreML,Neural Engine)。
引擎
通过 --engine pyannote(默认)或 --engine sortformer 选择引擎。
Pyannote(默认)
两阶段流水线:Pyannote 分段模型处理重叠窗口,通过基于活动的说话人串联(在重叠区使用皮尔逊相关系数)为每个说话人分配全局标签。事后提取 WeSpeaker 嵌入向量,可通过 enrollment 音频实现目标说话人识别。
Sortformer (CoreML)
NVIDIA 的端到端神经分离模型。直接预测最多 4 个说话人的逐帧活动,不需要单独的 embedding 或聚类阶段。通过 CoreML 在 Neural Engine 上运行,带流式状态缓冲(FIFO + 说话人缓存)。
Sortformer 不产生说话人嵌入向量。--target-speaker 和 --embedding-engine 标志仅在 Pyannote 引擎中可用。
Pyannote 流水线
默认流水线分为两个阶段运行:
阶段 1:分段 + 说话人串联
Pyannote segmentation-3.0 以 50% 重叠处理 10 秒滑动窗口。powerset 解码器将 7 类输出转换为每个说话人的概率(每窗口最多 3 个局部说话人)。相邻窗口共享 5 秒重叠区——通过在重叠区上对概率轨计算皮尔逊相关系数,并采用贪心互斥匹配,在窗口之间传播说话人身份,从而得到一致的全局说话人 ID。
阶段 2:事后 embedding
分离完成后,WeSpeaker ResNet34-LM 为每个说话人提取一个 256 维的 centroid embedding。这些 embedding 支持目标说话人提取(--target-speaker),但不驱动说话人分配本身。
CLI 使用
# 基本分离(默认 pyannote)
.build/release/audio diarize meeting.wav
# 端到端 Sortformer (CoreML)
.build/release/audio diarize meeting.wav --engine sortformer
# RTTM 输出格式(用于评估)
.build/release/audio diarize meeting.wav --rttm
# JSON 输出
.build/release/audio diarize meeting.wav --json
目标说话人提取
提供已知说话人的 enrollment 音频,从录音中只提取他的片段。流水线会计算 enrollment 音频的说话人 embedding,并找到余弦相似度最高的 cluster。
# 提取特定说话人的片段
.build/release/audio diarize meeting.wav --target-speaker enrollment.wav
DER 评分
通过与参考 RTTM 文件对比,评估分离质量。流水线会计算 Diarization Error Rate (DER),用来衡量被错误归属的时间比例。
# 与参考 RTTM 评分
.build/release/audio diarize meeting.wav --score-against reference.rttm
RTTM 输出
--rttm 标志会输出 Rich Transcription Time Marked 格式,是用于说话人分离评估的标准格式。每行格式如下:
SPEAKER filename 1 start_time duration <NA> <NA> speaker_id <NA> <NA>
选项
| 选项 | 说明 |
|---|---|
--target-speaker | 目标说话人提取用的 enrollment 音频(仅 pyannote) |
--embedding-engine | 说话人嵌入引擎:mlx 或 coreml(仅 pyannote) |
--vad-filter | 先用 Silero VAD 预过滤(仅 pyannote) |
--rttm | 以 RTTM 格式输出 |
--json | JSON 输出格式 |
--score-against | 用于 DER 评估的参考 RTTM 文件 |
说话人分离在有清晰发言轮次的录音上效果最佳。高度重叠的语音可能会降低精度。说话人数量由系统自动判断。
模型下载
模型会在首次使用时自动下载:
| 组件 | 模型 | 大小 | HuggingFace |
|---|---|---|---|
| 分段 | Pyannote-Segmentation-3.0 | ~5.7 MB | aufklarer/Pyannote-Segmentation-MLX |
| 说话人嵌入 | WeSpeaker-ResNet34-LM (MLX) | ~25 MB | aufklarer/WeSpeaker-ResNet34-LM-MLX |
| 说话人嵌入 | WeSpeaker-ResNet34-LM (CoreML) | ~25 MB | aufklarer/WeSpeaker-ResNet34-LM-CoreML |
| Sortformer | Sortformer Diarization (CoreML) | ~240 MB | aufklarer/Sortformer-Diarization-CoreML |
Swift API
import SpeechVAD
let pipeline = try await DiarizationPipeline.fromPretrained()
let result = pipeline.diarize(audio: samples, sampleRate: 16000)
for seg in result.segments {
print("Speaker \(seg.speakerId): [\(seg.startTime)s - \(seg.endTime)s]")
}
// 目标说话人提取
let targetEmb = pipeline.embeddingModel.embed(audio: enrollmentAudio, sampleRate: 16000)
let segments = pipeline.extractSpeaker(
audio: meetingAudio, sampleRate: 16000,
targetEmbedding: targetEmb
)