From 9bffe49e624a5ffa53fb2890faadd661f2c20063 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 19 Oct 2026 18:22:47 +0200 Subject: [PATCH] Bound video display cadence independently of capture timestamp gaps --- src/call/call_video_playout.c | 24 ++++++++++++++++++------ src/call/call_video_playout.h | 7 ++++++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/call/call_video_playout.c b/src/call/call_video_playout.c index 549bc4a4..fbd1a871 100644 --- a/src/call/call_video_playout.c +++ b/src/call/call_video_playout.c @@ -33,16 +33,18 @@ static void report(struct call_video_playout *p, int64_t now) { DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video playout call=%llu interval_us=%lld shown=%u speed_ppm=%u drop_order=%u " "cadence_us=%lld..%lld gap_pts_us=%lld gap_rx_us=%lld gap_dispatch_us=%lld " - "av_us=%lld..%lld dispatch_max_us=%lld data_shift_us=%lld audio_wait=%u clock_wait=%u", + "av_us=%lld..%lld dispatch_max_us=%lld data_shift_us=%lld audio_wait=%u clock_wait=%u source_gaps=%u source_gap_max_us=%lld", (unsigned long long)p->call_id, (long long)(now - p->report_us), p->shown, p->speed_ppm, p->order, (long long)(p->interval_min == INT64_MAX ? 0 : p->interval_min), (long long)p->interval_max, (long long)p->gap_pts, (long long)p->gap_received, (long long)p->gap_dispatch, (long long)(p->shown ? p->av_min : 0), (long long)(p->shown ? p->av_max : 0), - (long long)p->dispatch_max, (long long)p->data_shift, p->audio_wait, p->clock_wait); + (long long)p->dispatch_max, (long long)p->data_shift, p->audio_wait, p->clock_wait, + p->source_gaps, (long long)p->source_gap_max); p->report_us = now; p->shown = p->order = p->audio_wait = p->clock_wait = 0; p->interval_min = p->av_min = INT64_MAX; p->av_max = INT64_MIN; p->interval_max = p->dispatch_max = p->data_shift = 0; p->gap_pts = p->gap_received = p->gap_dispatch = 0; + p->source_gaps = 0; p->source_gap_max = 0; } enum call_video_playout_action call_video_playout_poll(struct call_video_playout *p, @@ -60,10 +62,11 @@ enum call_video_playout_action call_video_playout_poll(struct call_video_playout if (!audio_valid) { p->anchored = 0; p->clock_wait++; return CALL_VIDEO_PLAYOUT_WAIT; } // Ожидание новых данных уже прошло в реальном времени: повторно добавлять этот интервал нельзя. if (!f) { p->anchored = 0; return CALL_VIDEO_PLAYOUT_WAIT; } - if (f->pts_us <= p->last_pts || f->received_us < 0 || f->received_us > now) { - DEBUG_WARN(DEBUG_CATEGORY_CALL, "video playout invalid/order call=%llu pts=%lld last=%lld received=%lld now=%lld", + if (f->pts_us <= p->last_pts || f->received_us < 0 || f->received_us > now + || f->interval_us < 16666 || f->interval_us > 1000000) { + DEBUG_WARN(DEBUG_CATEGORY_CALL, "video playout invalid/order call=%llu pts=%lld last=%lld received=%lld now=%lld interval_us=%u", (unsigned long long)p->call_id, (long long)f->pts_us, (long long)p->last_pts, - (long long)f->received_us, (long long)now); + (long long)f->received_us, (long long)now, f->interval_us); p->order++; return CALL_VIDEO_PLAYOUT_DROP_ORDER; } // Аудио задаёт базовый темп; долг видео снимается с постоянной 0.5с, максимум 1.6x. @@ -73,7 +76,9 @@ enum call_video_playout_action call_video_playout_poll(struct call_video_playout int64_t speed = base + correction; p->speed_ppm = speed < VIDEO_MAX_SPEED_PPM ? (uint32_t)speed : VIDEO_MAX_SPEED_PPM; int64_t delta = p->last_pts >= 0 ? f->pts_us - p->last_pts : 0; - int64_t step = delta / p->speed_ppm * 1000000 + delta % p->speed_ppm * 1000000 / p->speed_ppm; + // PTS-разрыв захвата уже прошёл; готовую голову нельзя снова удерживать delta/speed. + int64_t cadence = delta < f->interval_us ? delta : f->interval_us; + int64_t step = cadence * 1000000 / p->speed_ppm; int64_t deadline = p->anchored ? p->last_deadline + step : now; p->deadline_us = deadline; if (av > VIDEO_AHEAD_US) { @@ -81,6 +86,13 @@ enum call_video_playout_action call_video_playout_poll(struct call_video_playout return CALL_VIDEO_PLAYOUT_WAIT; } if (deadline > now) return CALL_VIDEO_PLAYOUT_WAIT; + if (delta > (int64_t)f->interval_us * 3) { + if (!p->source_gaps) DEBUG_WARN(DEBUG_CATEGORY_CALL, + "video source gap call=%llu pts_delta_us=%lld interval_us=%u av_us=%lld display_step_us=%lld; no extra gap wait", + (unsigned long long)p->call_id, (long long)delta, f->interval_us, (long long)av, (long long)step); + p->source_gaps++; + if (delta > p->source_gap_max) p->source_gap_max = delta; + } p->last_pts = f->pts_us; p->last_deadline = now; p->anchored = 1; { if (now - deadline > p->dispatch_max) p->dispatch_max = now - deadline; diff --git a/src/call/call_video_playout.h b/src/call/call_video_playout.h index 6916a960..eb11f960 100644 --- a/src/call/call_video_playout.h +++ b/src/call/call_video_playout.h @@ -12,7 +12,10 @@ extern "C" { #endif -struct call_video_playout_frame { int64_t pts_us, received_us; }; +struct call_video_playout_frame { + int64_t pts_us, received_us; + uint32_t interval_us; // 1000000/format.fps: предел интервала показа, пауза capture не растягивает его. +}; enum call_video_playout_action { CALL_VIDEO_PLAYOUT_WAIT, CALL_VIDEO_PLAYOUT_SHOW, CALL_VIDEO_PLAYOUT_DROP_ORDER }; @@ -24,6 +27,8 @@ struct call_video_playout { int64_t last_show_pts, last_received, gap_pts, gap_received, gap_dispatch; uint32_t speed_ppm; // Темп аудио + коррекция отставания, максимум 1.6x. unsigned shown, order, audio_wait, clock_wait; + unsigned source_gaps; + int64_t source_gap_max; // Большие разрывы capture PTS, уже прошедшие до показа. }; int64_t call_video_playout_now_us(void);