Acknowledge Android frames after RenderThread consumption on all supported APIs

This commit is contained in:
evgeny committed 2026-10-19 17:34:44 +02:00
1 parent b11a1beb44
commit 02e7bbd9af
2 files changed
+21 -4

No files matched your search

@@ -14,7 +14,13 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
private val outputs = ConcurrentLinkedQueue<Output>()
private data class Presentation(val surface: Surface, val pts: Long)
private val presentations = ConcurrentLinkedQueue<Presentation>()
fun presented(surface: Surface, pts: Long) { if (running) presentations.add(Presentation(surface, pts)) }
@Volatile private var awaiting: Presentation? = null
fun presented(surface: Surface, pts: Long): Boolean {
val expected = awaiting ?: return true
if (!running || expected.surface !== surface) return true
if (expected.pts != pts) return false
presentations.add(expected); return true
}
private val displayed = MutableStateFlow<CallVideoFormat?>(null)
val display: StateFlow<CallVideoFormat?> = displayed
@Volatile private var verified = emptyMap<Int, CallVideoCodec.Companion.Decoder>()
@@ -47,7 +53,7 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
try { decoder?.stop() } catch (e: Exception) { LogManager.addLog("WARN", "call", "video decoder stop: ${e.message}") }
try { decoder?.release() } catch (e: Exception) { LogManager.addLog("WARN", "call", "video decoder release: ${e.message}") }
decoder = null; format = null; pictures.clear(); inputs.clear()
awaitingPts = -1; presentations.clear()
awaitingPts = -1; awaiting = null; presentations.clear()
draining = false; endSeen = false; lastOutput = -1
}
try {
@@ -64,7 +70,9 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
}
while (true) {
val consumed = presentations.poll() ?: break
if (consumed.surface === surface && consumed.pts == awaitingPts) { awaitingPts = -1; worked = true }
if (consumed.surface === surface && consumed.pts == awaitingPts) {
awaitingPts = -1; awaiting = null; worked = true
}
}
// Малый output lookahead; основной долг остаётся в encoded FIFO.
while (decoder != null && pictures.size < 2 && !endSeen) {
@@ -96,6 +104,7 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
if (decision > 0) wakeUs = now / 1000 + decision
else if (first != null) {
val render = decision == 0L
if (render) awaiting = Presentation(checkNotNull(surface), first.timing.pts)
decoder!!.releaseOutputBuffer(first.index, render); pictures.removeFirst(); worked = true
if (render) {
awaitingPts = first.timing.pts
@@ -3,6 +3,7 @@ package com.utun.chat.ui.screens
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.compose.foundation.background
@@ -123,10 +124,17 @@ 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) {
attached?.let { video.presented(it, texture.timestamp / 1000) }
val surface = attached ?: return
// TextureView callback предшествует dequeue RenderThread; timestamp читаем после commit.
if (Build.VERSION.SDK_INT >= 29) viewTreeObserver.registerFrameCommitCallback { consumed(texture, surface) }
else postOnAnimation { consumed(texture, surface) }
}
}
}
private fun consumed(texture: SurfaceTexture, surface: Surface) {
if (closed || attached !== surface) return
if (!video.presented(surface, texture.timestamp / 1000)) postOnAnimation { consumed(texture, surface) }
}
private fun fit() {
val f = format ?: return
if (width == 0 || height == 0) return