Skip to content

Commit

Permalink
Add probing stats, try to fix probing rate (#2277)
Browse files Browse the repository at this point in the history
* feat: Add stats for bandwidth probing to debug (rtcstats).

* fix: Use observed bitrate instead of encoder target when calculating probing needed.

* fix: Do not probe if the ideal layers are already selected.

* ref: Remove unused vals.
  • Loading branch information
bgrozev authored Jan 15, 2025
1 parent fc2f291 commit dbcceb0
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 25 deletions.
46 changes: 28 additions & 18 deletions jvb/src/main/kotlin/org/jitsi/videobridge/cc/BandwidthProbing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,36 @@ package org.jitsi.videobridge.cc

import org.jitsi.nlj.rtp.bandwidthestimation.BandwidthEstimator
import org.jitsi.nlj.util.Bandwidth
import org.jitsi.rtp.extensions.unsigned.toPositiveLong
import org.jitsi.nlj.util.BitrateTracker
import org.jitsi.nlj.util.bytes
import org.jitsi.utils.concurrent.PeriodicRunnable
import org.jitsi.utils.logging.DiagnosticContext
import org.jitsi.utils.logging.TimeSeriesLogger
import org.jitsi.utils.ms
import org.jitsi.utils.secs
import org.jitsi.videobridge.cc.allocation.BitrateControllerStatusSnapshot
import org.jitsi.videobridge.cc.config.BandwidthProbingConfig.Companion.config
import org.json.simple.JSONObject
import java.util.function.Supplier
import kotlin.random.Random

class BandwidthProbing(
private val probingDataSender: ProbingDataSender,
private val statusSnapshotSupplier: Supplier<BitrateControllerStatusSnapshot>
) : PeriodicRunnable(config.paddingPeriodMs), BandwidthEstimator.Listener {

/**
* The sequence number to use if probing with the JVB's SSRC.
*/
private var seqNum = Random.nextInt(0xFFFF)

/**
* The RTP timestamp to use if probing with the JVB's SSRC.
*/
private var ts: Long = Random.nextInt().toPositiveLong()

/**
* Whether or not probing is currently enabled
*/
/** Whether or not probing is currently enabled */
var enabled = false

private var lastTotalNeededBps = 0L
private var lastMaxPaddingBps = 0L
private var lastPaddingBps = 0L
private fun zeroStats() {
lastTotalNeededBps = 0
lastMaxPaddingBps = 0
lastPaddingBps = 0
}
private val probingBitrate = BitrateTracker(5.secs, 100.ms)

/**
* The number of bytes left over from one run of probing to the next. This
* avoids accumulated rounding errors causing us to under-shoot the probing
Expand All @@ -67,6 +67,7 @@ class BandwidthProbing(
override fun run() {
super.run()
if (!enabled) {
zeroStats()
return
}

Expand All @@ -77,21 +78,27 @@ class BandwidthProbing(

// How much padding do we need?
val totalNeededBps = bitrateControllerStatus.currentIdealBps - bitrateControllerStatus.currentTargetBps
if (totalNeededBps < 1) {
if (totalNeededBps < 1 || !bitrateControllerStatus.hasNonIdealLayer) {
// Don't need to send any probing.
bytesLeftOver = 0
zeroStats()
return
}

val latestBweCopy = latestBwe
if (bitrateControllerStatus.currentIdealBps <= latestBweCopy) {
zeroStats()
return
}

// How much padding can we afford?
val maxPaddingBps = latestBweCopy - bitrateControllerStatus.currentTargetBps
val paddingBps = totalNeededBps.coerceAtMost(maxPaddingBps)

lastTotalNeededBps = totalNeededBps
lastMaxPaddingBps = maxPaddingBps
lastPaddingBps = paddingBps

var timeSeriesPoint: DiagnosticContext.TimeSeriesPoint? = null
val newBytesNeeded = (config.paddingPeriodMs * paddingBps / 1000.0 / 8.0)
val bytesNeeded = newBytesNeeded + bytesLeftOver
Expand All @@ -113,6 +120,7 @@ class BandwidthProbing(

if (bytesNeeded >= 1) {
val bytesSent = probingDataSender.sendProbing(bitrateControllerStatus.activeSsrcs, bytesNeeded.toInt())
probingBitrate.update(bytesSent.bytes)
bytesLeftOver = (bytesNeeded - bytesSent).coerceAtLeast(0.0).toInt()
timeSeriesPoint?.addField("bytes_sent", bytesSent)?.addField("new_bytes_left_over", bytesLeftOver)
} else {
Expand All @@ -125,10 +133,12 @@ class BandwidthProbing(
}

fun getDebugState(): JSONObject = JSONObject().apply {
put("seqNum", seqNum)
put("ts", ts)
put("enabled", enabled)
put("latestBwe", latestBwe)
put("lastTotalNeededBps", lastTotalNeededBps)
put("lastMaxPaddingBps", lastMaxPaddingBps)
put("lastPaddingBps", lastPaddingBps)
put("probingBps", probingBitrate.getRateBps())
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,12 @@ class BitrateController<T : MediaSourceContainer> @JvmOverloads constructor(
var totalTargetBitrate = 0.bps
var totalIdealBitrate = 0.bps
val activeSsrcs = mutableSetOf<Long>()
var hasNonIdealLayer = false

val nowMs = clock.instant().toEpochMilli()
val allocation = bandwidthAllocator.allocation
allocation.allocations.forEach { singleAllocation ->
val allocationTargetBitrate: Bandwidth? = if (config.useVlaTargetBitrate) {
singleAllocation.targetLayer?.targetBitrate ?: singleAllocation.targetLayer?.getBitrate(nowMs)
} else {
singleAllocation.targetLayer?.getBitrate(nowMs)
}
val allocationTargetBitrate: Bandwidth? = singleAllocation.targetLayer?.getBitrate(nowMs)

allocationTargetBitrate?.let {
totalTargetBitrate += it
Expand All @@ -220,14 +217,19 @@ class BitrateController<T : MediaSourceContainer> @JvmOverloads constructor(
allocationIdealBitrate?.let {
totalIdealBitrate += it
}

if (singleAllocation.idealLayer != null && singleAllocation.idealLayer != singleAllocation.targetLayer) {
hasNonIdealLayer = true
}
}

activeSsrcs.removeIf { it < 0 }

return BitrateControllerStatusSnapshot(
currentTargetBps = totalTargetBitrate.bps.toLong(),
currentIdealBps = totalIdealBitrate.bps.toLong(),
activeSsrcs = activeSsrcs
activeSsrcs = activeSsrcs,
hasNonIdealLayer = hasNonIdealLayer
)
}

Expand Down Expand Up @@ -326,5 +328,6 @@ interface MediaSourceContainer {
data class BitrateControllerStatusSnapshot(
val currentTargetBps: Long = -1L,
val currentIdealBps: Long = -1L,
val activeSsrcs: Collection<Long> = emptyList()
val activeSsrcs: Collection<Long> = emptyList(),
val hasNonIdealLayer: Boolean
)

0 comments on commit dbcceb0

Please sign in to comment.