Files
live-caption/docs/asr-backends.md
T
blumia b37b9eb779 fix(asr): do not call InputFinished for a never-ending stream
A live caption stream never ends: the boundary of an utterance is silence,
not end-of-input, so InputFinished (which means "no more audio ever") does
not apply. Drop it and rely on the trailing audio alone; keep a short
silence padding as a safety net for end-of-file cases. Verified that the
file path still emits the final character without InputFinished.

实时字幕的音频流永不结束,一句话的边界是"静音"而不是"输入结束",所以
不该调用语义为"再也不会有音频"的 InputFinished。改为只补一小段静音兜底,
实测文件模式的结果依然完整。文档也一并更正。
2026-10-12 16:10:59 +08:00

95 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 推理后端与模型管理
## 结构:2 个实现覆盖所有模型
实测确认了一件很重要的事:**不同模型不需要各写一套代码**。所有流式 ONNX 模型走的是同一套 API,差别只是「指向哪个模型文件 + 几行选项」。
```
IAsr(src/core/iasr.h)
├─ Qwen3Asr llama.cpp / ggml GGUF 模型(Qwen3-ASR) AsrKind::PrefixRollback
└─ SherpaAsr sherpa-onnx / onnxruntime
├─ X-ASR-zh-en 中英流式
├─ zipformer-zh-2025 中文流式(k2-fsa 官方)
└─ Nemotron-3.5 40 语言流式(含日语) AsrKind::NativeStreaming
```
两条路径的区别(`StreamingAsr` 里按 `IAsr::kind()` 分流):
| | PrefixRollback | NativeStreaming |
|---|---|---|
| 模型类型 | 离线模型(LLM 解码器) | 有状态流式识别器 |
| 上层要做的 | 切块 + 前缀回滚 + KV 复用 | 只喂音频,取当前整句文本 |
| 每步成本 | 随句子长度增长 | 恒定 |
| 已稳定/不稳定 | 用 token 回滚算 | 用「与上次的公共前缀」算 |
## 模型注册表与存储
**新增模型 = 加一条数据,不改代码。**
- 注册表:`data/models.json`(编译进二进制,同时也给脚本用)
- 存储位置:`~/.local/share/live-caption/models/<id>/`
(即 `QStandardPaths::GenericDataLocation + /live-caption/models`)
- 下载:`scripts/fetch-models.sh [id...]` / `--all`;已存在的文件会跳过
```bash
./scripts/fetch-models.sh # 列出模型与安装状态
./scripts/fetch-models.sh zipformer-zh-2025
```
## 用法
```bash
# 默认后端:llama.cpp + Qwen3-ASR GGUF
./build/live-caption-cli --model M.gguf --mmproj MM.gguf
# 流式 ONNX 后端:按注册表 id 从模型目录取
./build/live-caption-cli --asr-backend sherpa --asr-model zipformer-zh-2025
./build/live-caption-cli --asr-backend sherpa --asr-model nemotron35-560ms --language ja
# 覆盖模型目录 / 语言提示
./build/live-caption-cli --asr-backend sherpa --asr-model ... --model-dir /path/to/models
```
## 实测对比(同一批音频,1 线程)
| 模型 | 中文 | 英文 | 日语 | RTF | 核·秒/秒音频 | 首个字幕 | 体积 | 许可 |
|---|---|---|---|---|---|---|---|---|
| Qwen3-ASR 0.6B(llama) | ✅ | ✅ | ✅ | 0.56 | 4.27 | ~2.5s | 973MB | Apache-2.0 |
| zipformer-zh-2025 | ✅ | — | — | 0.098 | **0.10** | **0.48s** | 167MB | Apache-2.0 |
| X-ASR-zh-en 480ms | ✅ | ✅ | — | 0.122 | 0.12 | 1.44s | 587MB | Apache-2.0 |
| Nemotron 3.5 560ms | ⚠️ 漏字 | ⚠️ 若干错词 | ⚠️ 有错字 | 0.35 | 0.35 | 1.12s | 651MB | OpenMDW-1.1 |
> 静态模型(zipformer-zh-2025)的多线程是**负优化**:onnxruntime 对小模型开多线程,
> 同步/自旋开销会让它更慢(8 线程比 1 线程慢 2.6 倍、CPU 高 28 倍)。所以适配器里固定
> `threads = 1`。
## 两个坑
1. **sherpa-onnx 的预编译库是旧 C++ ABI**(`_GLIBCXX_USE_CXX11_ABI=0`),和 Qt 的新 ABI 冲突,
C++ API 链接不上。**必须用它的 C API**(`sherpa-onnx/c-api/c-api.h`,`extern "C"`,无名字改写)。
2. **末尾要补一小段静音**(`streamFinish()` 里补 0.3s),否则最后一个字会被截掉。
但**不需要 `InputFinished`**:实时字幕的音频流永不结束,一句话的边界是「静音」而不是「输入结束」,
`InputFinished` 的语义只对文件成立。实测只补静音(不调 `InputFinished`)结果就是完整的。
真实场景里那段尾部静音本来就会被喂进去(VAD 判定静音要 0.8s,这 0.8s 已经送给模型了),
所以补静音只是个兜底。
## 自动语言识别
| 模型 | 自带 LID |
|---|---|
| Qwen3-ASR | ✅ 输出 `language X`(实测中英都准)|
| Nemotron 3.5 | ✅ `language=auto`,但实测会串(日语开头吐韩文)|
| X-ASR / zipformer | ❌ 固定语言对,不出语言标签 |
LID 不必由 ASR 模型提供:Chrome 就带了个独立的 2.5MB `ONDEVICE_langid.tflite`(我们在它的语言包目录里看到了),
Windows 同样。sherpa-onnx 也有专门的语种识别模型。所以**「按语言装包 + 用户选语言或小 LID 模型」**是可行路线。
## 推荐组合(按语言装包)
| 语言 | 实时预览 | 定稿 |
|---|---|---|
| 中文 | `zipformer-zh-2025`(最快最准) | Qwen3-ASR |
| 英文 | `xasr-zh-en-480` | Qwen3-ASR |
| 日语 | `nemotron35-560ms --language ja`(质量一般) | Qwen3-ASR(开源日语第一) |
| 其他 | — | Qwen3-ASR(52 语言兜底) |