# 语音输入弱网可靠性：问题记录与集中修复验收计划

- 记录日期：2026-10-07（Asia/Shanghai）。
- 状态：待集中修复与验收；本文是问题记录，不代表方案已经实现或验收通过。
- 优先级：高。录音发送给 AI 是设备的核心交互功能。
- 现场基线：已上线的 `0.8.0-dev`，源码提交 `78a8f0c75aea91319d7770cf33c029937cfc037f`。下文机制按该提交核对，不按更早的本地 checkout 或 0.4.0 设计文档推断。

## 用户现象

用户在外出使用时，长按 A 录音并发送给 AI 的过程容易失败，经常显示 `Capture cancelled`，怀疑与网络条件有关。希望短暂弱网时仍能完成语音交互，避免反复重录。

此前室内测试中，按键、完整录音上传、转写，以及 Pi 排队任务的逐条通知与朗读已通过。该结果不能替代移动热点、弱网和重连场景的验收。

## 已确认的机制与证据

1. 当前录音边采集边通过 WSS 上传。44.1 kHz、16-bit、单声道 PCM 每帧 40 ms / 3,528 字节，64 帧队列只容纳约 **2.56 秒**积压。队列满时录音终止，并清空队列；以前的串口已实际记录 `failure=capture-queue-full`。
2. 单次语音发送超时配置为 2 秒；发送失败、连接 generation 变化或断线会中断本次语音。设备没有完整录音留存和跨重连续传机制。
3. Gateway 的语音请求绑定 `connection_id`；取消会清空 PCM 与转写结果。同一个连接中的严格序号和一次性 SEND 决策，尚不足以支持跨连接的恢复与去重。
4. Gateway 从接收开始起设置 20 秒结束计时。改为慢速上传或重连续传时，需要将录音时长上限与上传／接收期限分开，避免录音尚未收全就开始转写。
5. 本次核对的 Gateway 日志中，28 段完整接收的录音均成功转写并提交给 AI。开始后没有完整结束的请求不能直接全部算成弱网失败，可能包含主动取消、短录音等情况。
6. 之前稳定性记录仍捕获一次 `PONG_TIMEOUT`：最近文本约 21 秒未收到、Pong 约 30 秒未收到，RSSI 为 -56 dBm；约 6 秒恢复同步，重连期间出现约 0.97 秒按键采样间隔。信号强度正常不能证明整个网络链路正常。

**判断：现有设计确实会把上传停顿放大成录音取消；今天外出的每一次取消仍缺少对应设备日志，不能逐次确定原因，也不能把所有取消都归因于网络。**

相关实现：

- `boards/common/include/voice_capture_limits.h`
- `boards/m5stack_stopwatch/audio_backend.cpp`：采集队列、队列满处理。
- `boards/m5stack_stopwatch/board.cpp`：Capture cancelled、断线取消及语音 UI。
- `firmware/main/cloud_client.cpp`：上传、超时、连接 generation 校验。
- `server/device_gateway/voice.py`：接收时限、连接绑定、转写与 Agent 提交。

## 期望行为

短暂网络不佳可以增加等待时间，但不应轻易丢失用户已经录下的话。设备应区分“录音完成”“上传完成”“转写完成”“AI 已接受”，并明确显示当前进度与可恢复操作。

录音仍须有时长和内存上限；用户主动取消应立即生效。长期离线应在有限等待后保留本次可重试内容并说明状态，不能无限重试或假报成功。设备重启后的恢复能力需单独决定，不默认引入音频落盘。

## 潜在解决思路（实施前评估）

### 1. 采集与网络脱钩，先保住完整录音

在 PSRAM 中保留本次完整录音。可边录边传，但已上传数据在完成确认前仍可恢复；网络阻塞不再决定麦克风是否继续采集。现有 20 秒上限对应约 1.764 MB 原始 PCM，具备评估基础；需测量与屏幕、Speech、TLS 同时运行时的峰值内存。

