118 lines
8.6 KiB
Markdown
118 lines
8.6 KiB
Markdown
# 本地 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` 通过。
|
||
- 设备:未烧录。首次录音未提前断句的声学原因仍需新日志和实机复测;本次主机测试
|
||
与构建不能证明真实噪声环境下的人声/静音识别效果。
|