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

91 lines
6.3 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-15。适用于 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 检测帧向上取整。
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`:本轮回退原输入窗口。
- `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 动态堆和栈峰值数据。