diff --git a/src/call/call_video_playout.c b/src/call/call_video_playout.c index a7fdf47a..3683693b 100644 --- a/src/call/call_video_playout.c +++ b/src/call/call_video_playout.c @@ -83,7 +83,7 @@ enum call_video_playout_action call_video_playout_poll(struct call_video_playout if (target > VIDEO_MAX_SPEED_PPM) target = VIDEO_MAX_SPEED_PPM; int64_t elapsed = p->rate_us ? now - p->rate_us : 0; if (elapsed > 100000) elapsed = 100000; - if (elapsed > 0) p->speed_ppm += (target - p->speed_ppm) * elapsed / (200000 + elapsed); + if (elapsed > 0) p->speed_ppm = (uint32_t)((int64_t)p->speed_ppm + (target - p->speed_ppm) * elapsed / (200000 + elapsed)); p->rate_us = now; int64_t delta = p->last_pts >= 0 ? f->pts_us - p->last_pts : 0; int64_t step = (int64_t)f->interval_us * 1000000 / p->speed_ppm; diff --git a/tests/test_call_video_playout.c b/tests/test_call_video_playout.c index a0d019ad..4c369511 100644 --- a/tests/test_call_video_playout.c +++ b/tests/test_call_video_playout.c @@ -1,4 +1,4 @@ -/* Управляемое время: весь поток сохраняется при паузах сети/CPU и ускорении аудио. */ +/* Управляемые arrival/presentation clocks: FIFO сохраняется при паузах сети/CPU/GUI. */ #include #include "../src/call/call_video_playout.h" #include "../lib/debug_config.h" @@ -16,48 +16,62 @@ static void simulate(unsigned pause_ms, int cpu, int64_t quantum, int accelerate arrival = origin + 1000000 + pause_ms * 1000ll; frames[i] = (struct call_video_playout_frame){origin + i * 33333ll, arrival, 33333}; } - unsigned shown = 0, accelerated = 0; - int64_t last_show = 0, recovered_lag = INT64_MAX, audio_pts = origin - (accelerated_audio ? 150000 : 0); - for (int64_t now = origin; now < origin + 20000000 && shown < COUNT; now += quantum) { + unsigned selected = 0, shown = 0, available = 0, accelerated = 0; + int64_t last_show = 0, ack_at = 0, recovered_lag = INT64_MAX, audio_pts = origin; + for (int64_t now = origin; now < origin + 22000000 && shown < COUNT; now += quantum) { uint32_t rate = accelerated_audio && now >= origin + 1500000 && now < origin + 2500000 ? 1100000 : 1000000; if (now > origin) audio_pts += quantum * rate / 1000000; if (cpu && now >= origin + 1000000 && now < origin + 1000000 + pause_ms * 1000ll) continue; + while (available < COUNT && frames[available].received_us <= now) ++available; + if (ack_at && now >= ack_at) { + call_video_playout_presented(&p, frames[shown].pts_us, now); + CHECK(!last_show || now > last_show); last_show = now; ++shown; ack_at = 0; + } struct call_clock_snapshot audio = {.pts_us = audio_pts, .rate_ppm = rate, .valid = !(now >= origin + 4000000 && now < origin + 4100000)}; - struct call_video_playout_frame *f = frames + shown; - int action = call_video_playout_poll(&p, f->received_us <= now ? f : NULL, NULL, now, &audio); + const struct call_video_playout_frame *f = selected < available ? frames + selected : NULL; + int action = call_video_playout_poll(&p, f, now, &audio, available - shown, 1); + if (now >= origin + 6000000 && shown) recovered_lag = audio.pts_us - frames[shown - 1].pts_us; if (action == CALL_VIDEO_PLAYOUT_WAIT) continue; - CHECK(action == CALL_VIDEO_PLAYOUT_SHOW && audio.valid); - CHECK(!last_show || now > last_show); + CHECK(action == CALL_VIDEO_PLAYOUT_SHOW && audio.valid && !ack_at); CHECK(f->pts_us - audio.pts_us <= 40000); - if (p.speed_ppm > 1050000) accelerated++; - if (now >= origin + 6000000) recovered_lag = audio.pts_us - f->pts_us; - last_show = now; shown++; - // Даже готовая очередь после фриза не выдаётся целиком за один вызов worker. - if (shown < COUNT && frames[shown].received_us <= now) - CHECK(call_video_playout_poll(&p, frames + shown, NULL, now, &audio) == CALL_VIDEO_PLAYOUT_WAIT); + if (p.speed_ppm > 1050000) ++accelerated; + ++selected; ack_at = now + 3000; // Не мгновенный GUI: кадр остаётся в общем запасе до ACK. + CHECK(call_video_playout_poll(&p, selected < available ? frames + selected : NULL, now, &audio, + available - shown, 1) == CALL_VIDEO_PLAYOUT_WAIT); } - DEBUG_INFO(DEBUG_CATEGORY_CALL, "sequential recovery pause_ms=%u cpu=%d quantum_us=%lld audio_fast=%d shown=%u/%u lag_us=%lld", + DEBUG_INFO(DEBUG_CATEGORY_CALL, "presentation recovery pause_ms=%u cpu=%d quantum_us=%lld audio_fast=%d shown=%u/%u lag_us=%lld", pause_ms, cpu, (long long)quantum, accelerated_audio, shown, COUNT, (long long)recovered_lag); - CHECK(shown == COUNT && accelerated && recovered_lag < 50000); + CHECK(shown == COUNT && accelerated); + CHECK(recovered_lag < (quantum < 2000 ? 100000 : 220000)); } -static void order_and_clock(void) { +static void presentation_deadline(void) { struct call_video_playout p; call_video_playout_init(&p, 9); struct call_video_playout_frame f = {origin, origin, 33333}; - struct call_clock_snapshot audio = {.pts_us = origin, .rate_ppm = 1000000}; - CHECK(call_video_playout_poll(&p, &f, NULL, origin, &audio) == CALL_VIDEO_PLAYOUT_WAIT); - audio.valid = 1; - CHECK(call_video_playout_poll(&p, &f, NULL, origin, &audio) == CALL_VIDEO_PLAYOUT_SHOW); - CHECK(call_video_playout_poll(&p, &f, NULL, origin + 1000, &audio) == CALL_VIDEO_PLAYOUT_DROP_ORDER); - f.pts_us += 33333; - audio.valid = 0; - CHECK(call_video_playout_poll(&p, &f, NULL, origin + 200000, &audio) == CALL_VIDEO_PLAYOUT_WAIT); - audio.valid = 1; audio.pts_us += 200000; - CHECK(call_video_playout_poll(&p, &f, NULL, origin + 200000, &audio) == CALL_VIDEO_PLAYOUT_SHOW); + struct call_clock_snapshot audio = {.pts_us = origin, .rate_ppm = 1000000, .valid = 1}; + CHECK(call_video_playout_poll(&p, &f, origin, &audio, 2, 1) == CALL_VIDEO_PLAYOUT_SHOW); + CHECK(p.shown == 0 && p.awaiting); // Выбор кадра ещё не показ. + call_video_playout_presented(&p, f.pts_us, origin); + CHECK(p.shown == 1 && !p.awaiting); + CHECK(call_video_playout_poll(&p, NULL, origin + 5000, &audio, 0, 1) == CALL_VIDEO_PLAYOUT_WAIT); + f.pts_us += 33333; f.received_us = origin + 12000; audio.pts_us += 12000; + CHECK(call_video_playout_poll(&p, &f, origin + 12000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_WAIT); + int64_t deadline = p.deadline_us; + audio.pts_us += 12000; + CHECK(call_video_playout_poll(&p, &f, origin + 24000, &audio, 8, 1) == CALL_VIDEO_PLAYOUT_WAIT); + CHECK(p.deadline_us == deadline); // Приход пачки и движение audio clock не двигают уже назначенный срок. + CHECK(call_video_playout_poll(&p, &f, origin + 35000, &audio, 8, 1) == CALL_VIDEO_PLAYOUT_SHOW); + call_video_playout_presented(&p, f.pts_us, origin + 40000); + CHECK(p.last_show_us == origin + 40000); + CHECK(call_video_playout_poll(&p, &f, origin + 41000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_DROP_ORDER); + f.pts_us += 33333; audio.valid = 0; + CHECK(call_video_playout_poll(&p, &f, origin + 200000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_WAIT); + audio.valid = 1; audio.pts_us = origin + 200000; + CHECK(call_video_playout_poll(&p, &f, origin + 200000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_SHOW); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS presentation clock: 12ms starvation does not collapse cadence; ACK alone advances time"); } -/* Готовые кадры могут разделяться паузой raw capture, пока decoder lookahead не пустел. */ static void queued_capture_gap(void) { const int64_t gaps[] = {500000, 2264933, 10000000}; const unsigned fps[] = {15, 30, 60}; @@ -66,20 +80,32 @@ static void queued_capture_gap(void) { unsigned interval = 1000000 / fps[j]; struct call_video_playout_frame first = {origin, origin, interval}, next = {origin + gaps[i], origin, interval}; struct call_clock_snapshot audio = {.pts_us = next.pts_us + 1000000, .valid = 1, .rate_ppm = 1000000}; - CHECK(call_video_playout_poll(&p, &first, &next, origin, &audio) == CALL_VIDEO_PLAYOUT_SHOW); - CHECK(call_video_playout_poll(&p, &next, NULL, origin, &audio) == CALL_VIDEO_PLAYOUT_WAIT); - CHECK(call_video_playout_poll(&p, &next, NULL, origin + interval, &audio) == CALL_VIDEO_PLAYOUT_SHOW); + CHECK(call_video_playout_poll(&p, &first, origin, &audio, 2, 1) == CALL_VIDEO_PLAYOUT_SHOW); + call_video_playout_presented(&p, first.pts_us, origin); + CHECK(call_video_playout_poll(&p, &next, origin, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_WAIT); + CHECK(call_video_playout_poll(&p, &next, origin + interval + 2000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_SHOW); CHECK(p.last_pts == next.pts_us && p.source_gaps == 1); } + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS queued capture gaps: 0.5/2.265/10s at 15/30/60fps, no repeated wait"); +} + +static void slowing_with_queue(void) { struct call_video_playout p; call_video_playout_init(&p, 12); - struct call_video_playout_frame f = {origin, origin, 33333}; struct call_clock_snapshot audio = {.pts_us = origin, .valid = 1, .rate_ppm = 1000000}; - CHECK(call_video_playout_poll(&p, &f, NULL, origin, &audio) == CALL_VIDEO_PLAYOUT_SHOW); - f.pts_us += 2000000; - CHECK(call_video_playout_poll(&p, &f, NULL, origin + 33333, &audio) == CALL_VIDEO_PLAYOUT_WAIT); - audio.pts_us = f.pts_us - 40000; - CHECK(call_video_playout_poll(&p, &f, NULL, origin + 50000, &audio) == CALL_VIDEO_PLAYOUT_SHOW); - DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS queued capture gaps: 0.5/2.265/10s at 15/30/60fps, no repeated wait, no audio lead violation"); + struct call_video_playout_frame f = {origin, origin, 33333}; + uint32_t fast = 0; unsigned shows = 0; + for (int64_t now = origin; now < origin + 2000000; now += 1000) { + audio.pts_us = now; + unsigned queue = now < origin + 1000000 ? 8 : 2; + int action = call_video_playout_poll(&p, &f, now, &audio, queue, 1); + if (action == CALL_VIDEO_PLAYOUT_SHOW) { + call_video_playout_presented(&p, f.pts_us, now); + f.pts_us += 33333; ++shows; + } + if (now == origin + 999000) fast = p.speed_ppm; + } + CHECK(shows && fast > 1500000 && p.speed_ppm < 1050000); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS queue governor: speed_ppm=%u -> %u as total frames fall 8 -> 2", fast, p.speed_ppm); } int main(void) { @@ -90,5 +116,5 @@ int main(void) { simulate(pauses[i], cpu, 1000, 0); simulate(pauses[i], cpu, 15625, 1); } - order_and_clock(); queued_capture_gap(); return 0; + presentation_deadline(); queued_capture_gap(); slowing_with_queue(); return 0; }