文章
不上云的会议转写:Whisper 与说话人分离
会议录音往往不能发送到外部服务:其中有约定、数字和人名。下面介绍我们如何搭建一套完全在工作电脑上运行的转写流程,以及它最终产出什么。这里描述的是我们自己的工作工具,而不是成品。
流程由哪些环节组成
首先用 ffmpeg 把录音转换为 16 kHz 的 WAV,这是语音识别模型需要的格式。接着由 Whisper large-v3 识别文字。在搭载 Apple Silicon 的 Mac 上,我们使用 mlx-whisper 版本,它通过 Metal 在 GPU 核心上运行。模型会自动识别语言,也可以手动指定。
说话人分离(diarization)是单独的一步,由 pyannote speaker-diarization-3.1 完成。Whisper 回答“说了什么”,pyannote 回答“谁说的”。两步的结果合并成一份带说话人标签和时间码的文本。
- ffmpeg:任意录音格式 → 16 kHz WAV。
- mlx-whisper,模型 whisper-large-v3:带时间码的文本。
- pyannote speaker-diarization-3.1:按说话人划分的片段。
- 输出:供阅读的 transcript.md 和供后续处理的 transcript.json。
如何离线工作
pyannote 模型托管在 Hugging Face 上,首次下载需要接受使用条款并提供令牌。之后模型保存在本地。我们的工具有离线模式:只要本地存储缺少任何一个模型文件,转写就会报错停止,而不会悄悄尝试下载缺失的部分。
对于没有独立显卡的笔记本,还有另一种方案。在我们的听写助手中,识别要么通过带 GPU 加速的 Whisper 完成,要么使用针对普通 CPU 优化的 Parakeet 模型。识别之前,语音活动检测(VAD)会去掉静音:多余的计算更少,空白片段上凭空出现的“幽灵”句子也更少。
识别熟悉的声音
pyannote 本身不知道名字:它输出 SPEAKER_00、SPEAKER_01 之类的标签。每次手动标注说话人很麻烦,所以我们维护一个声纹库。pyannote 为每位说话人给出一个平均声音向量,再用余弦相似度与声纹库比对。
只有当最佳候选的得分不低于 0.5,并且比第二名至少高出 0.1 时,才会标上名字。否则保留 SPEAKER_XX:诚实的标签胜过自信地标错人。每个人的声音只需录入一次,依据一份已经标注好的录音。如果已知参会人数,可以把它作为提示提供给说话人分离。
- 相似度阈值:默认 0.5。
- 领先第二名的差距:至少 0.1。
- 未识别的声音保留技术标签。
- 可以重新做说话人分离,而无需重新识别文字。
从转写到决定和任务
原始转写还不是会议纪要。得出结论之前,我们会标记谁加入、谁离开,把可靠的发言与静音、重叠和识别重复区分开。每个结论都保留时间码,以便对照录音核实。识别置信度低的片段可以变成待澄清的问题,但不能作为分配给具体某人的任务依据。
最常见的错误是把任何将来时的句子都记成决定。我们区分几个层级:提到了一个想法、未获同意的提议、有负责人和期限的已接受的下一步、有条件的行动,以及最终决定。例如,“如果预算获批,我们就注册”是有条件的目标,而不是决定。
- 讨论:“也许可以……”,没有负责人和期限。
- 提议:“我建议……”,尚未被接受。
- 下一步:有负责人、成果和日期。
- 条件:只有在指定事件发生后才行动。
- 决定:没有未决条件的最终选择。
结果存放在哪里
转写内容和声纹都是敏感数据。在我们的工具中,每个项目的数据、访问凭据和结果分开存放,处理只能在明确指定项目配置的情况下运行。内部门户由经过核实的记录——决定、任务、会议——构建,而不是由原始转写构建。
如果您要在自己那里搭建类似流程,请提前决定:录音存放在哪里、保存多久,谁能看到完整转写、谁只能看到最终任务,以及谁在发送前审核会议纪要。