blumia 6d64987d64 fix(stream): transcribe each append-mode chunk independently
The append mode used to feed chunks as successive conversation turns with
the context carried over. The model then copied the previous turn's text
verbatim instead of transcribing the new audio, so a continuous video
kept showing the same sentence forever. Transcribe each chunk
independently instead, matching sglang's segment streaming mode for
Qwen3-ASR; the fixed mode also stays flat at RTF 0.24 versus 0.57-1.08
for the incremental mode.

修复追加式的一个真 bug:原先把各段接成「携带上文的对话轮」,模型会抄上
一轮的文本 —— 连续视频里字幕永远重复同一句。改为每段独立转写(对应
sglang 给 Qwen3-ASR 的 segment 流式模式),内容随之正确变化;成本仍保持
平坦,RTF 0.24,而增量模式是 0.57~1.08。
2026-10-12 14:32:07 +08:00

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 \
  --model Qwen3-ASR-0.6B-Q8_0.gguf \
  --mmproj mmproj-Qwen3-ASR-0.6B-Q8_0.gguf

# 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

代码结构

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。

S
Description
No description provided
Readme
371 KiB
0 Stars 1 Watchers 0 Forks
Languages
C++ 89%
CMake 6.6%
Shell 3.6%
Python 0.8%