83 lines
2.4 KiB
C++
83 lines
2.4 KiB
C++
#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"
|