Add tested adaptive playback device buffer policy
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user