早上好,我是まさきん。
我在自己的 Mac 上用着这样一个语音输入工具:只在按住右 Control 键的期间录音,松开后就转成文字并替我粘贴出来。语音转写和文字整理,都是在 Mac 内部运行的。
制作时犹豫过的地方、测出来的数字、没能成功的尝试,我在这里整理一下。改良和测量是在 AI(Claude Code)的协助下进行的。代码没有公开,所以请当作一份思路记录来读。
※本文内容以发布时为准。模型和库的规格请以官方信息为准。
- 只在按住录音键期间录音,松开后转成文字并粘贴到光标位置
- 语音转写用 Whisper large-v3,文字整理用 Qwen3-8B 的4bit版。两者都用 MLX 运行
- 从 turbo 和 Qwen2.5-3B 换过来之后,每次发言只慢了0.3至0.8秒左右
- 思考模式每次发言要花4至22秒,同音词的混淆也没怎么改对,所以放弃了
- 粘贴目标对准按下录音键时位于最前面的应用
这是个什么样的工具
使用方法很简单。把光标放在文本框里,按住录音键说话。松开键之后稍等一下,文字就会被粘贴进去。
录音键默认是右 Control 键。工具常驻在菜单栏里,可以从菜单换成别的键。当前状态能从图标的显示看出来。
只有 Apple Silicon 的 Mac 才能运行。因为语音转写和文字整理都是用 MLX 来跑的。MLX 是面向 Apple silicon 的机器学习框架,官方 README 里有这样的说明。
使用的组件如下。
| 作用 | 使用的组件 | 补充 |
|---|---|---|
| 语音转写 | mlx-whisper | 模型是 whisper-large-v3-mlx |
| 文字整理 | mlx-lm | 模型是 Qwen3-8B 的4bit版 |
| 录音 | sounddevice | 以16kHz单声道录制 |
| 录音键的检测 | pynput | 右 Control 等 |
| 菜单栏 | rumps | 用图标显示状态 |
| 运行环境 | uv | Python 3.11至3.12 |
mlx-whisper 是用 MLX 运行 Whisper 的组件。据官方说明,Whisper 是 OpenAI 的开源语音识别模型,大小从约3,900万到约15亿参数不等。
处理流程
从松开键到文字被粘贴出来,会按下面的顺序处理。
- 只在按住录音键期间录音。松开之后还会继续录0.3秒
- 丢弃无声的录音,以及短于0.4秒的录音
- 用 Whisper 转写。同时传入术语提示
- 用替换规则修正写法
- 丢弃同一个词反复出现的结果,以及置信度低的结果
- 用 Qwen3 去掉口头填充词(“嗯”“那个”之类),再补上标点
- 再套用一次替换规则
- 通过剪贴板粘贴。剪贴板里原有的内容会恢复回去
中途设了比较多的丢弃环节,是这套做法的特点。Whisper 有时会从无声里凭空编出句子。所以,声音太小的录音根本不会交给语音转写。
整理之后也有一道保险。如果整理的结果和原来的句子相差太大,就改为粘贴整理前的句子。因为用提问的口吻说话时,LLM 有时会直接把回答写出来。
运行所需的条件
条件如下。我手头确认过能运行的,只有一台 MacBook Pro。
| 项目 | 条件 |
|---|---|
| 芯片 | Apple Silicon(M1 及以后) |
| 系统 | macOS 14 及以后。我手头确认过的只有 macOS 26 |
| 内存 | 常驻时约8GB,峰值约10GB。建议24GB 以上 |
| 磁盘 | 模型约7.2GB,Python 环境约1.2GB |
| 网络 | 只有首次获取模型时需要。装好之后,没有网络也能启动 |
| 键盘 | 要有能当录音键的键。默认是右 Control |
| 麦克风 | 内置、外接、蓝牙的都可以 |
内存是实测的数值。16GB 的 Mac 再加上其他应用,可能会不够宽裕。
内存不足时,换成 turbo 和 Qwen2.5-3B 就能降下来。只算模型的话,估计约3GB。代价是精度会下降。
MacBook 的内置键盘上没有右 Control 键。要么用外接键盘,要么在菜单里换成别的键。因为只在按住的期间才用,我觉得单独按下也不会发生任何事的修饰键比较合适。
仅首次获取模型,就会产生约7GB 的流量。在家里的网络下,这个量不会让人在意。不过,如果在外面用手机热点开始,可能会担心流量。
变慢了多少:实测
模型变大,耗时自然会增加。到底慢了多少,我在自己的 Mac 上测了一下。机器是搭载 Apple M5 Pro、内存48GB 的 MacBook Pro。
| 环节 | 更换前 | 更换后 | 耗时参考 |
|---|---|---|---|
| 语音转写 | whisper-large-v3-turbo | whisper-large-v3 | 从0.2至0.3秒增加到0.4至0.7秒 |
| 文字整理 | Qwen2.5-3B | Qwen3-8B | 从0.25秒左右增加到0.4至0.6秒 |
两者加起来,每次发言大约慢了0.3至0.8秒左右。如果是短句,我觉得这个差别并不会让人在意。
我也测了长一点的发言。说了25秒时,语音转写和文字整理各用了1.9秒。合计约3.9秒。耗时会随长度增加,但还没到让人等不及的程度。
不过,这张表只是用简短的合成语音测的。次数也少,请当作参考来看。
在实际使用应用的时候,也有发言花了3.6秒或5.3秒。光凭发言的长度解释不了,原因我还没有查。
语音转写:从 turbo 换成 large-v3
turbo 是把 large-v3 的解码器层数从32层减到4层的模型。官方模型卡里写着,它快了很多,代价是质量略有下降。
一开始我为了速度用的是 turbo。可是用合成语音一试,片假名写的产品名被它误认成了读音相同的普通词。换成 large-v3 后,输出的写法接近原来的拼写。
话虽如此,这次确认只用了3段简短的合成语音。换成真人的声音是否还有差别,我还没能确认清楚。耗时会略有增加,但我还是倾向于减少误认这一边。
关于模型大小和数字听错的问题,我在Whisper会把金额数字弄错里也写过。
文字整理:从 Qwen2.5-3B 换成 Qwen3-8B
整理的职责,只是去掉填充词、整理标点。不会增加内容,也不做概括。提示词里也是这么写的。
可是,3B 的模型在面对混乱的输入时会往里添词。在实际使用中出现过的一个混乱句子里,它加上了「のです」(no desu,表示解释语气的句尾)和「にゃあい」(nyaai,模仿猫叫“喵”的拟声词)。连「お日に」(o-hi-ni,一个语义不完整的片段)也被连语序一起改写成了「毎日」(mainichi,每天)。
换成 8B 之后,它基本上会把输入原样返回。填充词方面,3B 留下了「あのー」(anō,“那个……”)和「えーと」(ēto,“嗯……”)。8B 则连「あのー」「えっと」(etto,同样是“嗯……”)「ですね」(desu ne,句尾的语气词,相当于“是吧”)「まあ」(mā,“嘛”)都去掉了。
Qwen3 默认会输出思考过程。整理用不着这个,所以我用 enable_thinking=False 把它关掉了。模型卡里说明,这个参数是严格停止思考行为的开关。
prompt = tokenizer.apply_chat_template(
messages, add_generation_prompt=True, tokenize=False,
enable_thinking=False,
)
输出的长度也设了上限:最多为输入字数2倍的 token 数。这样即使整理环节失控,也不会无休止地写下去。
思考模式和更大的模型都放弃了
要纠正误转写,或许可以让模型多想一想。Qwen3 有思考模式,所以我试了试能不能用在文字整理上。
最开始,我试了 8B 的思考模式。每次发言,耗时增加了6至20秒。
接着,我还试了大小约16GB 的30B 思考专用模型。它是7至30秒。遇到长句时,用了2,500个 token 也没想完,还一直在用英语思考。
模型卡里给出了思考模式的推荐设置:Temperature 为0.6、TopP 为0.95、TopK 为20。还写着要避免贪婪生成。
为慎重起见,我按这个设置重新测了一遍。每个句子各试2次,结果如下表。
| 输入的句子(含缩短处理过的) | 想改正的地方 | 结果 | 耗时(2次) |
|---|---|---|---|
| 今週の試作について共有します | 把「試作」(shisaku,试制)改成「施策」(shisaku,措施) | 2次都没改对 | 9.2秒、12.1秒 |
| 前年同月費と比較します | 把「費」(hi,费用)改成「比」(hi,比率),即「前年同月比」(与去年同月相比) | 2次都没改对。1次删掉了字词,1次添加了多余的话 | 20.9秒、22.1秒 |
| 原因を分析して、改善作を検討します | 把「作」(saku)改成「策」(saku),即「改善策」(改善对策) | 2次都改对了 | 8.1秒、8.6秒 |
| 新商品の試作品が届いたので、来週の会議で確認します | 不用改(正确的句子) | 2次都原样保留 | 5.8秒、4.0秒 |
在想改正的3个句子里,干净地改对的只有1句。耗时为4至22秒。在另一个句子里,它还把输入里原有的一个助词去掉了。
思考模式不但要等很久,也不一定能改对。即使等上几秒到20秒,同音词的混淆仍可能留下来。
测试的数量很少,请只当作一种倾向来看。话虽如此,对于简短的语音输入来说,要等几秒到20秒,我判断不划算。也可以考虑只在长发言时才启用的可选模式。不过,从这次的结果看,效果与耗时并不相称。
我还试过在整理用的提示词里补充术语和指示。改对的只有写进术语里的那个词。遇到长句时,还出现过被改成别的词的情况。
同音词的混淆,上下文似乎有效
用着用着,「施策」(措施)有时会被转写成「試作」(试制)。它们是同音词,光靠声音是分不出来的。但如果知道前后在聊什么,应该就能分清。
于是我试了把上一句发言追加到 Whisper 的提示里。在3段合成语音中,「施策」被写成「試作」的那个例子被改对了。耗时从0.39秒变为0.43秒,几乎没有变化。
另外2段,加不加都能正确转写。也就是说,确认到的只有1个例子。
这个方法我还没有采用。想先用真人的声音确认之后再定。追加上一句发言,也会关系到丢弃“提示原样照抄”的判断。这一点也打算一并重新考虑。
术语文件和替换规则
专有名词和专业术语,光凭声音有时会写成别的拼写。所以我准备了术语文件。写法有2种。
- 一行只写一个词,它就会成为语音转写的提示
- 写成
誤 => 正的形式(左边是识别错的写法,右边是正确的写法),就会在转写之后进行替换
# 每行一个词: 作为语音转写的提示传入
Whisper
Qwen
# 左 => 右: 转写之后进行替换
ビッグクエリ => BigQuery
传给提示的词最多30个。上面的例子只是为了展示写法,和实际的内容不同。最后一行,是把片假名写的「ビッグクエリ」(biggu kueri,BigQuery 的日语读法)替换成 BigQuery。
设置替换规则时我注意的是,不要替换普通的词。为别的用途做的误转写词典里,有把普通词换成另一个词的条目。挪用到口述上,就连普通的句子也会被改坏。所以我没有挪用,而是另外为口述准备了一份。
让粘贴目标对准应用
在说完话之前,有时会切换到别的应用。这时如果直接粘贴,文字就会进到切换后的应用里。它未必是按下录音键时想输入的那个应用。
于是,我让工具记住按下录音键时位于最前面的应用。到粘贴的时候,如果最前面是别的应用,就先把记住的那个应用调到最前面,再粘贴。
最前面的应用用 lsappinfo 来查。调到最前面时,把 Bundle ID 交给 open -b。到变成最前面为止,大约0.3秒。
lsappinfo info -only bundleid "$(lsappinfo front)"
open -b com.apple.TextEdit
这里我踩了一个坑。一开始我用 NSWorkspace 的 frontmostApplication 来查最前面的应用。可是在没有运行事件循环的进程里,它会一直返回旧值。明明已经调到最前面了,却被判断成没有调到。
所以我换成了始终返回最新状态的 lsappinfo。
还有一点,正在打字的时候不会切换。打字途中如果最前面的应用变了,正在打的字就会进到别的应用里。所以会等输入停顿约0.6秒。最多等2秒,超过就切换。
应用已经退出、或者没能调到最前面时,不会粘贴。只是把文字放进剪贴板并通知一声。
这里也有限制。
- 能区分的只是应用这个层级。即使在同一个应用里切换标签页或窗格,也区分不出来
- 全屏应用位于另一个 Space(桌面空间)时,Space 也会跟着切换
- 粘贴之后,不会回到之前的应用
遇到的坑
不把 AirPods 戴进耳朵就是无声
一开始,出现过看上去录上了音、文字却进不来的状态。原因是 macOS 的默认输入设备是 AirPods。当时没有戴在耳朵里,所以声音完全没传过来,音量一直是0。
对策有两个。第一个,让菜单里可以固定输入麦克风。第二个,在每次录音之前重新生成设备列表。
负责处理声音的组件 PortAudio,会一直保留启动时的设备列表。这样下去,就跟不上接上 AirPods 之后默认输入的变化。重新生成的耗时是几毫秒。
丢弃提示原样照抄的判断过于严格
把原样输出提示文本的结果,我会直接丢弃而不粘贴。因为传入提示之后,在安静的部分有时会输出提示本身。在之前的文章里,faster-whisper 在安静的片段中也出现过重复提示的情况。
可是,最初的判断过于严格了。连只说了提示里的短词的正确发言,也被丢掉了。例如只说了「日本語の音声」(“日语的语音”)的时候。
于是我给匹配的长度设了下限。提示的基本句子在10个字符以上,带术语的整段提示在24个字符以上时,才会丢弃。
测试时覆盖了自己的剪贴板
粘贴的测试会碰到真实机器的剪贴板。当时漏掉了对写入剪贴板的函数的替换。结果测试用的句子覆盖了我自己的剪贴板。原来的内容没能恢复。
测试会触碰真实机器的处理时,要把可能被调用的函数全部替换掉。还要确认测试前后剪贴板里的内容没有变化。这就是这件事留下的教训。
其他限制
- 剪贴板只能恢复文本。如果之前复制的是图片,就会丢失
- 语音转写期间按下的键会被忽略。等结束之后再说下一句
- 说10秒左右的话,从松开到粘贴出来要2秒上下
- 异常退出之后,不会自动重新启动
适合的人·不适合的人
| 方面 | 适合的人 | 不适合的人 |
|---|---|---|
| Mac | 使用 Apple Silicon,内存比较宽裕 | 使用 Intel 的 Mac,或内存较少 |
| 用法 | 经常输入较短的文字 | 要写一字一句都必须准确的文档 |
| 环境 | 首次能获取约7GB 的数据 | 一直在节省流量 |
| 态度 | 不介意自己修改设置文件 | 想要装好就能用的应用 |
误转写不会变成零。所以我认为,这是一个粘贴出来的文字需要先用眼睛确认一遍的工具。
开始之前要确认的事
为想在 Mac 上尝试类似工具的朋友,整理了一下条件。
- 是 Apple Silicon(M1 及以后)的 Mac
- macOS 在 14 及以上
- 内存在24GB 以上。较少时换用更轻的模型
- 磁盘上留有约8.4GB 的空间,用于模型和环境
- 有能在首次下载约7GB 数据的网络
- 有能当录音键的键。没有的话就换成别的键
- 如果用 AirPods,要先戴进耳朵再说话
我觉得尤其重要的是内存和麦克风这两项。因为这两项要是运行之后才发现,返工的代价会很大。
- 优先精度时:语音转写用 large-v3,文字整理用 Qwen3-8B。每次发言会多等0.3至0.8秒左右
- 想节省内存时:用 turbo 和 Qwen2.5-3B。代价是精度下降
- 不需要整理时:在菜单里关掉。转写结果只套用替换规则就会被粘贴
常见问题
Q. Intel 的 Mac 也能运行吗?
我想是不能的。MLX 在官方 README 里被说明为面向 Apple silicon。我试过的也只有 Apple Silicon 的 Mac。
Q. 内存只有16GB 的 Mac 也能用吗?
常驻时用了约8GB,峰值约10GB。再加上其他应用,可能会不够宽裕。换成较轻的模型组合,内存会减少。代价是精度会下降。
Q. 没有网络也能用吗?
装好之后可以用。模型设置成只使用已获取的,我也确认过在无法联网的状态下能够启动。需要网络的只有首次获取模型。
Q. 录音键可以更换吗?
可以从菜单里的“录音键”更换。可选的有 Control、Option、Command、Shift 的左右两侧,以及 F1 至 F20。会输出字符的键、Enter 和 Space 不能用。右 Control、右 Option 和 F13 及以后的键,我想不容易和平时的快捷键冲突。
Q. 可以关掉整理吗?
可以。在菜单的“整理”开关里可以打开或关闭。关掉之后,不经过整理,只套用替换规则就粘贴出来。重启之后会恢复为打开。
Q. 说长一点的话,要等多久?
说25秒的话,约3.9秒。说10秒左右的话,2秒上下。耗时会随长度增加,所以说得越长,等待就越久。
Q. 切换到别的应用,也能正确粘贴吗?
会粘贴到按下录音键时所在的应用。如果已经切换走了,就先把那个应用调到最前面再粘贴。不过,能区分的只是应用这个层级。即使在同一个应用里切换标签页,也区分不出来。
参考的官方信息
- MLX(ml-explore 官方仓库)
- Whisper 的 MLX 版 README(ml-explore 官方仓库)
- openai/whisper-large-v3-turbo 模型卡(OpenAI、Hugging Face)
- mlx-community/whisper-large-v3-mlx(mlx-community、Hugging Face)
- Qwen/Qwen3-8B 模型卡(Qwen、Hugging Face)
- mlx-community/Qwen3-8B-4bit(mlx-community、Hugging Face)
总结
用 Whisper 转写,再用 Qwen3 整理。流程很简单。不过,模型的选择和粘贴目标的对准方式,让使用的手感发生了很大变化。
归根结底,起作用的是一些不起眼的功夫。比如,换成大一些的模型。让句尾不被截断的0.3秒,以及粘贴到按下键时所在的应用,也都有效。而思考模式这类花哨的方法,耗时和效果并不相称。
还有没试过的东西。比如把上一句发言加进上下文的方法,以及把语音转写和文字整理的耗时分开记录。想尝试语音输入的朋友,不妨先只运行 mlx-whisper 试试。
关于把自己的工作交给 AI 的话题,我在自动生成早间简报的文章里也写过。获取模型要用到流量,所以线路宽裕的话会更容易开始。
本文包含联盟营销广告。如果您通过本站链接申请相关商品或服务,我们可能会从合作企业获得报酬。此外,运营者是乐天集团的员工,也可能通过员工推荐计划获得报酬。文章内容与评价均基于运营者的真实体验与调查完成,与是否存在广告无关,但请您在阅读前了解上述关系。详情请参阅免责声明与联盟营销说明。 免责声明与联盟营销说明
本文包含机器翻译内容。正式条件请以乐天移动官方网站的多语言页面为准。
如果您对乐天移动的通信区域·信号状况有疑问,可以通过官方的 电波改善调查申请表 进行咨询。