hayamimi (早耳)
仅CPU即可实现实时多语言语音转文字。 实时字幕、浏览器仪表盘、说话人标签、即时翻译——无需GPU,无需云端API,内存占用低于2GB。
日本語版 README は README.ja.md にあります。
“早耳” (hayamimi) 在日语中意为"快耳"——指反应迅速的人。这正是本项目的设计目标:你还在说话时,部分字幕就已出现;停下后约100毫秒内,最终字幕行即完成定格。
为什么
大多数仅CPU实时转录方案会退而采用单一通用模型(如Whisper),并接受其准确率上限。hayamimi则根据每句话的语言,将其路由到最合适的专用模型,全部以量化(INT8)ONNX模型运行于sherpa-onnx之上——无需PyTorch,无需CUDA。
在真实的日语广播音频测试中(见docs/SCORECARD.md),这种路由方案获得了5.8%的字错误率(CER),不到whisper-large-v3-turbo在相同片段上13.8%错误率的一半,同时在一颗6核桌面CPU上以10-50倍实时速度运行。
特性
| 特性 | 说明 |
|---|---|
| 5路语言目录 | ja/zh/ko/yue/en+24种欧洲语言分别路由到各语言最优模型;其余语言(约1600种)回退到Meta的Omnilingual ASR |
| 部分字幕 | 说话过程中约每0.5秒更新一次进行中的草稿文本 |
| 快速最终结果 | 停止说话后,最终字幕行通常约100毫秒内出现(日语;其他语言见docs/GOALS.md) |
| 两遍精炼 | 静音2秒后,对最近的语音段进行批量重新解码,获得更高准确率的"干净"转录(日语真实广播CER从15.5%降至12.0%) |
| 说话人标签 | --speakers使用CAM++说话人嵌入为每句话标记S1/S2/…(区分轮流说话,非完整说话人分离) |
| 翻译 | --translate en,zh,ko实时翻译日语字幕行(英译通过FuguMT,中/韩译通过M2M-100) |
| 热词/用户词典 | --hotwords使解码偏向专有名词(目前对日语层级无效——见局限性);--replace进行事后查找替换,在所有语言下均有效 |
| OBS叠加层+仪表盘 | --serve启动本地HTTP服务器,提供浏览器源叠加层和实时仪表盘 |
| 网络音频输入 | --input ws接受通过WebSocket传输的麦克风音频(手机、ESP32/stackchan),并送入同一处理管线,包括--serve的仪表盘/叠加层 |
| 内存受限 | LRU模型驱逐机制使驻留模型保持在可配置上限内(默认总内存<2GB) |
| 仅CPU | 每个模型都通过sherpa-onnx以量化ONNX运行;无需GPU或PyTorch |
演示界面
--serve启动一个本地服务器,提供三个视图:
http://localhost:8833/dashboard—— 实时仪表盘:进行中语音的部分文本条、带语言徽章的最终字幕流、说话人标签、每行延迟、每行下方的内联翻译,以及第二列显示精炼(两遍)转录结果。http://localhost:8833/—— 极简OBS浏览器源叠加层(在OBS中添加此URL作为浏览器源即可生成直播字幕)。http://localhost:8833/transcript—— 纯滚动字幕历史。

