说话人分离

识别多说话人录音中"谁在什么时间说话"。提供两种说话人分离引擎:两阶段的 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说话人嵌入引擎:mlxcoreml(仅 pyannote)
--vad-filter先用 Silero VAD 预过滤(仅 pyannote)
--rttm以 RTTM 格式输出
--jsonJSON 输出格式
--score-against用于 DER 评估的参考 RTTM 文件
重要

说话人分离在有清晰发言轮次的录音上效果最佳。高度重叠的语音可能会降低精度。说话人数量由系统自动判断。

模型下载

模型会在首次使用时自动下载:

组件模型大小HuggingFace
分段Pyannote-Segmentation-3.0~5.7 MBaufklarer/Pyannote-Segmentation-MLX
说话人嵌入WeSpeaker-ResNet34-LM (MLX)~25 MBaufklarer/WeSpeaker-ResNet34-LM-MLX
说话人嵌入WeSpeaker-ResNet34-LM (CoreML)~25 MBaufklarer/WeSpeaker-ResNet34-LM-CoreML
SortformerSortformer Diarization (CoreML)~240 MBaufklarer/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
)