Drain accepted MediaCodec inputs before bitrate fallback replacement

This commit is contained in:
evgeny committed 2026-10-19 17:31:46 +02:00
1 parent 0e53a9f5dd
commit c0422a73c0
2 files changed
+24 -5

No files matched your search

@@ -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<Packet> {
val packets = mutableListOf<Packet>(); 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()
@@ -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)