Accepting the settings dialog quit the app: with a tray-resident program "last window closed" must not mean "quit", but QApplication's default made it so, and on first run the dialog is the only window. Disable quitOnLastWindowClosed. Also fix listOutputDevices hanging forever (its callbacks never signalled the PulseAudio main loop), enlarge the caption window's close button, and add capture-device selection (enumerated sinks, settings combo, --list-devices/--device) plus model integrity verification (size + sha256 in the registry, --verify-model and a "校验完整性" button). 点「确定」后程序自己退出:本程序常驻托盘,"最后一个窗口关闭"不该等于退出, 但 QApplication 默认如此,而首次启动时设置对话框正是唯一窗口。已关闭该默认 行为。同时修复 listOutputDevices 永久卡死(回调没有 signal 主循环)、放大 字幕窗关闭按钮,并新增采集设备选择与模型完整性校验(注册表含 size/sha256, 提供 --verify-model 与设置里的「校验完整性」按钮)。
5.6 KiB
5.6 KiB
live-caption
Linux 桌面实时字幕:采集系统正在播放的声音,实时转写成字幕显示(类似 Chrome 实时字幕)。 技术栈:C++ / Qt6 / CMake / Qwen3-ASR(llama.cpp,纯 C++,无 Python 依赖)。
当前进度
| 阶段 | 内容 | 状态 |
|---|---|---|
| Phase 0 | 可行性验证(采集 / 模型 / CPU 实时性) | ✅ 见 docs/phase0-verification.md |
| Phase 1 | 命令行原型(进程内推理 + monitor 采集 + 滑窗 + 稳定前缀) | ✅ 见 experiments/phase1/README.md |
| Phase 2 | 原生流式解码(8s 切片 + 增量 KV 复用 + 前缀回滚) | ✅ 见 docs/phase2-streaming.md |
| Phase 3 | Qt 悬浮字幕窗(折叠/展开、语言标签、置顶、实时链路) | ✅ 见 docs/phase3-caption-window.md |
| Phase 4 | 设置界面(模型切换、语言、字体、位置) | 待做 |
| Phase 5 | Wayland 支持、打包、模型分发 | 待做 |
关键结论(Phase 0 实测)
- 系统播放音频可从默认输出的 monitor 源采集(PipeWire + pipewire-pulse,走 libpulse,兼容 PulseAudio)。
Qwen3-ASR-0.6B在本机(i5-13420H,无独显)纯推理 RTF ≈ 0.19,实时有充足余量。- 模型与推理运行时必须锁版本:只有 ggml-org 官方 GGUF(
general.architecture = qwen3vl)与系统 llama.cpp 兼容;社区的 Q4 量化用的是qwen3asr/qwen3_asr,装不进去。 - 转写原始输出为
language <LANG><asr_text><正文>,语言名正好用于 UI 的语言标签。
快速开始
# 1. 准备模型(只需一次;网络可达 hf-mirror.com)
B=https://hf-mirror.com/ggml-org/Qwen3-ASR-0.6B-GGUF/resolve/main
aria2c -x16 -s16 -k4M -o Qwen3-ASR-0.6B-Q8_0.gguf "$B/Qwen3-ASR-0.6B-Q8_0.gguf"
aria2c -x16 -s16 -k4M -o mmproj-Qwen3-ASR-0.6B-Q8_0.gguf "$B/mmproj-Qwen3-ASR-0.6B-Q8_0.gguf"
# 2. 构建
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
# 3. 悬浮字幕窗(首次启动会打开设置对话框,引导下载并选择模型)
./build/live-caption
# 完全通过 GUI 操作:
# · 首次启动 → 设置对话框:下载模型 / 选择当前使用 / 选识别语言 / 下载源与代理
# · 点左上角的语言标签 → 再次打开设置
# · 托盘图标:显示/隐藏字幕、设置、退出
# · 窗口的 ✕ = 隐藏到托盘(用托盘菜单可以再显示回来)
# 命令行参数仍保留,仅用于自动化测试(显式给出时覆盖设置),例如:
# ./build/live-caption --asr-backend sherpa --asr-model zipformer-zh-2025 --no-tray
# 4. 命令行:单文件转写 / 流式转写 / 无窗口实时
./build/live-caption-cli --model M --mmproj MM --file some_16k_mono.wav
./build/live-caption-cli --model M --mmproj MM --stream-file some_16k_mono.wav
./build/live-caption-cli --model M --mmproj MM --cli-live
# 5. 单元测试(不需要模型,也不需要显示环境)
ctest --test-dir build --output-on-failure
推理后端
支持两条后端,用 --asr-backend 切换(详见 docs/asr-backends.md):
llama(默认):llama.cpp/ggml + Qwen3-ASR 的 GGUF 模型 —— 准确率高、52 种语言,但每步成本随段落增长。sherpa:sherpa-onnx + 流式 ONNX 模型(X-ASR / zipformer / Nemotron) —— 原生流式,每步成本恒定,CPU 约为前者的 1/35,中英准确率持平。
模型都放在 ~/.local/share/live-caption/models/,注册表是 data/models.json
(加模型只加一条数据,不改代码)。GUI 里可以直接下载(支持设置下载源与代理、
断点续传),命令行下也可以用 scripts/fetch-models.sh。
# 流式后端
./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 --list-devices # 列出可选采集设备
./build/live-caption-cli --cli-live --device <sink> # 指定采集源
./build/live-caption-cli --verify-model <id> # 校验模型完整性
代码结构
src/core/ → liblivecaption_core(静态库,**不依赖 Qt**)
iasr.h 推理后端抽象(便于用假实现测流式策略)
transcript.{h,cpp} 输出解析、语言名映射
audio.{h,cpp} monitor 采集 / WAV 读取 / 重采样
qwen3asr.{h,cpp} 进程内推理(libllama + libmtmd)
streamingasr.{h,cpp} 流式策略(8s 切片复用、前缀回滚、分段)
vad.h 能量 VAD
livepipeline.{h,cpp} 实时管线:采集→VAD→分段→流式转写
src/ui/ → liblivecaption_ui(静态库,Qt6::Widgets)
captionwindow.{h,cpp} 悬浮字幕窗
livetranscriber.{h,cpp} LivePipeline 的 Qt 适配器(只转信号)
apps/gui/ → live-caption (悬浮窗程序)
apps/cli/ → live-caption-cli (命令行程序,只链 Qt6::Core)
apps/common/ 两个程序共用的命令行选项
tests/ → 单元测试(QtTest,只链 core)
docs/ 各阶段说明与验证报告
experiments/ Phase 0/1 的验证脚本
拆分的意义:业务逻辑全在 core 且不依赖 Qt,所以流式策略、VAD、输出解析这些最容易出错的部分可以脱离真模型和显示环境做单元测试;GUI 与 CLI 各自独立演进,CLI 也不再需要链接 Qt Widgets。