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
}
@@ -110,17 +110,25 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_CallVideoNative_playoutReset(JNIE
call_video_playout_init(p, p->call_id);
}
JNIEXPORT jlong JNICALL Java_com_utun_chat_data_CallVideoNative_playoutStep(JNIEnv *e, jobject o, jlong handle,
jlong pts, jlong received, jlong next_pts, jlong next_received, jlong now, jint interval_us) {
jlong pts, jlong received, jlong now, jint interval_us, jint local_queued, jint reserve) {
(void)e; (void)o; struct call_video_playout *p = (void *)(uintptr_t)handle;
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video playout poll without handle"); return 5000; }
struct call_video_playout_frame f = {pts, received, interval_us}, next = {next_pts, next_received, interval_us};
struct call_video_playout_frame f = {pts, received, interval_us};
struct call_clock_snapshot clock = {0}; call_audio_get_clock(p->call_id, now, &clock);
int action = call_video_playout_poll(p, pts >= 0 ? &f : NULL, next_pts >= 0 ? &next : NULL, now, &clock);
struct call_video_port_status status = {0}; call_video_port_status(p->call_id, &status);
int action = call_video_playout_poll(p, pts >= 0 ? &f : NULL, now, &clock,
status.rx_frames + (local_queued > 0 ? (unsigned)local_queued : 0), reserve > 0 ? (unsigned)reserve : 1);
if (action == CALL_VIDEO_PLAYOUT_SHOW) return 0;
if (action != CALL_VIDEO_PLAYOUT_WAIT) return -action;
int64_t wait = p->deadline_us - now;
return wait < 100 ? 100 : wait > 5000 ? 5000 : wait;
}
JNIEXPORT void JNICALL Java_com_utun_chat_data_CallVideoNative_playoutPresented(JNIEnv *e, jobject o,
jlong handle, jlong pts, jlong now) {
(void)e; (void)o; struct call_video_playout *p = (void *)(uintptr_t)handle;
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video presented without playout handle"); return; }
call_video_playout_presented(p, pts, now);
}
JNIEXPORT void JNICALL Java_com_utun_chat_data_CallVideoNative_requestKey(JNIEnv *e, jobject o, jlong id) {
(void)e; (void)o; call_video_port_request_key(id);
}