diff --git a/README.md b/README.md index 29ebc24..444160b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # live-caption Linux 桌面实时字幕:采集系统正在播放的声音,实时转写成字幕显示(类似 Chrome 实时字幕)。 -技术栈:C++ / Qt6 / CMake / Qwen3-ASR(llama.cpp,纯 C++,无 Python 依赖)。 + +技术栈:C++ / Qt6 / CMake。推理后端两个,**全部纯 C++,运行时不需要 Python**: + +- `llama`:llama.cpp / ggml + Qwen3-ASR 的 GGUF 模型(准确率高、语言最全); +- `sherpa`:sherpa-onnx + 流式 ONNX 模型(zipformer / X-ASR / Nemotron,原生流式、CPU 占用低)。 ## 当前进度 @@ -9,45 +13,48 @@ Linux 桌面实时字幕:采集系统正在播放的声音,实时转写成 |---|---|---| | Phase 0 | 可行性验证(采集 / 模型 / CPU 实时性) | ✅ 见 [docs/phase0-verification.md](docs/phase0-verification.md) | | Phase 1 | 命令行原型(进程内推理 + monitor 采集 + 滑窗 + 稳定前缀) | ✅ 见 [experiments/phase1/README.md](experiments/phase1/README.md) | -| Phase 2 | 原生流式解码(8s 切片 + 增量 KV 复用 + 前缀回滚) | ✅ 见 [docs/phase2-streaming.md](docs/phase2-streaming.md) | +| Phase 2 | 原生流式解码(切片 + 增量 KV 复用 + 前缀回滚) | ✅ 见 [docs/phase2-streaming.md](docs/phase2-streaming.md) | | Phase 3 | Qt 悬浮字幕窗(折叠/展开、语言标签、置顶、实时链路) | ✅ 见 [docs/phase3-caption-window.md](docs/phase3-caption-window.md) | -| Phase 4 | 设置界面(模型切换、语言、字体、位置) | 待做 | -| Phase 5 | Wayland 支持、打包、模型分发 | 待做 | +| Phase 3.5 | 双后端(llama / sherpa)+ 模型注册表 + 流式 ONNX | ✅ 见 [docs/asr-backends.md](docs/asr-backends.md) | +| Phase 4 | 全 GUI 可用:设置对话框、应用内下载、托盘、采集设备、完整性校验 | ✅ 见 [docs/gui.md](docs/gui.md) | +| Phase 5 | Wayland 支持、打包、模型分发 | 部分(模型下载/分发已可用,见上) | ## 关键结论(Phase 0 实测) - 系统播放音频可从默认输出的 monitor 源采集(PipeWire + pipewire-pulse,走 libpulse,兼容 PulseAudio)。 - `Qwen3-ASR-0.6B` 在本机(i5-13420H,无独显)纯推理 RTF ≈ **0.19**,实时有充足余量。 + 注意口径:这是 `llama-cli` 纯生成的数字;程序内走 mtmd 音频编码的实现实测 RTF ≈ **0.56** + (见 [docs/asr-backends.md](docs/asr-backends.md) 的实测对比)。两者都远低于 1.0,够实时。 - 模型与推理运行时**必须锁版本**:只有 ggml-org 官方 GGUF(`general.architecture = qwen3vl`)与系统 llama.cpp 兼容;社区的 Q4 量化用的是 `qwen3asr` / `qwen3_asr`,装不进去。 - 转写原始输出为 `language <正文>`,语言名正好用于 UI 的语言标签。 ## 快速开始 ```bash -# 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. 构建 +# 1. 构建 cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j -# 3. 悬浮字幕窗(首次启动会打开设置对话框,引导下载并选择模型) +# 2. 悬浮字幕窗:首次启动会打开设置对话框,在里面下载并选择模型 ./build/live-caption # 完全通过 GUI 操作: -# · 首次启动 → 设置对话框:下载模型 / 选择当前使用 / 选识别语言 / 下载源与代理 +# · 首次启动 → 设置对话框:下载模型 / 选择当前使用 / 选识别语言 / 采集设备 / 下载源与代理 # · 点左上角的语言标签 → 再次打开设置 +# · 底部的「应用」= 应用但不关闭,方便实时预览(不透明度、字号等) +# · 「校验完整性」= 重算 sha256,确认模型文件没有损坏 # · 托盘图标:显示/隐藏字幕、设置、退出 # · 窗口的 ✕ = 隐藏到托盘(用托盘菜单可以再显示回来) -# 命令行参数仍保留,仅用于自动化测试(显式给出时覆盖设置),例如: -# ./build/live-caption --asr-backend sherpa --asr-model zipformer-zh-2025 --no-tray +# 命令行参数仍保留,仅用于自动化测试(显式给出时覆盖设置),全部选项见 docs/cli.md + +# 3. 也可以先用脚本把模型下到应用数据目录(不进 GUI) +./scripts/fetch-models.sh # 列出模型与安装状态 +./scripts/fetch-models.sh zipformer-zh-2025 # 下载指定模型 # 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 +./build/live-caption-cli --asr-backend sherpa --asr-model zipformer-zh-2025 --cli-live # 5. 单元测试(不需要模型,也不需要显示环境) ctest --test-dir build --output-on-failure @@ -55,14 +62,16 @@ ctest --test-dir build --output-on-failure ## 推理后端 -支持两条后端,用 `--asr-backend` 切换(详见 [docs/asr-backends.md](docs/asr-backends.md)): +支持两条后端,正常情况下**不用手动选**:GUI 会根据所选的模型自动决定用哪条 +(`data/models.json` 里每个模型都写了 `backend`)。命令行下可以用 `--asr-backend` 强制指定 +(详见 [docs/asr-backends.md](docs/asr-backends.md)): -- **`llama`(默认)**:llama.cpp/ggml + Qwen3-ASR 的 GGUF 模型 —— 准确率高、52 种语言,但每步成本随段落增长。 -- **`sherpa`**:sherpa-onnx + 流式 ONNX 模型(X-ASR / zipformer / Nemotron) —— 原生流式,每步成本恒定,CPU 约为前者的 1/35,中英准确率持平。 +- **`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`。 +模型都放在 `~/.local/share/live-caption/models//`,注册表是 `data/models.json` +(**加模型只加一条数据,不改代码**)。GUI 里可以直接下载(支持设置下载源与代理、断点续传、 +sha256 校验),命令行下也可以用 `scripts/fetch-models.sh`。 ```bash # 流式后端 @@ -72,7 +81,7 @@ ctest --test-dir build --output-on-failure # 辅助 ./build/live-caption-cli --list-devices # 列出可选采集设备 ./build/live-caption-cli --cli-live --device # 指定采集源 -./build/live-caption-cli --verify-model # 校验模型完整性 +./build/live-caption-cli --verify-model zipformer-zh-2025 # 校验模型完整性 ``` ## 代码结构 @@ -81,20 +90,40 @@ ctest --test-dir build --output-on-failure src/core/ → liblivecaption_core(静态库,**不依赖 Qt**) iasr.h 推理后端抽象(便于用假实现测流式策略) transcript.{h,cpp} 输出解析、语言名映射 - audio.{h,cpp} monitor 采集 / WAV 读取 / 重采样 + audio.{h,cpp} monitor 采集 / 输出设备枚举 / WAV 读取 / 重采样 qwen3asr.{h,cpp} 进程内推理(libllama + libmtmd) - streamingasr.{h,cpp} 流式策略(8s 切片复用、前缀回滚、分段) + sherpaasr.{h,cpp} sherpa-onnx C API 适配(流式 ONNX) + streamingasr.{h,cpp} 流式策略(切片复用 / 前缀回滚 / 原生流式分支) vad.h 能量 VAD livepipeline.{h,cpp} 实时管线:采集→VAD→分段→流式转写 +src/models/ → liblivecaption_models(静态库,Qt6::Core/Network) + modelstore.{h,cpp} 注册表解析、安装检查、sha256 校验 + modeldownloader.{h,cpp} 应用内下载(镜像 / 代理 / 断点续传 / 进度) src/ui/ → liblivecaption_ui(静态库,Qt6::Widgets) captionwindow.{h,cpp} 悬浮字幕窗 + settingsdialog.{h,cpp} 设置对话框 + appsettings.{h,cpp} QSettings 配置 livetranscriber.{h,cpp} LivePipeline 的 Qt 适配器(只转信号) -apps/gui/ → live-caption (悬浮窗程序) +apps/gui/ → live-caption (悬浮窗 + 托盘 + 设置) apps/cli/ → live-caption-cli (命令行程序,只链 Qt6::Core) -apps/common/ 两个程序共用的命令行选项 -tests/ → 单元测试(QtTest,只链 core) -docs/ 各阶段说明与验证报告 +apps/common/ 两个程序共用的命令行选项与后端工厂 +data/models.json 模型注册表(编译进二进制,加模型=加一条数据) +tests/ 单元测试(QtTest,只链 core) +scripts/fetch-models.sh 命令行下载模型 experiments/ Phase 0/1 的验证脚本 ``` -拆分的意义:**业务逻辑全在 core 且不依赖 Qt**,所以流式策略、VAD、输出解析这些最容易出错的部分可以脱离真模型和显示环境做单元测试;GUI 与 CLI 各自独立演进,CLI 也不再需要链接 Qt Widgets。 +拆分的意义:**业务逻辑全在 core 且不依赖 Qt**,所以流式策略、VAD、输出解析这些最容易出错的部分可以脱离真模型和显示环境做单元测试;模型管理单独一层,GUI 与 CLI 各自独立演进,CLI 也不再需要链接 Qt Widgets。 + +## 文档 + +| 文档 | 内容 | +|---|---| +| [docs/gui.md](docs/gui.md) | 图形界面:首次配置、设置项说明、托盘、下载与校验 | +| [docs/cli.md](docs/cli.md) | 全部命令行选项(含仅测试用的那些) | +| [docs/asr-backends.md](docs/asr-backends.md) | 双后端结构、模型注册表、实测对比、语言推荐 | +| [docs/troubleshooting.md](docs/troubleshooting.md) | 常见问题(窗口不见了、模型没下全、Wayland 置顶…) | +| [docs/testing.md](docs/testing.md) | 测试分层、回归用例、GUI 交互测试钩子 | +| [docs/phase0-verification.md](docs/phase0-verification.md) | Phase 0 可行性验证报告 | +| [docs/phase2-streaming.md](docs/phase2-streaming.md) | Phase 2 流式解码 | +| [docs/phase3-caption-window.md](docs/phase3-caption-window.md) | Phase 3 悬浮字幕窗 | diff --git a/apps/gui/main.cpp b/apps/gui/main.cpp index ac857bf..78636fa 100644 --- a/apps/gui/main.cpp +++ b/apps/gui/main.cpp @@ -95,7 +95,7 @@ int main(int argc, char ** argv) parser.addOption(QCommandLineOption("screenshot-delay", "截图前等待秒数", "sec", "6")); parser.addOption(QCommandLineOption("no-tray", "不创建托盘图标(测试用)")); parser.addOption(QCommandLineOption("screenshot-settings", "打开设置对话框截图后退出", "path")); - parser.addOption(QCommandLineOption("settings-autoclose", "复现用:自动关闭设置对话框(reject|close|accept|ok)", "how")); + parser.addOption(QCommandLineOption("settings-autoclose", "复现用:自动操作设置对话框(reject|close|accept|ok|apply)", "how")); parser.addOption(QCommandLineOption("quit-after", "运行若干秒后优雅退出(测试退出路径用)", "sec")); parser.process(app); diff --git a/docs/asr-backends.md b/docs/asr-backends.md index c0678f9..f31d83e 100644 --- a/docs/asr-backends.md +++ b/docs/asr-backends.md @@ -29,13 +29,44 @@ IAsr(src/core/iasr.h) - 注册表:`data/models.json`(编译进二进制,同时也给脚本用) - 存储位置:`~/.local/share/live-caption/models//` (即 `QStandardPaths::GenericDataLocation + /live-caption/models`) -- 下载:`scripts/fetch-models.sh [id...]` / `--all`;已存在的文件会跳过 +- 下载:GUI 的「模型 → 下载」,或 `scripts/fetch-models.sh [id...]` / `--all`;已存在的文件会跳过 ```bash ./scripts/fetch-models.sh # 列出模型与安装状态 ./scripts/fetch-models.sh zipformer-zh-2025 ``` +一条模型长这样: + +```json +{ + "id": "zipformer-zh-2025", + "name": "中文流式(k2-fsa 官方 2025-06-30)", + "backend": "sherpa-online", // llama-cpp | sherpa-online + "languages": ["zh"], + "license": "Apache-2.0", + "note": "167MB,最快最准的中文流式模型", + "files": { + "encoder": { "name": "encoder.int8.onnx", "url": "https://...", "size": 161000000, "sha256": "5ac5..." }, + "tokens": { "name": "tokens.txt", "url": "https://...", "size": 3000, "sha256": "6193..." } + } +} +``` + +`size` / `sha256` 就是完整性校验的依据:**打开/选择模型时只比大小**(快), +「校验完整性」或 `--verify-model` 才**重算 sha256**(慢,但能发现内容损坏)。 + +## 模型清单 + +| id | 语言 | 后端 | 许可 | 体积 | sha256(首个文件,前 16 位) | +|---|---|---|---|---|---| +| `qwen3-asr-0.6b-q8` | 52 语言 | llama-cpp | Apache-2.0 | 1019MB | `bca259818b50ca7c…` | +| `zipformer-zh-2025` | zh | sherpa-online | Apache-2.0 | 167MB | `5ac51e27981bb4da…` | +| `xasr-zh-en-480` | zh, en | sherpa-online | Apache-2.0 | 615MB | `0c3454033d249081…` | +| `nemotron35-560ms` | 16 语言(含 ja) | sherpa-online | OpenMDW-1.1 | 682MB | `012e9321373af990…` | + +分发包模型时记得带上许可(尤其 Nemotron 是 OpenMDW-1.1,不是 Apache-2.0)。 + ## 用法 ```bash @@ -65,14 +96,13 @@ IAsr(src/core/iasr.h) ## 设置对话框 -三个页签(模型 / 显示 / 高级),底部三个按钮: - -- **应用**:应用设置但**不关闭**对话框,方便实时预览(改不透明度、字号时会立刻看到效果); -- **取消**:放弃本次改动; -- **确定**:应用并关闭。 +三个页签(模型 / 显示 / 高级),底部三个按钮 **应用 / 取消 / 确定** —— +「应用」= 应用但不关闭,方便实时预览(改不透明度、字号时会立刻看到效果)。 只有**模型 / 识别语言 / 采集设备**变化才会重启识别后端;透明度、字号、行数、位置这类外观改动是即时生效的,不会打断正在进行的转写。 +各设置项的完整说明见 [gui.md](gui.md)。 + ## 采集设备选择 默认跟随系统默认输出;也可以在设置里指定某个输出设备(`--list-devices` 可列出): diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 0000000..527031b --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,106 @@ +# 命令行选项 + +两个程序: + +- **`live-caption`** —— 图形界面(悬浮窗 + 托盘 + 设置)。**日常使用不需要任何参数**; + 下面的参数里,它只关心「后端与模型」「流式参数」「GUI 专用 / 测试专用」几节。 +- **`live-caption-cli`** —— 命令行版,只链 Qt6::Core,适合无显示环境、脚本、CI。 + +两边共用的选项定义在 `apps/common/appcli.h`。 +`--help` 会用「(默认 X)」把默认值印在说明里。 + +## 后端与模型 + +| 选项 | 默认 | 说明 | +|---|---|---| +| `--asr-backend ` | `llama` | `llama`(Qwen3-ASR GGUF)或 `sherpa`(流式 ONNX) | +| `--asr-model ` | — | 模型注册表(`data/models.json`)里的 id;sherpa 后端用它定位文件,llama 后端也可用它走注册表 | +| `--model ` | — | 主模型 GGUF 路径(架构须为 `qwen3vl`) | +| `--mmproj ` | — | mmproj GGUF 路径(clip + qwen3a) | +| `--model-dir ` | `~/.local/share/live-caption/models` | 覆盖模型存储目录 | +| `--language ` | 空 = 自动 | 语言提示(`zh` / `en` / `ja` …),多语言模型需要 | +| `--threads ` | `8` | 推理线程数(只影响 Qwen3-ASR;流式模型固定单线程) | +| `--verbose` | 关 | 输出 llama.cpp / mtmd 的详细日志 | + +> 模型「未安装」时会提示缺失的文件名,并给出 `scripts/fetch-models.sh ` 的提示。 + +## 流式参数 + +参数的选择与实测见 [docs/phase2-streaming.md](phase2-streaming.md)。 +**这些参数只影响 `llama` 后端**(Qwen3-ASR);`sherpa` 的流式模型只用到「提交粒度」一个参数。 + +| 选项 | 默认 | 说明 | +|---|---|---| +| `--stream-chunk ` | `2.0` | 累积多少秒音频跑一步(提交粒度) | +| `--stream-mode ` | `incremental` | `incremental`(增量 KV 复用)/ `append`(追加式)/ `window`(滑窗) | +| `--stream-window ` | `16` | 滑窗上限;仅 `window` 模式,`0` = 不滑窗 | +| `--stream-segment ` | `30` | 单段最长时长,超过就提交并重置上下文;`0` = 不限 | +| `--unfixed-tokens ` | `5` | 前缀回滚:丢掉最后 N 个 token(这些算「不稳定尾部」) | +| `--unfixed-chunks ` | `2` | 前 N 个 chunk 不带前缀(冷启动) | +| `--max-tokens ` | `256` | 单段最多生成 token 数 | + +## 运行模式(仅 `live-caption-cli`) + +| 选项 | 说明 | +|---|---| +| `--file ` | 整段转写一个 WAV 后退出(离线精度基线) | +| `--stream-file ` | 以流式模式转写一个 WAV 后退出(验证流式策略) | +| `--cli-live` | 实时转写并只在命令行输出(不开窗口),用于无显示环境 | + +## 采集 + +| 选项 | 说明 | +|---|---| +| `--device ` | 采集源(输出设备的 sink 名);留空 = 跟随系统默认输出 | +| `--list-devices` | 列出可选的采集设备后退出(仅 `live-caption-cli`) | + +```bash +./build/live-caption-cli --list-devices +# alsa_output...HiFi__Speaker__sink ... Speaker [默认] +# alsa_output...HiFi__HDMI1__sink ... HDMI / DisplayPort 1 Output +./build/live-caption-cli --asr-model zipformer-zh-2025 --cli-live --device alsa_output...HiFi__HDMI1__sink +``` + +## 辅助 + +| 选项 | 说明 | +|---|---| +| `--verify-model ` | 重算 sha256 校验模型完整性后退出(仅 `live-caption-cli`) | + +```bash +./build/live-caption-cli --verify-model zipformer-zh-2025 +# ✅ 中文流式(k2-fsa 官方 2025-06-30) 校验通过 +``` + +## GUI 专用 / 测试专用 + +这些只在 `live-caption` 上有效。**除 `--opacity` 外都是给自动化测试用的**, +平时不需要:界面本身提供这些功能。 + +| 选项 | 默认 | 说明 | +|---|---|---| +| `--opacity ` | — | 背景不透明度(20–100),覆盖设置里的值 | +| `--no-tray` | — | 不创建托盘图标(测试用;此时 `✕` 会直接退出) | +| `--screenshot ` | — | 启动后自动截图并退出;同时截折叠态与展开态(`-expanded.png`) | +| `--screenshot-delay ` | `6` | 截图前等待秒数 | +| `--screenshot-settings ` | — | 打开设置对话框、截图后退出 | +| `--settings-autoclose ` | — | 自动操作设置对话框:`reject` / `close` / `accept` / `ok` / `apply`(`ok`/`apply` 是真的去点按钮) | +| `--quit-after ` | — | 运行若干秒后优雅退出(测退出路径) | + +`--settings-autoclose` + `--quit-after` 是当初定位「点确定程序就退出」崩溃时留下的复现手段, +也可以当作界面交互的冒烟测试: + +```bash +for how in reject close accept ok apply; do + ./build/live-caption --asr-model zipformer-zh-2025 --no-tray \ + --settings-autoclose $how --quit-after 8 + echo "$how → 退出码 $?" +done +``` + +## 退出码 + +| 码 | 含义 | +|---|---| +| `0` | 正常结束(含 `--screenshot*`、`--quit-after` 的优雅退出) | +| `1` | 启动失败:模型没加载起来(`live-caption-cli` 的 `--verify-model` 校验失败也是 1) | diff --git a/docs/gui.md b/docs/gui.md new file mode 100644 index 0000000..2e6d249 --- /dev/null +++ b/docs/gui.md @@ -0,0 +1,126 @@ +# 图形界面(Phase 4) + +`live-caption` 是图形界面程序:悬浮字幕窗 + 系统托盘 + 设置对话框。 +**不用任何命令行参数就能完整使用**;命令行参数只保留给自动化测试(显式给出时覆盖设置)。 + +## 首次运行 + +首次启动时如果还没有「已下载且被选中」的模型,程序会先弹出**设置对话框(标题为 +「实时字幕 — 首次配置」)**,并挡住主流程 —— 下载一个模型、在「当前使用」里选中它, +点「确定」之后才会出现字幕窗。 + +``` +第一次启动 → 设置对话框 → 下载模型 → 选择「当前使用」 → 确定 → 悬浮字幕窗 +``` + +## 字幕窗 + +| 操作 | 行为 | +|---|---| +| 拖动 | 移动窗口;位置会被记住 | +| 左上角语言标签 | 点击 → 打开设置对话框 | +| 底部箭头 `⌄` / `⌃` | 折叠(2 行)/ 展开(8 行) | +| 右上角 `✕` | **隐藏到托盘**(不是退出;用托盘菜单可以再显示回来) | + +窗口的文本分两段显示:**已稳定**部分是白色,**尚未稳定**的尾部是灰色。这就是 Chrome +实时字幕那种「字一个个长出来、末尾会改」的观感。 + +> 没有托盘时(`--no-tray`,或桌面环境不支持托盘)`✕` 会直接退出程序。 + +## 托盘 + +托盘菜单有三项: + +- **显示/隐藏字幕** +- **设置…** +- **退出** + +单击托盘图标也会切换字幕窗的显示/隐藏。 + +## 设置对话框 + +三个页签(模型 / 显示 / 高级),底部三个按钮: + +| 按钮 | 行为 | +|---|---| +| **应用** | 应用设置但**不关闭**对话框 —— 方便实时预览(改不透明度、字号时立刻能看到效果) | +| 取消 | 放弃本次改动(已经用「应用」生效过的改动不会被回滚) | +| 确定 | 应用并关闭 | + +**哪些改动要重启识别后端?** 只有**模型 / 识别语言 / 采集设备**这三项变化时才会重建后端并重启转写; +透明度、字号、行数、灰色尾部这类外观改动是即时生效的,不会打断正在进行的转写。 + +### 模型页 + +- **模型表格**:列出注册表里的所有模型(名称 / 语言 / 许可 / 状态)。第一列是注册表 id, + 状态列显示「已安装 / 未安装」,未安装时鼠标悬停会列出缺失的文件名。 +- **下载**:把选中的模型下到 `~/.local/share/live-caption/models//`。 + 支持设置下载源(镜像)与代理、断点续传、实时进度。下载完成会自动刷新列表。 +- **校验完整性**:对选中模型的所有文件**重算 sha256**,与注册表里的期望值比对; + 失败会列出损坏的文件名(可删除后重新下载)。文件大时需要几秒。 +- **删除**:删掉该模型已下载的文件。如果删的正是「当前使用」的模型,选择会被清空。 +- **打开模型目录**:在文件管理器里打开模型存储目录。 +- **采集设备**:默认「跟随系统默认输出」(即当前默认输出的 monitor)。 + 也可以指定某个具体输出设备(如 HDMI);列不出来或没列全时见 + [troubleshooting.md](troubleshooting.md)。 +- **当前使用**:选择实际生效的模型(只列出**已安装**的)。 +- **识别语言**:语言提示,多语言模型用它(例如 Nemotron 需要指定语言才不出错); + 「自动检测」= 留空。目前提供 自动/中文/英文/日语。 +- **下载源**:默认 `https://hf-mirror.com`(网络可达性最好的镜像);可改成其它镜像或官方源。 +- **代理**:不使用 / 跟随系统 / 手动设置(主机、端口、用户名、密码)。 + +### 显示页 + +| 项 | 说明 | +|---|---| +| 字号 | 字幕文字大小(9–40) | +| 用灰色显示尚未稳定的尾部 | 关掉后就只显示已稳定的文本 | +| 折叠行数 / 展开行数 | 折叠态与展开态各显示几行 | +| 背景不透明度 | 20%–100%,默认 84%(纯黑底,保证任何桌面背景下文字都可读) | + +### 高级页 + +| 项 | 说明 | +|---|---| +| 更新粒度(秒) | 累积多少秒音频跑一步;「自动」= 由后端决定(流式模型 0.32s) | +| 线程数 | 推理线程数;**只影响 Qwen3-ASR**,流式模型固定单线程(多线程反而更慢) | + +## 配置文件 + +所有设置存在 `QSettings` 里:`~/.config/live-caption/live-caption.conf`,组名 `[app]`。 + +```ini +[app] +modelId=zipformer-zh-2025 ; 当前使用的模型 id +language= ; 空 = 自动 +captureDevice= ; 空 = 跟随系统默认输出 +opacity=84 fontSize=15 ; 外观 +showUnstableTail=true +collapsedLines=2 expandedLines=8 +windowX=179 windowY=725 ; 拖动后记录的位置 +mirrorBase=https://hf-mirror.com +proxyMode=0 ; 0=不使用 1=跟随系统 2=手动 +chunkSec=0 threads=8 ; 高级 +``` + +删掉这个文件(或其中某个键)就回到默认值。模型文件在 +`~/.local/share/live-caption/models//`,不受这个文件影响。 + +## 自动化测试钩子(仅测试用) + +界面本身的行为可以完全用命令行脚本驱动,这些选项平时用不到: + +```bash +# 打开设置对话框并截图后退出 +./build/live-caption --screenshot-settings /tmp/settings.png + +# 自动关掉设置对话框,用来测「关闭/确定/取消/应用」各条路径是否崩溃 +./build/live-caption --asr-model zipformer-zh-2025 --no-tray \ + --settings-autoclose {reject|close|accept|ok|apply} --quit-after 8 + +# 字幕窗自截图(折叠态 + 展开态) +./build/live-caption --asr-model zipformer-zh-2025 --no-tray \ + --screenshot /tmp/win.png --screenshot-delay 6 +``` + +完整列表见 [cli.md](cli.md)。 diff --git a/docs/phase0-verification.md b/docs/phase0-verification.md index 20f7ea9..08ac725 100644 --- a/docs/phase0-verification.md +++ b/docs/phase0-verification.md @@ -32,6 +32,11 @@ > 流式含义:模型常驻内存时,处理 2 秒窗口约 0.3 s,滑动窗口 + 提交策略下端到端延迟可做到 **~1 s 级**,与 Chrome 实时字幕同量级。 +> **口径提示**:本页的 RTF ≈ 0.19 是 **`llama-cli` 纯生成**(`-t 8`)的数字。 +> 程序内走 `libmtmd` 音频编码的实现,实测 RTF ≈ **0.56**(单线程,见 +> [asr-backends.md](asr-backends.md#实测对比同一批音频1-线程));两个口径都远低于 1.0, +> 但引用数字时要注意区分。另外流式 ONNX 后端的 CPU 占用比它低约 35 倍。 + ### 2.1 0.6B vs 1.7B 实测对比 均为 Q8_0 主模型 + Q8_0 mmproj,`-t 8`。 diff --git a/docs/phase2-streaming.md b/docs/phase2-streaming.md index 8584385..b62e5ea 100644 --- a/docs/phase2-streaming.md +++ b/docs/phase2-streaming.md @@ -2,6 +2,11 @@ 状态:**完成**。准确率与离线转写一致,单步计算稳定在 2s 预算内。 +> 本文讲的这套「切块 + 前缀回滚 + 增量 KV 复用」是 **llama 后端(Qwen3-ASR)** 的路径 +> (`IAsr::AsrKind::PrefixRollback`)。Phase 3.5 加的流式 ONNX 后端走的是另一条分支 +> (`NativeStreaming`:模型自己有状态,上层只喂音频、取当前整句), +> 见 [asr-backends.md](asr-backends.md)。GUI 会按所选模型自动选这两条路径,不用手动切。 + ## 两种流式模式(实测对比) 代码里实现了两条流式路径,用 `--stream-mode` 切换。它们对应一个**无法同时满足的三选二**: diff --git a/docs/phase3-caption-window.md b/docs/phase3-caption-window.md index 34afb47..5097510 100644 --- a/docs/phase3-caption-window.md +++ b/docs/phase3-caption-window.md @@ -1,6 +1,8 @@ # Phase 3:Qt 悬浮字幕窗 状态:**完成**。悬浮窗可用,已接入实时采集 + 流式转写。 +(窗口之外的交互 —— 托盘、设置对话框、应用内下载 —— 属于 Phase 4,见 +[docs/gui.md](gui.md)。本文记录 Phase 3 当时的形态,并在末尾列出后续变化。) ![折叠态(英文)](images/phase3-en-collapsed.png) @@ -14,10 +16,15 @@ - **悬浮窗**:无边框、半透明深色圆角、始终置顶、不接受焦点(`Qt::FramelessWindowHint | WindowStaysOnTopHint | Tool | WindowDoesNotAcceptFocus`),可拖动。 - **折叠 / 展开**:折叠两行、展开八行,底部箭头切换(折叠态 `⌄`、展开态 `⌃`)。 -- **语言标签**:左上角圆角 pill,显示识别到的语言(英语 / 中文 …);点击会发 `languageClicked` 信号,设置界面留到 Phase 4。 + 行数可在设置里改(Phase 4)。 +- **语言标签**:左上角圆角 pill,显示识别到的语言(英语 / 中文 …);点击发 `languageClicked` + —— Phase 4 起就是用它打开设置对话框。 - **稳定/不稳定区分**:已稳定文本白色,不稳定尾部灰色 —— 与 CLI 版一致,也是 Chrome 那种"字长出来"的观感。 -- **背景**:**黑色 + 可调不透明度**(不是全透明;纯黑底才能保证任何桌面背景下文字都可读)。默认 84%,范围限制 20~100,`--opacity` 或 `setPanelOpacity()` 可调,Phase 4 接到设置里即可。 -- **关闭**:右上角 `✕` 退出。 + 灰色尾部可以用设置里的「用灰色显示尚未稳定的尾部」关掉(Phase 4)。 +- **背景**:**黑色 + 可调不透明度**(不是全透明;纯黑底才能保证任何桌面背景下文字都可读)。 + 默认 84%,范围限制 20~100,设置界面里是可拖的滑块(Phase 4),`--opacity` / `setPanelOpacity()` 也可调。 +- **关闭**:右上角 `✕`。Phase 4 起它的行为是**隐藏到托盘**(否则关掉就没法再打开了); + 只有在 `--no-tray` 或托盘不可用时才真正退出。 - **实时链路**:采集默认输出的 monitor → 流式转写 → 信号推给窗口,全部在主线程外的工作线程里跑。 ## 实时分段 @@ -29,7 +36,7 @@ Phase 2 的结论是段不能无限长(解码器对 KV 的注意力随总长 - **静音期间完全不送音频**:早期版本会把静音也喂进模型,模型对静音产生了幻觉(英文语音前冒出一个 `嗯。`); - 静音超过 0.8s 就收束这一段(提交文本、清空 KV),下一段重新开始。 -`--stream-segment`(默认 30s)仍作为兜底上界。 +`--stream-segment`(默认 30s)仍作为兜底上界,设置界面对应「高级 → 单段最长时长」。 ## 代码结构 @@ -37,12 +44,15 @@ Phase 2 的结论是段不能无限长(解码器对 KV 的注意力随总长 src/ ├─ captionwindow.{h,cpp} # 悬浮窗 ├─ livetranscriber.{h,cpp} # 采集 + VAD + 分段 + 流式引擎(QObject) -├─ streamingasr.{h,cpp} # 流式策略(前缀回滚、8s 切片、分段) +├─ streamingasr.{h,cpp} # 流式策略(前缀回滚、切片、分段) ├─ qwen3asr.{h,cpp} # 进程内推理(libllama + libmtmd) ├─ audio.{h,cpp} # WAV 读取 + monitor 采集 + 重采样 └─ transcript.{h,cpp} # 输出解析与语言名映射 ``` +> 目录后来重排过:`captionwindow` / `livetranscriber` 现在在 `src/ui/`, +> 其余在 `src/core/`,模型管理独立成 `src/models/`。见 [README 的代码结构](../README.md#代码结构)。 + ## 过程中踩的坑 1. **静音幻觉**:第一版把采集到的静音也喂给了模型,模型对静音产生幻觉,英文语音前面冒出一个 `嗯。`。改为静音期间完全不送音频。 @@ -53,25 +63,32 @@ src/ ## 用法 ```bash -# 默认就是悬浮窗模式 -./build/live-caption-cli --model MODEL --mmproj MMPROJ +# 悬浮窗程序(图形界面,Phase 4 起带托盘与设置) +./build/live-caption -# 调整背景不透明度(20-100) -./build/live-caption-cli --model MODEL --mmproj MMPROJ --opacity 60 +# 调整背景不透明度(20-100)—— 这是 GUI 程序的选项 +./build/live-caption --opacity 60 # 无窗口、纯命令行实时(便于在无显示环境调试) -./build/live-caption-cli --model MODEL --mmproj MMPROJ --cli-live +./build/live-caption-cli --asr-backend sherpa --asr-model zipformer-zh-2025 --cli-live # 自截图(验证渲染,不依赖外部截图工具);会依次截折叠态与展开态 -./build/live-caption-cli --model MODEL --mmproj MMPROJ \ - --screenshot out.png --screenshot-delay 12 +./build/live-caption --asr-backend sherpa --asr-model zipformer-zh-2025 \ + --no-tray --screenshot out.png --screenshot-delay 12 ``` > 自截图产物带 alpha 通道;要判断不透明度是否正确,**直接看像素 alpha**,不要靠肉眼看合成效果。 +> 所有选项见 [docs/cli.md](cli.md)。 + +## Phase 3 之后的变化(Phase 4) + +- ✕ 从「退出」改成「隐藏到托盘」,并加了托盘图标与菜单; +- 语言标签点击 → 打开设置对话框(模型下载/选择、语言、显示、采集设备、下载源与代理、校验); +- 背景不透明度、字号、行数、灰色尾部、窗口位置全部进了设置界面并持久化(`QSettings`); +- **采集设备可选**(原来固定跟随启动时的默认输出,切到 HDMI 不会跟着变); +- 细节见 [docs/gui.md](gui.md)。 ## 已知限制 / 后续 -- X11 下置顶与定位都正常;Wayland 下需要 layer-shell,属于 Phase 5。 -- 语言标签点击暂无设置界面(Phase 4)。 -- 采集跟随的是**启动时**的默认输出设备;设备切换(例如切到 HDMI)暂未处理。 +- X11 下置顶与定位都正常;Wayland 下需要 layer-shell,属于 Phase 5(见 [docs/troubleshooting.md](troubleshooting.md))。 - 界面尚未做 HiDPI 之外的适配(当前主屏宽度 72%、最大 900 逻辑像素)。 diff --git a/docs/testing.md b/docs/testing.md index 5da03de..a5a61df 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -5,9 +5,10 @@ | 目标 | 依赖 | 说明 | |---|---|---| | `livecaption_core` | 无 Qt | 全部业务逻辑;单元测试只依赖它 | -| `livecaption_ui` | Qt6::Widgets | 悬浮窗与 Qt 适配层 | -| `live-caption` | ui | 悬浮窗程序 | -| `live-caption-cli` | core + Qt6::Core | 命令行程序(**不链接 Qt Widgets**) | +| `livecaption_models` | Qt6::Core / Network | 注册表、安装检查、sha256 校验、应用内下载 | +| `livecaption_ui` | Qt6::Widgets | 悬浮窗、设置对话框、Qt 适配层 | +| `live-caption` | ui + models | 悬浮窗程序 | +| `live-caption-cli` | core + models + Qt6::Core | 命令行程序(**不链接 Qt Widgets**) | | `tst_*` | core + Qt6::Test | 单元测试 | ## 运行 @@ -17,7 +18,14 @@ cmake -S . -B build && cmake --build build -j ctest --test-dir build --output-on-failure # 也可以单独跑 build/tests/tst_xxx ``` -单元测试**不需要模型文件,也不需要显示环境**,秒级完成。 +单元测试**不需要模型文件,也不需要显示环境**,秒级完成。目前 4 套: + +| 测试 | 覆盖 | +|---|---| +| `tst_streamingasr` | 流式策略:拼接、分段、已稳定/不稳定尾部、冷启动前缀 | +| `tst_livepipeline` | 实时管线:静音过滤、不规则 chunk 不丢样本、静音分段 | +| `tst_vad` | 能量 VAD(轻话音的检测) | +| `tst_transcript` | 输出解析与语言名映射 | ## 为什么能这样测 @@ -48,14 +56,38 @@ ctest --test-dir build --output-on-failure # 也可以单独跑 build/tests/t 写测试的过程中还**抓到一个真 bug**:段恰好结束在 chunk 边界(没有待处理音频)时,`flush()` 只提交"已稳定"部分,最后几个词会被丢掉。已修(用完整文本 `lastRawFull_` 提交)。 +## 界面 / 集成测试钩子 + +界面与真实音频链路不做单元测试,但留有可脚本化的入口(全部选项见 [cli.md](cli.md)): + +```bash +# 设置对话框各条退出路径的冒烟测试(当初定位「点确定就退出」的崩溃就是靠它) +for how in reject close accept ok apply; do + ./build/live-caption --asr-model zipformer-zh-2025 --no-tray \ + --settings-autoclose $how --quit-after 8 + echo "$how → 退出码 $?" +done + +# 界面渲染自截图(折叠态 + 展开态),不依赖外部截图工具 +./build/live-caption --asr-model zipformer-zh-2025 --no-tray \ + --screenshot /tmp/win.png --screenshot-delay 6 +./build/live-caption --screenshot-settings /tmp/settings.png + +# 模型完整性与采集设备(不需要显示环境) +./build/live-caption-cli --verify-model zipformer-zh-2025 +./build/live-caption-cli --list-devices +``` + +崩溃时用 `coredumpctl list` / `coredumpctl info ` 看调用栈。 + ## 尚未覆盖 以下几类需要真模型或真实音频设备,属于集成测试,没放进单元测试: - **8s 编码切片对齐**:这是准确率的决定性因素(切 2s 会明显掉准确率),但必须用真模型跑才能验证。 - **增量 KV 复用的单步延迟**:需要真模型才能测。 -- 采集链路(PulseAudio/PipeWire 的 monitor 源)。 -- GUI 渲染。 +- 采集链路(PulseAudio/PipeWire 的 monitor 源、设备枚举)。 +- GUI 渲染的**像素级断言**(目前只有人工看截图)。 这些目前靠 `experiments/` 下的脚本与实机验证(见 `docs/phase0..3`)。 @@ -63,3 +95,4 @@ ctest --test-dir build --output-on-failure # 也可以单独跑 build/tests/t - 给需要模型的集成测试打 CTest label(如 `model`),默认跳过,用环境变量指模型路径时才跑。 - GUI 的渲染回归:已有的 `--screenshot` 自截图,可以断言面板像素 alpha(`(0,0,0,214)`),避免再次出现"样式表背景对自定义 QWidget 不生效 → 明明想半透明结果全透明"这类问题。 +- 给 `ModelStore::verify` 加一个用临时文件构造损坏模型的单元测试(现在只在集成层面手工验证过)。 diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..ffb0108 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,94 @@ +# 常见问题 + +## 点 `✕` 之后窗口不见了 + +这是设计如此:**`✕` 是隐藏到托盘**,不是退出(否则关掉就再也没法打开了)。 +用托盘菜单的「显示/隐藏字幕」或单击托盘图标可以再显示回来;要退出用托盘菜单的「退出」。 + +如果连托盘图标也没有(桌面环境不支持托盘,或用 `--no-tray` 启动),`✕` 会直接退出程序。 + +## 首次启动弹出设置对话框 + +没有「已下载且被选中」的模型时,程序必须先让你准备一个模型才能工作,所以会先弹设置。 +在「模型」页选一个模型点「下载」,然后在「当前使用」里选中它,点「确定」即可。 +直接点「取消」= 放弃启动(程序退出)。 + +## 没有字幕 / 转写全是空白 + +按顺序排查: + +1. **系统真的在播放声音吗?** 采集的是「正在播放的音频」,没有播放就没有字幕。 +2. **采集设备是否选错?** 默认跟随**系统默认输出**。如果声音是从 HDMI 出去的、而默认输出是 + Speaker(或反过来),就会采到静音。用 `--list-devices` 看有哪些设备,在设置里显式指定一个。 + ```bash + ./build/live-caption-cli --list-devices + ``` +3. **音量和静音状态**:确认系统和该应用的音量不为 0、没有静音。 +4. 从终端启动看输出,采集/加载失败会打印原因: + ```bash + ./build/live-caption # 或 ./build/live-caption-cli --cli-live ... + ``` + +## 提示「模型未安装」/ 缺失某些文件 + +模型文件不完整。两种办法: + +- 在设置对话框里选中该模型 → 「校验完整性」看哪些文件坏了 → 「删除」后重新「下载」; +- 命令行:`./build/live-caption-cli --verify-model ` 查看,`./scripts/fetch-models.sh ` 重新下。 + +## 「校验失败,损坏的文件: …」 + +说明文件内容与注册表里的 sha256 对不上(下载被截断、磁盘问题等)。在设置里**删除该模型再重新下载** +即可。下载支持断点续传,网络抖动不会前功尽弃。 + +## 下载很慢 / 下载失败 + +- 默认下载源是 `https://hf-mirror.com`(实测可达性最好)。可在设置里换成别的镜像或官方源。 +- 需要走代理时,在设置里把「代理」设为「跟随系统」或「手动设置」并填主机/端口/账号。 +- 大模型有几百 MB~1GB,首次下载慢是正常的;同一个文件不会重复下载。 + +## 日语 / 其它语言效果不好 + +这是模型能力问题,不是 bug。结论见 [asr-backends.md](asr-backends.md#推荐组合按语言装包) 的推荐组合: + +| 语言 | 建议 | +|---|---| +| 中文 | `zipformer-zh-2025`(最快最准)或 X-ASR | +| 英文 | `xasr-zh-en-480` | +| 日语 | `nemotron35-560ms` + 设置里把「识别语言」改成**日语**(不指定语言会串) | +| 其它 | `qwen3-asr-0.6b-q8`(52 语言兜底,CPU 占用高但最准) | + +注意:多语言模型(Nemotron / Qwen3-ASR)**要在设置里选对语言**,否则「自动」可能识别成别的语言。 + +## Wayland 下窗口不置顶 / 位置不对 + +X11 下正常。Wayland 没有通用的「置顶悬浮层」协议,需要 `wlr-layer-shell`,属于 Phase 5。 +当前建议在 X11 会话下使用。 + +## 构建时警告「未找到 sherpa-onnx,流式 ONNX 后端将不可用」 + +正常,说明流式后端被跳过了。要启用: + +```bash +ls ~/.local/opt/sherpa-onnx/include/sherpa-onnx/c-api/c-api.h +cmake -S . -B build -DSHERPA_ONNX_ROOT=$HOME/.local/opt/sherpa-onnx +``` + +它需要自带的 `libonnxruntime.so`(`ldd build/live-caption` 可以确认解析到了)。 + +## 启动时打印 `setHighDpiScaleFactorRoundingPolicy must be called…` + +与本事无关:一个 5 行的空白 Qt 程序在同样的会话里也会打印。是桌面会话设了 +`QT_SCALE_FACTOR_ROUNDING_POLICY` 导致的,可以忽略。 + +## Qwen3-ASR 的 GGUF 装不进去(加载失败) + +模型与运行时**必须锁版本**:只有 ggml-org 官方 GGUF(`general.architecture = qwen3vl`)能用; +社区量化常见的是 `qwen3asr` / `qwen3_asr`,不兼容。细节见 +[phase0-verification.md](phase0-verification.md)。 + +## 点了「确定」程序就退出了 + +这是早期版本的一个 bug(`QApplication` 默认「最后一个窗口关闭 = 退出」,而首次启动时设置对话框 +正是唯一窗口),**已经修好**。如果还在复现,请重新构建后再试;仍复现请附上 +`coredumpctl info ` 的调用栈。 diff --git a/experiments/phase0/README.md b/experiments/phase0/README.md index a0580f4..c9f5021 100644 --- a/experiments/phase0/README.md +++ b/experiments/phase0/README.md @@ -1,5 +1,9 @@ # Phase 0 验证实验 +> **历史文档**:Phase 0 的结论仍然有效(本页的数字与 [docs/phase0-verification.md](../../docs/phase0-verification.md) 一致), +> 但当时的脚本是临时实验脚本,现行入口是 GUI(`./build/live-caption`)与 +> `./build/live-caption-cli`;模型由注册表 `data/models.json` 管理,见 [docs/cli.md](../../docs/cli.md)。 + 目标:在动手写 C++/Qt 之前,先证实三个关键假设。 1. **系统播放音频可采集**:默认 sink 的 `monitor` 源能被读到。 diff --git a/experiments/phase1/README.md b/experiments/phase1/README.md index 79eeaa0..c082d96 100644 --- a/experiments/phase1/README.md +++ b/experiments/phase1/README.md @@ -3,6 +3,12 @@ 目标:把 `docs/phase0-verification.md` 验证过的链路写成一个真正的 C++/Qt/CMake 项目骨架,并在命令行跑通「采集系统输出 → 实时转写 → 打印」。 UI 悬浮窗留到 Phase 2。 +> **历史文档**:这里记的是 Phase 1 当时的样子。代码后来重排成了 `src/core` + `src/ui` + +> `src/models` + `apps/{gui,cli}`,入口是 `./build/live-caption`(图形界面)和 +> `./build/live-caption-cli`(命令行);模型改成按注册表 `data/models.json` 取,不再是手工 +> 传 `--model`/`--mmproj`。现行用法见 [README](../../README.md) 与 +> [docs/cli.md](../../docs/cli.md)。 + ## 目录 ```