You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

134 lines
5.5 KiB

/* Тест чистой логики VAD авто-PTT рации (radio_vad.h): ресемплер 48k→16k и
* фильтр-гистерезис + машина состояний. Без ONNX Runtime и без аудио-устройств —
* детерминированные синтетические последовательности.
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "../src/radio/radio_vad.h"
#define PI 3.14159265358979323846
static int g_failures = 0;
#define CHECK(cond, ...) do { \
if (!(cond)) { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_failures++; } \
} while (0)
static int near(float a, float b, float eps) {
return fabsf(a - b) <= eps;
}
/* ── ресемплер ── */
static void test_resample(void) {
printf("[radio_vad] resampler 48k -> 16k\n");
/* тишина → тишина */
{
int16_t in[960] = {0};
float out[RADIO_VAD_FRAME_SAMPLES_16K];
radio_vad_resample(in, 1, out);
for (int i = 0; i < RADIO_VAD_FRAME_SAMPLES_16K; i++) CHECK(out[i] == 0.0f, "silence out[%d]=%f", i, out[i]);
}
/* DC 0.5*32768 → выход ~0.5 (box-фильтр сохраняет DC) */
{
int16_t in[960];
float out[RADIO_VAD_FRAME_SAMPLES_16K];
for (int i = 0; i < 960; i++) in[i] = 16384;
radio_vad_resample(in, 1, out);
for (int i = 0; i < RADIO_VAD_FRAME_SAMPLES_16K; i++) CHECK(near(out[i], 0.5f, 0.001f), "DC out[%d]=%f", i, out[i]);
}
/* стерео L=R → тот же результат, что моно */
{
int16_t mono[960];
int16_t stereo[1920];
float om[RADIO_VAD_FRAME_SAMPLES_16K], os[RADIO_VAD_FRAME_SAMPLES_16K];
for (int i = 0; i < 960; i++) { mono[i] = (int16_t)(sin(2.0 * PI * 1000.0 * i / 48000.0) * 20000.0); }
for (int i = 0; i < 960; i++) { stereo[2*i] = mono[i]; stereo[2*i+1] = mono[i]; }
radio_vad_resample(mono, 1, om);
radio_vad_resample(stereo, 2, os);
for (int i = 0; i < RADIO_VAD_FRAME_SAMPLES_16K; i++) CHECK(near(om[i], os[i], 1e-4f), "stereo!=mono out[%d] %f vs %f", i, om[i], os[i]);
}
/* синус 1кГц: амплитуда сохраняется (box-фильтр даёт ~0.6% ослабления) */
{
int16_t in[960];
float out[RADIO_VAD_FRAME_SAMPLES_16K];
float peak = 0.0f;
for (int i = 0; i < 960; i++) in[i] = (int16_t)(sin(2.0 * PI * 1000.0 * i / 48000.0) * 30000.0);
radio_vad_resample(in, 1, out);
for (int i = 0; i < RADIO_VAD_FRAME_SAMPLES_16K; i++) { float a = fabsf(out[i]); if (a > peak) peak = a; }
CHECK(peak > 0.85f && peak <= 1.0f, "1kHz peak=%.3f (ожидали ~0.91)", peak);
}
}
/* ── машина состояний ── */
static void test_fsm(void) {
printf("[radio_vad] fsm (confirm / hangover / busy)\n");
struct radio_vad_fsm f;
const float thr = 0.5f;
const int confirm = 2;
const uint64_t hangover = 2000; /* 200мс */
uint64_t now = 0;
/* подтверждение: 2 окна выше порога → старт */
radio_vad_fsm_reset(&f);
now += 320;
CHECK(radio_vad_fsm_update(&f, 0.9f, thr, confirm, hangover, 0, now) == 0, "confirm#1 должен молчать");
now += 320;
CHECK(radio_vad_fsm_update(&f, 0.9f, thr, confirm, hangover, 0, now) == 1, "confirm#2 должен дать START");
CHECK(f.talking == 1, "после старта talking=1");
/* одиночный пик не запускает передачу */
radio_vad_fsm_reset(&f);
now += 320;
CHECK(radio_vad_fsm_update(&f, 0.9f, thr, confirm, hangover, 0, now) == 0, "одиночный пик не стартует");
now += 320;
CHECK(radio_vad_fsm_update(&f, 0.1f, thr, confirm, hangover, 0, now) == 0, "сброс подтверждения");
/* hangover: после старта тишина ~200мс → STOP */
radio_vad_fsm_reset(&f);
now += 320; radio_vad_fsm_update(&f, 0.9f, thr, confirm, hangover, 0, now);
now += 320; CHECK(radio_vad_fsm_update(&f, 0.9f, thr, confirm, hangover, 0, now) == 1, "старт");
{
int stops = 0;
for (int i = 0; i < 10; i++) {
now += 320;
int a = radio_vad_fsm_update(&f, 0.1f, thr, confirm, hangover, 0, now);
if (a == -1) { stops++; break; }
CHECK(a == 0, "hangover: до ~200мс не стопим");
}
CHECK(stops == 1, "hangover должен дать STOP");
CHECK(f.talking == 0, "после STOP talking=0");
}
/* busy: чужой разговор гасит нашу передачу сразу */
radio_vad_fsm_reset(&f);
now += 320; radio_vad_fsm_update(&f, 0.9f, thr, confirm, hangover, 0, now);
now += 320; radio_vad_fsm_update(&f, 0.9f, thr, confirm, hangover, 0, now);
CHECK(f.talking == 1, "старт перед busy");
now += 320;
CHECK(radio_vad_fsm_update(&f, 0.9f, thr, confirm, hangover, 1, now) == -1, "busy должен дать STOP");
/* busy блокирует старт */
radio_vad_fsm_reset(&f);
for (int i = 0; i < 5; i++) {
now += 320;
CHECK(radio_vad_fsm_update(&f, 0.9f, thr, confirm, hangover, 1, now) == 0, "busy блокирует старт");
}
CHECK(f.talking == 0, "busy: не стартуем");
}
int main(void) {
test_resample();
test_fsm();
if (g_failures == 0) { printf("TEST PASSED\n"); return 0; }
printf("TEST FAILED (%d failures)\n", g_failures);
return 1;
}