From d7788b87ac9f71b5b339598f4eb3edbbe2354c5d Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 15 Oct 2026 15:18:35 +0200 Subject: [PATCH] Distinguish TextureView update notifications from committed frames --- .../com/utun/chat/data/CallVideoPlayer.kt | 4 +-- .../utun/chat/ui/screens/CallVideoPanel.kt | 27 ++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) 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 60c5f2b4..b506da02 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 @@ -123,10 +123,10 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin decoder = MediaCodec.createByCodecName(implementation.name) decoder!!.configure(type.decoderFormat(next.width, next.height, next.rotation, config), surface, null, 0) decoder!!.setOnFrameRenderedListener({ _, pts, at -> - // Время события, а не доставки callback: Android может группировать уведомления. + // На некоторых SurfaceTexture backend at повторяет PTS: логируем как reported, не как доказанное время показа. if (running && pts % 1_000_000 < 34_000) LogManager.addLog("DEBUG", "debug", "video timing stage=surface call=$id generation=${next.generation} pts=$pts " + - "mono_us=${at / 1000} callback_us=${System.nanoTime() / 1000}") + "reported_us=${at / 1000} callback_us=${System.nanoTime() / 1000}") }, renderHandler) decoder!!.start(); format = next; lastProgress = now LogManager.addLog("INFO", "call", "video decoder started call=$id codec=${implementation.name} format=$next") diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt index 90d1badf..d6fd73a9 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt @@ -5,6 +5,7 @@ import android.content.pm.PackageManager import android.content.Context import android.graphics.Matrix import android.graphics.SurfaceTexture +import android.os.Build import android.view.Surface import android.view.TextureView import androidx.activity.compose.rememberLauncherForActivityResult @@ -128,6 +129,19 @@ private class CallVideoTexture(context: Context, private val video: CallVideoEng private var closed = false private var format: CallVideoFormat? = null private var large = false + private var commitPending = false + private var lastCommitPts = -1L + private val committed = Runnable { + commitPending = false + if (!closed) surfaceTexture?.let { texture -> + val pts = texture.timestamp / 1000 + if (pts > 0 && pts != lastCommitPts && pts % 1_000_000 < 34_000) + com.utun.chat.data.LogManager.addLog("DEBUG", "debug", + "video timing stage=commit pts=$pts mono_us=${System.nanoTime() / 1000} generation=${format?.generation} " + + "visible=$isShown alpha=$alpha size=${width}x$height") + lastCommitPts = pts + } + } fun update(next: CallVideoFormat?, expanded: Boolean) { if (format == next && large == expanded) return format = next; large = expanded; fit() @@ -144,11 +158,14 @@ private class CallVideoTexture(context: Context, private val video: CallVideoEng override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) { fit() } override fun onSurfaceTextureDestroyed(texture: SurfaceTexture): Boolean { closeOutput(); return false } override fun onSurfaceTextureUpdated(texture: SurfaceTexture) { - // MediaCodec передаёт исходный PTS в SurfaceTexture; не вычитаем его из локальных часов телефона. + // applyUpdate лишь передаёт работу RenderThread: timestamp здесь ещё может принадлежать предыдущему кадру. val pts = texture.timestamp / 1000 if (pts > 0 && pts % 1_000_000 < 34_000) com.utun.chat.data.LogManager.addLog("DEBUG", "debug", - "video timing stage=texture pts=$pts mono_us=${System.nanoTime() / 1000} generation=${format?.generation} " + + "video timing stage=texture_update pts=$pts mono_us=${System.nanoTime() / 1000} generation=${format?.generation} " + "visible=$isShown alpha=$alpha size=${width}x$height") + if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated && !commitPending) { + commitPending = true; viewTreeObserver.registerFrameCommitCallback(committed) + } } } } @@ -168,7 +185,11 @@ private class CallVideoTexture(context: Context, private val video: CallVideoEng com.utun.chat.data.LogManager.addLog("DEBUG", "call", "video fit view=${width}x$height frame=${w}x$h expanded=$large turn=$turn") } private fun closeOutput() { attached?.let(video::detachSurface); attached = null } - fun close() { closed = true; closeOutput() } + fun close() { + closed = true + if (Build.VERSION.SDK_INT >= 29 && viewTreeObserver.isAlive) viewTreeObserver.unregisterFrameCommitCallback(committed) + closeOutput() + } } @Composable