Files

83 lines
2.4 KiB
C++
Raw Permalink Normal View History

#include <QtTest>
#include <cmath>
#include <vector>
#include "vad.h"
namespace {
constexpr size_t kFrame = 320; // 20ms @16k
constexpr double kPi = 3.14159265358979323846;
// 生成 amplitude 幅值的 440Hz 正弦,用来模拟"有内容"的音频
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;
}
int countSpeech(lc::Vad & vad, const std::vector<float> & audio)
{
int speech = 0;
for (size_t off = 0; off + kFrame <= audio.size(); off += kFrame) {
if (vad.feed(audio.data() + off, kFrame)) ++speech;
}
return speech;
}
} // namespace
// VAD 的回归测试。
//
// 关键教训:噪声底必须用"最小值跟踪"。曾经用均值跟踪,遇到本来就很轻的音频
// (峰值 RMS 只有 0.03 量级的录音)会把阈值抬到语音电平之上,把绝大部分话音
// 判成静音 —— 实时字幕于是每句都被切断。
class TstVad : public QObject {
Q_OBJECT
private slots:
void backgroundStaysSilent()
{
lc::Vad vad;
const auto bg = tone(kFrame * 200, 0.001f); // rms ≈ 0.0007
QCOMPARE(countSpeech(vad, bg), 0);
}
// 轻语音必须被判成语音:这里语音 rms ≈ 0.0028,
// 旧的"均值跟踪 + 0.004 绝对下限"实现会全部漏掉。
void quietSpeechIsDetected()
{
lc::Vad vad;
const auto bg = tone(kFrame * 40, 0.001f); // rms ≈ 0.0007
countSpeech(vad, bg);
const auto sp = tone(kFrame * 100, 0.004f); // rms ≈ 0.0028
const int speech = countSpeech(vad, sp);
QVERIFY2(speech >= 95,
qPrintable(QString("轻语音只被识别出 %1/100 帧").arg(speech)));
}
void loudSpeechIsDetected()
{
lc::Vad vad;
const auto sp = tone(kFrame * 50, 0.2f);
QVERIFY(countSpeech(vad, sp) >= 48);
}
void floorFollowsQuietInput()
{
lc::Vad vad;
const auto bg = tone(kFrame * 200, 0.001f);
countSpeech(vad, bg);
// 噪声底应该已经下探到背景噪声量级,而不是停在初值
QVERIFY2(vad.noiseFloor() < 0.002f,
qPrintable(QString("噪声底仍为 %1").arg(vad.noiseFloor())));
}
};
QTEST_MAIN(TstVad)
#include "tst_vad.moc"