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 e8b2f41b..cb2f9f2c 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 @@ -24,6 +24,7 @@ internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name private var partialFlags = 0 private var preparing = false private var lastOutput = -1L + private var lastInput = -1L var pendingFrames = 0 private set @@ -72,7 +73,7 @@ internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name } } input.close(); codec.queueInputBuffer(index, 0, format.width * format.height * 3 / 2, pts, 0) - pendingFrames++; return true + lastInput = pts; pendingFrames++; return true } /** Вызывается перед очередным IDR; подтверждение ядру — только после проверки этого IDR. */ @@ -132,6 +133,23 @@ internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name } return packets } + /** Забрать все принятые входы перед заменой: EOS не требует новых кадров камеры. */ + fun finish(): List { + val packets = mutableListOf(); val started = System.nanoTime(); var eos = false + while (pendingFrames > 0 && System.nanoTime() - started < 2_000_000_000) { + if (!eos) { + val input = codec.dequeueInputBuffer(0) + if (input >= 0) { + codec.queueInputBuffer(input, 0, 0, lastInput + 1, MediaCodec.BUFFER_FLAG_END_OF_STREAM); eos = true + } + } + packets += drain() + if (pendingFrames > 0) Thread.sleep(1) + } + check(pendingFrames == 0) { "Encoder drain timeout name=$name pending=$pendingFrames" } + LogManager.addLog("DEBUG", "call", "video encoder drained name=$name frames=${packets.size} us=${(System.nanoTime() - started) / 1000}") + return packets + } override fun close() { try { codec.stop() } catch (e: Exception) { LogManager.addLog("WARN", "call", "encoder stop: ${e.message}") } codec.release() 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 e84f1cb4..dbf9459d 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 @@ -133,7 +133,7 @@ class CallVideoEngine(private val context: Context, private val id: Long) { private fun fail(message: String) { LogManager.addLog("ERROR", "call", "video call=$id $message") wanted = wanted.copy(enabled = false); mutable.value = mutable.value.copy(settings = wanted, status = message) - closeCamera(); encoder?.close(); encoder = null; active = null + closeCamera(); encoder?.close(); encoder = null; pendingFrames.clear(); active = null if (confirmingRequest != 0L) CallVideoNative.applied(id, format?.generation ?: 0, confirmingRequest, 0) confirmingRequest = 0 } @@ -249,6 +249,7 @@ class CallVideoEngine(private val context: Context, private val id: Long) { // References сохранены: уже закодированные P-кадры остаются пригодны до нового KEY. confirmingRequest = requestId } else { + drain(finish = true) old.close(); encoder = null try { encoder = CallVideoCodec(f, pendingRate, name) @@ -345,9 +346,9 @@ class CallVideoEngine(private val context: Context, private val id: Long) { }, handler) } catch (e: Exception) { complete(); rejected("Camera session: ${e.message}") } } - private fun drain() { + private fun drain(finish: Boolean = false) { val codec = encoder ?: return; val f = format ?: return - for (p in codec.drain()) { + for (p in if (finish) codec.finish() else codec.drain()) { encodedBytes += p.bytes.size; encodedFrames++ if (confirmingRequest != 0L && p.key && p.pts >= confirmingPts) { CallVideoNative.applied(id, f.generation, confirmingRequest, codec.bitrate); confirmingRequest = 0 @@ -385,7 +386,7 @@ class CallVideoEngine(private val context: Context, private val id: Long) { if (stopped) return stopped = true; player.stop(); CallVideoNative.close(id) handler.post { - closeCamera(); encoder?.close(); encoder = null + closeCamera(); encoder?.close(); encoder = null; pendingFrames.clear() preview.close(); previewTarget?.release(); previewTarget = null if (camera == null && !opening) thread.quitSafely() else handler.postDelayed({ thread.quitSafely() }, 3000)