/* Тест Silero VAD-обёртки (lib/silero_vad) поверх ONNX Runtime. * * Проверяет: * - загрузку модели и жизненный цикл (create/destroy/reset); * - обработку ошибок (пустой/несуществующий путь, NULL-аргументы); * - что на тишине вероятность речи близка к нулю (реальный инференс, не мусор); * - что путь pcm16 даёт тот же результат, что и float (конвертация /32768); * - что вероятность всегда лежит в [0;1]. * * Модель ищется в argv[1], по умолчанию "../lib/silero_vad.onnx" (запуск из tests/). */ #include #include #include #include "../lib/silero_vad.h" #include "../lib/debug_config.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) /* Детерминированный «гласный»: модулированный гармонический комплекс (f0 ~120 Гц). */ static void gen_vowel(float* out, int n, int offset) { int i; for (i = 0; i < n; i++) { double t = (double)(offset + i) / 16000.0; double f0 = 120.0 + 20.0 * sin(2.0 * PI * 3.0 * t); double env = 0.5 + 0.5 * sin(2.0 * PI * 2.0 * t); double s = sin(2.0 * PI * f0 * t) + 0.5 * sin(2.0 * PI * 2.0 * f0 * t) + 0.3 * sin(2.0 * PI * 3.0 * f0 * t) + 0.2 * sin(2.0 * PI * 4.0 * f0 * t); out[i] = (float)(s * env * 0.25); } } static int test_lifecycle_and_errors(const char* model_path) { silero_vad_t* vad; printf("[silero_vad] lifecycle + error paths\n"); vad = silero_vad_create(NULL); CHECK(vad == NULL, "create(NULL) should fail"); if (vad) silero_vad_destroy(vad); vad = silero_vad_create(""); CHECK(vad == NULL, "create(\"\") should fail"); vad = silero_vad_create("/nonexistent/silero_vad.onnx"); CHECK(vad == NULL, "create(bad path) should fail"); vad = silero_vad_create(model_path); CHECK(vad != NULL, "create(%s) should succeed", model_path); if (!vad) return 1; silero_vad_reset(vad); silero_vad_reset(vad); /* повторный reset безопасен */ silero_vad_destroy(vad); silero_vad_destroy(NULL); /* destroy(NULL) безопасен */ return 0; } static int test_silence(const char* model_path) { silero_vad_t* vad; float zeros[SILERO_VAD_WINDOW_SAMPLES]; float prob = -1.0f; float max_prob = 0.0f; int i, rc; printf("[silero_vad] silence -> prob ~ 0\n"); memset(zeros, 0, sizeof(zeros)); vad = silero_vad_create(model_path); CHECK(vad != NULL, "create failed"); if (!vad) return 1; for (i = 0; i < 32; i++) { rc = silero_vad_process(vad, zeros, &prob); CHECK(rc == 0, "process(%d) rc=%d", i, rc); CHECK(prob >= 0.0f && prob <= 1.0f, "prob out of range: %f", prob); if (prob > max_prob) max_prob = prob; } printf(" silence max_prob=%.4f\n", max_prob); CHECK(max_prob < 0.2f, "silence probability too high: %f", max_prob); silero_vad_destroy(vad); return 0; } static int test_pcm16_matches_float(const char* model_path) { silero_vad_t* vad; int16_t pcm[SILERO_VAD_WINDOW_SAMPLES]; float samples[SILERO_VAD_WINDOW_SAMPLES]; float prob_f = -1.0f, prob_i = -1.0f; int rc; printf("[silero_vad] pcm16 path == float path\n"); vad = silero_vad_create(model_path); CHECK(vad != NULL, "create failed"); if (!vad) return 1; gen_vowel(samples, SILERO_VAD_WINDOW_SAMPLES, 0); for (int i = 0; i < SILERO_VAD_WINDOW_SAMPLES; i++) { pcm[i] = (int16_t)(samples[i] * 32768.0f); } rc = silero_vad_process(vad, samples, &prob_f); CHECK(rc == 0, "process(float) rc=%d", rc); silero_vad_reset(vad); /* одинаковое состояние перед вторым прогоном */ rc = silero_vad_process_pcm16(vad, pcm, &prob_i); CHECK(rc == 0, "process_pcm16 rc=%d", rc); printf(" float prob=%.5f pcm16 prob=%.5f\n", prob_f, prob_i); CHECK(fabsf(prob_f - prob_i) < 1e-4f, "pcm16 != float: %f vs %f", prob_i, prob_f); silero_vad_destroy(vad); return 0; } static int test_default_model(void) { silero_vad_t* vad; float zeros[SILERO_VAD_WINDOW_SAMPLES]; float prob = -1.0f; int rc; printf("[silero_vad] create_default (embedded model)\n"); memset(zeros, 0, sizeof(zeros)); vad = silero_vad_create_default(); CHECK(vad != NULL, "create_default failed"); if (!vad) return 1; rc = silero_vad_process(vad, zeros, &prob); CHECK(rc == 0, "process rc=%d", rc); CHECK(prob >= 0.0f && prob <= 1.0f, "prob out of range: %f", prob); CHECK(prob < 0.2f, "silence prob too high: %f", prob); printf(" embedded silence prob=%.4f\n", prob); silero_vad_destroy(vad); return 0; } static int test_null_args(const char* model_path) { silero_vad_t* vad; float samples[SILERO_VAD_WINDOW_SAMPLES]; float prob; printf("[silero_vad] NULL-arg guards\n"); memset(samples, 0, sizeof(samples)); vad = silero_vad_create(model_path); CHECK(vad != NULL, "create failed"); if (!vad) return 1; CHECK(silero_vad_process(NULL, samples, &prob) != 0, "process(NULL vad) should fail"); CHECK(silero_vad_process(vad, NULL, &prob) != 0, "process(NULL samples) should fail"); CHECK(silero_vad_process(vad, samples, NULL) != 0, "process(NULL prob) should fail"); CHECK(silero_vad_process_pcm16(vad, NULL, &prob) != 0, "process_pcm16(NULL) should fail"); silero_vad_destroy(vad); return 0; } int main(int argc, char** argv) { const char* model_path = (argc > 1) ? argv[1] : "../lib/silero_vad.onnx"; debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_VAD, DEBUG_LEVEL_INFO); printf("Silero VAD test, model: %s\n", model_path); test_lifecycle_and_errors(model_path); test_silence(model_path); test_pcm16_matches_float(model_path); test_null_args(model_path); test_default_model(); if (g_failures == 0) { printf("TEST PASSED\n"); return 0; } printf("TEST FAILED (%d failures)\n", g_failures); return 1; }