#include #include #include #include #include "fakeasr.h" #include "livepipeline.h" namespace { constexpr double kPi = 3.14159265358979323846; std::vector tone(size_t samples, float amplitude) { std::vector 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 & 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"