Skip to content

Commit 9a2123a

Browse files
Use RateLimit to limit keyframe request sending rate to endpoints. (#2349)
* Use RateLimit to limit keyframe request sending rate to endpoints. * Also limit keyframe request rate to the RTT.
1 parent 838e8f1 commit 9a2123a

File tree

3 files changed

+34
-14
lines changed

3 files changed

+34
-14
lines changed

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/rtcp/KeyframeRequester.kt

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.jitsi.nlj.rtcp
1818

19+
import org.jitsi.config.JitsiConfig
20+
import org.jitsi.metaconfig.config
1921
import org.jitsi.nlj.Event
2022
import org.jitsi.nlj.PacketInfo
2123
import org.jitsi.nlj.SetLocalSsrcEvent
@@ -28,15 +30,16 @@ import org.jitsi.rtp.rtcp.rtcpfb.payload_specific_fb.RtcpFbFirPacketBuilder
2830
import org.jitsi.rtp.rtcp.rtcpfb.payload_specific_fb.RtcpFbPliPacket
2931
import org.jitsi.rtp.rtcp.rtcpfb.payload_specific_fb.RtcpFbPliPacketBuilder
3032
import org.jitsi.utils.MediaType
31-
import org.jitsi.utils.NEVER
33+
import org.jitsi.utils.RateLimit
34+
import org.jitsi.utils.durationOfDoubleSeconds
3235
import org.jitsi.utils.logging2.Logger
3336
import org.jitsi.utils.logging2.cdebug
3437
import org.jitsi.utils.logging2.createChildLogger
38+
import org.jitsi.utils.min
3539
import java.time.Clock
3640
import java.time.Duration
3741
import java.time.Instant
3842
import 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

jitsi-media-transform/src/main/resources/reference.conf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ jmt {
6666
accepted-fingerprint-hash-functions = [ sha-512, sha-384, sha-256, sha-1 ]
6767

6868
}
69+
keyframe {
70+
// The minimum interval between consecutive keyframe requests for a media source.
71+
// (This interval will also be limited by the current round-trip time to the sender of that source.)
72+
min-interval = 200 ms
73+
// The maximum number of requests for a media source per max-request-interval
74+
max-requests = 3
75+
// The interval over which to compute max-requests
76+
max-request-interval = 10 s
77+
}
6978
srtp {
7079
// The maximum number of packets that can be discarded early (without going through the SRTP stack for
7180
// authentication), or -1 to authenticate all packets.

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<kotlin.version>2.0.0</kotlin.version>
2828
<kotest.version>5.9.1</kotest.version>
2929
<junit.version>5.10.2</junit.version>
30-
<jitsi.utils.version>1.0-143-g65e2bd7</jitsi.utils.version>
30+
<jitsi.utils.version>1.0-145-g6673f0f</jitsi.utils.version>
3131
<jicoco.version>1.1-159-gf9c2712</jicoco.version>
3232
<mockk.version>1.13.11</mockk.version>
3333
<ktlint-maven-plugin.version>3.2.0</ktlint-maven-plugin.version>

0 commit comments

Comments
 (0)