# 中文语音控制链路 ## 整改前的实际链路 ```text getUserMedia -> ScriptProcessor(4096) -> 浏览器 RMS 阈值与前后裁剪 -> 单个 16 kHz PCM WAV -> POST /api/speech/transcribe -> 单个 sherpa-onnx OfflineStream -> SenseVoiceSmall 单次 final decode -> regex 文本清理 -> 直接覆盖输入框 -> 用户提交后进入既有 IntentIR / capability / CAN 链路 ``` 旧实现没有 AudioWorklet、ASR Web Worker、WASM、streaming partial、VAD segment、 chunk overlap 或 segment assembler。因此观测到的“空调打开打……靠背靠座椅靠背” 不是 partial/final 被前端重复 append,而是 SenseVoice 单次 decode 已返回该 raw 文本; 旧正则层只是尝试掩盖 acoustic false start,且没有结构化 cancelled intent。 ## 整改后的链路 ```text Microphone (优先 TAKSTAR V2) -> AudioContext(跨 utterance 复用) -> AudioWorklet(旧环境 ScriptProcessor fallback) -> 自适应 RMS endpoint + 350 ms 前置 / 450 ms 后置采集 -> 16 kHz PCM WAV + utteranceId -> Silero VAD(0.25 s pre-roll / 0.35 s post-roll,重叠先合并) -> SenseVoiceSmall zh + ITN(每个 VAD segment 一个 OfflineStream) -> final ASR callback -> TranscriptAssembler(replace / commit / order / idempotency / overlap) -> rawText -> UnderstandingTranscriptProcessor -> cleanText + active commands + cancelledFragments + uncertain -> 页面展示与提交安全门 -> 用户确认后进入既有 IntentIR / capability / CAN 链路 ``` 模型、tokens、VAD native session 和浏览器 AudioContext 都只初始化一次。服务端请求以 `utteranceId + SHA-256(audio)` 幂等:相同音频返回缓存,不同音频复用同一 ID 返回 409。 ## 数据边界 - `raw_text`:SenseVoice 原始证据,只去模型标签和多余空白,永不被语义文本覆盖。 - `clean_text`:用于输入框显示的理解式文本。 - `commands`:与工程 capability catalog 对齐的 active Intent 预览。 - `cancelled_fragments`:否定、自我修正、false start 或显式撤销的证据及 cancelled Intent。 - `uncertain`:参数越界、缺少方向/单位/设备、拼接冲突等不能安全执行的情况。 - `execution_eligible`:存在 active commands、无 uncertain、无校验错误时才为 true。 语音 API 本身不执行任何能力。页面会拒绝直接提交仍带有原始 uncertain 标记的语音结果; 用户编辑后才解除该标记。 ## Capability 驱动的语义链路 语音层不再维护可执行设备表。能力事实按以下优先级读取: 1. `domains/*/config/capabilities.yaml` 与 `config/native_can_capabilities/*.yaml`; 2. `CapabilityRegistry` 统一加载 catalog; 3. `NativeCanRuntime.supported_capabilities`、domain runtime、seat compiler 和 mode profile registry 确认可执行性与 executor binding; 4. `CapabilityResolver` 只把中文槽位或既有一句话解析 action 适配到上述事实。 `UnderstandingTranscriptProcessor` 会优先复用 `application/native_can_intent.py` 已有的确定性解析结果,所以驾驶台默认目标、CH1 主驾能力和网页一句话编排保持一致。`vocabulary.py` 只包含 alias、同义词、颜色与 scope 词法映射,不声明车辆支持哪些能力。 每个 command 同时保留 `target_scope`、`property`、`operation`、`value`、`unit`、 `qualifiers`、IntentIR action、executor binding 和 executor payload。`clean_text` 从 这些结构化 command 渲染;unsupported、ambiguous、incomplete 不会产生假的 executor payload,且 UI 会阻止整条原始语音直接提交。 当前代码证据中,前大灯只有反馈和只读 UI,没有控制 capability;因此“打开前大灯” 为 `recognized_unsupported`。零重力是 `config/modes/vehicles/denza_n7/zero_gravity.yaml` 定义的副驾四轴 mode profile,不是主驾单一 capability;明确要求主驾时返回 `recognized_unsupported`。 能力矩阵始终从 catalog 动态生成: ```bash python tools/report_capability_matrix.py --format markdown ``` ## Debug trace 生产默认关闭。设置 `SPEECH_DEBUG=1` 后,每条 JSON 日志包含: - `utteranceId`; - `segmentId`、sequence、开始/结束时间、音频 overlap; - raw ASR final、进入拼接器前文本、拼接后文本; - clean text、active/cancelled Intent、uncertain reason; - audio、VAD、每段 ASR、assembly、semantic、recovery、total 耗时。 `SPEECH_DEBUG_RESPONSE=1` 才把 trace 放进 API 响应。浏览器使用 `?speechDebug=1` 查看采集实现、设备、阈值、utteranceId 和服务器结果。 ## 默认 VAD 与识别配置 | 配置 | 默认值 | |---|---:| | language | `zh` | | use_itn | `true` | | Silero threshold | `0.45` | | min speech | `0.16 s` | | min silence | `0.65 s` | | max speech | `12.0 s` | | pre-roll | `0.25 s` | | post-roll | `0.35 s` | 参数可通过 `SPEECH_VAD_*` 调整。真实 TAKSTAR V2 车内噪声录音仍需要继续扩展 audio regression suite;没有真实证据时不自动切换到 int8,也不自动下载 Recovery 模型。