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.
 
 
 
 
 
 

103 lines
4.4 KiB

// test_voice_jitter.cpp — проверка адаптивного джиттер-буфера голоса (SoundTouch).
//
// Декодер подменяется генератором синуса (кодированный кадр — маленький маркер),
// чтобы проверить буфер без Opus: 1) ёмкость/дроп, 2) догон (глубокий буфер →
// темп > 1.0x), 3) стационар (темп ~1.0x без runaway).
#include "call/voice_jitter.h"
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
static int g_fail = 0;
#define CHECK(cond, ...) do { \
if (!(cond)) { g_fail++; printf("FAIL: " __VA_ARGS__); printf("\n"); } \
} while (0)
/* фейковый декодер: генерирует кадр синуса (960 сэмплов) из любого «кодированного» маркера. */
static int sine_decode(void* arg, const uint8_t* enc, int len, int16_t* pcm, int max_samples) {
(void)arg; (void)enc; (void)len;
for (int i = 0; i < max_samples; i++)
pcm[i] = (int16_t)(sin(2.0 * M_PI * 440.0 * (double)i / 48000.0) * 8000.0);
return max_samples;
}
int main(void) {
const int FRAME = 960;
const int CAP = 50;
uint8_t marker[8] = {0};
std::vector<int16_t> out(480, 0);
int buffer_ms = 0, tempo_x100 = 0;
uint32_t dropped = 0, underruns = 0;
/* ── 1. ёмкость и дроп ── */
{
vj* j = vj_create(48000, 1, FRAME, 20, CAP, sine_decode, nullptr);
CHECK(j != nullptr, "create failed");
for (int i = 0; i < CAP; i++) vj_push(j, marker, (int)sizeof(marker));
vj_get_stats(j, &buffer_ms, &tempo_x100, &dropped, &underruns);
printf(" capacity: pushed %d frames, buffer_ms=%d dropped=%u\n", CAP, buffer_ms, (unsigned)dropped);
CHECK(dropped == 0, "expected no drop at capacity, dropped=%u", (unsigned)dropped);
CHECK(buffer_ms == CAP * 20, "expected buffer_ms=%d, got %d", CAP * 20, buffer_ms);
for (int i = 0; i < 5; i++) vj_push(j, marker, (int)sizeof(marker));
vj_get_stats(j, &buffer_ms, &tempo_x100, &dropped, &underruns);
printf(" overflow: dropped=%u (expect 5)\n", (unsigned)dropped);
CHECK(dropped == 5, "expected 5 drops, got %u", (unsigned)dropped);
vj_destroy(j);
}
/* ── 2. догон: глубокий буфер → tempo > 1.0x ── */
{
vj* j = vj_create(48000, 1, FRAME, 20, CAP, sine_decode, nullptr);
for (int i = 0; i < 40; i++) vj_push(j, marker, (int)sizeof(marker)); /* 800мс burst */
int max_tempo = 100;
int total_out = 0;
for (int it = 0; it < 200; it++) {
vj_get_stats(j, &buffer_ms, &tempo_x100, &dropped, &underruns);
if (buffer_ms == 0) break;
if (tempo_x100 > max_tempo) max_tempo = tempo_x100;
int n = vj_pull(j, out.data(), (int)out.size());
total_out += n;
}
vj_get_stats(j, &buffer_ms, &tempo_x100, &dropped, &underruns);
printf(" catch-up: max_tempo_x100=%d total_out=%d final_buffer_ms=%d\n",
max_tempo, total_out, buffer_ms);
CHECK(max_tempo > 110, "expected tempo to exceed 1.10x during deep buffer, got %d", max_tempo);
CHECK(total_out < 40 * FRAME, "expected output < input (stretch), out=%d in=%d", total_out, 40 * FRAME);
CHECK(buffer_ms == 0, "expected drained buffer, buffer_ms=%d", buffer_ms);
vj_destroy(j);
}
/* ── 3. стационар: feed 1 frame → pull 1 frame, tempo ~1.0x ── */
{
vj* j = vj_create(48000, 1, FRAME, 20, CAP, sine_decode, nullptr);
int max_tempo = 100;
for (int i = 0; i < 60; i++) {
vj_push(j, marker, (int)sizeof(marker));
int got = 0;
while (got < FRAME) {
int n = vj_pull(j, out.data(), (int)out.size());
if (n <= 0) break;
got += n;
}
vj_get_stats(j, &buffer_ms, &tempo_x100, &dropped, &underruns);
if (tempo_x100 > max_tempo) max_tempo = tempo_x100;
}
printf(" steady-state: max_tempo_x100=%d (expect ~100)\n", max_tempo);
CHECK(max_tempo <= 110, "tempo should stay ~1.0x in steady state, got %d", max_tempo);
vj_destroy(j);
}
if (g_fail == 0) {
printf("TEST PASSED\n");
return 0;
}
printf("TEST FAILED (%d checks)\n", g_fail);
return 1;
}