预先分配并检查容量，明确处理内存不足、达到时长上限和显式取消。开始、结束标记不能因普通音频队列满而丢失。首先保证原始采样格式的可靠传输；降低采样率或压缩可后续评估，不先牺牲识别效果。

### 2. 有确认的分块上传与重连恢复

以设备身份和唯一录音 ID 标识请求，固定录音开始时的 Agent、session、target generation 和输入上下文。Gateway 返回已完整接收的位置；重连后查询状态并续传。重复块须幂等处理，缺块和完整性不符须明确报错，不能静默丢音频。

录音时长、上传等待、转写等待分别设置有界期限。只有收到明确的完整录音结束信息并核验数据后，才进入转写。重试采用有限退避，不让后台网络操作阻塞 A/B、取消或正常 UI。

### 3. 明确的恢复界面与 Agent 提交去重

增加“等待网络／上传中／重试／已接收”等状态，避免把网络故障统一显示为 Capture cancelled。上传或转写失败后，应允许复用保留的录音重试，而不是要求重新说话。

AI 提交需有唯一请求 ID 和可查询的接受状态。**未收到确认不等于 AI 未收到；不能在结果不确定时盲目再次提交。** 应先查询，必要时显示“状态待确认”，避免重复执行指令。保留现有 Confirm / Direct 选择和 Pi follow-up 队列行为。

切换目标、原生 Approval 到来、目标关闭与主动取消的处理需明确设计；保留音频不代表允许自动发送到新 session。恢复时不得误发、跨目标发送或绕过原生审批。

### 4. 补充故障分类与可关联的诊断

记录录音 ID、采集／上传字节数、队列高水位、失败阶段、断线类别、重试次数和各阶段耗时。区分采集硬件失败、内存不足、队列满、网络中断、上传超时、转写失败、Agent 提交失败及用户取消。

日志只含诊断元数据，不含 PCM、base64、转写、会话正文或凭据。录音默认仅在有界内存中暂存，成功或主动取消后释放。

## 集中修复与验收清单

先确定协议、内存预算和状态机，再同步修改 firmware 与 Gateway；不要仅扩大队列或延长一个超时后就宣称解决。

| 场景 | 必须核对的结果 |
| --- | --- |
| 正常网络，5／10／接近 20 秒录音 | 完整采集、上传、转写和接受确认；无尾部截断 |
| 上传持续低于录音生成速率 | 录音不因上传积压取消；松开 A 后可继续上传，UI 可操作 |
| 录音或上传中断网 5／10 秒，再恢复 | 保留完整录音，恢复上传；不误发、不重复提交 |
| 上传完成或 AI 接受后，确认包丢失 | 状态查询／重试不产生第二条 AI 指令 |
| 重复块、缺块、乱序、旧连接迟到数据 | 幂等处理或明确拒绝；只转写完整且属于本次请求的录音 |
| 转写超时或服务错误 | 错误阶段明确，可按设计复用录音重试 |
| 主动取消、切换目标、Approval、新录音 | 取消及时、旧请求隔离，不错误恢复到别的 session |
| 长期离线、达到时长上限、内存压力 | 有界、可解释的行为；不崩溃、不假报 SENT、不无限重试 |
| Pi 已有任务运行时发送语音；Codex 语音输入 | 保留正确的原生排队／提交行为，各自确认接受状态 |
| 实际外出移动热点，多轮连续使用 | 核对成功率、音频完整性、等待时长、按钮响应与断线恢复 |

软件测试应使用可重复的限速、丢连接和丢确认注入，覆盖状态边界；真机验收同时记录设备和 Gateway 元数据，并由用户确认转写及实际 AI 会话结果。修复后的目标值和测试轮数应在验收前明确，不能只凭一次室内成功结论收口。

## 后续收口记录（待填写）

- 修复提交、协议变化、固件 SHA 与 Gateway 发布版本：待填写。
- 内存峰值、录音完整性、弱网成功率与各阶段等待时长：待填写。
- 软件测试和外出／移动热点真机验收证据：待填写。
- 用户确认、剩余限制与最终结论：待填写。
