三个月前,我有一个 200 MB 的 Electron 应用,可以转录语音到文字。它能用,但臃肿、启动慢,而且需要网络连接才能获得最佳体验。
所以我从零重写了它。结果是 PushSense —— 一个 5.5 MB 的原生 macOS 应用,完全在设备端运行。
云端转录的问题
每个主要的听写方案——Google、Apple、Microsoft——都将你的音频发送到云端。这意味着:
- 隐私风险:你的语音数据离开你的机器
- 延迟:网络往返每句话增加 200-500ms
- 成本:API 配额、速率限制、计费账户
- 可用性:没有网络 = 无法转录
解决方案:whisper.cpp + Swift
我选择了 whisper.cpp —— OpenAI Whisper 模型的优化 C++ 实现。它在进程内运行,通过 Metal 走 GPU。App 本体之所以小,是因为权重放在一个只需下载一次的独立模型文件里,而不是打进 bundle。
Swift 封装处理 macOS 集成:热键检测、AVAudioEngine 音频采集,以及向当前输入字段注入文本。
数据
- 安装体积:5.5 MB(对比 Electron 的 200 MB)
- 加载模型后的空闲常驻内存:344 MB
- 转录时峰值内存(Small q5_1):484 MB
- 27.7 秒语音转录耗时 1.11 秒 —— 约 25 倍实时
- 网络依赖:无
- 需要的 API 密钥:零
空闲时 CPU 接近 0:热键监听是被动事件 tap,推理跑在 GPU 上,所以听写不会和你手头的正事抢 CPU。决定内存开销的是模型,不是 App —— 完整方法(包括两条没走通的路)写在那篇 benchmark 里。
四个真正花了功夫的细节
工程量基本不在「调用 Whisper」,而在它周围的边角。
强制简体输出。Whisper 原生支持中英混说,但它的中文输出会在简繁之间漂移。解法是用系统自带的 CFStringTransform 对最终文本做一次转换 —— 繁体字符漏不出来,拉丁字母和 emoji 原样保留。
静音必须什么都不输出。面对纯静音,Whisper 会很自信地编出一段像模像样的文字。不处理的话,蹭一下快捷键就会打出一句你从没说过的话。PushSense 明确识别并丢弃这类结果。它看上去像「没有功能」,实际上恰恰相反。
是「打字」,不是「粘贴」。转录结果以合成按键的方式送出,而不是走剪贴板。这就是为什么需要辅助功能权限 —— 也是为什么听写完你之前复制的东西还在。
配置存在 Keychain 里。没有配置文件要管,也不会在多台机器之间漂移。
我的收获
最大的洞见不是技术性的——而是关于如何交付。一个 5.5 MB、离线可用、无需账户、无需 API 密钥的应用——这不是"精简版"。这就是产品本身。
免费下载。获取最新版本。