From dbcceb070697a11d8d0e3c999caed648e2117588 Mon Sep 17 00:00:00 2001 From: bgrozev Date: Wed, 15 Jan 2025 14:16:48 -0600 Subject: [PATCH] Add probing stats, try to fix probing rate (#2277) * 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. --- .../jitsi/videobridge/cc/BandwidthProbing.kt | 46 +++++++++++-------- .../cc/allocation/BitrateController.kt | 17 ++++--- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/jvb/src/main/kotlin/org/jitsi/videobridge/cc/BandwidthProbing.kt b/jvb/src/main/kotlin/org/jitsi/videobridge/cc/BandwidthProbing.kt index 84c0dcfc3d..36347b698a 100644 --- a/jvb/src/main/kotlin/org/jitsi/videobridge/cc/BandwidthProbing.kt +++ b/jvb/src/main/kotlin/org/jitsi/videobridge/cc/BandwidthProbing.kt @@ -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 ) : 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 @@ -67,6 +67,7 @@ class BandwidthProbing( override fun run() { super.run() if (!enabled) { + zeroStats() return } @@ -77,14 +78,16 @@ 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 } @@ -92,6 +95,10 @@ class BandwidthProbing( 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 @@ -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 { @@ -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 { diff --git a/jvb/src/main/kotlin/org/jitsi/videobridge/cc/allocation/BitrateController.kt b/jvb/src/main/kotlin/org/jitsi/videobridge/cc/allocation/BitrateController.kt index 9ccb8f519b..76155ed2f0 100644 --- a/jvb/src/main/kotlin/org/jitsi/videobridge/cc/allocation/BitrateController.kt +++ b/jvb/src/main/kotlin/org/jitsi/videobridge/cc/allocation/BitrateController.kt @@ -196,15 +196,12 @@ class BitrateController @JvmOverloads constructor( var totalTargetBitrate = 0.bps var totalIdealBitrate = 0.bps val activeSsrcs = mutableSetOf() + 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 @@ -220,6 +217,10 @@ class BitrateController @JvmOverloads constructor( allocationIdealBitrate?.let { totalIdealBitrate += it } + + if (singleAllocation.idealLayer != null && singleAllocation.idealLayer != singleAllocation.targetLayer) { + hasNonIdealLayer = true + } } activeSsrcs.removeIf { it < 0 } @@ -227,7 +228,8 @@ class BitrateController @JvmOverloads constructor( return BitrateControllerStatusSnapshot( currentTargetBps = totalTargetBitrate.bps.toLong(), currentIdealBps = totalIdealBitrate.bps.toLong(), - activeSsrcs = activeSsrcs + activeSsrcs = activeSsrcs, + hasNonIdealLayer = hasNonIdealLayer ) } @@ -326,5 +328,6 @@ interface MediaSourceContainer { data class BitrateControllerStatusSnapshot( val currentTargetBps: Long = -1L, val currentIdealBps: Long = -1L, - val activeSsrcs: Collection = emptyList() + val activeSsrcs: Collection = emptyList(), + val hasNonIdealLayer: Boolean )