这次做的是一个 Windows 桌面语音输入工具:按住右 Alt 开始说话,松开停止,识别出的文字直接输入到当前光标所在位置。
它不是一个完整商业产品,更像是把输入法式语音转文字的核心链路先跑通:前端负责设置和状态展示,Rust 负责系统能力,火山 ASR 负责识别。
项目已经完成 Tauri 桌面壳、本地配置、右 Alt 按住触发、麦克风采集、简易 VAD、火山 ASR WebSocket 客户端、系统文本输入和托盘驻留。
问题:语音输入最难的不是 UI
这个工具看起来只是一个小窗口,但真正麻烦的是底层链路:
- 要能采集麦克风 PCM 音频。
- 要能监听全局热键,并区分按下和松开。
- 要把语音识别结果输入到当前焦点位置。
- 要能驻留系统托盘,而不是关窗口就退出。
- 要把 API 密钥和统计数据放在本地,不接云端账号系统。
- 要接入火山 ASR 的 WebSocket 二进制协议。
如果把这些都塞到前端里,复杂度会很快失控。所以第一版就把职责切开:HTML/CSS/JavaScript 写 UI,Rust 写所有系统逻辑。
方案:Tauri 做壳,Rust 接系统能力
整体架构很简单:
HTML/CSS/JS UI
↓ Tauri commands/events
Rust session orchestrator
↓
hotkey / audio / VAD / ASR / input / tray / config
前端只有三段布局:
- 顶部显示今日字数、打字速度、累计字数。
- 中部显示 API Key、Resource ID、快捷键、自启、自动发送。
- 底部放一个余额充值占位,不做登录、充值、扣费。
Rust 后端按模块拆开:
asr.rs 火山 ASR WebSocket 协议
audio.rs 麦克风 PCM 采集
config.rs 本地 JSON 配置
hotkey.rs Windows 右 Alt 状态检测
input.rs 系统文本输入
session.rs 按住说话会话编排
stats.rs 字数统计
tray.rs 系统托盘
vad.rs 简易静音过滤
这套拆分的好处是,每个模块都能单独理解。比如统计、配置、VAD、协议帧解析都可以写单元测试,不需要真的打开麦克风。
过程
1. 先把可运行骨架搭出来
项目从空目录开始,先搭 Tauri v2 + Vanilla 前端。UI 不做复杂交互,只保证能配置密钥、显示统计、承接后端事件。
这一步的目标不是漂亮,而是先让“前端设置 -> Rust 配置 -> 本地保存”这条线跑通。
2. 本地配置只存 JSON
配置文件放在 Tauri 的 app config 目录里:
%APPDATA%\com.codexpd.yuyin2\settings.json
里面保存:
{
"app_id": "YOUR_APP_ID",
"access_token": "YOUR_ACCESS_TOKEN",
"api_key": "YOUR_API_KEY",
"resource_id": "volc.bigasr.sauc.duration",
"hotkey": "RightAlt",
"autostart": false,
"auto_send": true
}
真实密钥不进源码,也不写进文章。
3. 右 Alt 热键踩了一个坑
最开始用通用全局键盘监听库处理右 Alt,把它当成 AltGr。实际测试发现,在 Windows 上这个映射并不稳定,表现就是用户按右 Alt 没反应。
最后改成 Windows 原生方式:
GetAsyncKeyState(VK_RMENU)
用 10ms 轮询检测状态变化:
false -> true:开始收音true -> false:停止收音
这比抽象热键库更直接,也更符合“按住说话”的产品语义。
4. 火山 ASR 不是普通 JSON WebSocket
火山流式 ASR 文档里最容易漏掉的一点是:它使用自定义二进制 WebSocket 协议。
客户端需要处理:
- 请求头鉴权。
- 初始化 JSON payload。
- PCM 音频帧封包。
- 最后一包负序列。
- 服务端二进制响应解析。
enable_nonstream: true,让它支持实时增量和结束后的整句订正。
鉴权也分两种:
- 旧版控制台:App ID + Access Token。
- 新版控制台:API Key。
Secret Key 不用于这个 WebSocket ASR 接口。
5. 桌面应用一定要有本地日志
这个项目里最有用的调试文件是:
%APPDATA%\com.codexpd.yuyin2\yuyin2.log
日志记录这些关键节点:
- 右 Alt 已按下。
- 麦克风采集已启动。
- 右 Alt 已松开。
- ASR 连接失败原因。
桌面程序不像 Web 页面那样天然有浏览器控制台。没有日志时,用户说“没反应”,其实可能是热键没触发、麦克风没启动、ASR 鉴权失败、协议帧错误,或者输入模拟失败。日志能把问题切开。
结果
当前项目已经完成核心能力:
- Tauri v2 桌面 UI。
- 本地 JSON 配置。
- 系统托盘驻留。
- 右 Alt 按住说话。
- 麦克风 PCM 采集。
- 简易 VAD 静音过滤。
- 火山 ASR 二进制 WebSocket 客户端。
- 系统文本输入。
- 字数统计。
测试侧也补了 13 个 Rust 单元测试,覆盖配置、统计、VAD、热键状态转换、ASR 协议帧和响应解析。
真实 App ID、Access Token、API Key 不应该提交到仓库,也不应该写进博客。文章里只保留配置结构和占位值。
总结
这个项目最大的收获不是 UI,而是把桌面语音输入拆成了几个稳定边界:
- 热键检测要贴近平台,不要过度依赖抽象。
- ASR 接入要严格按协议做,不要把二进制 WebSocket 当普通 JSON WebSocket。
- 桌面工具要尽早加本地日志。
- 第一版先跑通核心链路,登录、充值、扣费都可以后置。
接下来可以继续打磨悬浮窗、快捷键修改、最终文本订正替换,以及 macOS 和 Android 的平台适配。但第一步已经成立:光标在哪里,按住右 Alt 说话,文字就应该去哪里。