Apply presentation-clock queue pacing and three decoded slots to Android

This commit is contained in:
evgeny committed 2026-10-19 19:08:51 +02:00
1 parent ecae1cc3c2
commit 895cdd5ac2
3 files changed
+24 -13

No files matched your search

@@ -16,8 +16,9 @@ internal object CallVideoNative {
external fun playoutDestroy(handle: Long)
external fun playoutReset(handle: Long)
/** >0: ждать микросекунды; 0: показать; <0: пропустить только показ. */
external fun playoutStep(handle: Long, pts: Long, received: Long, nextPts: Long, nextReceived: Long,
now: Long, intervalUs: Int): Long
external fun playoutStep(handle: Long, pts: Long, received: Long, now: Long, intervalUs: Int,
localQueued: Int, reserve: Int): Long
external fun playoutPresented(handle: Long, pts: Long, now: Long)
external fun requestKey(id: Long)
external fun range(id: Long, minimum: Int, maximum: Int): Boolean
external fun inspect(codec: Int, bytes: ByteArray): LongArray?
@@ -12,14 +12,14 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
@Volatile private var running = true
private data class Output(val surface: Surface?, val release: () -> Unit, val detach: Surface? = null)
private val outputs = ConcurrentLinkedQueue<Output>()
private data class Presentation(val surface: Surface, val pts: Long)
private data class Presentation(val surface: Surface, val pts: Long, val at: Long = 0)
private val presentations = ConcurrentLinkedQueue<Presentation>()
@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
presentations.add(expected.copy(at = System.nanoTime() / 1000)); return true
}
private val displayed = MutableStateFlow<CallVideoFormat?>(null)
val display: StateFlow<CallVideoFormat?> = displayed
@@ -71,11 +71,12 @@ 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) {
CallVideoNative.playoutPresented(playout, consumed.pts, consumed.at)
awaitingPts = -1; awaiting = null; worked = true
}
}
// Малый output lookahead; основной долг остаётся в encoded FIFO.
while (decoder != null && pictures.size < 2 && !endSeen) {
while (decoder != null && pictures.size + (if (awaitingPts >= 0) 1 else 0) < 3 && !endSeen) {
val out = decoder!!.dequeueOutputBuffer(info, 0)
if (out >= 0) {
worked = true; lastProgress = System.nanoTime()
@@ -98,9 +99,9 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
val now = System.nanoTime()
val valid = NativeLib.callAudioClock(id, now / 1000, clock) && clock[5] != 0L
val first = if (awaitingPts < 0) pictures.peekFirst() else null
val nextPicture = if (first != null && pictures.size > 1) pictures.peekLast() else null
val localQueued = inputs.size + pictures.size + (if (pending != null) 1 else 0) + (if (awaitingPts >= 0) 1 else 0)
val decision = CallVideoNative.playoutStep(playout, first?.timing?.pts ?: -1, first?.timing?.received ?: 0,
nextPicture?.timing?.pts ?: -1, nextPicture?.timing?.received ?: 0, now / 1000, first?.timing?.interval ?: 0)
now / 1000, first?.timing?.interval ?: 0, localQueued, 1)
if (decision > 0) wakeUs = now / 1000 + decision
else if (first != null) {
val render = decision == 0L
@@ -120,11 +121,12 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
close()
}
// Ожидание срока output не останавливает подачу input; очередь остаётся ограниченной.
if (pending == null && inputs.size + pictures.size < 8) {
val decodedSpace = pictures.size + (if (awaitingPts >= 0) 1 else 0) < 3
if (pending == null && inputs.size < 8 && decodedSpace) {
pending = CallVideoNative.pull(id, meta); if (pending != null) pulled++
}
val data = pending
if (data != null && surface?.isValid == true) {
if (data != null && surface?.isValid == true && decodedSpace) {
val next = CallVideoFormat.from(meta)
if (next.generation == rejectedGeneration) { pending = null; continue }
val key = meta[11] != 0L
@@ -167,7 +169,7 @@ internal class CallVideoPlayer(private val id: Long, private val failure: (Strin
if (now - reportAt >= 1_000_000_000) {
LogManager.addLog("DEBUG", "call", "video playback call=$id intervalMs=${(now - reportAt) / 1_000_000} " +
"pulled=$pulled queued=$queued decoded=$decoded rendered=$rendered skipped=$late inflight=${inputs.size} " +
"pending=${pending != null} ready=${pictures.size} awaitingSurfacePts=$awaitingPts " +
"pending=${pending != null} ready=${pictures.size} totalLocal=$localQueued awaitingSurfacePts=$awaitingPts " +
"surfaceWaitMs=${if (awaitingPts >= 0) (now - lastRendered) / 1_000_000 else 0} audioValid=$valid audioPts=${clock[4]}")
reportAt = now; pulled = 0; queued = 0; decoded = 0; rendered = 0; late = 0
}