1616
1717package org.jitsi.nlj.rtcp
1818
19+ import org.jitsi.config.JitsiConfig
20+ import org.jitsi.metaconfig.config
1921import org.jitsi.nlj.Event
2022import org.jitsi.nlj.PacketInfo
2123import org.jitsi.nlj.SetLocalSsrcEvent
@@ -28,15 +30,16 @@ import org.jitsi.rtp.rtcp.rtcpfb.payload_specific_fb.RtcpFbFirPacketBuilder
2830import org.jitsi.rtp.rtcp.rtcpfb.payload_specific_fb.RtcpFbPliPacket
2931import org.jitsi.rtp.rtcp.rtcpfb.payload_specific_fb.RtcpFbPliPacketBuilder
3032import org.jitsi.utils.MediaType
31- import org.jitsi.utils.NEVER
33+ import org.jitsi.utils.RateLimit
34+ import org.jitsi.utils.durationOfDoubleSeconds
3235import org.jitsi.utils.logging2.Logger
3336import org.jitsi.utils.logging2.cdebug
3437import org.jitsi.utils.logging2.createChildLogger
38+ import org.jitsi.utils.min
3539import java.time.Clock
3640import java.time.Duration
3741import java.time.Instant
3842import java.util.concurrent.atomic.AtomicInteger
39- import kotlin.math.min
4043
4144/* *
4245 * [KeyframeRequester] handles a few things around keyframes:
@@ -54,11 +57,11 @@ class KeyframeRequester @JvmOverloads constructor(
5457 private val logger = createChildLogger(parentLogger)
5558
5659 // Map a SSRC to the timestamp (represented as an [Instant]) of when we last requested a keyframe for it
57- private val keyframeRequests = mutableMapOf<Long , Instant >()
60+ private val keyframeLimiter = mutableMapOf<Long , RateLimit >()
61+ private val keyframeLimiterSyncRoot = Any ()
5862 private val firCommandSequenceNumber: AtomicInteger = AtomicInteger (0 )
59- private val keyframeRequestsSyncRoot = Any ()
6063 private var localSsrc: Long? = null
61- private var waitInterval = DEFAULT_WAIT_INTERVAL
64+ private var waitInterval = minInterval
6265
6366 // Stats
6467
@@ -127,14 +130,14 @@ class KeyframeRequester @JvmOverloads constructor(
127130 if (! streamInformationStore.supportsPli && ! streamInformationStore.supportsFir) {
128131 return false
129132 }
130- synchronized(keyframeRequestsSyncRoot) {
131- return if (Duration .between(keyframeRequests.getOrDefault(mediaSsrc, NEVER ), now) < waitInterval) {
132- logger.cdebug {
133- " Sent a keyframe request less than $waitInterval ago for $mediaSsrc , ignoring request"
134- }
133+ synchronized(keyframeLimiterSyncRoot) {
134+ val limiter = keyframeLimiter.computeIfAbsent(mediaSsrc) {
135+ RateLimit (defaultMinInterval = minInterval, maxRequests = maxRequests, interval = maxRequestInterval)
136+ }
137+ return if (! limiter.accept(now, waitInterval)) {
138+ logger.cdebug { " Ignoring keyframe request for $mediaSsrc , rate limited" }
135139 false
136140 } else {
137- keyframeRequests[mediaSsrc] = now
138141 logger.cdebug { " Keyframe requester requesting keyframe for $mediaSsrc " }
139142 true
140143 }
@@ -217,11 +220,19 @@ class KeyframeRequester @JvmOverloads constructor(
217220
218221 fun onRttUpdate (newRtt : Double ) {
219222 // avg(rtt) + stddev(rtt) would be more accurate than rtt + 10.
220- waitInterval = Duration .ofMillis( min(DEFAULT_WAIT_INTERVAL .toMillis(), newRtt.toLong() + 10 ))
223+ waitInterval = min(minInterval, durationOfDoubleSeconds((newRtt + 10 ) / 1e3 ))
221224 }
222225
223226 companion object {
224- private val DEFAULT_WAIT_INTERVAL = Duration .ofMillis(100 )
227+ private val minInterval: Duration by config {
228+ " jmt.keyframe.min-interval" .from(JitsiConfig .newConfig)
229+ }
230+ private val maxRequests: Int by config {
231+ " jmt.keyframe.max-requests" .from(JitsiConfig .newConfig)
232+ }
233+ private val maxRequestInterval: Duration by config {
234+ " jmt.keyframe.max-request-interval" .from(JitsiConfig .newConfig)
235+ }
225236 }
226237}
227238
0 commit comments