From 152d89f0dc914096d5162794ed1b7c5f162518aa Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 15 Oct 2026 01:11:02 +0200 Subject: [PATCH] Apply Android call bitrate changes in place on validated keyframes --- .../java/com/utun/chat/data/CallVideoCodec.kt | 16 +++++++++++++++- .../java/com/utun/chat/data/CallVideoEngine.kt | 14 ++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoCodec.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoCodec.kt index e889bdf3..e4afda60 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoCodec.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoCodec.kt @@ -10,7 +10,9 @@ import android.os.Bundle import java.nio.ByteBuffer /** Codec принадлежит одному worker. YUV input отделён от камеры: замена на IDR не меняет capture PTS. */ -internal class CallVideoCodec(requested: CallVideoFormat, val bitrate: Int, val name: String) : AutoCloseable { +internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name: String) : AutoCloseable { + var bitrate = bitrate + private set var format = requested private set data class Packet(val pts: Long, val key: Boolean, val bytes: ByteArray) @@ -70,6 +72,18 @@ internal class CallVideoCodec(requested: CallVideoFormat, val bitrate: Int, val input.close(); codec.queueInputBuffer(index, 0, format.width * format.height * 3 / 2, pts, 0); return true } + /** Вызывается перед очередным IDR; подтверждение ядру — только после проверки этого IDR. */ + fun setBitrate(value: Int): Boolean { + if (value == bitrate) return true + return try { + codec.setParameters(Bundle().apply { putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, value) }) + LogManager.addLog("INFO", "call", "video bitrate updated without restart name=$name old=$bitrate target=$value") + bitrate = value; true + } catch (e: Exception) { + LogManager.addLog("WARN", "call", "video live bitrate refused name=$name target=$value: ${e.message}"); false + } + } + fun drain(): List { val packets = mutableListOf(); val info = MediaCodec.BufferInfo() repeat(16) { diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt index 60a0430d..aa25ccbb 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt @@ -64,6 +64,7 @@ class CallVideoEngine(private val context: Context, private val id: Long) { private var pendingRequest = 0L private var pendingRate = 0 private var confirmingRequest = 0L + private var confirmingPts = -1L private var realtime = false private var sensorOffset: Long? = null private var openedAt = 0L @@ -235,17 +236,22 @@ class CallVideoEngine(private val context: Context, private val id: Long) { if (key && pendingRequest != 0L) { val requestId = pendingRequest; pendingRequest = 0 val old = checkNotNull(encoder); val oldRate = old.bitrate; val name = old.name - old.close(); encoder = null - try { + confirmingPts = pts + if (old.setBitrate(pendingRate)) { + confirmingRequest = requestId; needKey = true + } else { + old.close(); encoder = null + try { encoder = CallVideoCodec(f, pendingRate, name) encoder!!.prepare(); check(encoder!!.format == f) { "Bitrate change altered encoder format" } confirmingRequest = requestId; needKey = true report("Applying ${pendingRate / 1000} kbit/s on IDR") - } catch (e: Exception) { + } catch (e: Exception) { LogManager.addLog("WARN", "call", "video bitrate replacement failed: ${e.message}; restoring $oldRate") encoder?.close(); encoder = CallVideoCodec(f, oldRate, name) encoder!!.prepare(); check(encoder!!.format == f) confirmingRequest = requestId; needKey = true + } } } if (encoder?.submit(image, pts, key) == true) lastPts = pts @@ -292,7 +298,7 @@ class CallVideoEngine(private val context: Context, private val id: Long) { for (p in codec.drain()) { encodedBytes += p.bytes.size; encodedFrames++ if (p.key) lastKey = p.pts - if (confirmingRequest != 0L && p.key) { + if (confirmingRequest != 0L && p.key && p.pts >= confirmingPts) { CallVideoNative.applied(id, f.generation, confirmingRequest, codec.bitrate); confirmingRequest = 0 mutable.value = mutable.value.copy(bitrate = codec.bitrate) LogManager.addLog("INFO", "call", "video bitrate applied id=$id generation=${f.generation} bitrate=${codec.bitrate} pts=${p.pts}")