Apply presentation-clock queue pacing and three decoded slots to Android
This commit is contained in:
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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user