Browse Source

Fix very bad quality reported due to stalled stats

When no packets are transmitted the packet lost ratio is set to a value
higher than 1 to move towards a "no transmitted data" state faster,
although not immediately. Both the "no transmitted data" and "very bad
quality" states are based on the packet lost ratio, so this causes the
"very bad quality" state to be triggered as soon as no packets are
transmitted.

However, the stats reported by the browser can sometimes stall for a
second (it is unclear whether the browser does not update the stats or
Janus does not send them, though). When that happens the stats are still
reported, but with the same values as in the previous report. As there
were no transmitted packets the "very bad quality" state was (wrongly)
triggered.

To solve this now if there were no transmitted packets in the last
report the analysis is kept on hold. If in the next report the number of
packets has changed then the previous report is considered to have
stalled and the new values are distributed between the previous and
current report. If the number of packets is still zero then the previous
report is considered to have been legit and the previous and current
values are added as normal.

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
pull/5885/head
Daniel Calviño Sánchez 4 years ago
parent
commit
aadfd0e37b
  1. 135
      src/utils/webrtc/analyzers/PeerConnectionAnalyzer.js

135
src/utils/webrtc/analyzers/PeerConnectionAnalyzer.js

@ -106,6 +106,23 @@ function PeerConnectionAnalyzer() {
video: new AverageStatValue(5, STAT_VALUE_TYPE.CUMULATIVE),
}
this._stagedPackets = {
audio: [],
video: [],
}
this._stagedPacketsLost = {
audio: [],
video: [],
}
this._stagedRoundTripTime = {
audio: [],
video: [],
}
this._stagedTimestamps = {
audio: [],
video: [],
}
this._analysisEnabled = {
audio: true,
video: true,
@ -462,7 +479,114 @@ PeerConnectionAnalyzer.prototype = {
}
},
/**
* Adds the stats reported by the browser to the average stats used to do
* the analysis.
*
* The stats reported by the browser can sometimes stall for a second (or
* more, but typically they stall only for a single report). When that
* happens the stats are still reported, but with the same number of packets
* as in the previous report (timestamp and round trip time are updated,
* though). In that case the given stats are not added yet to the average
* stats; they are kept on hold until more stats are provided by the browser
* and it can be determined if the previous stats were stalled or not. If
* they were stalled the previous and new stats are distributed, and if they
* were not they are added as is to the average stats.
*
* @param {string} kind the type of the stats ("audio" or "video")
* @param {number} packets the cumulative number of packets
* @param {number} packetsLost the cumulative number of lost packets
* @param {number} timestamp the cumulative timestamp
* @param {number} roundTripTime the relative round trip time
*/
_addStats(kind, packets, packetsLost, timestamp, roundTripTime) {
if (this._stagedPackets[kind].length === 0) {
if (packets !== this._packets[kind].getLastRawValue()) {
this._commitStats(kind, packets, packetsLost, timestamp, roundTripTime)
} else {
this._stageStats(kind, packets, packetsLost, timestamp, roundTripTime)
}
return
}
this._stageStats(kind, packets, packetsLost, timestamp, roundTripTime)
// If the packets have changed now it is assumed that the previous stats
// were stalled.
if (packets > 0) {
this._distributeStagedStats(kind)
}
while (this._stagedPackets[kind].length > 0) {
const stagedPackets = this._stagedPackets[kind].shift()
const stagedPacketsLost = this._stagedPacketsLost[kind].shift()
const stagedTimestamp = this._stagedTimestamps[kind].shift()
const stagedRoundTripTime = this._stagedRoundTripTime[kind].shift()
this._commitStats(kind, stagedPackets, stagedPacketsLost, stagedTimestamp, stagedRoundTripTime)
}
},
_stageStats(kind, packets, packetsLost, timestamp, roundTripTime) {
this._stagedPackets[kind].push(packets)
this._stagedPacketsLost[kind].push(packetsLost)
this._stagedTimestamps[kind].push(timestamp)
this._stagedRoundTripTime[kind].push(roundTripTime)
},
/**
* Distributes the values of the staged stats proportionately to their
* timestamps.
*
* Once the stats unstall the new stats are a sum of the values that should
* have been reported before and the actual new values. The stats typically
* stall for just a second, but they can stall for an arbitrary length too.
* Due to this the staged stats need to be distributed based on their
* timestamps.
*
* @param {string} kind the type of the stats ("audio" or "video")
*/
_distributeStagedStats(kind) {
let packetsBase = this._packets[kind].getLastRawValue()
let packetsLostBase = this._packetsLost[kind].getLastRawValue()
let timestampsBase = this._timestamps[kind].getLastRawValue()
let packetsTotal = 0
let packetsLostTotal = 0
let timestampsTotal = 0
for (let i = 0; i < this._stagedPackets[kind].length; i++) {
packetsTotal += (this._stagedPackets[kind][i] - packetsBase)
packetsBase = this._stagedPackets[kind][i]
packetsLostTotal += (this._stagedPacketsLost[kind][i] - packetsLostBase)
packetsLostBase = this._stagedPacketsLost[kind][i]
timestampsTotal += (this._stagedTimestamps[kind][i] - timestampsBase)
timestampsBase = this._stagedTimestamps[kind][i]
}
packetsBase = this._packets[kind].getLastRawValue()
packetsLostBase = this._packetsLost[kind].getLastRawValue()
timestampsBase = this._timestamps[kind].getLastRawValue()
for (let i = 0; i < this._stagedPackets[kind].length; i++) {
const weight = (this._stagedTimestamps[kind][i] - timestampsBase) / timestampsTotal
timestampsBase = this._stagedTimestamps[kind][i]
this._stagedPackets[kind][i] = packetsBase + packetsTotal * weight
packetsBase = this._stagedPackets[kind][i]
this._stagedPacketsLost[kind][i] = packetsLostBase + packetsLostTotal * weight
packetsLostBase = this._stagedPacketsLost[kind][i]
// Timestamps and round trip time are not distributed, as those
// values are properly updated even if the stats are stalled.
}
},
_commitStats(kind, packets, packetsLost, timestamp, roundTripTime) {
if (packets >= 0) {
this._packets[kind].add(packets)
}
@ -474,7 +598,9 @@ PeerConnectionAnalyzer.prototype = {
// are got from the helper object.
// If there were no transmitted packets in the last stats the ratio
// is higher than 1 both to signal that and to force the quality
// towards a very bad quality faster, but not immediately.
// towards "no transmitted data" faster, but not immediately.
// However, note that the quality will immediately change to "very
// bad quality".
let packetsLostRatio = 1.5
if (this._packets[kind].getLastRelativeValue() > 0) {
packetsLostRatio = this._packetsLost[kind].getLastRelativeValue() / this._packets[kind].getLastRelativeValue()
@ -514,6 +640,13 @@ PeerConnectionAnalyzer.prototype = {
return CONNECTION_QUALITY.UNKNOWN
}
// The stats might be in a temporary stall and the analysis is on hold
// until further stats arrive, so until that happens the last known
// state is returned again.
if (this._stagedPackets[kind].length > 0) {
return this._connectionQuality[kind]
}
const packetsLostRatioWeightedAverage = packetsLostRatio.getWeightedAverage()
if (packetsLostRatioWeightedAverage >= 1) {
this._logStats(kind, 'No transmitted data, packet lost ratio: ' + packetsLostRatioWeightedAverage)

Loading…
Cancel
Save