问题不是声音太小、不是发音不准、不是网络卡顿,而是声音太碎。从任何单点看系统都正常——合成成功、播放成功、电平有值、转写有字——但平台仍判定"这个人没在说话"。
原设计:大模型流式输出,攒到一句结束就拿去合成 → 快、内存小、出错只错一句。在任何人机对话产品里都是好设计。
实测每句 3~10 秒。放进一场对话:说完 → 停 → 合成下一句 → 再播。句尾有衰减、句首有起音、中间还有合成与传输时间。
原本连贯的一段表达 → 变成一串彼此独立的小片。
关键差异:我们看的是句子(每句都完整、都被播了);平台看的是一条连续的音频流——判据是能量的连续性与时长,不看文本。
折磨人的地方:查日志什么都正常,平台转写里甚至零散记下了我们说的字。所有单点证据都说"我们说了",只有轮次判定说"你没说"。
错误修法:调大音量 → 换音色 → 查网络。没一个对。
当接收方说"没听到"时,先别急着提高音量,先看发出去的东西在时间上长什么样。
分工调整:服务端只下发文字,一个字都不合成。两个关键词:并行 + 连续。
answer_start / answer(delta) / answer_end → speakQueue;bridgePump 每 500ms 轮询;setSinkId 定向进 CABLE InputBRIDGE_TTS_ENGINE=edge / BRIDGE_TTS_VOICE=zh-CN-YunxiNeural / BRIDGE_TTS_TRIES=2第一步不是开始合成,而是先把服务端配音关掉(/api/tts/enabled=false)。两边都合成 = 两路音频同时灌进虚拟声卡 = 彻底混乱。
固定顺序:先挂实时通道监听 → 再关服务端配音 → 才开始本机合成播放。顺序不能反。
接管一件事的第一步是先切断原来的那条路。否则做的不是接管,是叠加。
| # | 副作用 | 处理 |
|---|---|---|
| 1 | 自激:整段连续注入后助手听到自己 | 单线期 pause/resume 压制(代价:听对率 100%→15%~62%)→ 双线后物理隔离,开关改回 false |
| 2 | 进程挂起:setInterval 未 unref() → 批量卡在第 1 场 | 一行修复:bridgeTimer.unref() |
| 3 | 机械音回退 | 明确禁止(BRIDGE_TTS_ALLOW_SAPI_FALLBACK=false)。改为:失败重试 → 用尽则记账不播 |
两道看门狗(防的不是错误,是沉默):合成超时 12000ms 判流坏;播放超时 = 自身时长 ×3 + 10s 则跳过。依据:sim14 实测 5.57s 音频用了 47s;sim15 泵冻结 46s。
新碎片源 = 批次。流式只决定何时开口,不该决定分几批。批次太小(24~30 字 ≈5~7s)还要叠加固定开销(合成 1.3~2.8s + 守门 + 400ms 停顿 + 500ms 泵 tick)→ 批间洞 3~6s,占空比 ~68%。
实测(sim14):平台把同一问题重问 8 遍、11 次打断、53 批作废、546s 只完成 2 轮。
为什么必须引入流式:原开口滞后约 12s(首字 2.5s + 生成 8.4s + 合成 2.5s + 守门 0.5s),而平台静默窗口仅 23.4~26.1s;但 answer_start → 首句成形中位仅 0.54s——等的不是第一句,是整段。
规则:STREAM_FIRST_MIN_CHARS=15(低门槛求快)vs STREAM_NEXT_MIN_CHARS=80(高门槛攒大,≈16s)→ 一轮 192 字只分 2~3 批而非 8 批。
注释原话:不要把这两个值调成接近的——它们相等就等于回到逐句播报。
讲的都是自己这边的音频工程:怎么把话说连贯、什么时候该停、失败怎么办。没有一句话是关于绕过平台判断的——只是让表达方式落在正常人类表达范围内,而不是找漏洞。摄像头始终真人。
README.md §4.5 坑四(每句 3-10 秒 / 桥接 TTS / 3 句 2.0 秒 / smoke16 12.3min·10 轮)、§4.6 坑五(unref() 与批量卡第 1 场);CODEBUDDY.md 桥接 TTS 段;autobot/config.js BRIDGE_TTS_* / BRIDGE_PAUSE_IV / 桥接看门狗 / STREAM_FIRST_MIN_CHARS 与 STREAM_NEXT_MIN_CHARS。