🎬 观看演示视频 —— 真实4语言音频(ja/en/ko/zh)实时转录,从录制会话中逐帧精确回放。
网络音频输入
--input ws启动WebSocket接收端点,而不是读取本地麦克风,因此手机或stackchan级ESP32开发板可以通过局域网传输麦克风音频,并在hayamimi的正常管线中完成转录:
.venv/Scripts/python scripts/realtime_transcribe.py --input ws --serve
# -> ws://<host>:8766/ingest接受音频;http://localhost:8833/dashboard显示结果
协议:连接到/ingest,发送一个JSON文本帧({"sr": 16000, "format": "pcm_s16le", "channels": 1}),然后以二进制帧流式传输原始pcm_s16le音频。服务器会对非16kHz音频进行重采样,并以仪表盘SSE流相同的JSON事件格式回复部分/最终/翻译/精炼结果,因此客户端也可以自行显示字幕。同时只接受一个音频生产客户端;scripts/ws_mic_client.py是一个无依赖的参考客户端(以实时速度流式传输wav文件),同时可作为手机/ESP32实现的模板。
要求
Python 3.10+,且ffmpeg在PATH中。在Windows 11上开发和测试;macOS/Linux预计也可运行(所有运行时均跨平台),但尚未完成端到端CI测试——欢迎反馈。
快速开始
python -m venv .venv
# Windows
.venv\Scripts\pip install -r requirements.txt
.venv\Scripts\python scripts\download_models.py
# macOS / Linux
.venv/bin/pip install -r requirements.txt
.venv/bin/python scripts/download_models.py
# 从麦克风进行实时转录
.venv/Scripts/python scripts/realtime_transcribe.py # Windows
.venv/bin/python scripts/realtime_transcribe.py # macOS/Linux
# 带仪表盘+OBS叠加层
.venv/Scripts/python scripts/realtime_transcribe.py --serve
# -> 在浏览器中打开 http://localhost:8833/dashboard
scripts/download_models.py会下载约3.1GB的预训练模型到models/目录(已被git忽略)。传入--minimal可仅安装约1.1GB的日/英版本(ReazonSpeech、whisper-tiny、Silero VAD、日语标点)。各模型许可证义务见THIRD_PARTY_NOTICES.md。
CLI参考
所有标志均位于scripts/realtime_transcribe.py:
| 标志 | 默认值 | 描述 |
|---|---|---|
--wav PATH | 麦克风输入 | 从16kHz单声道WAV文件模拟流式输入,而不是使用麦克风 |
--no-realtime | 关 | 配合--wav,在块之间不等待(快速批处理) |
--input {mic,wav,ws} | 麦克风,或给出--wav时为wav | 音频源;ws通过网络接收音频(见下文) |
--ws-host HOST | 0.0.0.0 | 绑定--input ws的/ingest端点的主机 |
--ws-port PORT | 8766 | --input ws的/ingest端点端口 |
--threads N | 4 | 每个模型的推理线程数 |
--no-partial | 关 | 禁用进行中的草稿字幕 |
--min-silence SEC | 0.35 | 结束一段语音的静音时长;越小则最终结果越敏捷,但分割更多 |
--max-speech SEC | 12.0 | 连续说话达到该秒数后强制完成一段语音 |
--max-resident N | 3 | 驻留的非tier0模型最大数量(LRU驱逐);<=0为无限 |
--serve [PORT] | 关,8833 | 在http://localhost:PORT提供服务仪表盘+OBS叠加层 |
--no-refine | 关 | 禁用第二遍对语音组的重新解码 |
--transcript PATH | 无 | 将精炼后的转录行追加到该文件 |
--hotwords PATH | 无 | 热词列表(每行一个),使解码偏向专有名词——目前对日语层级无效(ReazonSpeech的字节级BPE tokens.txt无法编码它们;启动时会警告有多少失败)。日语专有名词请改用--replace |
--replace PATH | 无 | 用户词典:每行错误=正确,应用于所有输出 |
--lang-switch-guard SEC | 2.0 | 将短于该时长的新语言检测视为噪声:它永远不能计入确认切换(见--lid-switch-confirm),并会在空解码时抑制omnilingual回退(0禁用) |
--lid-switch-confirm N | 2 | 会话实际切换语言前需要连续的新语言检测次数(每次均>=--lang-switch-guard);提高该值可让单语言会话更稳定 |
--speakers | 关 | 为语音段标记说话人ID(S1、S2、…) |
--translate [LANGS] | 关,en | 将日语字幕行翻译为这些逗号分隔的语言(en/zh/ko) |
架构
┌─────────────┐
mic / wav ───────────▶ │ Silero VAD │ 0.35s语音结束 + 0.8s预卷
└──────┬──────┘
│ 语音段
▼
┌───────────────────────────┐
│ whisper-tiny 口语LID │ 在语音段仍输入期间,对前约4秒运行
│ (+ 字符集仲裁) │
└─────────────┬─────────────┘
│ 语言标签
┌───────────────┼────────────────┬─────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
┌───────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ ja │ │ zh │ │ ko/yue │ │ en + 24 │ │ ~1600 │
│ Reazon│ │Paraformer│ │SenseVoice│ │EU langs │ │ other │
│Speech │ │ -zh │ │ small │ │Parakeet │ │Omnilingual│
│Zipform│ │ │ │ │ │TDT v3 │ │ ASR │
└───┬───┘ └────┬────┘ └────┬─────┘ └────┬────┘ └────┬─────┘
└───────────────┴────────────────┴─────────────┴─────────────┘
│
部分字幕 (约每0.5秒) │ 最终结果 (语音结束后约0.1秒)
◀───────────────────────────┴───────────────────────▶
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌──────────────────┐ ┌────────────────┐
│ 日语标点恢复 │ │ 说话人标签 │ │ 翻译 │
│ (BERT恢复) │ │ (CAM++,--speakers)│ │ (FuguMT/M2M-100)│
└────────────────┘ └──────────────────┘ └────────────────┘
│
2秒静音:对最近语音段进行批量重新解码(两遍精炼)
│
▼
dashboard / OBS叠加层 / 字幕文件
模型在首次使用时惰性加载;LRU缓存会驱逐最近最少使用的非日语模型(--max-resident),因此无论会话涉及多少种语言,内存都保持有界。
实测性能
端到端(LID -> 路由 -> 解码 -> 日语标点),真实语音,无预卷/两遍(单片段)。en使用WER,其他语言使用CER(yue经过t2s标准化)。完整方法论见docs/SCORECARD.md。
| 语言 | 片段数 | LID准确率 | 路由 | 平均错误率 | 平均RTF |
|---|---|---|---|---|---|
| ja | 15 | 15/15 | ReazonSpeech | 7.5% | 0.071 |
| en | 15 | 15/15 | Parakeet v3 | 2.3% | 0.109 |
| zh | 12 | 12/12 | Paraformer-zh | 5.3% | 0.102 |
| ko | 12 | 12/12 | SenseVoice | 8.1% | 0.062 |
| yue | 12 | 12/12 | SenseVoice | 6.1% | 0.061 |
所有路由的RTF(实时因子)均远低于0.2,意味着每条路由在纯CPU上运行速度比实时快9-16倍——完整目标表见docs/GOALS.md,完整迭代日志(30+项测量改动,延迟/内存/准确率权衡及各自取舍原因)见docs/BENCHMARKS.md。
该日志中的关键数据:
- 日语CER 5.8%(束搜索)在真实广播音频上,对比
whisper-large-v3-turbo相同片段的13.8%——错误率不到一半。 - 平均最终延迟约100毫秒(日语,含标点);启用所有特性、5种语言浸泡测试下平均236毫秒/最大552毫秒。
- <2GB内存 在
--max-resident 3时(--max-resident 2时为1.35GB)。
局限性(诚实清单)
- 不支持句内代码切换。 路由器为每句话选择一种语言;一句话内混合日语和英语时,次要语言部分会被破坏或丢弃。句级切换(例如口译员交替说完整句子)效果良好;句内词级切换不支持。
- 过场音效/提示音/背景音乐后出现的极短语音可能被错误路由。 语言切换保护(
--lang-switch-guard,配合--lid-switch-confirm)可缓解此问题,但会话的第一句话(在建立会话语言之前)以及自信但错误的LID+解码组合(乱码文本恰好匹配错误语言字符集)是已知盲区——量化前后对比见docs/BENCHMARKS.md迭代#29。 --hotwords目前对日语(ReazonSpeech)层级无效。 ReazonSpeech的tokens.txt是字节级BPE,与hayamimi用于热词的modeling_unit=cjkchar编码不兼容,因此每个热词都无法编码(sherpa-onnx仅以stderr警告报告,且仍以状态码0退出——见GitHub issue #1)。hayamimi现在会打印启动警告,告知有多少热词编码失败;日语专有名词请改用--replace。真正的修复需要ReazonSpeech发布版附带的匹配bpe.model(目前未提供),或从零编写字节BPE热词编码器——列为后续工作。- 两个重叠的说话人不会被分离。
--speakers进行的是轮流说话标注(每个VAD段一个嵌入,最近质心分配),而非真正的说话人分离——同时说话只会得到一个标签。 - 翻译质量有实际上限,不仅是调参问题。 FuguMT(ja->en)和M2M-100(ja->zh/ko)是小模型;重复循环被抑制但未消除,且数字在ja->zh/ko翻译中不能可靠保留(依赖此功能处理任何数字或金融内容前,请先参阅
docs/TRANSLATE.md和docs/TRANSLATE_M2M.md中的实测失败案例)。 - 端到端麦克风管线尚未经项目自身测试以外的独立验证——见
docs/GOALS.md的剩余工作部分。如果你的结果与上述数字不同,请提交issue。
许可证
源代码为MIT(LICENSE,版权归oboroge0)。本仓库不包含任何模型权重——scripts/download_models.py在安装时从原始发布者处获取,每个模型都有各自的许可证(THIRD_PARTY_NOTICES.md中有完整表格)。
有一个模型不是宽松许可证: ja->en翻译模型(mojicast-fugumt-ja-en-ct2,由--translate en使用)为CC BY-SA 4.0(相同方式共享)。如果你重新分发该模型的权重,必须保留署名,并同样以CC BY-SA 4.0许可分发。这不影响hayamimi自身代码的许可证,也不影响--translate zh,ko(M2M-100,MIT)。
致谢
hayamimi构建于以下项目之上,没有它们就没有hayamimi:
- k2-fsa/sherpa-onnx —— 这里所有模型都通过它进行ONNX Runtime推理。
- ReazonSpeech(Reazon人类交互实验室)—— 支撑本项目准确率主张的日语ASR模型。
- NVIDIA NeMo / Parakeet —— 英语+24种欧洲语言。
- Meta AI Omnilingual ASR —— 约1600种语言的回退,让"多语言"不是空话。
- FunASR / SenseVoice(阿里巴巴达摩院) —— 中文、韩语和粤语ASR。
- Mojicast(ishiki-emo)—— 实时字幕管线的设计灵感,以及本项目使用的转换后标点/翻译模型工件的来源。Mojicast本身是一款完整的离线实时字幕应用,值得一试。
- Silero VAD —— 语音活动检测。
- 3D-Speaker(阿里巴巴达摩院) ——
--speakers背后的CAM++说话人嵌入模型。 - Kiwi —— 韩语形态分词器,用于修复SenseVoice的按空格分隔韩语输出。
贡献
见CONTRIBUTING.md。