Sfoglia il codice sorgente

docs(harness): 记录「不出字」的根因与排查(虚拟麦克风)+ 回答「为什么之前没这问题」

用户确认:把输入设备换成真实麦克风后语音恢复正常。并问「为什么之前没这个问题」。

答案(有两条证据,不是猜):
① 采集路径与旧实现**一字不差**(git show 6783c69^:src/three-libs/asr/index.ts 对比):
   旧的也是 getUserMedia({audio:true,video:false}) + createMediaStreamSource →
   createScriptProcessor(0,1,1) → connect(destination) → source.connect(processor)。
   选哪台设备由 Chrome/Windows 决定,两版代码在这里没有任何差别 ——
   旧实现遇到同一台 Steam 虚拟麦,表现会完全一样。
② 记录里从来没有「语音输入可用」的正面确认(全仓库只有"厂商若失效会不可用"这类预警);
   即便切 NLS 那轮,我写的仍是「浏览器实测没做…请用户点一次麦克风」。
   ⇒ 所以「切换后正常了」很可能是这条路径**第一次真正跑通**。

另一种可能是系统默认输入设备在那之后被改过(装/启动 Steam、插拔耳机、驱动更新),
那是应用外的变化,代码侧无从感知。

落档:
- progress.md 续 41:完整诊断链(浏览器路径复现 → 电平日志 → 音轨 label 一击命中)
- asr-nls.md 新增 §4.8「已知环境坑:虚拟麦克风」,含现象、一眼认出的日志、修法,
  并明确标注"这不是本实现引入的问题"

无代码改动;validate-harness 通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
gongtianxiao 1 mese fa
parent
commit
fae6d0bcf3
2 ha cambiato i file con 66 aggiunte e 0 eliminazioni
  1. 22 0
      harness/docs/reference/asr-nls.md
  2. 44 0
      harness/progress.md

+ 22 - 0
harness/docs/reference/asr-nls.md

@@ -120,6 +120,28 @@ A/B 实测(模拟 1s 握手积压、同一段语音):
 > 剩下的 ~150ms 是客户端+链路,~480ms 的更新节奏是引擎的 —— 想再快只能换引擎/参数,
 > **不要再往客户端"提前发送"上想办法**(文档要求按实时速率发;4× 是追赶积压的上限,不是常态)。
 
+## 4.8 已知环境坑:虚拟麦克风会让识别"永远不出字"(2026-09-24 实测)
+
+**现象**:点了麦克风、进了录音态、音频一帧帧都在发(`[nls] 已推音频 …`),
+但网关**一条结果都不回**、也不报错(没有 `TaskFailed`)。采集电平恒为 `峰值=0.0000`。
+
+**根因**:Windows/Chrome 选的输入设备是**虚拟麦克风**。实测踩到的是
+`Steam Streaming Microphone`(装 Steam 时装的虚拟输入设备,永远输出静音)。
+`getUserMedia` 成功、音轨 `muted=false state=live`、回调照常触发 —— 但采样全是 0。
+
+**怎么一眼认出来**:看采集开始那两行日志
+```
+[nls] 音轨:label="麦克风 (Steam Streaming Microphone)" muted=false enabled=true state=live
+[nls] 采集电平:峰值=0.0000 RMS=0.0000     ← 连续 3 秒这样就会自动告警并点名设备
+```
+**怎么修**:Windows「设置 → 系统 → 声音 → 输入」换成真实麦克风(并在「更多声音设置 → 录制」
+里禁用虚拟麦),或改浏览器的站点麦克风设置。
+
+> ⚠️ **这不是本实现引入的问题**:旧厂商 SDK 用的是**一模一样**的采集路径
+> (`getUserMedia({audio:true,video:false})` + `createMediaStreamSource` →
+> `createScriptProcessor(0,1,1)` → `connect(destination)`),选哪台设备由 Chrome/Windows 决定 ——
+> 旧实现遇到同一台虚拟麦,表现完全相同。排查经过见 `progress.md` 续 41。
+
 ## 5. 对外契约(**与旧 SDK 一致,所以两个壳组件不用改**)
 
 ```ts

+ 44 - 0
harness/progress.md

@@ -4280,6 +4280,50 @@ payload 里含模型里**不存在**的列时,`addContent` 会忽略该列照
   切换说明)、`current-api-call-sites.md`(§0 A 表第 6 行、§三 的旧路径标注为已删)、
   `asr-realtime.md`(历史文档里指向已删文件的**链接**改成纯文本,避免 404 链接)
 
+### 续 41:语音「不出字」的根因是系统选了个虚拟麦克风(用户问「为什么之前没这问题」)
+
+**用户反馈**:切到 NLS 后浏览器里「进了录音状态但是不出字」。
+诊断日志(用户在 Console 里看到的原文):
+```
+[nls] 开始采集:AudioContext=running 采样率=48000Hz processor 缓冲=2048
+[nls] 音轨:label="麦克风 (Steam Streaming Microphone)" muted=false enabled=true state=live
+[nls] 采集电平:峰值=0.0000 RMS=0.0000      ← 连续 20 秒全 0
+[nls] ← TranscriptionStarted
+[nls] 已推音频 2000ms(20 帧)… 34000ms      ← 音频确实在推
+```
+**根因**:Chrome 抓到的是 **Steam Streaming Microphone**(装 Steam 时装的**虚拟输入设备**,
+永远输出静音)。用户对着真麦说话,浏览器在听那个虚拟设备 → 音频"推出去了"但全是 0 →
+网关一条结果都不回、也不报错(格式没错,所以没有 TaskFailed)。
+**换回真实麦克风后正常**(用户 2026-09-24 确认)。
+
+**排除过程(都留了证据)**:
+1. 先在 node 里**复刻浏览器那条采集/组帧路径**打真实网关(新增
+   [`harness/tools/verify-nls-browser-path.mjs`](tools/verify-nls-browser-path.mjs):
+   同一份 `encodePcm16kMono`、同样 2048 样本分块、同样 42.7ms 入队 + 50ms 拼帧)
+   → **识别正确**,证明组帧/重采样/协议都没问题
+2. 采集端加电平日志 → 定位到"流是静的"
+3. 加音轨状态日志 → `label="麦克风 (Steam Streaming Microphone)"` 一击命中
+
+**用户问「为什么之前没这个问题」——答:大概率是"之前没试过这条路"**,证据两条:
+- **采集路径与旧实现一字不差**(`git show 6783c69^:src/three-libs/asr/index.ts` 对比):
+  旧的也是 `getUserMedia({audio:true,video:false})` + `createMediaStreamSource` →
+  `createScriptProcessor(0,1,1)` → `connect(destination)` → `source.connect(processor)`。
+  选哪台设备由 **Chrome/Windows 决定**,两版代码在这里没有任何差别 ——
+  旧实现遇到同一台虚拟麦,表现会完全一样。
+- **记录里从来没有「语音输入可用」的正面确认**(grep 全仓库只有"厂商若失效会不可用"这类预警)。
+  即便到切 NLS 那一轮,我写的仍是「浏览器实测没做…请用户点一次麦克风」。
+  所以"切换后正常了"很可能是这条路径**第一次真正跑通**。
+- 另一种可能是**系统默认输入设备在那之后被改过**(装/启动 Steam、插拔耳机、驱动更新都可能
+  把默认设备换成虚拟麦)—— 那是应用外的变化,代码侧无从感知。
+
+**代码侧的改进(把这次排查固化下来)**:
+- 连续 3 秒全静音 → 直接**点名当前输入设备**并列出常见原因(虚拟设备 / 系统静音 /
+  隐私开关 / 被会议软件独占)+ 两步操作位置
+- 采集开始打**音轨自身状态**(label / muted / enabled / state / deviceId / 声道),
+  并监听 `mute` / `unmute` 事件
+- 采集/发送/下行全链路诊断日志(`[nls]` 前缀):第一块音频的输入输出尺寸、推流进度、
+  每 2s 电平、网关事件名与结果、TaskFailed 原文、以及"录着音却一帧没发出去"的兜底告警
+
 ### 续 40:修「界面卡在正在初始化,请稍候...」(我上一轮删出来的回归)
 
 **用户反馈**(截图:整页模糊 + 中间一行「正在初始化,请稍候...」):「现在界面有问题」。