call: P2P аудиозвонки — сигналинг, релей медиа, аудио-контур, headless/desktop/android
- src/call/: сигналинг (call.c, call_proto.h, ETCP_RT_ID_CALL 0x36) + релей медиа + headless аудио-сокет (call_headless.c, call_audio_bind) + Opus/jitter/тоны - chat: события CHAT_EVT_CALL_* (34–40), CHAT_EVT_DM_MESSAGES (41), DM-трамплины - chatgui: call window, miniaudio I/O, audio_frame_ring, test_audio_jitter - chatgui-android: CallScreen/IncomingCallDialog, аудио-роутинг, JNI, рингтон - tests: test_call, test_call_headless
This commit is contained in:
1 parent
0157f7f96b
commit
b11e77e4c0
78 files changed
+7053
-70
No files matched your search
@@ -0,0 +1,101 @@
|
||||
// test_audio_jitter.cpp — проверка адаптивного джиттер-буфера (SoundTouch).
|
||||
//
|
||||
// 1. Нет потерь до ёмкости (50 кадров = 1000мс), дроп после переполнения.
|
||||
// 2. Догон: глубокий буфер → темп растёт выше 1.0x (ускоренное воспроизведение).
|
||||
// 3. Стационар: при неглубоком буфере темп остаётся ~1.0x (без runaway).
|
||||
|
||||
#include "call/audio_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)
|
||||
|
||||
static void make_sine(std::vector<int16_t>& frame, double freq, double phase0) {
|
||||
for (size_t i = 0; i < frame.size(); i++)
|
||||
frame[i] = (int16_t)(sin(phase0 + 2.0 * M_PI * freq * (double)i / 48000.0) * 8000.0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const int FRAME = 960;
|
||||
const int CAP = 50;
|
||||
std::vector<int16_t> frame(FRAME, 0);
|
||||
std::vector<int16_t> out(480, 0);
|
||||
int buffer_ms = 0, tempo_x100 = 0;
|
||||
|
||||
/* ── 1. ёмкость и дроп ── */
|
||||
{
|
||||
ajb* j = ajb_create(48000, 1, FRAME, 20, CAP);
|
||||
CHECK(j != nullptr, "create failed");
|
||||
|
||||
make_sine(frame, 440.0, 0.0);
|
||||
for (int i = 0; i < CAP; i++) ajb_push(j, frame.data(), FRAME);
|
||||
ajb_get_stats(j, &buffer_ms, &tempo_x100);
|
||||
printf(" capacity: pushed %d frames, buffer_ms=%d dropped=%u\n", CAP, buffer_ms, (unsigned)ajb_dropped(j));
|
||||
CHECK(ajb_dropped(j) == 0, "expected no drop at capacity, dropped=%u", (unsigned)ajb_dropped(j));
|
||||
CHECK(buffer_ms == CAP * 20, "expected buffer_ms=%d, got %d", CAP * 20, buffer_ms);
|
||||
|
||||
for (int i = 0; i < 5; i++) ajb_push(j, frame.data(), FRAME);
|
||||
printf(" overflow: dropped=%u (expect 5)\n", (unsigned)ajb_dropped(j));
|
||||
CHECK(ajb_dropped(j) == 5, "expected 5 drops, got %u", (unsigned)ajb_dropped(j));
|
||||
ajb_destroy(j);
|
||||
}
|
||||
|
||||
/* ── 2. догон: глубокий буфер → tempo > 1.0x ── */
|
||||
{
|
||||
ajb* j = ajb_create(48000, 1, FRAME, 20, CAP);
|
||||
make_sine(frame, 440.0, 0.0);
|
||||
for (int i = 0; i < 40; i++) ajb_push(j, frame.data(), FRAME); /* 800мс burst */
|
||||
|
||||
int max_tempo = 100;
|
||||
int total_out = 0;
|
||||
for (int it = 0; it < 200; it++) {
|
||||
ajb_get_stats(j, &buffer_ms, &tempo_x100);
|
||||
if (buffer_ms == 0) break;
|
||||
if (tempo_x100 > max_tempo) max_tempo = tempo_x100;
|
||||
int n = ajb_pull(j, out.data(), (int)out.size());
|
||||
total_out += n;
|
||||
}
|
||||
ajb_get_stats(j, &buffer_ms, &tempo_x100);
|
||||
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);
|
||||
ajb_destroy(j);
|
||||
}
|
||||
|
||||
/* ── 3. стационар: feed 1 frame → pull 1 frame, tempo ~1.0x ── */
|
||||
{
|
||||
ajb* j = ajb_create(48000, 1, FRAME, 20, CAP);
|
||||
make_sine(frame, 440.0, 0.0);
|
||||
int max_tempo = 100;
|
||||
for (int i = 0; i < 60; i++) {
|
||||
ajb_push(j, frame.data(), FRAME);
|
||||
int got = 0;
|
||||
while (got < FRAME) {
|
||||
int n = ajb_pull(j, out.data(), (int)out.size());
|
||||
if (n <= 0) break;
|
||||
got += n;
|
||||
}
|
||||
ajb_get_stats(j, &buffer_ms, &tempo_x100);
|
||||
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);
|
||||
ajb_destroy(j);
|
||||
}
|
||||
|
||||
if (g_fail == 0) {
|
||||
printf("TEST PASSED\n");
|
||||
return 0;
|
||||
}
|
||||
printf("TEST FAILED (%d checks)\n", g_fail);
|
||||
return 1;
|
||||
}
|
||||
Reference in new issue
Block a user