Files
lucky-cat/docs/local-speech-endpoint.zh-CN.md
T

118 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本地 VAD 语音端点检测
更新:2026-10-23。适用于 lucky-cat;实现所有权见 [ADR 0006](adr/0006-local-speech-endpoint.md)。
## 当前行为
音频仍然边采集边上传。服务端保持 `push2talk`,设备用本地 VAD 更早决定何时
发送 `StopSpeech`,不切换云端识别模式或修改语音回复语义。
| 配置 | 默认值 | 含义 |
| --- | --- | --- |
| `CONFIG_TQ_SPEECH_ENDPOINT_ENABLE` | y | 仅单击对话模式自动判停,可关闭回退 |
| `CONFIG_TQ_SPEECH_ENDPOINT_MIN_SPEECH_MS` | 200 | 开始判停前需要连续有效人声 |
| `CONFIG_TQ_SPEECH_ENDPOINT_SILENCE_MS` | 900 | 确认人声后,连续 VAD 静音达到该值结束输入 |
| `CONFIG_TQ_PTT_CLICK_CAPTURE_TIMEOUT_MS` | 8000 | 保留原最长窗口兜底 |
本地唤醒仍先完成联网和本地回应提示音,再开始网络拾音和 VAD。按键入口复用
原窗口起点,可能包含连接等待;本次不改变这一点。持续静音或未达到人声阈值的
短促声音不触发提前结束,继续走原超时。阈值以 20 ms 检测帧向上取整。
等待连接、等待提示音和拾音期间,重复按键/唤醒请求沿用当前轮次,不递增
generation、不重置输入窗口,也不重新启用 VAD。收尾期间保留原有下一轮排队机制。
当前单击模式关闭长按识别,长按松开也可能产生 `SHORT_PRESS`;该事件同样受上述
幂等保护,不能让仍在录音的控制层换代并遗留旧代次 VAD。
900 ms 指 VAD 输出连续静音,不等于从物理声音结束到 StopSpeech 的精确时长;
库内判定延迟、20 ms 分帧、现有 80 ms 控制轮询、调度及尾包发送都会影响时间。
句中停顿超过阈值可能被截断;慢语速、轻声、儿童、远场及噪声需实机调参。
## 所有权、并发与收尾
- `platform` 提供固定两个 PCM 观察者槽位,同一份 AEC 后 16 kHz 单声道 PCM
分别交给已有本地命令观察者与领域端点检测器。无第二条采集链或新音频队列。
- `domain_speech_endpoint.c` 使用 WebRTC `vad_create(VAD_MODE_1)` 和 `vad_process()`,
将输入重分帧为 320 样本。PCM 回调零等待取锁,最多处理 512 样本,不执行
协议、等待或资源销毁;状态查询和生命周期使用有界互斥锁。
- 丢失回调、非法输入或超过 100 ms 的输入间断重置连续人声/静音计时,保持保守
判停。只保留“曾确认有人声”,不能把缺失数据当静音补算。
- 控制层只接受活动 `STREAMING`、提示音门禁关闭且 generation 匹配的结束快照。
旧事件不会结束新一轮。初始化失败记录日志,保留原超时。
- 每轮仅尝试启动一次检测。收尾/取消/正常停机注销订阅并释放 VAD;注销超时
禁用订阅、保留实例和在途计数以便重试,不后台释放。
- 平台停止先暂停并等待在途采集,再由原采集任务补零编码尾帧。编码留在原
32 KB 采集任务栈,控制线程只等待结果。发送队列与在途包全部排空后才正常
发送 `StopSpeech`。停止失败走传输废弃路径,不提交正常结束、不无限重试。
- stop 超时后仍可能完成之前请求的尾帧和发送;资源保留,禁止启动新一轮,必须
重试收尾或走 deinit。deinit 才拥有常驻 worker 的退出/join/强制删除和资源销毁。
## 日志与资源
- `VAD armed`:代次、阈值及启用后的内部 RAM/PSRAM 空闲快照。
- `VAD endpoint`:代次、处理音频时长、连续静音、最大回调处理墙钟时间与间断数。
- `StopSpeech sent after uplink drain`:正常输入结束指令已发出。
- `VAD unavailable; retain capture timeout`:本轮回退原输入窗口。
- `PTT activation ignored: turn already active`:重复触发已忽略,原代次与超时窗口保留。
- `VAD capture timeout`:兜底结束前的一次领域快照,包含 PTT/端点代次、活动状态、
是否已确认人声、是否结束、处理音频时长、当前连续静音、最大回调墙钟时间和间断数。
`speech=0` 表示尚未确认连续人声;`audio_ms` 很小或 `gaps` 较多可提示输入问题。
这是瞬时状态,不能据此还原整段声学分类;查询失败只记录错误,继续原超时收尾。
- `Abort voice turn after incomplete drain`:失败废弃,不能记作成功回复。
固定 PCM 重分帧数组为 640 B,另有领域状态/互斥锁和平台第二个观察者槽、
采集与生命周期锁。不新增 worker、任务栈、DMA 或模型分区资源。
VAD 库内部动态分配、原采集栈高水位和联网后的最小内部 RAM 必须实机测量;
`internal_free` 是并发系统的空闲快照,不能直接当成 VAD 的精确分配量。
`max_process_us` 为墙钟时间,含抢占,不是纯 CPU 时间。PSRAM 不增加固定像素/
音频队列缓冲,但库分配采用的实际堆区域仍以运行日志为准。
## 验证与验收
`python3 tools/test_speech_endpoint.py` 编译并执行生产 C,使用可控 VAD/RTOS 桩:
覆盖分帧、纯静音、短噪声、句中停顿、代次、输入间断、初始化失败、注销超时、
订阅并发替换、队列出队与 busy 竞态、原采集上下文尾帧补零、排空与失败重试;
控制层直接执行生产激活函数,覆盖各等待阶段/拾音阶段重复触发、原端点继续生效、
超时不续期、收尾排队、前置检查期间并发触发和快照查询失败后的兜底。
这些测试验证控制逻辑,不验证真实声学识别率或 ESP32 时序。
实机至少检查:
1. 按键和唤醒各测试短句、长句、未说话、轻声、噪声和不同长度句中停顿。
2. 连续多轮、拾音中重复单击/长按松开、快速取消/重新触发、服务端提前结束、
TTS 播放及唤醒提示音门禁。重复触发应保持代次和原窗口,收尾后新一轮仍可正常启动。
3. 拾音期间断网、OTA/待机切换、发送拥塞、内存不足与重复初始化。
4. 记录最后人声 → endpoint → StopSpeech → 首个下行音频 → 实际播放的分段延迟;
同时统计误截断、未判停、队列丢包、回调耗时、栈高水位和内部 RAM 最低余量。
本次构建/主机验证结果见下方记录;没有实机日志前,不承诺提速秒数或声学阈值合适。
### 2026-10-15 本次验证记录
- 主机:`test_speech_endpoint.py` 全部通过,启用/关闭分支均编译并执行;
五个现有 `test_lcd_{status_hud,emote_transport,emote_timeline,listening_gfx,fortune_gfx}.py`
回归脚本通过(脚本自带的 profiling 开/关路径也通过)。
- 构建:ESP-IDF 5.5.4,VAD 开启和关闭两个独立 sdkconfig 均构建成功;
项目默认配置保持开启。关闭配置与构建在临时目录,未覆盖项目 sdkconfig。
- 固件:开启 3,167,888 B,关闭 3,157,248 B,开关差异 +10,640 B;
同源 MAP 的内部静态 data +16 B、BSS +832 B,合计 +848 B,IRAM 代码无增量。
这是同一份修改中 VAD 开关的对比,双方均包含共享 PCM/尾包收尾修正,
不能当成整个变更相对旧提交的净成本。模型分区未变,没有新增固定 PSRAM/DMA 缓冲。
- 架构:原检查脚本在已有 `control_plane_battery_service.c` 的 `portMAX_DELAY`
处失败,已对照 HEAD 确认不是本次引入。仅排除该既有检查后,其余架构检查通过;
本次新增路径无无限等待或跨层内部头依赖。`git diff --check` 通过。
- 设备:未烧录,尚无实机误截断率、延迟收益、VAD 动态堆和栈峰值数据。
### 2026-10-23 重复激活修复验证
- 修复:控制层在前置检查前及提交意图的临界区内检查活动轮次,忽略重复激活;
保持领域端点的代次隔离,不替换正在使用的 VAD。新增超时诊断只在兜底时记录。
- 主机:重新运行 `test_speech_endpoint.py` 全部通过,包含 VAD 开启/关闭的领域和
控制层 C 分支;上述五个显示回归脚本也全部通过。
- 构建:激活 ESP-IDF 5.5.4 后执行 `idf.py build` 成功,使用当前 VAD 开启配置;
本次未执行关闭配置的固件构建。应用镜像 3,168,368 B,单个 OTA 槽剩余 1,550,224 B。
本次不新增任务、队列、堆分配或固定音频/DMA 缓冲。
- 架构:原守卫仍停在电量模块既有的 `portMAX_DELAY`,已对照 HEAD 确认;仅排除
该既有检查后,其余守卫通过。`git diff --check` 通过。
- 设备:未烧录。首次录音未提前断句的声学原因仍需新日志和实机复测;本次主机测试
与构建不能证明真实噪声环境下的人声/静音识别效果。