双人访谈说话人分离实测失效:138 分钟节目 91.9% 语音塌缩进单一说话人
环境
- macOS (Apple Silicon M2)
- funasr 1.4.2,说话人模型 CAM++,VAD
fsmn-vad
- 输入:小宇宙单集,138 分钟,女主持人 × 男嘉宾双人访谈
复现步骤
python3 ~/.claude/skills/video-transcript/scripts/transcript.py \
"https://www.xiaoyuzhoufm.com/episode/6a15a2cbff7b9a8c0a5b953f"
小宇宙单集链接自动启用 --speakers 模式。转录完成后打开 *_逐字稿.md。
现象
整场双人访谈的每一句话都被标成单一「说话人 A」,说话人分离完全没有发生。README 宣称的"自动分离,映射成主持人 / 嘉宾"在此场景失效。
数据证据(transcription.json,共 4185 段 / 138.3 分钟音频)
| Speaker |
段数 |
总时长 |
时间跨度 |
平均段长 |
| Speaker 3 |
3838 |
117.7 分(91.9%) |
1–137 分 |
1.8 秒 |
| Speaker 4 |
281 |
8.1 分 |
1–137 分 |
1.7 秒 |
| Speaker 6 |
27 |
0.9 分 |
71–72 分 |
2.1 秒 |
| Speaker 2 |
18 |
0.8 分 |
0–1 分 |
2.7 秒 |
| Speaker 5/7/1/0 |
21 |
<1 分 |
散落/片尾 |
— |
两个关键事实:
- 被分出来的 Speaker 4 确实是主持人的语音(如"你说的是你受到了播客的影响还是你""个 open i 在做 agent 的一个研究员我说哎这个好"),证明 CAM++ 有能力产出区分,但 91.9% 的语音(含大部分主持人发言)仍被归进主簇。
- 其余小簇全部集中在声学突变处:冷开场念白(0–1 分)、一个 1 分钟异常窗口(71–72 分)、片尾(137–138 分)。CAM++ 只敢在声学条件剧烈变化的地方新建簇,主体对话全程无法区分两人。
根因分析(两层叠加)
① 上游:CAM++ 聚类在长音频上全局塌缩。
diarize_asr.py:176 的注释写明设计取舍:
"不切块是有意的:CAM++ 的说话人编号只在单次推理内一致,分块会让同一个人在不同块里拿到不同编号。"
于是整段 2h18m 单次推理跑完。代价是 embedding 在 2 小时尺度上漂移 + 聚类阈值过松 → 全局过合并:防住了"同一人分块后编号不一致"(分身),却挡不住"不同人被吸进同一簇"(塌缩)。播客常见的抢话、重叠语音、短应答("嗯嗯/对")被 VAD 切碎后也加速了这个塌缩。
② 下游放大:后处理的"单口检测"把残存分离也抹掉了。
speaker_postprocess.py:388:
# 单口:第一说话人 ≥ 90%
if len(ordered) == 1 or stats.get(ordered[0], 0) / total >= 0.90:
mapping = {spk: name for spk in labels} # 全部映射成"说话人 A"
主簇占比 91.9% ≥ 90% → 判定为"单口播客",8 个标签全部映射成「说话人 A」。这个阈值无法区分"本来就是单口"和"分离失败导致的假单口"——CAM++ 残存分出的 281 段真实主持人语音被这行逻辑直接擦掉。
建议修复(按成本排序)
- 应急:后处理加
--keep-speakers / --num-speakers 2 开关,跳过 ≥90% 单口分支,至少保住残存的分离结果。
- 中等:暴露
num_speakers(或聚类距离阈值)给 CAM++ 聚类,对已知双人节目显式约束簇数。
- 治本:改为"分块局部 diarization + 跨块 speaker 匹配合并"——按 5–10 分钟切块、块内聚类、块间用 embedding 相似度融合说话人编号,可同时解决"分身"与"塌缩"两个问题;或评估 pyannote 等专为长音频设计的方案。
附加说明
- 单人独播/课程录音不受影响(单说话人场景恰好命中单口分支)。
- 该功能是播客链路的主打卖点,双人访谈是最常见的使用场景之一,当前实测不可用,建议在 README 中注明局限或在修复前降低该特性的宣传口径。
双人访谈说话人分离实测失效:138 分钟节目 91.9% 语音塌缩进单一说话人
环境
fsmn-vad复现步骤
小宇宙单集链接自动启用
--speakers模式。转录完成后打开*_逐字稿.md。现象
整场双人访谈的每一句话都被标成单一「说话人 A」,说话人分离完全没有发生。README 宣称的"自动分离,映射成主持人 / 嘉宾"在此场景失效。
数据证据(transcription.json,共 4185 段 / 138.3 分钟音频)
两个关键事实:
根因分析(两层叠加)
① 上游:CAM++ 聚类在长音频上全局塌缩。
diarize_asr.py:176的注释写明设计取舍:于是整段 2h18m 单次推理跑完。代价是 embedding 在 2 小时尺度上漂移 + 聚类阈值过松 → 全局过合并:防住了"同一人分块后编号不一致"(分身),却挡不住"不同人被吸进同一簇"(塌缩)。播客常见的抢话、重叠语音、短应答("嗯嗯/对")被 VAD 切碎后也加速了这个塌缩。
② 下游放大:后处理的"单口检测"把残存分离也抹掉了。
speaker_postprocess.py:388:主簇占比 91.9% ≥ 90% → 判定为"单口播客",8 个标签全部映射成「说话人 A」。这个阈值无法区分"本来就是单口"和"分离失败导致的假单口"——CAM++ 残存分出的 281 段真实主持人语音被这行逻辑直接擦掉。
建议修复(按成本排序)
--keep-speakers/--num-speakers 2开关,跳过 ≥90% 单口分支,至少保住残存的分离结果。num_speakers(或聚类距离阈值)给 CAM++ 聚类,对已知双人节目显式约束簇数。附加说明