Skip to content

feat: 搜索框语音输入(macOS 离线听写);以声搜声按实测不上 - #7

Merged
RGB-loop merged 3 commits into
mainfrom
feat-voice-search
Oct 17, 2026
Merged

RGB-loop merged 3 commits into
mainfrom
feat-voice-search

Conversation

@RGB-loop

Copy link
Copy Markdown
Owner

做了什么

搜索框语音输入(macOS 桌面版):点麦克风说一句话,离线转写成文字进搜索框(可编辑),随后走正常文字搜索。遵循实测结论(2026-10-17 probe):语音不直接编码成向量去搜,而是 语音 → 文字 → 文字搜索。

以声搜声:这次不上(量化证据见下)。

转写引擎选择(按证据)

候选 A:Embed2ASR(EG2 音频塔 + 9.66M GRU-CTC reader,Apache-2.0)— 否

  • 架构上跑不了:它要音频塔 pooling 之前的 hidden states(1536 维 / 25 fps);我们的 LiteRT-LM C API 只暴露池化后的 768 维向量。上游仅 torch + transformers,无 ONNX 导出;要上它得给桌面和 Docker 各带第二份音频编码器(~340 MB)并自行导出模型。
  • 精度也不达标:官方基准中文 CER 15.6%(Common Voice zh-TW)、英文 WER 9.0%(LibriSpeech clean)/ 24.5%(FLEURS),错误以同音字为主——对短查询检索是致命的。
  • 许可本身兼容(Apache-2.0),但以上两条任一都足以否决。

候选 B:macOS 系统离线听写(SpeechAnalyzer,macOS 26+)— 采用

  • 新 API 的文件转写不需要语音识别授权弹窗(实测 SFSpeechRecognizer 老路径在无 Info.plist 的 helper 里直接 SIGABRT,新路径无此问题);只需麦克风权限(entitlement com.apple.security.device.audio-input + NSMicrophoneUsageDescription 已加)。
  • 模型是系统组件、全程本机运行;未下载时不代下载(应用零出站请求),界面指引去 系统设置 → 键盘 → 听写。

转写质量(20 条合成短查询:say -v Tingting/-v Samantha → 16 kHz mono WAV,经打包用的 vixel-speech 实测)

集合 结果
中文 10 条 CER 6.4%(7/10 全对;3 条各错 1 个同音字:狗→走、和→河、弹→但)
英文 10 条 WER 0%(仅大小写/标点差异)
加粉噪 4 条 2 条不变,狗→走(同前),1 条掉首词(train→"arriving at the station")
速度 20 条共 4 s(约 0.2 s/条,M 系列)

语音搜索 vs 打字搜索 top-1 一致性(demo 库拷贝)

footage 库 17/20、照片 demo 库 19/20(合计 36/40)。4 处不一致里 2 处是转写同音字造成(狗→走、和→河),2 处是句末标点让边界查询在相关性门槛两侧翻转("a dog barking" vs "A dog barking.")——门槛边缘的固有抖动,不是语音链路问题。

没上的平台(诚实说明)

  • Windows:没有可在本机验证的达标离线引擎(系统离线听写质量无法在本环境测量),不盲发。
  • 网页/服务器版:浏览器 Web Speech API 会把音频上传(Chrome → Google),违背"完全离线"承诺,禁用;服务器侧也没有达标引擎。网页版不显示麦克风(voice.status 恒不可用)。原任务书里"HTTPS 下才有麦克风"的提示因此暂不适用——根本没有可用引擎时给个"需要 HTTPS"的提示反而误导。

以声搜声:不上,证据如下

标定脚本 scripts/audio-search-eval.cjs(已入库,可复测):12 个正例 = footage 有音轨片子在 37% 时长处另截的 8 s 音频;负例 = 正弦/粉噪/中英文语音 + 跨库查询。

