Skip to content

Commit 9f47794

Browse files
Handle the case where VLA-provided frame rate is less than the onstage preferred frame rate. (#2268)
Correctly calculate temporal layers' frame rates from VLA (presuming 2:1 ratios).
1 parent 9af1c6c commit 9f47794

File tree

6 files changed

+31
-9
lines changed

6 files changed

+31
-9
lines changed

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/rtp/codec/av1/Av1DDRtpLayerDesc.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,7 @@ class Av1DDRtpLayerDesc(
7979
* {@inheritDoc}
8080
*/
8181
override fun toString(): String {
82-
return "subjective_quality=" + index +
83-
",DT=" + dt
82+
return "subjective_quality=$index,DT=$dt,height=$height,frameRate=$frameRate"
8483
}
8584

8685
companion object {

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/rtp/codec/vpx/VpxRtpLayerDesc.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ constructor(
118118
* {@inheritDoc}
119119
*/
120120
override fun toString(): String {
121-
return "subjective_quality=$index,temporal_id=$tid,spatial_id=$sid,height=$height"
121+
return "subjective_quality=$index,temporal_id=$tid,spatial_id=$sid,height=$height,frameRate=$frameRate"
122122
}
123123

124124
/**

jitsi-media-transform/src/main/kotlin/org/jitsi/nlj/transform/node/incoming/VlaReaderNode.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,22 +76,26 @@ class VlaReaderNode(
7676
vla.forEachIndexed { streamIdx, stream ->
7777
val rtpEncoding = sourceDesc?.rtpEncodings?.get(streamIdx)
7878
stream.spatialLayers.forEach { spatialLayer ->
79+
val maxTl = spatialLayer.targetBitratesKbps.size - 1
80+
7981
spatialLayer.targetBitratesKbps.forEachIndexed { tlIdx, targetBitrateKbps ->
8082
rtpEncoding?.layers?.find {
8183
// With VP8 simulcast all layers have sid -1
8284
(it.sid == spatialLayer.id || it.sid == -1) && it.tid == tlIdx
8385
}?.let { layer ->
84-
logger.debug(
86+
logger.debug {
8587
"Setting target bitrate for rtpEncoding=$rtpEncoding layer=$layer to " +
8688
"${targetBitrateKbps.kbps} (res=${spatialLayer.res})"
87-
)
89+
}
8890
layer.targetBitrate = targetBitrateKbps.kbps
8991
spatialLayer.res?.let { res ->
9092
if (layer.height > 0 && layer.height != res.height) {
9193
logger.warn("Updating layer height from ${layer.height} to ${res.height}")
9294
}
9395
layer.height = res.height
94-
layer.frameRate = res.maxFramerate.toDouble()
96+
/* Presume 2:1 frame rate ratios for temporal layers */
97+
val framerateFraction = 1.0 / (1 shl (maxTl - tlIdx))
98+
layer.frameRate = res.maxFramerate.toDouble() * framerateFraction
9599
}
96100
}
97101
}

jvb/src/main/kotlin/org/jitsi/videobridge/cc/allocation/BandwidthAllocator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ internal class BandwidthAllocator<T : MediaSourceContainer>(
174174

175175
logger.trace {
176176
"Allocating: sortedSources=${sortedSources.map { it.sourceName }}, " +
177-
" effectiveConstraints=${newEffectiveConstraints.map { "${it.key.sourceName}=${it.value}" }}"
177+
"effectiveConstraints=${newEffectiveConstraints.map { "${it.key.sourceName}=${it.value}" }}"
178178
}
179179

180180
// Compute the bandwidth allocation.

jvb/src/main/kotlin/org/jitsi/videobridge/cc/allocation/BitrateController.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.jitsi.utils.event.SyncEventEmitter
2626
import org.jitsi.utils.logging.DiagnosticContext
2727
import org.jitsi.utils.logging.TimeSeriesLogger
2828
import org.jitsi.utils.logging2.Logger
29+
import org.jitsi.utils.logging2.createChildLogger
2930
import org.jitsi.utils.secs
3031
import org.jitsi.videobridge.cc.config.BitrateControllerConfig.Companion.config
3132
import org.jitsi.videobridge.message.ReceiverVideoConstraintsMessage
@@ -53,6 +54,8 @@ class BitrateController<T : MediaSourceContainer> @JvmOverloads constructor(
5354
) {
5455
val eventEmitter = SyncEventEmitter<EventHandler>()
5556

57+
private val logger = createChildLogger(parentLogger)
58+
5659
private val bitrateAllocatorEventHandler = BitrateAllocatorEventHandler()
5760

5861
/**
@@ -133,6 +136,9 @@ class BitrateController<T : MediaSourceContainer> @JvmOverloads constructor(
133136
fun accept(packetInfo: PacketInfo): Boolean {
134137
if (packetInfo.layeringChanged) {
135138
// This needs to be done synchronously, so it's complete before the accept, below.
139+
logger.debug {
140+
"Layering information changed for packet from ${packetInfo.endpointId}, updating bandwidth allocation"
141+
}
136142
bandwidthAllocator.update()
137143
}
138144
return packetHandler.accept(packetInfo)

jvb/src/main/kotlin/org/jitsi/videobridge/cc/allocation/SingleSourceAllocation.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.jitsi.utils.logging2.LoggerImpl
2626
import org.jitsi.videobridge.cc.config.BitrateControllerConfig.Companion.config
2727
import java.lang.Integer.max
2828
import java.time.Clock
29+
import kotlin.math.min
2930

3031
/**
3132
* A bitrate allocation that pertains to a specific source. This is the internal representation used in the allocation
@@ -68,6 +69,7 @@ internal class SingleSourceAllocation(
6869
}
6970
timeSeriesLogger.trace(ratesTimeSeriesPoint)
7071
}
72+
logger.addContext(mapOf("remote_endpoint_id" to endpointId))
7173
}
7274

7375
fun isOnStage() = onStage
@@ -294,9 +296,15 @@ internal class SingleSourceAllocation(
294296
* oversending.
295297
*/
296298
private fun selectLayersForCamera(layers: List<LayerSnapshot>, constraints: VideoConstraints): Layers {
297-
val minHeight = layers.map { it.layer.height }.minOrNull() ?: return Layers.noLayers
299+
val minHeight = layers.minOfOrNull { it.layer.height } ?: return Layers.noLayers
300+
val maxFps = layers.maxOfOrNull { it.layer.frameRate } ?: return Layers.noLayers
298301
val noActiveLayers = layers.none { (_, bitrate) -> bitrate > 0 }
299302
val (preferredHeight, preferredFps) = getPreferred(constraints)
303+
val effectivePreferredFps = if (maxFps > 0) {
304+
min(maxFps, preferredFps)
305+
} else {
306+
preferredFps
307+
}
300308

301309
val ratesList: MutableList<LayerSnapshot> = ArrayList()
302310
// Initialize the list of layers to be considered. These are the layers that satisfy the constraints, with
@@ -306,7 +314,7 @@ internal class SingleSourceAllocation(
306314
val lessThanPreferredHeight = layer.height < preferredHeight
307315
val lessThanOrEqualMaxHeight = layer.height <= constraints.maxHeight || !constraints.heightIsLimited()
308316
// If frame rate is unknown, consider it to be sufficient.
309-
val atLeastPreferredFps = layer.frameRate < 0 || layer.frameRate >= preferredFps
317+
val atLeastPreferredFps = layer.frameRate < 0 || layer.frameRate >= effectivePreferredFps
310318
if (lessThanPreferredHeight ||
311319
(lessThanOrEqualMaxHeight && atLeastPreferredFps) ||
312320
layer.height == minHeight
@@ -321,6 +329,11 @@ internal class SingleSourceAllocation(
321329

322330
val effectivePreferredHeight = max(preferredHeight, minHeight)
323331
val preferredIndex = ratesList.lastIndexWhich { it.layer.height <= effectivePreferredHeight }
332+
logger.trace {
333+
"Selected rates list $ratesList, preferred index $preferredIndex " +
334+
"from layers $layers with constraints $constraints"
335+
}
336+
324337
return Layers(ratesList, preferredIndex, -1)
325338
}
326339

0 commit comments

Comments
 (0)