Files
live-caption/tests/fakeasr.h
T
blumia 85b206f1cd feat(stream): add append-only streaming mode with flat cost
Add an append-only streaming path that feeds each audio chunk as a new
conversation turn, so nothing is ever recomputed: the KV grows by
append, no prefix is re-decoded and no audio is re-encoded. Per-step
cost becomes flat instead of growing with the segment (RTF 1.06 -> 0.26
on 62.6s of continuous speech), but accuracy requires ~8s chunks because
the audio encoder needs its 8s attention window, so text only appears
every 8s. The incremental mode stays the default.

新增追加式流式:每块音频作为一个新的对话轮追加,前缀不重算、旧音频不重
编码,单步成本不再随段落增长(连续 62.6s 语音 RTF 从 1.06 降到 0.26)。
但准确率要求 chunk≈8s,否则会被截断,所以出字粒度是 8s;增量式仍为默认。
2026-10-12 14:22:29 +08:00

91 lines
2.7 KiB
C++

#pragma once
#include <cctype>
#include <cstddef>
#include <string>
#include <vector>
#include "iasr.h"
// 可脚本化的假推理后端:让流式策略与实时管线可以脱离真模型测试。
//
// dropLastTokens 用"空格分词"模拟词表:丢掉最后 N 个词,并保留原有格式
// (这点很重要 —— 续写开头那个空格是有意义的)。
class FakeAsr : public lc::IAsr {
public:
// 每次 streamAppend 依次返回这里的文本;用完后返回空串
std::vector<std::string> scripted;
// 记录(供断言用)
std::vector<std::string> prefixesSeen;
std::vector<std::string> turnsSeen;
std::vector<size_t> samplesSeen;
int resets = 0;
int sampleRate() const override { return 16000; }
lc::AsrResult transcribeWithPrefix(const float *, size_t,
const std::string &) override
{
lc::AsrResult r;
r.ok = true;
if (call_ < scripted.size()) {
r.raw = scripted[call_];
r.transcript = lc::parseAsrOutput(r.raw);
}
++call_;
return r;
}
lc::AsrStreamStep streamAppend(const float *, size_t nSamples,
const std::string & prefix, int) override
{
prefixesSeen.push_back(prefix);
samplesSeen.push_back(nSamples);
lc::AsrStreamStep s;
s.ok = true;
if (call_ < scripted.size()) s.newText = scripted[call_];
++call_;
s.newTokens = 1;
s.encodeSeconds = 0.001;
s.inferSeconds = 0.001;
return s;
}
lc::AsrStreamStep streamAppendTurn(const float *, size_t nSamples, int) override
{
samplesSeen.push_back(nSamples);
lc::AsrStreamStep s;
s.ok = true;
if (call_ < scripted.size()) s.newText = scripted[call_];
++call_;
turnsSeen.push_back(s.newText);
s.newTokens = 1;
s.inferSeconds = 0.001;
return s;
}
void streamReset() override { ++resets; }
std::string dropLastTokens(const std::string & text, int dropLast) const override
{
if (dropLast <= 0 || text.empty()) return text;
const auto isSpace = [](char c) { return std::isspace((unsigned char) c) != 0; };
size_t pos = text.size();
for (int i = 0; i < dropLast; ++i) {
while (pos > 0 && isSpace(text[pos - 1])) --pos; // 跳过尾部空白
while (pos > 0 && !isSpace(text[pos - 1])) --pos; // 跳过一个词
if (pos == 0) return {};
}
while (pos > 0 && isSpace(text[pos - 1])) --pos;
return text.substr(0, pos);
}
private:
size_t call_ = 0;
};