比对 top-1 分离度
音频查询 ↔ 现有片段向量(8 帧+音轨联合编码) 3/12 正例 z 0.20–2.71 vs 负例 z 最高 3.34——画面主导联合向量,声音被淹没
音频查询 ↔ 纯音频向量(音频文件的索引方式) 8/12 正例最低余弦 0.917 < 非目标最高 0.959;粉噪对瀑布 0.947、440 Hz 对野马 0.856

两路都标不出"没有明显匹配就直说"的门槛——上了只会自信地给错结果,与产品原则(库里没有的直说)冲突。EG2 音频嵌入空间对环境声过于紧致;换更有判别力的音频模型后可用该脚本复评。

验证

  • npm run typecheck ✅ npm test ✅(365 通过,新增 pcm 纯函数单测)npm run test:db ✅ tsc -p tsconfig.server.json ✅(接口契约测试锁定 voice.* 为 desktop-only)
  • 开发版(--user-data-dir 指向 demo data 拷贝)CDP 实测端到端:voice.status → 麦克风按钮渲染 → 以 WAV 字节代替实时麦克风走 window.api.voice.transcribe IPC(zh_1.wav → "海边的日落")→ 文字进搜索框触发正常搜索。复现方法:npm run dev -- -- --user-data-dir=<数据拷贝> --remote-debugging-port=9223,在渲染进程里 window.api.voice.transcribe(bytes)。
  • 打包验证(涉及原生 helper + Info.plist):npm run build && npm run package ✅;sh scripts/packaged-video-check.sh → videos=3 segments=4 OK(视频索引不受影响);包内 Resources/speech/vixel-speech 可转写、NSMicrophoneUsageDescription 与 audio-input entitlement 均在、ad-hoc 签名校验通过。

风险 / 边界

  • 听写语言跟随界面语言(zh-CN ↔ en-US);混说另一种语言的词靠系统模型的混合能力。
  • 真人麦克风口音/环境噪声会比 say 合成语音差;同音字错误(约 6% 字错率)落在搜索框里可见可改,这正是不用"语音直搜向量"的原因。
  • SpeechTranscriber.supportedLocales 是系统框架调用,与系统听写同源;应用自身零网络请求(渲染进程网络白名单未变)。
  • 实时流式听写(边说边出字)没做——文件式一次转写 0.2–0.5 s,对 ≤15 s 的查询足够。

查询 ↔ 片段向量(8 帧 + 音轨联合编码):top-1 3/12,正例 z 0.20–2.71 vs 负例 z 最高 3.34,
画面主导联合向量,声音被淹没;查询 ↔ 纯音频向量:top-1 8/12,正例最低余弦 0.917 <
非目标最高 0.959(粉噪对瀑布 0.947)。两路都标不出'没有明显匹配就直说'的门槛,
按产品原则不上(脚本留作证据与复测入口)。
语音 → 文字 → 正常文字搜索(直接拿语音向量搜索实测不可靠:中文失败、门槛不可分)。
转写走 macOS 26+ 的 SpeechAnalyzer(vixel-speech 辅助程序,swiftc 构建产物不进 git):
模型是系统组件、全程本机运行、文件输入无需语音识别授权;模型未下载时不代下载,
界面指引去系统设置。实测合成查询 zh CER 6.4%(3/47 同音字错)、en WER 0%,单条 < 0.5 s。

引擎取舍:Embed2ASR(EG2 音频塔 + GRU-CTC reader,Apache-2.0)需要 pooling 前的
hidden states,LiteRT-LM C API 只给池化向量,跑不了;其公布中文 CER 15.6% 也不达标。
Windows / 网页版暂不提供(无达标离线引擎;Web Speech API 上传音频,违背离线承诺)。

渲染进程 getUserMedia 录音(≤15 s,电平表,Esc 取消)→ 16 kHz WAV 经 IPC 到主进程。
打包:mac extraResources + NSMicrophoneUsageDescription + audio-input entitlement。
@RGB-loop
RGB-loop merged commit eeb3a2e into main Oct 17, 2026
2 checks passed
@RGB-loop RGB-loop mentioned this pull request Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant