Files
live-caption/tests/tst_livepipeline.cpp
T

127 lines
3.9 KiB
C++
Raw Normal View History

#include <QtTest>
#include <algorithm>
#include <cmath>
#include <vector>
#include "fakeasr.h"
#include "livepipeline.h"
namespace {
constexpr double kPi = 3.14159265358979323846;
std::vector<float> tone(size_t samples, float amplitude)
{
std::vector<float> v(samples);
for (size_t i = 0; i < samples; ++i) {
v[i] = amplitude * (float) std::sin(2.0 * kPi * 440.0 * (double) i / 16000.0);
}
return v;
}
size_t totalSamples(const std::vector<size_t> & v)
{
size_t n = 0;
for (size_t s : v) n += s;
return n;
}
lc::LivePipelineOptions options()
{
lc::LivePipelineOptions o;
o.stream.chunkSec = 0.5; // 0.5s 一块,测试里能快些触发
return o;
}
} // namespace
// 实时管线的回归测试(用 FakeAsr + 直接喂音频,不碰 PulseAudio)。
class TstLivePipeline : public QObject {
Q_OBJECT
private slots:
// 静音不能送进模型。
// 曾经把采集到的静音也喂了进去,模型对静音产生幻觉(英文语音前冒出一个"嗯。")。
void silenceIsNotForwarded()
{
FakeAsr fake;
lc::LivePipeline pipeline(fake, options());
const auto silence = tone(16000 * 3, 0.001f); // 3s 背景噪声
pipeline.process(silence.data(), silence.size());
QCOMPARE(fake.samplesSeen.size(), size_t(0));
}
// 不丢样本。
// 曾经每次只处理 floor(可用/320) 帧并丢掉余数,每轮丢约 160 个样本,
// 造成假静音和句子被截断。
void noSamplesDroppedAcrossIrregularChunks()
{
FakeAsr fake;
lc::LivePipeline pipeline(fake, options());
const auto speech = tone(16000 * 10, 0.1f); // 10s 语音
const size_t sizes[] = {1000, 7, 3333, 999, 12345, 1};
size_t off = 0;
int i = 0;
while (off < speech.size()) {
const size_t n = std::min(sizes[i++ % 6], speech.size() - off);
pipeline.process(speech.data() + off, n);
off += n;
}
const size_t forwarded = totalSamples(fake.samplesSeen);
// 允许剩下的不足一块留在引擎里(< 0.5s),其余都必须送到
QVERIFY2(forwarded >= 16000 * 9,
qPrintable(QString("只送到了 %1 个样本(共 %2)")
.arg(forwarded).arg(speech.size())));
}
// 静音够久要收束这一段(提交文本并重置上下文)
void silenceEndsSegment()
{
FakeAsr fake;
fake.scripted = {"a b c d e f"};
lc::LivePipeline pipeline(fake, options());
QString lastCommitted;
pipeline.setCaptionCallback(
[&lastCommitted](const std::string & committed, const std::string &,
const std::string &) {
lastCommitted = QString::fromStdString(committed);
});
const auto speech = tone(16000 * 1, 0.1f);
pipeline.process(speech.data(), speech.size());
const int resetsAfterSpeech = fake.resets;
const auto silence = tone(16000 * 2, 0.001f);
pipeline.process(silence.data(), silence.size());
QVERIFY2(fake.resets > resetsAfterSpeech, "静音后没有收束这一段");
QVERIFY(!pipeline.inSegment());
}
// 语音之后继续有语音:不应该被切断成很多段
void continuousSpeechStaysInOneSegment()
{
FakeAsr fake;
lc::LivePipeline pipeline(fake, options());
const int resetsAfterConstruct = fake.resets; // 构造 StreamingAsr 时会 reset 一次
const auto speech = tone(16000 * 4, 0.1f);
for (size_t off = 0; off < speech.size(); off += 4000) {
const size_t n = std::min(size_t(4000), speech.size() - off);
pipeline.process(speech.data() + off, n);
}
QVERIFY(pipeline.inSegment());
QCOMPARE(fake.resets, resetsAfterConstruct);
}
};
QTEST_MAIN(TstLivePipeline)
#include "tst_livepipeline.moc"