From 717d965fec64df22cc799073f378b100d01f8e99 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 15 Oct 2026 16:22:18 +0200 Subject: [PATCH] Keep Android wake deadlines absolute and report causes of the largest video gaps --- src/call/call_video_playout.c | 12 +++++++++--- src/call/call_video_playout.h | 1 + .../java/com/utun/chat/data/CallVideoPlayer.kt | 16 +++++++++++----- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/call/call_video_playout.c b/src/call/call_video_playout.c index a47bdb68..74e46daa 100644 --- a/src/call/call_video_playout.c +++ b/src/call/call_video_playout.c @@ -42,14 +42,17 @@ static void report(struct call_video_playout *p, int64_t now) { if (now - p->report_us < 1000000) return; DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video playout call=%llu interval_us=%lld shown=%u drop_late=%u drop_schedule=%u drop_order=%u " - "cadence_us=%lld..%lld av_us=%lld..%lld dispatch_max_us=%lld data_shift_us=%lld audio_wait=%u clock_wait=%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", (unsigned long long)p->call_id, (long long)(now - p->report_us), p->shown, p->late, p->skipped, 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); p->report_us = now; p->shown = p->late = p->skipped = 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; } enum call_video_playout_action call_video_playout_poll(struct call_video_playout *p, @@ -94,9 +97,12 @@ enum call_video_playout_action call_video_playout_poll(struct call_video_playout if (p->last_show_us) { int64_t gap = now - p->last_show_us; if (gap < p->interval_min) p->interval_min = gap; - if (gap > p->interval_max) p->interval_max = gap; + if (gap > p->interval_max) { + p->interval_max = gap; p->gap_pts = f->pts_us - p->last_show_pts; + p->gap_received = f->received_us - p->last_received; p->gap_dispatch = now - deadline; + } } - p->last_show_us = now; p->shown++; + p->last_show_us = now; p->last_show_pts = f->pts_us; p->last_received = f->received_us; p->shown++; } return action; } diff --git a/src/call/call_video_playout.h b/src/call/call_video_playout.h index 39a4309a..fecebd3f 100644 --- a/src/call/call_video_playout.h +++ b/src/call/call_video_playout.h @@ -21,6 +21,7 @@ struct call_video_playout { int anchored, audio_valid; int64_t last_pts, last_deadline, deadline_us; 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; unsigned shown, late, skipped, order, audio_wait, clock_wait; }; diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt index d04a1a75..26efb4cb 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoPlayer.kt @@ -45,12 +45,13 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin val renderHandler = Handler(Looper.getMainLooper()) fun close() { try { decoder?.stop() } catch (e: Exception) { LogManager.addLog("WARN", "call", "video decoder stop: ${e.message}") } - decoder?.release(); decoder = null; format = null; pictures.clear(); inputs.clear() + try { decoder?.release() } catch (e: Exception) { LogManager.addLog("WARN", "call", "video decoder release: ${e.message}") } + decoder = null; format = null; pictures.clear(); inputs.clear() draining = false; endSeen = false; lastOutput = -1 } try { while (running) { - var worked = false; var waitUs = 5000L + var worked = false; var wakeUs = System.nanoTime() / 1000 + 5000 try { while (true) { val next = outputs.poll() ?: break @@ -89,7 +90,7 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin val first = pictures.peekFirst(); val nextPicture = if (pictures.size > 1) pictures.peekLast() else null val decision = CallVideoNative.playoutStep(playout, first?.timing?.pts ?: -1, first?.timing?.received ?: 0, nextPicture?.timing?.pts ?: -1, nextPicture?.timing?.received ?: 0, now / 1000, valid, clock[4]) - if (decision > 0) waitUs = decision + if (decision > 0) wakeUs = now / 1000 + decision else if (first != null) { val render = decision == 0L if (first.timing.pts % 1_000_000 < 34_000) LogManager.addLog("DEBUG", "debug", @@ -103,7 +104,11 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin } else late++ lastProgress = now } - if (endSeen && pictures.isEmpty()) close() + if (endSeen && pictures.isEmpty()) { + check(inputs.isEmpty()) { "Video EOS before all output frames" } + LogManager.addLog("DEBUG", "call", "video decoder drained call=$id generation=${format?.generation}") + close() + } // Ожидание срока output не останавливает подачу input; очередь остаётся ограниченной. if (pending == null && inputs.size + pictures.size < 8) { pending = CallVideoNative.pull(id, meta); if (pending != null) pulled++ @@ -172,7 +177,8 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin close(); CallVideoNative.playoutReset(playout) pending = null; displayed.value = null; failure(e.message ?: "Video decoder failed") } - if (!worked) Thread.sleep(waitUs / 1000, (waitUs % 1000 * 1000).toInt()) + val waitUs = wakeUs - System.nanoTime() / 1000 + if (!worked && waitUs > 0) Thread.sleep(waitUs / 1000, (waitUs % 1000 * 1000).toInt()) } } finally { close(); CallVideoNative.playoutDestroy(playout); displayed.value = null; output?.release?.invoke()