Add tested adaptive playback device buffer policy

This commit is contained in:
evgeny committed 2026-10-15 11:07:39 +02:00
1 parent 92a942ea8b
commit cbc7b296bb
2 files changed
+184

No files matched your search

@@ -0,0 +1,68 @@
package com.utun.chat.data
/** Запас устройства, не сетевой JB. Владелец сериализует sample/applied/settle одним lock. */
internal class CallPlaybackBuffer(val blockFrames: Int = 960) {
data class Change(val frames: Int, val reason: String, val underruns: Int = 0)
private var actual = 0
private var capacity = 0
private var floor = blockFrames
private var previousUnderruns = 0
private var previousPlayed = -1L
private var previousMs = 0L
private var stableSinceMs = 0L
private var settleUntilMs = 0L
private var probeIntervalMs = 30_000L
fun reset(nowMs: Long, frames: Int, capacityFrames: Int, underruns: Int) {
require(frames > 0 && capacityFrames >= frames && underruns >= 0)
actual = frames
capacity = capacityFrames
floor = blockFrames
previousUnderruns = underruns
previousPlayed = -1
previousMs = nowMs
probeIntervalMs = 30_000
settle(nowMs)
}
/** Старт и запрос/подтверждение маршрута не участвуют в обучении. */
fun settle(nowMs: Long) {
settleUntilMs = nowMs + 2_000
stableSinceMs = settleUntilMs
}
fun sample(nowMs: Long, underruns: Int, played: Long): Change? {
val delta = underruns - previousUnderruns
val progress = played >= 0 && previousPlayed >= 0 && played > previousPlayed
val timely = nowMs - previousMs in 1..1_000
previousUnderruns = underruns
previousPlayed = played
previousMs = nowMs
if (nowMs < settleUntilMs) return null
if (delta < 0) {
settle(nowMs)
return Change(actual, "counter-reset")
}
if (delta > 0) {
probeIntervalMs = (probeIntervalMs * 2).coerceAtMost(300_000)
settle(nowMs)
val next = (actual + blockFrames).coerceAtMost(capacity)
if (next == actual) settleUntilMs = nowMs + 10_000
return Change(next, if (next > actual) "underrun" else "capacity-limit", delta)
}
if (!progress || !timely) stableSinceMs = nowMs
if (actual <= floor || nowMs - stableSinceMs < probeIntervalMs) return null
stableSinceMs = nowMs
return Change((actual - blockFrames).coerceAtLeast(floor), "stable-probe")
}
/** Устройство может округлить/ограничить заявку: учимся по принятому размеру. */
fun applied(nowMs: Long, change: Change, frames: Int, capacityFrames: Int) {
require(frames > 0 && capacityFrames >= frames)
if (change.frames < actual && frames >= actual) floor = frames
actual = frames
capacity = capacityFrames
settle(nowMs)
}
}