说话人分离(Diarization)技术实践:让 AI 知道一段录音里究竟是谁在说话
摘要: ASR 可以把语音转成文字,但面对多人会议、客服通话时,还有一个问题:这些话分别是谁说的?说话人分离(Speaker Diarization)就是为解决这个问题而设计的。它利用语音分割、声纹特征提取与聚类,将不同说话人的发言划分出来。本文以开源 pyannote.audio Community-1 为例,通过会议录音分析和多语言客服通话转写两个案例,介绍如何使用 Python 为录音添加说话人标签。
一、什么是说话人分离?
假设我们录制了一场三人会议:
张三:我们下个月准备上线新系统。
李四:测试工作已经完成了吗?
张三:目前还差最后一轮验证。
王五:那我来跟进剩下的问题。
普通 ASR 可能只会输出连续的文字,无法告诉我们每句话由谁说出。
Diarization 要做的,就是把它们拆开:
00:01 - 00:04 SPEAKER_00
00:05 - 00:07 SPEAKER_01
00:08 - 00:11 SPEAKER_00
00:12 - 00:15 SPEAKER_02
注意,模型输出的 SPEAKER_00、SPEAKER_01 只是匿名说话人编号。
Diarization 能判断哪些话来自同一个人,但不知道这个人叫张三还是李四。
如果需要识别真实身份,还要结合已授权的声纹注册数据或其他身份信息。
1.1 它和 VAD、ASR 有什么区别?
| 模型 | 核心问题 | 输出 |
|---|---|---|
| VAD | 什么时候有人说话? | 语音时间段 |
| ASR | 说了什么? | 文字 |
| Diarization | 谁在什么时候说话? | 时间段 + 说话人编号 |
| Speaker Recognition | 说话人是否为已知人员? | 身份匹配结果 |
这些任务经常被组合使用,但并不是同一种技术。
1.2 核心原理
目前比较典型的说话人分离流水线可以分为四个阶段:
原始录音
│
▼
语音活动检测与分割
│
▼
提取说话人 Embeddings
│
▼
相似度比较与聚类
│
▼
生成说话人时间轴
这里又遇到了之前介绍过的 Embeddings。
但这次并不是文本 Embeddings,而是 Speaker Embeddings(说话人声纹向量)。
模型会从语音中提取音色、声道共振特征、发声习惯等声学信息,将其映射成向量。
随后,利用聚类算法把相似的语音片段归为同一个说话人。
这也解释了为什么 Diarization 可以支持多语言。
它主要比较的是“声音像不像同一个人”,而不是“这个人说了什么词”。
即使一位用户上一句说中文、下一句说英文,模型理论上仍然可以将其归为同一个说话人。
不过,多语言兼容并不代表在所有语言、口音和录音环境中具有相同准确率。
二、选择哪个开源模型?
目前可以关注两条主要技术路线:
| 方案 | 技术特点 | 适合场景 |
|---|---|---|
| pyannote Community-1 | 语音分割、Speaker Embeddings、聚类流水线 | 多语言会议、客服录音 |
| NVIDIA Sortformer | 端到端 Transformer 说话人分离 | 多人语音、实时应用研究 |
本文选择 pyannote/speaker-diarization-community-1。
它于 2025 年随 pyannote.audio 4.0 发布,具有几个值得关注的特点:
- 不要求输入指定语言,支持跨语言说话人分离。
- 支持普通说话人时间轴与重叠发言检测。
- 可以指定说话人数量或数量范围。
- 支持 CPU、GPU 和离线部署。
- 提供专门用于 ASR 时间戳对齐的
exclusive_speaker_diarization。
官方在中文语音数据集上公布过以下评估结果:
| 中文数据集 | 旧版 3.1 DER | Community-1 DER |
|---|---|---|
| AISHELL-4 | 12.2% | 11.7% |
| AliMeeting(单通道) | 24.5% | 20.3% |
DER(Diarization Error Rate)是说话人分离错误率,越低越好。这里的数据来自官方公开基准测试,包含重叠语音评估,并不代表所有实际录音都能达到同样效果。
官方项目:
Community-1 模型采用 CC-BY-4.0 许可,首次下载需要登录 Hugging Face 并接受模型页面的访问条件。
三、实践案例一:自动分析多人会议录音
3.1 业务场景
假设我们有一段 30 分钟的中文会议录音,希望知道:
- 会议中有几个人发言?
- 每个人什么时候开始和停止发言?
- 哪些语音片段属于同一个人?
这类场景不需要立即运行 ASR,先完成说话人分离即可。
3.2 环境准备
建议使用 Python 3.11 或更高版本。
安装 FFmpeg,并确认 PyTorch 与自己的 CPU 或 CUDA 环境兼容,然后安装:
pip install "pyannote.audio>=4,<5"
到 Hugging Face 的 Community-1 模型页面接受访问条件,然后创建一个具有模型读取权限的 Access Token。
设置环境变量:
HF_TOKEN=你的HuggingFace访问令牌
假设准备的录音是 meeting.wav。
如果是 MP3,可以先转成 16kHz 单声道:
ffmpeg -i meeting.mp3 -ar 16000 -ac 1 meeting.wav
实际上 Community-1 可以自动完成重采样和声道混合,这里只是让输入更统一。
3.3 Python 完整实现
import os
import torch
from pyannote.audio import Pipeline
# 1. 加载开源说话人分离模型
pipeline = Pipeline.from_pretrained(
"pyannote/speaker-diarization-community-1",
token=os.environ["HF_TOKEN"]
)
# 2. 有GPU则使用GPU
if torch.cuda.is_available():
pipeline.to(torch.device("cuda"))
# 3. 处理会议录音
# 假设会议有2~5名发言人
output = pipeline(
"meeting.wav",
min_speakers=2,
max_speakers=5
)
# 4. 输出说话人时间轴
for turn, _, speaker in (
output.speaker_diarization.itertracks(
yield_label=True
)
):
print(
f"{turn.start:.2f}s - "
f"{turn.end:.2f}s | {speaker}"
)
# 5. 导出标准RTTM文件
with open("meeting.rttm", "w", encoding="utf-8") as f:
output.speaker_diarization.write_rttm(f)
假设这段会议确实有三个人,理想输出可能如下:
1.20s - 4.50s | SPEAKER_00
4.80s - 7.30s | SPEAKER_01
7.50s - 11.20s | SPEAKER_00
11.50s - 15.80s| SPEAKER_02
以上为演示结果,真实输出由录音决定。
这里有两个比较实用的参数:
# 已知只有两名说话人
output = pipeline("meeting.wav", num_speakers=2)
# 不确定人数,但估计有2~5人
output = pipeline(
"meeting.wav",
min_speakers=2,
max_speakers=5
)
对于只有客服和客户两人的电话录音,明确传入 num_speakers=2 往往是值得尝试的优化,但前提是录音确实只有两名说话人。
输出的 RTTM 是说话人分离领域常用的标注格式,可以用于后续可视化或评测。
四、实践案例二:给中英文客服通话自动标注说话人
4.1 业务场景
第二个案例更接近完整的语音 AI 应用。
假设客服通话中出现了中英文混合:
客服:您好,请问有什么可以帮助您?
客户:I want to check my refund status.
客服:好的,请提供您的订单编号。
我们希望最后得到一份这样的记录:
[00:01] SPEAKER_00:您好,请问有什么可以帮助您?
[00:04] SPEAKER_01:I want to check my refund status.
[00:08] SPEAKER_00:好的,请提供您的订单编号。
只使用 Diarization 不够,因为它不会识别文字。
只使用 ASR 也不够,因为它不一定知道不同句子属于谁。
因此需要把两者结合起来。
4.2 整体架构
中英文客服录音
│
├─────────────┐
▼ ▼
pyannote Whisper ASR
│ │
▼ ▼
说话人时间轴 文字 + 时间戳
│ │
└──────┬──────┘
▼
时间轴对齐
│
▼
带说话人的转写记录
这里有一个难点:ASR 的文字时间戳与 Diarization 的说话人分段,并不一定完全一致。
Community-1 专门提供了一个 exclusive_speaker_diarization 结果。
它与普通分离结果的区别是:每个时刻最多只保留一个说话人。
这样更方便把 ASR 识别出的字词归属到一个人身上。
代价是:两个人同时说话时,互斥结果会丢失部分重叠发言信息。
4.3 Python 实现
为了方便复现,使用开源的 Faster-Whisper 进行 ASR:
pip install faster-whisper
下面的代码沿用前一个案例已经加载的 pipeline。
from faster_whisper import WhisperModel
audio = "call.wav"
# 1. 执行说话人分离
output = pipeline(audio, num_speakers=2)
# 获取互斥说话人时间段
turns = [
(turn.start, turn.end, speaker)
for turn, _, speaker in
output.exclusive_speaker_diarization.itertracks(
yield_label=True
)
]
# 2. 加载多语言ASR
# CPU INT8便于复现,可按设备更换为CUDA
asr = WhisperModel(
"small",
device="cpu",
compute_type="int8"
)
# 3. 识别文字和词级时间戳
segments, info = asr.transcribe(
audio,
word_timestamps=True,
beam_size=5
)
# 4. 按时间重叠程度匹配说话人
def find_speaker(start, end):
scores = {}
for s, e, speaker in turns:
overlap = max(
0.0,
min(end, e) - max(start, s)
)
scores[speaker] = (
scores.get(speaker, 0.0) + overlap
)
if not scores or max(scores.values()) == 0:
return "UNKNOWN"
return max(scores, key=scores.get)
# 5. 按词的时间戳分配说话人
records = []
for segment in segments:
for word in segment.words or []:
speaker = find_speaker(
word.start,
word.end
)
records.append({
"start": word.start,
"speaker": speaker,
"text": word.word
})
# 6. 合并相邻同一说话人的文字
groups = []
for record in records:
if groups and groups[-1]["speaker"] == record["speaker"]:
groups[-1]["text"] += record["text"]
else:
groups.append(record.copy())
for item in groups:
print(
f'[{item["start"]:.2f}s] '
f'{item["speaker"]}: {item["text"].strip()}'
)
这里使用的是最基础的词级时间重叠匹配方法,并不是一个经过独立评测的完整对齐算法。
如果客户和客服同时讲话、ASR 时间戳出现偏移,仍然可能发生说话人归属错误。此外,small 模型对频繁中英混说的识别能力有限,可以根据实际效果换用更大的多语言 ASR。
在正式系统里,还可以把结果进一步交给 LLM,生成客服质检报告、客户诉求摘要,或者统计客服与客户各自的发言时间。
五、实际使用时,有哪些坑?
第一,Diarization 不等于声纹身份认证。
SPEAKER_00 只能说明这些片段被模型判断为同一个说话人。
不同录音中的 SPEAKER_00 并不自动代表同一个真实的人。
第二,多人同时说话是最难处理的情况之一。
当两个人抢话、打断、同时发声时,分离错误率通常会升高。
原始说话人时间轴可以表示重叠发言;但上面的互斥对齐方法会将同一时刻分配给一个人,因此不适合要求完整还原交叠发言的场景。
第三,多语言兼容不等于多语言 ASR。
pyannote 本身主要依靠声学特征,不需要根据中文、英文分别配置模型。
但如果要输出文字,ASR 仍然需要覆盖相应语言。特别是中英文在一句话内交替时,转写效果可能下降。
第四,离线分离与实时分离是不同难度的任务。
本文使用的是完整录音的离线处理方式。
如果想在实时语音客服中判断当前是哪位用户说话,还需要考虑增量聚类、说话人状态保持、历史修正,以及实时延迟。不能把离线处理代码直接当成低延迟流式方案。
5.1 如何评估模型?
最常用的指标是 DER:
[
DER=\frac{FA+MISS+CONF}{TOTAL}
]
其中:
FA:把非语音错误识别成语音的时长。MISS:漏掉真实语音的时长。CONF:将语音归属到错误说话人的时长。TOTAL:真实说话人语音总时长。
DER 越低,代表整体说话人分离效果越好。
但评估时应同时记录是否计算重叠语音、是否使用边界容忍区间,否则不同系统的 DER 不能简单横向比较。
对于客服录音,建议额外关注说话人数量准确率、换人时间点检测,以及最终带说话人标签的转写准确率。
六、总结
说话人分离是一个看起来不起眼,但在实际语音应用中非常关键的技术。
它与前面研究过的模型可以形成如下流程:
多人语音
│
├─────────────┐
▼ ▼
Diarization ASR
│ │
▼ ▼
说话人时间轴 转写文字
│ │
└──────┬──────┘
▼
带说话人标签的文本
│
▼
LLM / 决策模型
│
▼
会议纪要 / 客服质检
本文两个案例说明了它的主要价值:
- 会议录音分析: 自动区分不同发言人的时间段。
- 多语言客服转写: 结合 ASR,为中文、英文等语音内容添加说话人标签。
和上一期介绍的 VAD 不同,Diarization 不只关心有没有人说话,还要尝试判断前后两段话是不是来自同一个人。
最后,可以用三个问题概括这套语音处理体系:
VAD:有人在说话吗?
ASR:他说了什么?
Diarization:这句话是谁说的?
把这三个问题分别解决,再交给 LLM 进行语义理解,才更容易构建一个真正能处理多人对话的语音 AI 系统。
- 点赞
- 收藏
- 关注作者
评论(0)