diff --git a/src/call/call_video_playout.c b/src/call/call_video_playout.c index 42f0a073..30dd6542 100644 --- a/src/call/call_video_playout.c +++ b/src/call/call_video_playout.c @@ -122,7 +122,11 @@ enum call_video_playout_action call_video_playout_poll(struct call_video_playout int64_t step = (int64_t)f->interval_us * 1000000 / p->speed_ppm; if (p->head_pts != f->pts_us) { p->head_pts = f->pts_us; - p->head_due_us = p->anchored ? p->last_show_us + step - p->lead_us : now; + p->head_show_us = p->anchored ? p->last_deadline + step : now; + // После опоздания сохраняем все кадры, но не устраиваем мгновенный burst. + int64_t earliest = p->last_show_us + (int64_t)f->interval_us * 1000000 / VIDEO_MAX_SPEED_PPM; + if (p->anchored && p->head_show_us < earliest) p->head_show_us = earliest; + p->head_due_us = p->head_show_us - (p->anchored ? p->lead_us : 0); } int64_t deadline = p->head_due_us; p->deadline_us = deadline; @@ -151,7 +155,15 @@ void call_video_playout_presented(struct call_video_playout *p, int64_t pts, int int64_t av = p->pending_av, lead = now - p->selected_us; // GUI freeze не становится постоянным опережением dispatch для последующих кадров. if (lead <= f->interval_us / 2) p->lead_us = (p->lead_us * 7 + lead) / 8; - p->awaiting = 0; p->anchored = 1; p->last_deadline = now; p->head_pts = -1; + int64_t error = now - p->head_show_us; + if (!p->anchored || error > f->interval_us / 2 || error < -(int64_t)f->interval_us / 2) p->last_deadline = now; + else { + int64_t correction = error / 8; + if (correction > 1000) correction = 1000; + if (correction < -1000) correction = -1000; + p->last_deadline = p->head_show_us + correction; + } + p->awaiting = 0; p->anchored = 1; p->head_pts = -1; { if (av < p->av_min) p->av_min = av; if (av > p->av_max) p->av_max = av; diff --git a/src/call/call_video_playout.h b/src/call/call_video_playout.h index 6142c700..81726c11 100644 --- a/src/call/call_video_playout.h +++ b/src/call/call_video_playout.h @@ -34,6 +34,7 @@ struct call_video_playout { struct call_video_playout_frame pending; // Единственный выбранный кадр до подтверждения потребителем. int awaiting; int64_t selected_us, pending_av, head_pts, head_due_us, lead_us, rate_us; + int64_t head_show_us; // Плановая презентация головы; GUI ACK корректирует фазу плавно. unsigned queued, reserve; // reserve — будущие кадры сверх головы, включая необходимый pipeline backend. // NULL пишет DEBUG напрямую; timer устанавливает неблокирующий callback с копированием события. void (*log_event)(void *, const struct call_video_playout_event *); diff --git a/tests/test_call_video_playout.c b/tests/test_call_video_playout.c index 4c369511..350b7a57 100644 --- a/tests/test_call_video_playout.c +++ b/tests/test_call_video_playout.c @@ -108,6 +108,27 @@ static void slowing_with_queue(void) { DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS queue governor: speed_ppm=%u -> %u as total frames fall 8 -> 2", fast, p.speed_ppm); } +static void render_latency_change(void) { + struct call_video_playout p; call_video_playout_init(&p, 13); + struct call_video_playout_frame f = {origin, origin, 33333}; + struct call_clock_snapshot audio = {.valid = 1, .rate_ppm = 1000000}; + int64_t now = origin; + for (unsigned i = 0; i < 240; ++i) { + f.pts_us = origin + i * 33333ll; audio.pts_us = f.pts_us + 100000; + int action = call_video_playout_poll(&p, &f, now, &audio, 2, 1); + if (action == CALL_VIDEO_PLAYOUT_WAIT) { + now = p.deadline_us; + CHECK(call_video_playout_poll(&p, &f, now, &audio, 2, 1) == CALL_VIDEO_PLAYOUT_SHOW); + } else CHECK(action == CALL_VIDEO_PLAYOUT_SHOW); + now += i < 120 ? 3000 : 9000; + call_video_playout_presented(&p, f.pts_us, now); + ++now; + } + int64_t phase = p.last_show_us - (origin + 239 * 33333ll); + CHECK(phase >= 0 && phase < 15000); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS render latency 3->9ms: phase_us=%lld; no accumulated per-frame GUI delay", (long long)phase); +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); const unsigned pauses[] = {50, 100, 200, 500}; @@ -116,5 +137,5 @@ int main(void) { simulate(pauses[i], cpu, 1000, 0); simulate(pauses[i], cpu, 15625, 1); } - presentation_deadline(); queued_capture_gap(); slowing_with_queue(); return 0; + presentation_deadline(); queued_capture_gap(); slowing_with_queue(); render_latency_change(); return 0; }