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.
157 lines
6.7 KiB
157 lines
6.7 KiB
// 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 <cstdio> |
|
#include <cstring> |
|
#include <cstdlib> |
|
#include <cmath> |
|
|
|
#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); |
|
} |
|
|
|
/* закодировать n синусоидальных кадров (seq 0..n-1, freq[i]) одним encoder'ом. */ |
|
static int encode_frames(const int* freqs, int n, uint8_t pkt[][256], int* lens) { |
|
opus_codec_encoder_t* enc = opus_codec_encoder_create(48000, 1); |
|
if (!enc) { FAIL("encoder create failed"); return 0; } |
|
opus_codec_encoder_bitrate_set(enc, 32000); |
|
int ok = 1; |
|
for (int f = 0; f < n; f++) { |
|
int16_t pcm[960]; |
|
for (int i = 0; i < 960; i++) |
|
pcm[i] = (int16_t)(sin(2.0 * M_PI * freqs[f] * (double)i / 48000.0) * 10000.0); |
|
lens[f] = opus_codec_encode(enc, pcm, 960, pkt[f], 256); |
|
if (lens[f] <= 0) { ok = 0; break; } |
|
} |
|
opus_codec_encoder_destroy(enc); |
|
return ok; |
|
} |
|
|
|
/* подать кадры pkt[idx[i]] с seq[i] через on_frame */ |
|
static void feed_frames(const uint8_t pkt[][256], const int* lens, const int* seqs, const int* idx, int n) { |
|
for (int i = 0; i < n; i++) |
|
radio_audio_on_frame(NULL, GID, SRC_A, 1, (uint16_t)seqs[i], 0, pkt[idx[i]], lens[idx[i]], NULL); |
|
} |
|
|
|
/* прогнать burst и собрать npulls*960 сэмплов выхода */ |
|
static void run_burst(const uint8_t pkt[][256], const int* lens, const int* seqs, const int* idx, |
|
int n, int16_t* result, int npulls) { |
|
radio_audio_start(GID); |
|
feed_frames(pkt, lens, seqs, idx, n); |
|
int16_t out[960]; |
|
int pos = 0; |
|
for (int p = 0; p < npulls; p++) { |
|
radio_audio_pull_pcm(GID, out, 960); |
|
for (int i = 0; i < 960; i++) result[pos++] = out[i]; |
|
} |
|
radio_audio_stop(); |
|
} |
|
|
|
/* вытянуть 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, 8); |
|
int mx = pull_max_abs(16); |
|
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, 8); |
|
push_const(SRC_B, 1, 20000, 8); |
|
int mx = pull_max_abs(16); |
|
radio_audio_stop(); |
|
if (mx > 30000) OK(); else FAIL("max|pcm|=%d (ожидали > 30000 — сумма двух источников)", mx); |
|
} |
|
|
|
TEST("reorder: seq 0,2,1,3,5,4 воспроизводятся в порядке 0,1,2,3,4,5"); { |
|
const int npulls = 16; |
|
int freqs[6] = {300, 1000, 2000, 3000, 4000, 5000}; |
|
uint8_t pkt[6][256]; |
|
int lens[6]; |
|
int ok = 0; |
|
if (encode_frames(freqs, 6, pkt, lens)) { |
|
int seqs_in[6] = {0, 1, 2, 3, 4, 5}, idx_in[6] = {0, 1, 2, 3, 4, 5}; |
|
int seqs_out[6] = {0, 2, 1, 3, 5, 4}, idx_out[6] = {0, 2, 1, 3, 5, 4}; |
|
int16_t buf_in[npulls * 960], buf_out[npulls * 960]; |
|
run_burst(pkt, lens, seqs_in, idx_in, 6, buf_in, npulls); |
|
run_burst(pkt, lens, seqs_out, idx_out, 6, buf_out, npulls); |
|
ok = (memcmp(buf_in, buf_out, sizeof(buf_in)) == 0); |
|
} |
|
if (ok) OK(); else FAIL("out-of-order должен дать тот же PCM, что и in-order (reorder не сработал)"); |
|
} |
|
|
|
TEST("late seq: кадр с seq < next_seq дропается"); { |
|
const int npulls = 16; |
|
int freqs[6] = {300, 1000, 2000, 3000, 4000, 5000}; |
|
uint8_t pkt[6][256]; |
|
int lens[6]; |
|
int ok = 0; |
|
if (encode_frames(freqs, 6, pkt, lens)) { |
|
int seqs_ref[6] = {0, 1, 2, 3, 4, 5}, idx_ref[6] = {0, 1, 2, 3, 4, 5}; |
|
int seqs_late[7] = {0, 1, 2, 3, 4, 5, 0}, idx_late[7] = {0, 1, 2, 3, 4, 5, 0}; /* последний — поздний дубль seq=0 */ |
|
int16_t buf_ref[npulls * 960], buf_late[npulls * 960]; |
|
run_burst(pkt, lens, seqs_ref, idx_ref, 6, buf_ref, npulls); |
|
run_burst(pkt, lens, seqs_late, idx_late, 7, buf_late, npulls); |
|
ok = (memcmp(buf_ref, buf_late, sizeof(buf_ref)) == 0); |
|
} |
|
if (ok) OK(); else FAIL("поздний дубль seq=0 должен быть дропнут (PCM совпадает с эталоном)"); |
|
} |
|
|
|
printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED); |
|
return G_FAILED > 0 ? 1 : 0; |
|
}
|
|
|