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

4.5 KiB
Raw Blame History

推理后端与模型管理

结构: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;已存在的文件会跳过
./scripts/fetch-models.sh                # 列出模型与安装状态
./scripts/fetch-models.sh zipformer-zh-2025

用法

# 默认后端: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 语言兜底)