From be7c3c58110d36f4578b6a5e85ff835f0b6b1d24 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 00:04:08 +0200 Subject: [PATCH] Bound desktop decoder work and drain Android codec with monotonic EOS timestamps --- .../java/com/utun/chat/data/StreamPlayerEngine.kt | 13 ++++++++----- tools/chatgui/src/streamplayer_window.cpp | 10 ++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt index 58217a65..fb44a721 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt @@ -62,9 +62,10 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv var generation = 0L var decoded = 0L var discarded = 0L - var decodedThrough = -1L + var lastSubmittedPts = -1L var pendingOutput = -1 var pendingPts = 0L + var pendingEos = false var eosQueued = false var decoderEnded = false var endUntil = 0L @@ -87,7 +88,7 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv fun resetVideo() { try { decoder?.stop(); decoder?.release() } catch (e: Exception) { LogManager.addLog("WARN", "video", "decoder reset id=$id: ${e.message}") } - decoder = null; videos.clear(); submitted.clear(); pendingOutput = -1; decodedThrough = -1 + decoder = null; videos.clear(); submitted.clear(); pendingOutput = -1; lastSubmittedPts = -1; pendingEos = false waitingKey = true; eosQueued = false; decoderEnded = false mutableStatus.value = R.string.stream_waiting_key } @@ -237,14 +238,14 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv val input = decoder!!.getInputBuffer(index) ?: error("Missing decoder input") check(frame.data.size <= input.capacity()); input.clear(); input.put(frame.data) decoder!!.queueInputBuffer(index, 0, frame.data.size, frame.pts, 0) - submitted.addLast(frame.pts); videos.removeFirst(); decoded++ + submitted.addLast(frame.pts); lastSubmittedPts = frame.pts; videos.removeFirst(); decoded++ } } val codec = decoder if (codec != null) { if (ended && videos.isEmpty() && !eosQueued) { val index = codec.dequeueInputBuffer(0) - if (index >= 0) { codec.queueInputBuffer(index, 0, 0, decodedThrough.coerceAtLeast(0) + 1, + if (index >= 0) { codec.queueInputBuffer(index, 0, 0, lastSubmittedPts.coerceAtLeast(0), MediaCodec.BUFFER_FLAG_END_OF_STREAM); eosQueued = true } } repeat(8) { @@ -254,12 +255,14 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0 && info.size == 0) { codec.releaseOutputBuffer(index, false); decoderEnded = true; return@repeat } - pendingOutput = index; pendingPts = info.presentationTimeUs; decodedThrough = pendingPts + pendingOutput = index; pendingPts = info.presentationTimeUs + pendingEos = info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0 while (submitted.isNotEmpty() && submitted.first <= pendingPts) submitted.removeFirst() } if (pendingPts > position) return@repeat val render = pendingPts >= position - 100000 codec.releaseOutputBuffer(pendingOutput, render); pendingOutput = -1 + if (pendingEos) { decoderEnded = true; pendingEos = false } if (!render) discarded++ else mutableStatus.value = R.string.stream_live } } diff --git a/tools/chatgui/src/streamplayer_window.cpp b/tools/chatgui/src/streamplayer_window.cpp index e9e7f869..76d3aa60 100644 --- a/tools/chatgui/src/streamplayer_window.cpp +++ b/tools/chatgui/src/streamplayer_window.cpp @@ -309,6 +309,11 @@ void StreamPlayerWindow::decode() { int received; while ((received = avcodec_receive_frame(decoder, frame)) == 0) { decoded++; + if (frame->width <= 0 || frame->width > 4096 || frame->height <= 0 || frame->height > 4096) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop H264 dimensions invalid id=%llu size=%dx%d", + (unsigned long long)p.id, frame->width, frame->height); + fail(text("Invalid stream video dimensions")); p.running = false; break; + } int64_t pts = frame->best_effort_timestamp == AV_NOPTS_VALUE ? (int64_t)record.pts : frame->best_effort_timestamp; { std::lock_guard guard(p.lock); if (pts < p.clock(nowUs()) - 100000) { discarded++; continue; } } converter = sws_getCachedContext(converter, frame->width, frame->height, (AVPixelFormat)frame->format, @@ -322,6 +327,11 @@ void StreamPlayerWindow::decode() { } if (record.format.rotation) image = image.transformed(QTransform().rotate(record.format.rotation)); std::lock_guard guard(p.lock); + if (p.videos.size() >= 4) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop video preparation exceeded id=%llu frames=%zu", + (unsigned long long)p.id, p.videos.size()); + fail(text("Stream video decoder failed")); p.running = false; break; + } p.videoBytes += (size_t)image.sizeInBytes(); p.videos.push_back({std::move(image), pts}); } if (received != AVERROR(EAGAIN) && received != AVERROR_EOF && p.running) {