From 27cbf2ba44cccdbfb5f384914d1ee12047dce434 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 20 Oct 2026 00:09:57 +0200 Subject: [PATCH] Recover persistent video lag by skipping decoded presentation --- src/call/call_video_playout.c | 24 ++++++++++++++++++++---- src/call/call_video_playout.h | 8 +++++--- tools/chatgui/src/call_video_engine.cpp | 2 +- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/call/call_video_playout.c b/src/call/call_video_playout.c index 30dd6542..4e86b28d 100644 --- a/src/call/call_video_playout.c +++ b/src/call/call_video_playout.c @@ -51,11 +51,11 @@ void call_video_playout_log_event(const struct call_video_playout_event *event) (long long)now, (long long)p->selected_us); return; } DEBUG_DEBUG(DEBUG_CATEGORY_CALL, - "video playout call=%llu interval_us=%lld shown=%u speed_ppm=%u drop_order=%u " + "video playout call=%llu interval_us=%lld shown=%u speed_ppm=%u drop_order=%u drop_display=%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 source_gaps=%u source_gap_max_us=%lld " "queued=%u reserve=%u awaiting=%d lead_us=%lld", - (unsigned long long)p->call_id, (long long)(now - p->report_us), p->shown, p->speed_ppm, p->order, + (unsigned long long)p->call_id, (long long)(now - p->report_us), p->shown, p->speed_ppm, p->order, p->late, (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), @@ -76,7 +76,7 @@ static void report(struct call_video_playout *p, int64_t now) { if (!p->report_us) p->report_us = now; if (now - p->report_us < 1000000) return; log_event(p, CALL_VIDEO_LOG_REPORT, NULL, now); - p->report_us = now; p->shown = p->order = p->audio_wait = p->clock_wait = 0; + p->report_us = now; p->shown = p->order = p->late = 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; @@ -95,7 +95,7 @@ enum call_video_playout_action call_video_playout_poll(struct call_video_playout struct call_video_playout_frame changed = {.pts_us = audio}; log_event(p, CALL_VIDEO_LOG_CLOCK, &changed, now); } - if (!audio_valid) { p->head_pts = -1; p->clock_wait++; return CALL_VIDEO_PLAYOUT_WAIT; } + if (!audio_valid) { p->head_pts = -1; p->recovery_us = 0; p->skipping = 0; p->clock_wait++; return CALL_VIDEO_PLAYOUT_WAIT; } // Пустой FIFO не означает разрыв часов; срок сохраняется до появления следующей головы. if (!f || p->awaiting) return CALL_VIDEO_PLAYOUT_WAIT; if (f->pts_us <= p->last_pts || f->received_us < 0 || f->received_us > now @@ -107,6 +107,22 @@ enum call_video_playout_action call_video_playout_poll(struct call_video_playout int64_t av = f->pts_us - audio; uint32_t base = clock->rate_ppm > 1000000 ? clock->rate_ppm : 1000000; unsigned target_frames = (reserve < 8 ? reserve : 8) + 1; + const int64_t working_lag = (int64_t)target_frames * f->interval_us + 50000; + // Сначала даём ускорению 500 мс. Пропуск показа нужен лишь когда долг не сокращается + // (например, вход 60 fps и экран 60 Hz). Небольшие паузы сохраняют все кадры. + if (-av <= working_lag || queued <= target_frames + 1) { + p->recovery_us = 0; p->skipping = 0; + } else if (-av > working_lag + 100000) { + if (!p->recovery_us) { p->recovery_us = now; p->recovery_lag = -av; } + else if (now - p->recovery_us >= 500000) { + if (p->recovery_lag + av < f->interval_us / 4) p->skipping = 1; + p->recovery_us = now; p->recovery_lag = -av; + } + } + if (p->skipping) { + p->last_pts = f->pts_us; p->head_pts = -1; p->late++; + return CALL_VIDEO_PLAYOUT_DROP_LATE; // Часы last_show/last_deadline меняются исключительно по ACK. + } int64_t target = base; if (queued > target_frames) target += (int64_t)(queued - target_frames) * 100000; else if (queued < target_frames) target -= 20000; // До одного будущего кадра набираем запас без стартовой паузы. diff --git a/src/call/call_video_playout.h b/src/call/call_video_playout.h index 81726c11..18892138 100644 --- a/src/call/call_video_playout.h +++ b/src/call/call_video_playout.h @@ -1,6 +1,6 @@ /* Общий таймер показа видео, один владелец — RX worker. Изображениями не владеет. * poll вызывается для первого готового decoded кадра. queued — все ещё не показанные полные кадры. - * SHOW/DROP_ORDER потребляют первый кадр, WAIT сохраняет его. Decoder работает по свободным output-буферам. + * SHOW/DROP_ORDER/DROP_LATE потребляют decoded кадр, WAIT сохраняет его. Encoded входы не пропускаются. * received_us ставится на входе RX port, до ожидания worker; все локальные времена — MONOTONIC, не BOOTTIME. * SHOW резервирует один кадр до presented(pts, фактическое время); только ACK продвигает часы. * Короткое опустошение сохраняет расписание. Темп плавно зависит от суммарного запаса кадров. @@ -18,7 +18,7 @@ struct call_video_playout_frame { uint32_t interval_us; // 1000000/format.fps; разрыв capture PTS не создаёт повторную паузу. }; enum call_video_playout_action { - CALL_VIDEO_PLAYOUT_WAIT, CALL_VIDEO_PLAYOUT_SHOW, CALL_VIDEO_PLAYOUT_DROP_ORDER + CALL_VIDEO_PLAYOUT_WAIT, CALL_VIDEO_PLAYOUT_SHOW, CALL_VIDEO_PLAYOUT_DROP_ORDER, CALL_VIDEO_PLAYOUT_DROP_LATE }; struct call_video_playout_event; struct call_video_playout { @@ -28,7 +28,9 @@ struct call_video_playout { int64_t report_us, last_show_us, interval_min, interval_max, av_min, av_max, dispatch_max, data_shift; 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 shown, order, late, audio_wait, clock_wait; + int64_t recovery_us, recovery_lag; // Окно проверки: ускоренный показ действительно уменьшает отставание? + int skipping; // Только показ decoded кадров при устойчивом долге, который не удаётся сократить темпом. unsigned source_gaps; int64_t source_gap_max; // Большие разрывы capture PTS, уже прошедшие до показа. struct call_video_playout_frame pending; // Единственный выбранный кадр до подтверждения потребителем. diff --git a/tools/chatgui/src/call_video_engine.cpp b/tools/chatgui/src/call_video_engine.cpp index 9ccacf50..a741d4ca 100644 --- a/tools/chatgui/src/call_video_engine.cpp +++ b/tools/chatgui/src/call_video_engine.cpp @@ -593,7 +593,7 @@ void CallVideoEngine::present() { action = call_video_playout_poll(&playout, slot ? &slot->timing : nullptr, started, &clock, total, 1 + m->pipeline.load(std::memory_order_acquire)); if (action == CALL_VIDEO_PLAYOUT_SHOW) publishImage(slot->image, slot->timing.pts_us); - else if (action == CALL_VIDEO_PLAYOUT_DROP_ORDER) { + else if (action == CALL_VIDEO_PLAYOUT_DROP_ORDER || action == CALL_VIDEO_PLAYOUT_DROP_LATE) { m->pictures.release(); ++completed; m->rxWake.notify(); } } else ++metrics.busy;