Files
live-caption/docs/phase3-caption-window.md
T
blumia 4ed4e23282 feat(ui): add floating caption window with live transcription
Add a frameless always-on-top caption window wired to the live capture
pipeline. Silence-based segmentation gates the audio so the model never
sees silence, and the panel background is painted in paintEvent with a
configurable opacity instead of a style sheet.

新增悬浮字幕窗(无边框、置顶、可折叠/展开),接入实时采集与流式转写。
分段由静音检测驱动,静音期间不送音频,避免模型对静音产生幻觉。底板
改为在 paintEvent 中绘制,不透明度可调。
2026-10-12 13:32:16 +08:00

78 lines
4.8 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.
# Phase 3:Qt 悬浮字幕窗
状态:**完成**。悬浮窗可用,已接入实时采集 + 流式转写。
![折叠态(英文)](images/phase3-en-collapsed.png)
![展开态(英文)](images/phase3-en-expanded.png)
![折叠态(中文)](images/phase3-zh-collapsed.png)
![不透明度 50%](images/phase3-opacity50.png)
## 已实现
- **悬浮窗**:无边框、半透明深色圆角、始终置顶、不接受焦点(`Qt::FramelessWindowHint | WindowStaysOnTopHint | Tool | WindowDoesNotAcceptFocus`),可拖动。
- **折叠 / 展开**:折叠两行、展开八行,底部箭头切换(折叠态 `⌄`、展开态 `⌃`)。
- **语言标签**:左上角圆角 pill,显示识别到的语言(英语 / 中文 …);点击会发 `languageClicked` 信号,设置界面留到 Phase 4。
- **稳定/不稳定区分**:已稳定文本白色,不稳定尾部灰色 —— 与 CLI 版一致,也是 Chrome 那种"字长出来"的观感。
- **背景**:**黑色 + 可调不透明度**(不是全透明;纯黑底才能保证任何桌面背景下文字都可读)。默认 84%,范围限制 20~100,`--opacity` 或 `setPanelOpacity()` 可调,Phase 4 接到设置里即可。
- **关闭**:右上角 `✕` 退出。
- **实时链路**:采集默认输出的 monitor → 流式转写 → 信号推给窗口,全部在主线程外的工作线程里跑。
## 实时分段
Phase 2 的结论是段不能无限长(解码器对 KV 的注意力随总长增长)。Phase 3 用**静音检测**决定段边界:
- 能量 VAD(噪声底用"快降慢升"的最小值跟踪,见 Phase 1 的教训);
- 检测到语音才开始把音频送进引擎,并保留 250ms 前导避免吃掉词头;
- **静音期间完全不送音频**:早期版本会把静音也喂进模型,模型对静音产生了幻觉(英文语音前冒出一个 `嗯。`);
- 静音超过 0.8s 就收束这一段(提交文本、清空 KV),下一段重新开始。
`--stream-segment`(默认 30s)仍作为兜底上界。
## 代码结构
```
src/
├─ captionwindow.{h,cpp} # 悬浮窗
├─ livetranscriber.{h,cpp} # 采集 + VAD + 分段 + 流式引擎(QObject)
├─ streamingasr.{h,cpp} # 流式策略(前缀回滚、8s 切片、分段)
├─ qwen3asr.{h,cpp} # 进程内推理(libllama + libmtmd)
├─ audio.{h,cpp} # WAV 读取 + monitor 采集 + 重采样
└─ transcript.{h,cpp} # 输出解析与语言名映射
```
## 过程中踩的坑
1. **静音幻觉**:第一版把采集到的静音也喂给了模型,模型对静音产生幻觉,英文语音前面冒出一个 `嗯。`。改为静音期间完全不送音频。
2. **自定义 QWidget 子类的样式表背景不生效**:`CaptionWindow { background-color: rgba(0,0,0,215) }` 这条规则在真实渲染时**根本没画**,面板全透明,只有文字和按钮浮在桌面上。原因是 Qt 不会为自定义 `QWidget` 子类绘制样式表背景(需要 `WA_StyledBackground`,或自己画)。改成在 `paintEvent()` 里用 `QPainter::drawRoundedRect` 画底板 —— 这也是 `WA_TranslucentBackground` 顶层窗口的推荐做法。
3. **验证方法骗了自己**:`QWidget::grab()` 的产物当时是合成到**深灰底色**上看的,「黑色 + 84%」和「全透明」在深色背景上几乎看不出区别,于是把第 2 条盖住了。改成**直接检查像素 alpha**(面板中央应为 `(0,0,0,≈214)`、圆角外应为 `(0,0,0,0)`),并合成到亮色背景上复核。
4. **DPI 警告不关本程序的事**:`setHighDpiScaleFactorRoundingPolicy must be called before creating the QGuiApplication instance` 连 5 行的空白 Qt 程序也会打印 —— 是 Deepin 会话环境设了 `QT_SCALE_FACTOR_ROUNDING_POLICY` 导致的。
## 用法
```bash
# 默认就是悬浮窗模式
./build/live-caption-cli --model MODEL --mmproj MMPROJ
# 调整背景不透明度(20-100)
./build/live-caption-cli --model MODEL --mmproj MMPROJ --opacity 60
# 无窗口、纯命令行实时(便于在无显示环境调试)
./build/live-caption-cli --model MODEL --mmproj MMPROJ --cli-live
# 自截图(验证渲染,不依赖外部截图工具);会依次截折叠态与展开态
./build/live-caption-cli --model MODEL --mmproj MMPROJ \
--screenshot out.png --screenshot-delay 12
```
> 自截图产物带 alpha 通道;要判断不透明度是否正确,**直接看像素 alpha**,不要靠肉眼看合成效果。
## 已知限制 / 后续
- X11 下置顶与定位都正常;Wayland 下需要 layer-shell,属于 Phase 5。
- 语言标签点击暂无设置界面(Phase 4)。
- 采集跟随的是**启动时**的默认输出设备;设备切换(例如切到 HDMI)暂未处理。
- 界面尚未做 HiDPI 之外的适配(当前主屏宽度 72%、最大 900 逻辑像素)。