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 中绘制,不透明度可调。
85 lines
3.6 KiB
C++
85 lines
3.6 KiB
C++
#pragma once
|
||
|
||
#include <cstddef>
|
||
#include <functional>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
namespace lc {
|
||
|
||
class Qwen3Asr;
|
||
|
||
// 官方流式解码的参数(见 docs/phase2-streaming.md)
|
||
struct StreamOptions {
|
||
double chunkSec = 2.0; // 累积多少秒音频跑一步
|
||
double windowSec = 16.0; // 仅 window 模式使用:滑窗上限,<=0 表示不滑窗
|
||
int unfixedTokenNum = 5; // 前缀回滚:丢掉最后 N 个 token
|
||
int unfixedChunkNum = 2; // 前 N 个 chunk 不带前缀(冷启动)
|
||
int maxTokensPerStep = 256;
|
||
|
||
// true = 增量 KV 复用:只编码新音频,已解码音频留在 KV(推荐)
|
||
// false = 每次重编码整个窗口(Phase 2 早期的做法,仅用于对比)
|
||
bool incremental = true;
|
||
|
||
// 单段最长时长(秒):超过就提交并重置上下文(0 = 不限)。
|
||
// 真实场景应由静音检测决定分段;这里是一个兜底上界,
|
||
// 因为解码器对 KV 的注意力随总长增长,段太长单步会超预算。
|
||
double maxUtteranceSec = 30.0;
|
||
};
|
||
|
||
// 把「累积音频 → 带前缀继续生成 → 前缀回滚」这套策略封装起来。
|
||
// 输出分两部分:
|
||
// committed —— 已稳定文本(就是下一步要回喂的前缀)
|
||
// full —— 含不稳定尾部(最后 unfixedTokenNum 个 token)
|
||
class StreamingAsr {
|
||
public:
|
||
struct Step {
|
||
int chunkIndex = 0;
|
||
std::string language;
|
||
std::string committed; // 稳定部分
|
||
std::string tail; // 不稳定尾部(committed 之后的部分)
|
||
std::string full; // 含不稳定尾部
|
||
std::string rawFull; // 未 trim 的完整文本(含 language 头)
|
||
double windowSeconds = 0.0;
|
||
double encodeSeconds = 0.0;
|
||
double inferSeconds = 0.0;
|
||
int audioTokens = 0;
|
||
bool ok = true;
|
||
std::string error;
|
||
};
|
||
|
||
StreamingAsr(Qwen3Asr & asr, StreamOptions opt = {});
|
||
|
||
// 喂入 16k 单声道音频;每凑够 chunkSec 就跑一步并回调
|
||
void feed(const float * pcm, size_t n, const std::function<void(const Step &)> & onStep);
|
||
// 处理不足一块的残留音频,并作为最后一步提交全部文本。
|
||
// 若没有待处理音频,也把当前已稳定文本提交为最终(静音分段时会用到)。
|
||
void flush(const std::function<void(const Step &)> & onStep);
|
||
|
||
// 结束当前这段(例如检测到静音):清空 KV 与状态,下一段重新开始
|
||
void reset();
|
||
// 重置全部状态(含历史文本)
|
||
void resetAll();
|
||
|
||
// 全部已提交文本(含此前各段,已剥离 language 头)
|
||
std::string committedText() const;
|
||
const std::string & language() const { return language_; }
|
||
int chunkCount() const { return chunkIndex_; }
|
||
|
||
private:
|
||
void runStep(const std::vector<float> & audio, bool finalStep,
|
||
const std::function<void(const Step &)> & onStep);
|
||
|
||
Qwen3Asr & asr_;
|
||
StreamOptions opt_;
|
||
std::vector<float> buffer_; // window 模式:当前窗口音频
|
||
std::vector<float> pending_; // 尚未凑够一块的音频
|
||
double totalSeconds_ = 0.0;
|
||
std::string committedRaw_; // 当前段的原始已提交文本(含 language 头,用于回喂)
|
||
std::string historyText_; // 此前各段已提交的正文(已剥离 language 头)
|
||
std::string language_;
|
||
int chunkIndex_ = 0;
|
||
};
|
||
|
||
} // namespace lc
|