From 408c0be0219ef445a2ef5ecf1bd83f2b084dc81f Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 15 Oct 2026 15:56:31 +0200 Subject: [PATCH] Test video cadence against data stalls scheduler delays and audio corrections --- tests/Makefile.am | 4 + tests/test_call_video_playout.c | 125 ++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 tests/test_call_video_playout.c diff --git a/tests/Makefile.am b/tests/Makefile.am index 6f43b584..70c0babf 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -11,6 +11,7 @@ EXTRA_DIST = fixtures/reality_openssl_hello.hex data/silero_speech.pcm data/sile check_PROGRAMS = \ test_route_tx_stats \ test_call_video_rate \ + test_call_video_playout \ test_call_video \ test_memory_pool_and_config \ test_serialize \ @@ -682,6 +683,9 @@ test_route_tx_stats_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_call_video_rate_SOURCES = test_call_video_rate.c test_call_video_rate_LDADD = $(LIBUTUN) $(COMMON_LIBS) +test_call_video_playout_SOURCES = test_call_video_playout.c +test_call_video_playout_LDADD = $(LIBUTUN) $(COMMON_LIBS) + test_call_video_SOURCES = test_call_video.c test_call_video_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_call_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/chat -I$(top_srcdir)/src/dm -I$(top_srcdir)/src/call -I$(top_srcdir)/src/routing_layer -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/lib diff --git a/tests/test_call_video_playout.c b/tests/test_call_video_playout.c new file mode 100644 index 00000000..99e9896c --- /dev/null +++ b/tests/test_call_video_playout.c @@ -0,0 +1,125 @@ +/* Управляемое время: сеть, decoder и поздний запуск worker — независимые события. */ +#include +#include "../src/call/call_video_playout.h" +#include "../lib/debug_config.h" + +#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "playout test line=%d: %s", __LINE__, #x); exit(1); } } while (0) +#define SHOW CALL_VIDEO_PLAYOUT_SHOW +#define WAIT CALL_VIDEO_PLAYOUT_WAIT +static const int64_t origin = 1000000; + +static void start(struct call_video_playout *p) { + call_video_playout_init(p, 7); + struct call_video_playout_frame f = {origin, origin}; + CHECK(call_video_playout_poll(p, &f, NULL, origin, 1, origin) == SHOW); +} + +static void test_absolute_cpu(void) { + struct call_video_playout p; start(&p); + struct call_video_playout_frame f = {origin + 33333, origin + 20000}; + CHECK(call_video_playout_poll(&p, &f, NULL, origin + 32049, 1, origin + 32049) == WAIT); + CHECK(p.deadline_us == origin + 32050); + CHECK(call_video_playout_poll(&p, &f, NULL, origin + 41050, 1, origin + 41050) == SHOW); + CHECK(p.last_deadline == origin + 32050 && p.dispatch_max == 9000 && p.data_shift == 0); + f = (struct call_video_playout_frame){origin + 66666, origin + 50000}; + CHECK(call_video_playout_poll(&p, &f, NULL, origin + 64100, 1, origin + 64100) == SHOW); + CHECK(p.last_deadline == origin + 64100); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS playout: CPU +9ms never shifts the next absolute deadline"); +} + +static void test_data_stall(void) { + struct call_video_playout p; start(&p); + struct call_video_playout_frame f = {origin + 33333, origin + 52050}; + CHECK(call_video_playout_poll(&p, &f, NULL, origin + 58050, 1, origin + 58050) == SHOW); + CHECK(p.last_deadline == origin + 52050 && p.data_shift == 20000 && p.dispatch_max == 6000); + int64_t last_lag = 52050 - 33333; + for (int i = 2; i <= 40; i++) { + f = (struct call_video_playout_frame){origin + i * 33333, origin + i * 33333 - 20000}; + int64_t due = origin + 20000 + i * 32050; + if (due < f.received_us) due = f.received_us; + CHECK(call_video_playout_poll(&p, &f, NULL, due, 1, due) == SHOW); + int64_t lag = due - f.pts_us; + CHECK(lag <= last_lag && lag >= -20000); last_lag = lag; + } + CHECK(last_lag == -20000); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS playout: confirmed data stall drains at 1.04x; decode delay is excluded"); +} + +static void test_backlog(void) { + struct call_video_playout p; start(&p); + struct call_video_playout_frame frames[4]; + for (int i = 0; i < 4; i++) frames[i] = (struct call_video_playout_frame){origin + (i + 1) * 33333, origin}; + int64_t now = origin + 102000; + CHECK(call_video_playout_poll(&p, frames, frames + 1, now, 1, now) == CALL_VIDEO_PLAYOUT_DROP_LATE); + CHECK(call_video_playout_poll(&p, frames + 1, frames + 2, now, 1, now) == CALL_VIDEO_PLAYOUT_DROP_SCHEDULE); + CHECK(call_video_playout_poll(&p, frames + 2, frames + 3, now, 1, now) == SHOW); + CHECK(call_video_playout_poll(&p, frames + 3, NULL, now, 1, now) == WAIT); + CHECK(p.deadline_us == origin + 128200); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS playout: CPU pause drops obsolete presentation, never bursts through ready frames"); +} + +static void test_audio_bounds(void) { + struct call_video_playout p; start(&p); + int64_t previous = origin; + for (int i = 1; i < 300; i++) { + struct call_video_playout_frame f = {origin + i * 33333, origin}; + int64_t now = previous; + CHECK(call_video_playout_poll(&p, &f, NULL, now, 1, now) == WAIT); + now = p.deadline_us; + CHECK(call_video_playout_poll(&p, &f, NULL, now, 1, now) == SHOW); + CHECK(f.pts_us - now >= -50000 && f.pts_us - now <= 40000); + CHECK(now - previous >= 32050 && now - previous <= 33333); previous = now; + } + CHECK(p.last_pts - previous == 40000); + CHECK(call_video_playout_poll(&p, NULL, NULL, previous + 10000, 0, 0) == WAIT && !p.anchored); + struct call_video_playout_frame f = {p.last_pts + 33333, previous + 20000}; + CHECK(call_video_playout_poll(&p, &f, NULL, previous + 100000, 1, f.pts_us) == SHOW); + CHECK(p.last_deadline == previous + 60000); // Аудио восстановилось: фаза ограничена, долг CPU не перенесён. + CHECK(call_video_playout_poll(&p, &f, NULL, previous + 100001, 1, f.pts_us) == CALL_VIDEO_PLAYOUT_DROP_ORDER); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS playout: audio upper boundary, clock outage, recovery and PTS order"); +} + +/* На 10с: burst доставки, смена FPS, 1.6x аудио, недостоверные часы и задержка worker. + * Показ разрешён только в текущем окне A/V; время приёма не подменяется временем poll. */ +static void test_combined(void) { + struct call_video_playout p; call_video_playout_init(&p, 9); + struct call_video_playout_frame frames[400]; + int64_t pts = origin; + for (int i = 0; i < 400; i++) { + int64_t arrival = pts - 15000 + (i % 5) * 1000; + if (i >= 30 && i < 34) arrival = origin + 1100000; + if (i && arrival < frames[i - 1].received_us) arrival = frames[i - 1].received_us; + frames[i] = (struct call_video_playout_frame){pts, arrival}; + pts += i < 180 ? 33333 : 16667; + } + int head = 0, shown = 0, dropped = 0; + int64_t last = -1, audio = origin - 1000; + for (int64_t now = origin; now < origin + 10000000 && head < 400; now += 1000) { + audio += now >= origin + 4000000 && now < origin + 4200000 ? 1600 : 1000; + if (now >= origin + 2000000 && now < origin + 2080000) continue; + int valid = now < origin + 3000000 || now >= origin + 3100000; + for (;;) { + if (head == 400) break; + struct call_video_playout_frame *f = frames + head, *next = head + 1 < 400 ? frames + head + 1 : NULL; + if (f->received_us > now) f = NULL; + if (next && next->received_us > now) next = NULL; + int action = call_video_playout_poll(&p, f, next, now, valid, audio); + if (action == WAIT) break; + CHECK(action != CALL_VIDEO_PLAYOUT_DROP_ORDER && f); + if (action == SHOW) { + CHECK(valid && f->pts_us > last && f->pts_us - audio >= -50000 && f->pts_us - audio <= 40000); + last = f->pts_us; shown++; + } else dropped++; + head++; + if (action == SHOW) break; + } + } + CHECK(head == 400 && shown > 300 && dropped > 0); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS playout: combined jitter/CPU/FPS/audio tempo simulation shown=%d dropped=%d", shown, dropped); +} + +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); + test_absolute_cpu(); test_data_stall(); test_backlog(); test_audio_bounds(); test_combined(); + return 0; +}