Files
utun2/tests/test_audio_compressor.c
T
evgeny 38c9f229bc stcp: ed25519 pubkey exchange in handshake, fix instance destroy segfault, fix chat_tcp_test
- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll
- stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close
- utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP),
  add deferred drain in both Phase J and L
- stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39,
  exchange ed25519_pubkey in both handshake directions
- etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in
  etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb
- chat_sync.c: fix lk->conn NULL dereference for TCP links
- test_stcp.c: update stcp_server_create/stcp_client_connect callsites
- tools/chat_tcp_test: change transport udp→tcp
2026-08-07 00:48:27 +03:00

206 lines
7.0 KiB
C

#define _DEFAULT_SOURCE
#include "audio_compressor.h"
#include "debug_config.h"
#include "mem.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
static int tests_run = 0, tests_passed = 0;
#define TEST(expr, msg) do { \
tests_run++; \
if (expr) { tests_passed++; } \
else { fprintf(stderr, "FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); } \
} while(0)
/* ── helpers ── */
static void fill_sine(int16_t* buf, size_t n, int freq, int sample_rate) {
for (size_t i = 0; i < n; i++)
buf[i] = (int16_t)(14000.0f * sinf(2.0f * (float)M_PI * (float)freq * (float)i / (float)sample_rate));
}
static void fill_silence(int16_t* buf, size_t n) {
memset(buf, 0, n * sizeof(int16_t));
}
/* ── tests ── */
static void test_create_destroy(void) {
struct audio_compressor* ac = audio_compressor_create();
TEST(ac != NULL, "create");
audio_compressor_configure(ac, &(audio_compressor_config_t){0});
audio_compressor_reset(ac);
audio_compressor_destroy(ac);
/* check no leaks via u_get_allocated_count */
size_t unfreed = u_get_allocated_count();
TEST(unfreed == 0, "no leaks after destroy");
if (unfreed > 0) {
fprintf(stderr, " unfreed blocks: %zu\n", unfreed);
u_report_unfreed_blocks();
}
}
static void test_disabled_passthrough(void) {
struct audio_compressor* ac = audio_compressor_create();
audio_compressor_configure(ac, &(audio_compressor_config_t){0});
audio_compressor_set_enabled(ac, 0);
int16_t in[960]; fill_sine(in, 960, 440, 48000);
audio_compressor_push(ac, in, 960);
const int16_t* out = audio_compressor_output(ac);
size_t sz = audio_compressor_output_size(ac);
TEST(sz == 960, "passthrough size");
int match = 1;
for (size_t i = 0; i < sz; i++) { if (out[i] != in[i]) { match = 0; break; } }
TEST(match, "passthrough data unchanged");
audio_compressor_destroy(ac);
}
static void test_basic_push_flush(void) {
struct audio_compressor* ac = audio_compressor_create();
audio_compressor_config_t cfg = {0};
cfg.sample_rate = 48000; cfg.channels = 1;
cfg.block_duration_ms = 20; cfg.lookback_ms = 200; cfg.lookahead_ms = 100;
cfg.max_gain_db = 30.0f; cfg.rise_rate_per_sec = 4.0f; cfg.target_level = 1.0f;
audio_compressor_configure(ac, &cfg);
int16_t in[960];
/* push 20 blocks (400ms) — enough to fill lookback+lookahead and flush */
for (int b = 0; b < 20; b++) {
fill_sine(in, 960, 440, 48000);
audio_compressor_push(ac, in, 960);
}
audio_compressor_flush(ac);
const int16_t* out = audio_compressor_output(ac);
size_t sz = audio_compressor_output_size(ac);
TEST(sz > 0, "output not empty");
TEST(sz >= 960 * 10, "output has reasonable size");
/* check not all silence */
float sum_sq = 0.0f;
for (size_t i = 0; i < sz; i++) { float v = (float)out[i] / 32768.0f; sum_sq += v * v; }
float rms = sqrtf(sum_sq / (float)sz);
TEST(rms > 0.001f, "output not silence");
audio_compressor_destroy(ac);
}
static void test_silence_in_silence_out(void) {
struct audio_compressor* ac = audio_compressor_create();
audio_compressor_config_t cfg = {0};
cfg.sample_rate = 48000; cfg.channels = 1;
cfg.block_duration_ms = 20; cfg.lookback_ms = 200; cfg.lookahead_ms = 100;
cfg.max_gain_db = 30.0f; cfg.rise_rate_per_sec = 4.0f; cfg.target_level = 1.0f;
audio_compressor_configure(ac, &cfg);
int16_t in[960]; fill_silence(in, 960);
for (int b = 0; b < 20; b++)
audio_compressor_push(ac, in, 960);
audio_compressor_flush(ac);
const int16_t* out = audio_compressor_output(ac);
size_t sz = audio_compressor_output_size(ac);
TEST(sz > 0, "output not empty (silence)");
/* silence should still be silence (envelope ≈ 0.0001, gain limited) */
float sum_sq = 0.0f;
for (size_t i = 0; i < sz; i++) { float v = (float)out[i] / 32768.0f; sum_sq += v * v; }
float rms = sqrtf(sum_sq / (float)sz);
TEST(rms < 0.01f, "silence stays near zero");
audio_compressor_destroy(ac);
}
static void test_reset(void) {
struct audio_compressor* ac = audio_compressor_create();
audio_compressor_config_t cfg = {0};
cfg.sample_rate = 48000; cfg.channels = 1;
cfg.block_duration_ms = 20; cfg.lookback_ms = 200; cfg.lookahead_ms = 100;
cfg.max_gain_db = 30.0f; cfg.rise_rate_per_sec = 4.0f; cfg.target_level = 1.0f;
audio_compressor_configure(ac, &cfg);
int16_t in[960];
fill_sine(in, 960, 440, 48000);
for (int b = 0; b < 15; b++) audio_compressor_push(ac, in, 960);
audio_compressor_flush(ac);
audio_compressor_reset(ac);
fill_sine(in, 960, 880, 48000);
for (int b = 0; b < 15; b++) audio_compressor_push(ac, in, 960);
audio_compressor_flush(ac);
const int16_t* out = audio_compressor_output(ac);
size_t sz = audio_compressor_output_size(ac);
TEST(sz > 960 * 10, "output after reset has data");
float sum_sq = 0.0f;
for (size_t i = 0; i < sz; i++) { float v = (float)out[i] / 32768.0f; sum_sq += v * v; }
float rms = sqrtf(sum_sq / (float)sz);
TEST(rms > 0.001f, "output after reset not silence");
audio_compressor_destroy(ac);
}
static void test_stress(void) {
srand((unsigned)time(NULL));
for (int cycle = 0; cycle < 200; cycle++) {
struct audio_compressor* ac = audio_compressor_create();
audio_compressor_config_t cfg = {0};
cfg.sample_rate = 48000; cfg.channels = 1;
cfg.block_duration_ms = 20; cfg.lookback_ms = (rand() % 200) + 50;
cfg.lookahead_ms = (rand() % 100) + 20;
cfg.max_gain_db = (float)(rand() % 31);
cfg.rise_rate_per_sec = 0.4f + (float)(rand() % 27) / 10.0f;
cfg.target_level = 0.1f + (float)(rand() % 30) / 100.0f;
audio_compressor_configure(ac, &cfg);
int16_t in[960];
int blocks = (rand() % 30) + 5;
for (int b = 0; b < blocks; b++) {
for (int i = 0; i < 960; i++)
in[i] = (int16_t)((rand() % 28000) - 14000);
audio_compressor_push(ac, in, 960);
}
audio_compressor_flush(ac);
audio_compressor_reset(ac);
blocks = (rand() % 10) + 1;
for (int b = 0; b < blocks; b++) {
for (int i = 0; i < 960; i++)
in[i] = (int16_t)((rand() % 2000) - 1000);
audio_compressor_push(ac, in, 960);
}
audio_compressor_flush(ac);
const int16_t* out = audio_compressor_output(ac);
size_t sz = audio_compressor_output_size(ac);
TEST(sz > 0, "stress output not empty");
audio_compressor_destroy(ac);
size_t unfreed = u_get_allocated_count();
TEST(unfreed == 0, "stress no leaks");
if (unfreed > 0) break;
}
}
int main(void) {
debug_set_level(DEBUG_LEVEL_NONE);
test_create_destroy();
test_disabled_passthrough();
test_basic_push_flush();
test_silence_in_silence_out();
test_reset();
test_stress();
fprintf(stderr, "\n%d/%d tests passed\n", tests_passed, tests_run);
return tests_passed == tests_run ? 0 : 1;
}