// test_radio_audio.cpp — тест аудио-движка рации (jitter + микшер). // // Проверяет сервисный слой src/radio/radio_audio.c без полного UTUN_INSTANCE: // 1. single-source: подаём кодированные Opus-кадры через radio_audio_on_frame, // radio_audio_pull_pcm выдаёт декодированный ненулевой PCM (jitter+decode). // 2. mixing: два источника с большими амплитудами — сумма насыщается (soft-clip), // что доказывает микширование нескольких источников. // // Сборка/запуск: через CMake (tools/chatgui), линкуется utun_voice + utun. #include "radio/radio_audio.h" extern "C" { #include "../../lib/opus_codec.h" } #include #include #include #include #define GID 0x1234567890abcdefULL #define SRC_A 0xAAAAULL #define SRC_B 0xBBBBULL static int G_PASSED = 0, G_FAILED = 0, G_TOTAL = 0; #define TEST(n) do { G_TOTAL++; printf(" %-58s", n); fflush(stdout); } while(0) #define OK() do { G_PASSED++; printf("OK\n"); } while(0) #define FAIL(f,...) do { G_FAILED++; printf("FAIL: " f "\n", ##__VA_ARGS__); } while(0) /* закодировать N кадров постоянного PCM и подать через on_frame */ static void push_const(uint64_t src, uint16_t stream, int value, int nframes) { opus_codec_encoder_t* enc = opus_codec_encoder_create(48000, 1); if (!enc) { FAIL("encoder create failed"); return; } opus_codec_encoder_bitrate_set(enc, 32000); int16_t pcm[960]; for (int i = 0; i < 960; i++) pcm[i] = (int16_t)value; for (int f = 0; f < nframes; f++) { uint8_t opus[256]; int len = opus_codec_encode(enc, pcm, 960, opus, (int)sizeof(opus)); if (len <= 0) { FAIL("encode failed"); break; } radio_audio_on_frame(NULL, GID, src, stream, (uint16_t)f, 0, opus, len, NULL); } opus_codec_encoder_destroy(enc); } /* вытянуть pull-цикл, вернуть max |sample| */ static int pull_max_abs(int npulls) { int mx = 0; int16_t out[960]; for (int p = 0; p < npulls; p++) { int n = radio_audio_pull_pcm(GID, out, 960); (void)n; for (int i = 0; i < 960; i++) { int a = out[i] < 0 ? -out[i] : out[i]; if (a > mx) mx = a; } } return mx; } int main() { printf("=== test_radio_audio ===\n"); fflush(stdout); TEST("single-source: decode через jitter-буфер выдаёт ненулевой PCM"); { radio_audio_start(GID); push_const(SRC_A, 1, 10000, 5); int mx = pull_max_abs(10); radio_audio_stop(); if (mx > 1000) OK(); else FAIL("max|pcm|=%d (ожидали > 1000)", mx); } TEST("mixing: два источника складываются (soft-clip)"); { radio_audio_start(GID); push_const(SRC_A, 1, 20000, 5); push_const(SRC_B, 1, 20000, 5); int mx = pull_max_abs(10); radio_audio_stop(); if (mx > 30000) OK(); else FAIL("max|pcm|=%d (ожидали > 30000 — сумма двух источников)", mx); } printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED); return G_FAILED > 0 ? 1 : 0; }