mediasoup 3.27.0 → 3.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/node/lib/Consumer.d.ts.map +1 -1
- package/node/lib/Consumer.js +4 -51
- package/node/lib/Producer.d.ts.map +1 -1
- package/node/lib/Producer.js +4 -8
- package/node/lib/ProducerTypes.d.ts +4 -2
- package/node/lib/ProducerTypes.d.ts.map +1 -1
- package/node/lib/Router.d.ts.map +1 -1
- package/node/lib/Router.js +4 -0
- package/node/lib/Transport.d.ts.map +1 -1
- package/node/lib/Transport.js +7 -10
- package/node/lib/fbs/rtp-parameters/rtp-header-extension-uri.d.ts +3 -4
- package/node/lib/fbs/rtp-parameters/rtp-header-extension-uri.d.ts.map +1 -1
- package/node/lib/fbs/rtp-parameters/rtp-header-extension-uri.js +3 -4
- package/node/lib/ortc.d.ts +0 -2
- package/node/lib/ortc.d.ts.map +1 -1
- package/node/lib/ortc.js +11 -21
- package/node/lib/rtpMappingFbsUtils.d.ts +6 -0
- package/node/lib/rtpMappingFbsUtils.d.ts.map +1 -0
- package/node/lib/rtpMappingFbsUtils.js +65 -0
- package/node/lib/rtpParametersFbsUtils.d.ts.map +1 -1
- package/node/lib/rtpParametersFbsUtils.js +0 -6
- package/node/lib/rtpParametersTypes.d.ts +1 -1
- package/node/lib/rtpParametersTypes.d.ts.map +1 -1
- package/node/lib/rtpStreamFbsUtils.d.ts +4 -0
- package/node/lib/rtpStreamFbsUtils.d.ts.map +1 -0
- package/node/lib/rtpStreamFbsUtils.js +51 -0
- package/node/lib/supportedRtpCapabilities.d.ts.map +1 -1
- package/node/lib/supportedRtpCapabilities.js +6 -13
- package/node/lib/test/test-Consumer.js +1 -13
- package/node/lib/test/test-DataConsumer.js +9 -0
- package/node/lib/test/test-DataProducer.js +38 -0
- package/node/lib/test/test-PipeTransport.js +27 -29
- package/node/lib/test/test-Producer.js +97 -0
- package/node/lib/test/test-ortc.js +34 -14
- package/package.json +10 -10
- package/worker/fbs/FBS/meson.build +25 -0
- package/worker/fbs/meson.build +15 -18
- package/worker/fbs/rtpParameters.fbs +0 -1
- package/worker/fuzzer/src/FuzzerUtils.cpp +5 -3
- package/worker/fuzzer/src/RTC/RTP/FuzzerPacket.cpp +16 -2
- package/worker/include/DepLibUV.hpp +13 -0
- package/worker/include/RTC/BWE/AlrDetector.hpp +97 -0
- package/worker/include/RTC/BWE/BitrateProber.hpp +217 -0
- package/worker/include/RTC/BWE/BitrateUtils.hpp +39 -0
- package/worker/include/RTC/BWE/BweTypes.hpp +171 -3
- package/worker/include/RTC/BWE/FeedbackAdapter.hpp +0 -14
- package/worker/include/RTC/BWE/InterArrival.hpp +117 -0
- package/worker/include/RTC/BWE/IntervalBudget.hpp +81 -0
- package/worker/include/RTC/BWE/LossBasedController.hpp +625 -0
- package/worker/include/RTC/BWE/OveruseDetector.hpp +71 -0
- package/worker/include/RTC/BWE/OveruseEstimator.hpp +115 -0
- package/worker/include/RTC/BWE/ProbeBitrateEstimator.hpp +84 -0
- package/worker/include/RTC/BWE/ProbeController.hpp +379 -0
- package/worker/include/RTC/BWE/ProbePacketGenerator.hpp +130 -0
- package/worker/include/RTC/BWE/ProbingScheduler.hpp +187 -0
- package/worker/include/RTC/BWE/RembGenerator.hpp +96 -0
- package/worker/include/RTC/BWE/RemoteBitrateEstimatorAbsSendTime.hpp +237 -0
- package/worker/include/RTC/BWE/RobustThroughputEstimator.hpp +26 -0
- package/worker/include/RTC/BWE/SendPacketHistory.hpp +161 -25
- package/worker/include/RTC/BWE/TargetRateController.hpp +306 -0
- package/worker/include/RTC/BWE/TransportWideCcFeedbackGenerator.hpp +146 -0
- package/worker/include/RTC/BWE/TrendlineEstimator.hpp +73 -16
- package/worker/include/RTC/Consts.hpp +70 -0
- package/worker/include/RTC/DataConsumer.hpp +3 -5
- package/worker/include/RTC/DirectTransport.hpp +4 -5
- package/worker/include/RTC/PipeTransport.hpp +4 -5
- package/worker/include/RTC/PlainTransport.hpp +4 -5
- package/worker/include/RTC/RTCP/FeedbackPsRemb.hpp +6 -7
- package/worker/include/RTC/RTCP/FeedbackRtpTransport.hpp +3 -1
- package/worker/include/RTC/RTP/Codecs/Tools.hpp +1 -1
- package/worker/include/RTC/RTP/HeaderExtensionIds.hpp +0 -1
- package/worker/include/RTC/RTP/Packet.hpp +16 -4
- package/worker/include/RTC/RTP/ProbationGenerator.hpp +0 -8
- package/worker/include/RTC/RTP/RtpStream.hpp +12 -9
- package/worker/include/RTC/RTP/RtpStreamRecv.hpp +22 -4
- package/worker/include/RTC/RTP/RtpStreamSend.hpp +4 -2
- package/worker/include/RTC/RTP/RtxStream.hpp +10 -4
- package/worker/include/RTC/RateCalculator.hpp +103 -24
- package/worker/include/RTC/RemoteCaptureTimeEstimator.hpp +33 -3
- package/worker/include/RTC/{RtcLogger.hpp → RtcLogger/RtpPacket.hpp} +5 -3
- package/worker/include/RTC/RtcLogger/SendBurst.hpp +88 -0
- package/worker/include/RTC/RtpDictionaries.hpp +3 -4
- package/worker/include/RTC/SCTP/association/Association.hpp +1 -1
- package/worker/include/RTC/SCTP/association/StreamResetHandler.hpp +36 -0
- package/worker/include/RTC/SCTP/public/AssociationMetrics.hpp +0 -1
- package/worker/include/RTC/SCTP/public/SctpTypes.hpp +9 -9
- package/worker/include/RTC/SCTP/rx/DataTracker.hpp +1 -1
- package/worker/include/RTC/SCTP/rx/ReassemblyQueue.hpp +11 -1
- package/worker/include/RTC/SctpDictionaries.hpp +2 -2
- package/worker/include/RTC/TcpConnection.hpp +2 -1
- package/worker/include/RTC/Transport.hpp +23 -8
- package/worker/include/RTC/TransportTuple.hpp +61 -6
- package/worker/include/RTC/WebRtcTransport.hpp +4 -5
- package/worker/include/Shared.hpp +5 -0
- package/worker/include/SharedInterface.hpp +10 -0
- package/worker/include/Utils.hpp +72 -3
- package/worker/include/common.hpp +12 -4
- package/worker/include/handles/SendCallbacks.hpp +34 -0
- package/worker/include/handles/TcpConnectionHandle.hpp +4 -12
- package/worker/include/handles/UdpSocketHandle.hpp +4 -8
- package/worker/meson.build +45 -1
- package/worker/meson_options.txt +1 -0
- package/worker/mocks/include/MockShared.hpp +16 -2
- package/worker/mocks/include/handles/MockBackoffTimerHandle.hpp +1 -1
- package/worker/scripts/package-lock.json +6 -6
- package/worker/src/RTC/BWE/AimdRateControl.cpp +4 -5
- package/worker/src/RTC/BWE/AlrDetector.cpp +80 -0
- package/worker/src/RTC/BWE/BitrateProber.cpp +256 -0
- package/worker/src/RTC/BWE/BitrateUtils.cpp +54 -0
- package/worker/src/RTC/BWE/DelayBasedBwe.cpp +2 -2
- package/worker/src/RTC/BWE/FeedbackAdapter.cpp +20 -37
- package/worker/src/RTC/BWE/InterArrival.cpp +216 -0
- package/worker/src/RTC/BWE/IntervalBudget.cpp +93 -0
- package/worker/src/RTC/BWE/LossBasedController.cpp +1194 -0
- package/worker/src/RTC/BWE/OveruseDetector.cpp +128 -0
- package/worker/src/RTC/BWE/OveruseEstimator.cpp +166 -0
- package/worker/src/RTC/BWE/ProbeBitrateEstimator.cpp +222 -0
- package/worker/src/RTC/BWE/ProbeController.cpp +645 -0
- package/worker/src/RTC/BWE/ProbePacketGenerator.cpp +140 -0
- package/worker/src/RTC/BWE/ProbingScheduler.cpp +196 -0
- package/worker/src/RTC/BWE/RembGenerator.cpp +169 -0
- package/worker/src/RTC/BWE/RemoteBitrateEstimatorAbsSendTime.cpp +497 -0
- package/worker/src/RTC/BWE/RobustThroughputEstimator.cpp +47 -0
- package/worker/src/RTC/BWE/SendPacketHistory.cpp +173 -50
- package/worker/src/RTC/BWE/TargetRateController.cpp +433 -0
- package/worker/src/RTC/BWE/TransportWideCcFeedbackGenerator.cpp +377 -0
- package/worker/src/RTC/BWE/TrendlineEstimator.cpp +102 -5
- package/worker/src/RTC/Consumer.cpp +7 -5
- package/worker/src/RTC/DataConsumer.cpp +8 -10
- package/worker/src/RTC/DirectTransport.cpp +14 -8
- package/worker/src/RTC/DtlsTransport.cpp +8 -8
- package/worker/src/RTC/ICE/IceCandidate.cpp +4 -4
- package/worker/src/RTC/ICE/IceServer.cpp +1 -1
- package/worker/src/RTC/PipeTransport.cpp +13 -8
- package/worker/src/RTC/PlainTransport.cpp +13 -8
- package/worker/src/RTC/Producer.cpp +14 -31
- package/worker/src/RTC/RTCP/Bye.cpp +1 -1
- package/worker/src/RTC/RTCP/FeedbackPsRemb.cpp +18 -0
- package/worker/src/RTC/RTCP/FeedbackPsRpsi.cpp +2 -2
- package/worker/src/RTC/RTCP/FeedbackPsVbcm.cpp +2 -1
- package/worker/src/RTC/RTCP/FeedbackRtpTransport.cpp +2 -2
- package/worker/src/RTC/RTCP/Sdes.cpp +1 -1
- package/worker/src/RTC/RTP/Codecs/DependencyDescriptor.cpp +2 -1
- package/worker/src/RTC/RTP/Packet.cpp +26 -11
- package/worker/src/RTC/RTP/ProbationGenerator.cpp +5 -6
- package/worker/src/RTC/RTP/RtpStream.cpp +1 -1
- package/worker/src/RTC/RTP/RtpStreamRecv.cpp +69 -48
- package/worker/src/RTC/RTP/RtpStreamSend.cpp +31 -31
- package/worker/src/RTC/RTP/RtxStream.cpp +22 -7
- package/worker/src/RTC/RateCalculator.cpp +133 -28
- package/worker/src/RTC/RemoteCaptureTimeEstimator.cpp +56 -1
- package/worker/src/RTC/{RtcLogger.cpp → RtcLogger/RtpPacket.cpp} +2 -2
- package/worker/src/RTC/RtcLogger/SendBurst.cpp +124 -0
- package/worker/src/RTC/RtpDictionaries/RtpHeaderExtensionUri.cpp +2 -12
- package/worker/src/RTC/RtpDictionaries/RtpParameters.cpp +1 -1
- package/worker/src/RTC/SCTP/association/Association.cpp +16 -2
- package/worker/src/RTC/SCTP/association/StreamResetHandler.cpp +106 -1
- package/worker/src/RTC/SCTP/public/AssociationMetrics.cpp +1 -1
- package/worker/src/RTC/SCTP/rx/InterleavedReassemblyStreams.cpp +3 -3
- package/worker/src/RTC/SCTP/rx/ReassemblyQueue.cpp +2 -1
- package/worker/src/RTC/SCTP/rx/TraditionalReassemblyStreams.cpp +4 -4
- package/worker/src/RTC/SCTP/tx/RetransmissionTimeout.cpp +4 -4
- package/worker/src/RTC/SctpDictionaries/SctpStreamParameters.cpp +6 -10
- package/worker/src/RTC/SimulcastProducerStreamManager.cpp +2 -2
- package/worker/src/RTC/SrtpSession.cpp +2 -2
- package/worker/src/RTC/SvcProducerStreamManager.cpp +2 -2
- package/worker/src/RTC/TcpConnection.cpp +2 -2
- package/worker/src/RTC/Transport.cpp +112 -75
- package/worker/src/RTC/TransportTuple.cpp +4 -4
- package/worker/src/RTC/WebRtcServer.cpp +4 -3
- package/worker/src/RTC/WebRtcTransport.cpp +20 -13
- package/worker/src/Utils/BitStream.cpp +24 -0
- package/worker/src/handles/BackoffTimerHandle.cpp +1 -1
- package/worker/src/handles/TcpConnectionHandle.cpp +13 -23
- package/worker/src/handles/UdpSocketHandle.cpp +14 -21
- package/worker/tasks.py +43 -18
- package/worker/test/include/RTC/BWE/helpers/LinkSimulator.hpp +68 -14
- package/worker/test/include/RTC/BWE/helpers/RemoteBitrateEstimatorAbsSendTimeTestHelper.hpp +125 -0
- package/worker/test/src/RTC/BWE/TestAlrDetector.cpp +110 -0
- package/worker/test/src/RTC/BWE/TestBitrateProber.cpp +418 -0
- package/worker/test/src/RTC/BWE/TestBitrateUtils.cpp +61 -0
- package/worker/test/src/RTC/BWE/TestDelayBasedBwe.cpp +4 -4
- package/worker/test/src/RTC/BWE/TestFeedbackAdapter.cpp +53 -37
- package/worker/test/src/RTC/BWE/TestInterArrival.cpp +643 -0
- package/worker/test/src/RTC/BWE/TestIntervalBudget.cpp +130 -0
- package/worker/test/src/RTC/BWE/TestLossBasedController.cpp +1199 -0
- package/worker/test/src/RTC/BWE/TestOveruseDetector.cpp +519 -0
- package/worker/test/src/RTC/BWE/TestProbeBitrateEstimator.cpp +266 -0
- package/worker/test/src/RTC/BWE/TestProbeController.cpp +1682 -0
- package/worker/test/src/RTC/BWE/TestProbePacketGenerator.cpp +265 -0
- package/worker/test/src/RTC/BWE/TestProbingScheduler.cpp +260 -0
- package/worker/test/src/RTC/BWE/TestRembGenerator.cpp +285 -0
- package/worker/test/src/RTC/BWE/TestRemoteBitrateEstimatorAbsSendTime.cpp +414 -0
- package/worker/test/src/RTC/BWE/TestSendPacketHistory.cpp +296 -77
- package/worker/test/src/RTC/BWE/TestTargetRateController.cpp +337 -0
- package/worker/test/src/RTC/BWE/TestTransportWideCcFeedbackGenerator.cpp +578 -0
- package/worker/test/src/RTC/BWE/TestTrendlineEstimator.cpp +45 -0
- package/worker/test/src/RTC/BWE/helpers/LinkSimulator.cpp +133 -29
- package/worker/test/src/RTC/BWE/helpers/RemoteBitrateEstimatorAbsSendTimeTestHelper.cpp +625 -0
- package/worker/test/src/RTC/RTCP/TestFeedbackPsRemb.cpp +19 -0
- package/worker/test/src/RTC/RTP/TestPacket.cpp +58 -4
- package/worker/test/src/RTC/RTP/TestProbationGenerator.cpp +3 -2
- package/worker/test/src/RTC/RTP/TestRtpStreamRecv.cpp +50 -0
- package/worker/test/src/RTC/RTP/TestRtpStreamSend.cpp +59 -0
- package/worker/test/src/RTC/SCTP/association/TestStreamResetHandler.cpp +111 -0
- package/worker/test/src/RTC/TestRateCalculator.cpp +322 -60
- package/worker/test/src/RTC/TestRemoteCaptureTimeEstimator.cpp +95 -7
- package/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +34 -16
- package/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +31 -13
- package/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +37 -19
- package/worker/test/src/RTC/TestTransportCongestionControlServer.cpp +10 -10
- package/worker/test/src/Utils/TestTime.cpp +120 -21
|
@@ -0,0 +1,1194 @@
|
|
|
1
|
+
#define MS_CLASS "RTC::BWE::LossBasedController"
|
|
2
|
+
// #define MS_LOG_DEV_LEVEL 3
|
|
3
|
+
|
|
4
|
+
#include "RTC/BWE/LossBasedController.hpp"
|
|
5
|
+
#include "Logger.hpp"
|
|
6
|
+
#include "RTC/BWE/BitrateUtils.hpp"
|
|
7
|
+
#include "RTC/Consts.hpp"
|
|
8
|
+
#include <cmath>
|
|
9
|
+
#include <limits>
|
|
10
|
+
|
|
11
|
+
namespace RTC
|
|
12
|
+
{
|
|
13
|
+
namespace BWE
|
|
14
|
+
{
|
|
15
|
+
/* Static. */
|
|
16
|
+
|
|
17
|
+
// How long the first hold lasts, each following one lasting longer.
|
|
18
|
+
static constexpr int64_t InitHoldDurationUs{ 300 * 1000 };
|
|
19
|
+
// How long a hold may last no matter how many have piled up.
|
|
20
|
+
static constexpr int64_t MaxHoldDurationUs{ 60 * 1000 * 1000 };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Loss to expect from a link described by the given pair of values while
|
|
24
|
+
* sending at the given rate: its inherent loss, plus the share of the traffic
|
|
25
|
+
* that doesn't fit while sending above its capacity.
|
|
26
|
+
*/
|
|
27
|
+
static double getLossProbability(double inherentLoss, int64_t lossLimitedBitrate, int64_t sendingRate)
|
|
28
|
+
{
|
|
29
|
+
MS_TRACE();
|
|
30
|
+
|
|
31
|
+
// NOTE: Clamped rather than asserted because a candidate refined by
|
|
32
|
+
// Newton's method can land outside before being brought back.
|
|
33
|
+
inherentLoss = std::clamp(inherentLoss, 0.0, 1.0);
|
|
34
|
+
|
|
35
|
+
double lossProbability{ inherentLoss };
|
|
36
|
+
|
|
37
|
+
if (sendingRate > 0 && lossLimitedBitrate > 0 && sendingRate > lossLimitedBitrate)
|
|
38
|
+
{
|
|
39
|
+
lossProbability += (1 - inherentLoss) *
|
|
40
|
+
static_cast<double>(sendingRate - lossLimitedBitrate) /
|
|
41
|
+
static_cast<double>(sendingRate);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Leave room on both ends, since the likelihood takes the logarithm of
|
|
45
|
+
// this value and of its complement.
|
|
46
|
+
return std::clamp(lossProbability, 1.0e-6, 1.0 - 1.0e-6);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Size in kilobytes, which is the unit the likelihood is computed in so that
|
|
51
|
+
* its terms stay in a sane range.
|
|
52
|
+
*/
|
|
53
|
+
static double toKiloBytes(int64_t bytes)
|
|
54
|
+
{
|
|
55
|
+
MS_TRACE();
|
|
56
|
+
|
|
57
|
+
return static_cast<double>(bytes) / 1000.0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Instance methods. */
|
|
61
|
+
|
|
62
|
+
LossBasedController::LossBasedController() : LossBasedController(LossBasedControllerOptions{})
|
|
63
|
+
{
|
|
64
|
+
MS_TRACE();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
LossBasedController::LossBasedController(LossBasedControllerOptions options)
|
|
68
|
+
: options(std::move(options))
|
|
69
|
+
{
|
|
70
|
+
MS_TRACE();
|
|
71
|
+
|
|
72
|
+
// NOTE: Every option below is set from C++ alone, never from the network
|
|
73
|
+
// nor from the API, so a value out of range can only be a programming
|
|
74
|
+
// error. Ranges that are not checked here are the ones an option may
|
|
75
|
+
// legitimately take to the extreme, such as a factor that is meant to be
|
|
76
|
+
// disabled by setting it to zero.
|
|
77
|
+
MS_ASSERT(
|
|
78
|
+
this->options.bitrateRampupUpperBoundFactor > 1.0,
|
|
79
|
+
"bitrate rampup upper bound factor must be greater than 1 [value:%f]",
|
|
80
|
+
this->options.bitrateRampupUpperBoundFactor);
|
|
81
|
+
MS_ASSERT(
|
|
82
|
+
this->options.bitrateRampupUpperBoundFactorInHold > 1.0,
|
|
83
|
+
"bitrate rampup upper bound factor in hold must be greater than 1 [value:%f]",
|
|
84
|
+
this->options.bitrateRampupUpperBoundFactorInHold);
|
|
85
|
+
MS_ASSERT(
|
|
86
|
+
this->options.bitrateRampupHoldThreshold >= 0.0,
|
|
87
|
+
"bitrate rampup hold threshold must not be negative [value:%f]",
|
|
88
|
+
this->options.bitrateRampupHoldThreshold);
|
|
89
|
+
MS_ASSERT(
|
|
90
|
+
this->options.rampupAccelerationMaxFactor >= 0.0,
|
|
91
|
+
"rampup acceleration max factor must not be negative [value:%f]",
|
|
92
|
+
this->options.rampupAccelerationMaxFactor);
|
|
93
|
+
MS_ASSERT(
|
|
94
|
+
this->options.rampupAccelerationMaxoutTimeUs > 0,
|
|
95
|
+
"rampup acceleration maxout time must be positive [value:%" PRIi64 "]",
|
|
96
|
+
this->options.rampupAccelerationMaxoutTimeUs);
|
|
97
|
+
|
|
98
|
+
for (const auto candidateFactor : this->options.candidateFactors)
|
|
99
|
+
{
|
|
100
|
+
MS_ASSERT(
|
|
101
|
+
candidateFactor > 0.0, "candidate factors must be positive [value:%f]", candidateFactor);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// At least one candidate other than the current estimate must be
|
|
105
|
+
// reachable, or there is nothing for an update to choose from.
|
|
106
|
+
MS_ASSERT(
|
|
107
|
+
this->options.appendAcknowledgedRateCandidate ||
|
|
108
|
+
this->options.appendDelayBasedEstimateCandidate ||
|
|
109
|
+
std::ranges::any_of(
|
|
110
|
+
this->options.candidateFactors,
|
|
111
|
+
[](double candidateFactor) -> bool
|
|
112
|
+
{
|
|
113
|
+
return candidateFactor != 1.0;
|
|
114
|
+
}),
|
|
115
|
+
"no candidates can be generated, so give a candidate factor other than 1, or let the "
|
|
116
|
+
"acknowledged bitrate or the delay based estimate be candidates");
|
|
117
|
+
|
|
118
|
+
MS_ASSERT(
|
|
119
|
+
this->options.higherBitrateBiasFactor >= 0.0,
|
|
120
|
+
"higher bitrate bias factor must not be negative [value:%f]",
|
|
121
|
+
this->options.higherBitrateBiasFactor);
|
|
122
|
+
MS_ASSERT(
|
|
123
|
+
this->options.inherentLossLowerBound >= 0.0 && this->options.inherentLossLowerBound < 1.0,
|
|
124
|
+
"inherent loss lower bound must be in [0, 1) [value:%f]",
|
|
125
|
+
this->options.inherentLossLowerBound);
|
|
126
|
+
MS_ASSERT(
|
|
127
|
+
this->options.lossThresholdOfHighBitratePreference >= 0.0 &&
|
|
128
|
+
this->options.lossThresholdOfHighBitratePreference < 1.0,
|
|
129
|
+
"loss threshold of high bitrate preference must be in [0, 1) [value:%f]",
|
|
130
|
+
this->options.lossThresholdOfHighBitratePreference);
|
|
131
|
+
MS_ASSERT(
|
|
132
|
+
this->options.bitratePreferenceSmoothingFactor > 0.0 &&
|
|
133
|
+
this->options.bitratePreferenceSmoothingFactor <= 1.0,
|
|
134
|
+
"bitrate preference smoothing factor must be in (0, 1] [value:%f]",
|
|
135
|
+
this->options.bitratePreferenceSmoothingFactor);
|
|
136
|
+
MS_ASSERT(
|
|
137
|
+
this->options.inherentLossUpperBoundBitrateBalance > 0,
|
|
138
|
+
"inherent loss upper bound bitrate balance must be positive [value:%" PRIi64 "]",
|
|
139
|
+
this->options.inherentLossUpperBoundBitrateBalance);
|
|
140
|
+
MS_ASSERT(
|
|
141
|
+
this->options.inherentLossUpperBoundOffset >= this->options.inherentLossLowerBound &&
|
|
142
|
+
this->options.inherentLossUpperBoundOffset < 1.0,
|
|
143
|
+
"inherent loss upper bound offset must be in [inherentLossLowerBound, 1) [value:%f, "
|
|
144
|
+
"inherentLossLowerBound:%f]",
|
|
145
|
+
this->options.inherentLossUpperBoundOffset,
|
|
146
|
+
this->options.inherentLossLowerBound);
|
|
147
|
+
MS_ASSERT(
|
|
148
|
+
this->options.initialInherentLossEstimate >= 0.0 &&
|
|
149
|
+
this->options.initialInherentLossEstimate < 1.0,
|
|
150
|
+
"initial inherent loss estimate must be in [0, 1) [value:%f]",
|
|
151
|
+
this->options.initialInherentLossEstimate);
|
|
152
|
+
MS_ASSERT(
|
|
153
|
+
this->options.newtonIterations > 0,
|
|
154
|
+
"number of Newton iterations must be positive [value:%" PRIi64 "]",
|
|
155
|
+
this->options.newtonIterations);
|
|
156
|
+
MS_ASSERT(
|
|
157
|
+
this->options.newtonStepSize > 0.0,
|
|
158
|
+
"Newton step size must be positive [value:%f]",
|
|
159
|
+
this->options.newtonStepSize);
|
|
160
|
+
MS_ASSERT(
|
|
161
|
+
this->options.observationDurationLowerBoundUs > 0,
|
|
162
|
+
"observation duration lower bound must be positive [value:%" PRIi64 "]",
|
|
163
|
+
this->options.observationDurationLowerBoundUs);
|
|
164
|
+
// NOTE: The window is also what every observation is indexed modulo, so an
|
|
165
|
+
// empty one would divide by zero.
|
|
166
|
+
MS_ASSERT(
|
|
167
|
+
this->options.observationWindowSize >= 2,
|
|
168
|
+
"observation window size must be at least 2 [value:%" PRIi64 "]",
|
|
169
|
+
this->options.observationWindowSize);
|
|
170
|
+
MS_ASSERT(
|
|
171
|
+
this->options.sendingRateSmoothingFactor >= 0.0 &&
|
|
172
|
+
this->options.sendingRateSmoothingFactor < 1.0,
|
|
173
|
+
"sending rate smoothing factor must be in [0, 1) [value:%f]",
|
|
174
|
+
this->options.sendingRateSmoothingFactor);
|
|
175
|
+
MS_ASSERT(
|
|
176
|
+
this->options.immediateUpperBoundTemporalWeightFactor > 0.0 &&
|
|
177
|
+
this->options.immediateUpperBoundTemporalWeightFactor <= 1.0,
|
|
178
|
+
"immediate upper bound temporal weight factor must be in (0, 1] [value:%f]",
|
|
179
|
+
this->options.immediateUpperBoundTemporalWeightFactor);
|
|
180
|
+
MS_ASSERT(
|
|
181
|
+
this->options.immediateUpperBoundBitrateBalance > 0,
|
|
182
|
+
"immediate upper bound bitrate balance must be positive [value:%" PRIi64 "]",
|
|
183
|
+
this->options.immediateUpperBoundBitrateBalance);
|
|
184
|
+
MS_ASSERT(
|
|
185
|
+
this->options.immediateUpperBoundLossOffset >= 0.0 &&
|
|
186
|
+
this->options.immediateUpperBoundLossOffset < 1.0,
|
|
187
|
+
"immediate upper bound loss offset must be in [0, 1) [value:%f]",
|
|
188
|
+
this->options.immediateUpperBoundLossOffset);
|
|
189
|
+
MS_ASSERT(
|
|
190
|
+
this->options.temporalWeightFactor > 0.0 && this->options.temporalWeightFactor <= 1.0,
|
|
191
|
+
"temporal weight factor must be in (0, 1] [value:%f]",
|
|
192
|
+
this->options.temporalWeightFactor);
|
|
193
|
+
MS_ASSERT(
|
|
194
|
+
this->options.bitrateBackoffLowerBoundFactor <= 1.0,
|
|
195
|
+
"bitrate backoff lower bound factor must not be greater than 1 [value:%f]",
|
|
196
|
+
this->options.bitrateBackoffLowerBoundFactor);
|
|
197
|
+
MS_ASSERT(
|
|
198
|
+
this->options.maxIncreaseFactor > 0.0,
|
|
199
|
+
"max increase factor must be positive [value:%f]",
|
|
200
|
+
this->options.maxIncreaseFactor);
|
|
201
|
+
MS_ASSERT(
|
|
202
|
+
this->options.delayedIncreaseWindowUs > 0,
|
|
203
|
+
"delayed increase window must be positive [value:%" PRIi64 "]",
|
|
204
|
+
this->options.delayedIncreaseWindowUs);
|
|
205
|
+
MS_ASSERT(
|
|
206
|
+
this->options.minNumObservations > 0,
|
|
207
|
+
"min number of observations must be positive [value:%" PRIi64 "]",
|
|
208
|
+
this->options.minNumObservations);
|
|
209
|
+
MS_ASSERT(
|
|
210
|
+
this->options.lowerBoundByAckedRateFactor >= 0.0,
|
|
211
|
+
"lower bound by acknowledged rate factor must not be negative [value:%f]",
|
|
212
|
+
this->options.lowerBoundByAckedRateFactor);
|
|
213
|
+
|
|
214
|
+
this->currentBestEstimate.inherentLoss = this->options.initialInherentLossEstimate;
|
|
215
|
+
|
|
216
|
+
this->observations.resize(this->options.observationWindowSize);
|
|
217
|
+
this->temporalWeights.resize(this->options.observationWindowSize);
|
|
218
|
+
this->immediateUpperBoundTemporalWeights.resize(this->options.observationWindowSize);
|
|
219
|
+
|
|
220
|
+
CalculateTemporalWeights();
|
|
221
|
+
|
|
222
|
+
this->lastHoldInfo.durationUs = InitHoldDurationUs;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
bool LossBasedController::IsReady() const
|
|
226
|
+
{
|
|
227
|
+
MS_TRACE();
|
|
228
|
+
|
|
229
|
+
return this->currentBestEstimate.lossLimitedBitrate > 0 &&
|
|
230
|
+
this->numObservations >= this->options.minNumObservations;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
bool LossBasedController::IsReadyToUseInStartPhase() const
|
|
234
|
+
{
|
|
235
|
+
MS_TRACE();
|
|
236
|
+
|
|
237
|
+
return IsReady() && this->options.useInStartPhase;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
void LossBasedController::Reset()
|
|
241
|
+
{
|
|
242
|
+
MS_TRACE();
|
|
243
|
+
|
|
244
|
+
// Everything the constructor leaves behind, since what was observed
|
|
245
|
+
// describes a link that is not the one being used anymore. The temporal
|
|
246
|
+
// weights are left alone because they only depend on the options.
|
|
247
|
+
this->observations.assign(this->options.observationWindowSize, Observation{});
|
|
248
|
+
this->partialObservation = {};
|
|
249
|
+
|
|
250
|
+
this->currentBestEstimate = {};
|
|
251
|
+
this->currentBestEstimate.inherentLoss = this->options.initialInherentLossEstimate;
|
|
252
|
+
|
|
253
|
+
this->lastHoldInfo = {};
|
|
254
|
+
this->lastHoldInfo.durationUs = InitHoldDurationUs;
|
|
255
|
+
|
|
256
|
+
this->result = {};
|
|
257
|
+
this->numObservations = 0;
|
|
258
|
+
this->lastSendTimeOfLatestObservationUs.reset();
|
|
259
|
+
this->averageReportedLossRatio = 0.0;
|
|
260
|
+
this->acknowledgedBitrate = Types::BitrateInfinite;
|
|
261
|
+
this->delayBasedEstimate = Types::BitrateInfinite;
|
|
262
|
+
this->minBitrate = 0;
|
|
263
|
+
this->maxBitrate = Types::BitrateInfinite;
|
|
264
|
+
this->immediateUpperBoundBitrate.reset();
|
|
265
|
+
this->immediateLowerBoundBitrate.reset();
|
|
266
|
+
this->bitrateLimitInCurrentWindow = Types::BitrateInfinite;
|
|
267
|
+
this->recoveringAfterLossAtUs.reset();
|
|
268
|
+
this->lastBitrateReducedAtUs.reset();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
LossBasedController::Result LossBasedController::GetResult() const
|
|
272
|
+
{
|
|
273
|
+
MS_TRACE();
|
|
274
|
+
|
|
275
|
+
if (!IsReady())
|
|
276
|
+
{
|
|
277
|
+
return { .bitrate = this->delayBasedEstimate, .state = State::DELAY_BASED_ESTIMATE };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return this->result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
void LossBasedController::SetAcknowledgedBitrate(int64_t acknowledgedBitrate)
|
|
284
|
+
{
|
|
285
|
+
MS_TRACE();
|
|
286
|
+
|
|
287
|
+
this->acknowledgedBitrate = acknowledgedBitrate;
|
|
288
|
+
|
|
289
|
+
CalculateImmediateLowerBoundBitrate();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
void LossBasedController::SetBitrateLimits(int64_t minBitrate, int64_t maxBitrate)
|
|
293
|
+
{
|
|
294
|
+
MS_TRACE();
|
|
295
|
+
|
|
296
|
+
this->minBitrate = minBitrate;
|
|
297
|
+
this->maxBitrate = maxBitrate;
|
|
298
|
+
|
|
299
|
+
CalculateImmediateLowerBoundBitrate();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
void LossBasedController::SetBitrateEstimate(int64_t bitrate)
|
|
303
|
+
{
|
|
304
|
+
MS_TRACE();
|
|
305
|
+
|
|
306
|
+
this->currentBestEstimate.lossLimitedBitrate = bitrate;
|
|
307
|
+
this->result = { .bitrate = bitrate, .state = State::DELAY_BASED_ESTIMATE };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
void LossBasedController::UpdateBitrateEstimate(
|
|
311
|
+
const std::vector<Types::PacketResult>& packetResults, int64_t delayBasedEstimate, bool inAlr)
|
|
312
|
+
{
|
|
313
|
+
MS_TRACE();
|
|
314
|
+
|
|
315
|
+
this->delayBasedEstimate = delayBasedEstimate;
|
|
316
|
+
|
|
317
|
+
if (!PushBackObservation(packetResults) || !this->lastSendTimeOfLatestObservationUs.has_value())
|
|
318
|
+
{
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const int64_t lastSendTimeUs = this->lastSendTimeOfLatestObservationUs.value();
|
|
323
|
+
|
|
324
|
+
// Nothing has been estimated yet, so the delay based path is the only
|
|
325
|
+
// thing that can say where to start from.
|
|
326
|
+
if (this->currentBestEstimate.lossLimitedBitrate == 0)
|
|
327
|
+
{
|
|
328
|
+
if (delayBasedEstimate == Types::BitrateInfinite)
|
|
329
|
+
{
|
|
330
|
+
MS_WARN_TAG(bwe, "no delay based estimate to start the loss based one from");
|
|
331
|
+
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this->currentBestEstimate.lossLimitedBitrate = delayBasedEstimate;
|
|
336
|
+
this->result = { .bitrate = delayBasedEstimate, .state = State::DELAY_BASED_ESTIMATE };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Take the candidate whose pair of values makes the observed losses most
|
|
340
|
+
// likely, preferring the higher bitrate when two are equally likely.
|
|
341
|
+
ChannelParameters bestCandidate = this->currentBestEstimate;
|
|
342
|
+
double maxObjective{ std::numeric_limits<double>::lowest() };
|
|
343
|
+
|
|
344
|
+
for (ChannelParameters candidate : GetCandidates(inAlr))
|
|
345
|
+
{
|
|
346
|
+
ApplyNewtonsMethod(candidate);
|
|
347
|
+
|
|
348
|
+
const double candidateObjective = GetObjective(candidate);
|
|
349
|
+
|
|
350
|
+
if (
|
|
351
|
+
candidateObjective > maxObjective ||
|
|
352
|
+
(candidateObjective == maxObjective &&
|
|
353
|
+
candidate.lossLimitedBitrate > bestCandidate.lossLimitedBitrate))
|
|
354
|
+
{
|
|
355
|
+
maxObjective = candidateObjective;
|
|
356
|
+
bestCandidate = candidate;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (bestCandidate.lossLimitedBitrate < this->currentBestEstimate.lossLimitedBitrate)
|
|
361
|
+
{
|
|
362
|
+
this->lastBitrateReducedAtUs = lastSendTimeUs;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Don't increase the estimate while the loss observed is worse than the
|
|
366
|
+
// one the chosen candidate says the link has by itself, since then the
|
|
367
|
+
// excess is ours to fix.
|
|
368
|
+
if (
|
|
369
|
+
this->averageReportedLossRatio > bestCandidate.inherentLoss &&
|
|
370
|
+
this->options.notIncreaseIfInherentLossLessThanAverageLoss &&
|
|
371
|
+
this->currentBestEstimate.lossLimitedBitrate < bestCandidate.lossLimitedBitrate)
|
|
372
|
+
{
|
|
373
|
+
bestCandidate.lossLimitedBitrate = this->currentBestEstimate.lossLimitedBitrate;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (IsInLossLimitedState())
|
|
377
|
+
{
|
|
378
|
+
// Right after a decrease the estimate may not grow past what that
|
|
379
|
+
// decrease allowed, so that it doesn't jump straight back to the bitrate
|
|
380
|
+
// that caused the loss.
|
|
381
|
+
if (
|
|
382
|
+
this->recoveringAfterLossAtUs.has_value() &&
|
|
383
|
+
this->recoveringAfterLossAtUs.value() + this->options.delayedIncreaseWindowUs >
|
|
384
|
+
lastSendTimeUs &&
|
|
385
|
+
bestCandidate.lossLimitedBitrate > this->bitrateLimitInCurrentWindow)
|
|
386
|
+
{
|
|
387
|
+
bestCandidate.lossLimitedBitrate = this->bitrateLimitInCurrentWindow;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const bool increasingWhenLossLimited = IsEstimateIncreasingWhenLossLimited(
|
|
391
|
+
this->currentBestEstimate.lossLimitedBitrate, bestCandidate.lossLimitedBitrate);
|
|
392
|
+
|
|
393
|
+
// While loss limited, growing beyond what the link is known to be
|
|
394
|
+
// delivering is a guess, so it's bounded by the acknowledged bitrate.
|
|
395
|
+
if (increasingWhenLossLimited && this->acknowledgedBitrate != Types::BitrateInfinite)
|
|
396
|
+
{
|
|
397
|
+
double rampupFactor{ this->options.bitrateRampupUpperBoundFactor };
|
|
398
|
+
|
|
399
|
+
if (
|
|
400
|
+
this->lastHoldInfo.bitrate != Types::BitrateInfinite &&
|
|
401
|
+
this->acknowledgedBitrate <
|
|
402
|
+
BitrateUtils::ApplyBitrateFactor(
|
|
403
|
+
this->lastHoldInfo.bitrate, this->options.bitrateRampupHoldThreshold))
|
|
404
|
+
{
|
|
405
|
+
rampupFactor = this->options.bitrateRampupUpperBoundFactorInHold;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
bestCandidate.lossLimitedBitrate = std::max(
|
|
409
|
+
this->currentBestEstimate.lossLimitedBitrate,
|
|
410
|
+
std::min(
|
|
411
|
+
bestCandidate.lossLimitedBitrate,
|
|
412
|
+
BitrateUtils::ApplyBitrateFactor(this->acknowledgedBitrate, rampupFactor)));
|
|
413
|
+
|
|
414
|
+
// Growing by a single bit is what lets the state stop being
|
|
415
|
+
// decreasing. Without it, a bound that leaves the estimate untouched
|
|
416
|
+
// keeps it decreasing for good.
|
|
417
|
+
if (
|
|
418
|
+
this->result.state == State::DECREASING &&
|
|
419
|
+
bestCandidate.lossLimitedBitrate == this->currentBestEstimate.lossLimitedBitrate)
|
|
420
|
+
{
|
|
421
|
+
bestCandidate.lossLimitedBitrate =
|
|
422
|
+
BitrateUtils::AddBitrates(this->currentBestEstimate.lossLimitedBitrate, 1);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const int64_t boundedBitrate = std::max(
|
|
428
|
+
GetImmediateLowerBoundBitrate(),
|
|
429
|
+
std::min(
|
|
430
|
+
{ bestCandidate.lossLimitedBitrate,
|
|
431
|
+
GetImmediateUpperBoundBitrate(),
|
|
432
|
+
this->delayBasedEstimate }));
|
|
433
|
+
|
|
434
|
+
// A bitrate that had to be brought down to its bounds is not an estimate
|
|
435
|
+
// of the link anymore, so the pair of values is not kept as such.
|
|
436
|
+
if (this->options.boundBestCandidate && boundedBitrate < bestCandidate.lossLimitedBitrate)
|
|
437
|
+
{
|
|
438
|
+
this->currentBestEstimate.lossLimitedBitrate = boundedBitrate;
|
|
439
|
+
this->currentBestEstimate.inherentLoss = 0;
|
|
440
|
+
}
|
|
441
|
+
else
|
|
442
|
+
{
|
|
443
|
+
this->currentBestEstimate = bestCandidate;
|
|
444
|
+
|
|
445
|
+
// A link is never worth describing as delivering less than what it is
|
|
446
|
+
// known to be delivering, unless that bound has been turned off.
|
|
447
|
+
if (this->options.lowerBoundByAckedRateFactor > 0.0)
|
|
448
|
+
{
|
|
449
|
+
this->currentBestEstimate.lossLimitedBitrate =
|
|
450
|
+
std::max(this->currentBestEstimate.lossLimitedBitrate, GetImmediateLowerBoundBitrate());
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// While holding, the estimate may not grow above the bitrate being held,
|
|
455
|
+
// which is the whole point of holding.
|
|
456
|
+
if (
|
|
457
|
+
this->result.state == State::DECREASING && this->lastHoldInfo.atUs > lastSendTimeUs &&
|
|
458
|
+
boundedBitrate < this->delayBasedEstimate)
|
|
459
|
+
{
|
|
460
|
+
// A link is never worth holding below what it is known to be
|
|
461
|
+
// delivering, unless that bound has been turned off.
|
|
462
|
+
if (this->options.lowerBoundByAckedRateFactor > 0.0)
|
|
463
|
+
{
|
|
464
|
+
this->lastHoldInfo.bitrate =
|
|
465
|
+
std::max(GetImmediateLowerBoundBitrate(), this->lastHoldInfo.bitrate);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
this->result.bitrate = std::min(this->lastHoldInfo.bitrate, boundedBitrate);
|
|
469
|
+
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (
|
|
474
|
+
IsEstimateIncreasingWhenLossLimited(this->result.bitrate, boundedBitrate) &&
|
|
475
|
+
boundedBitrate < this->delayBasedEstimate && boundedBitrate < this->maxBitrate)
|
|
476
|
+
{
|
|
477
|
+
this->result.state = State::INCREASING;
|
|
478
|
+
}
|
|
479
|
+
else if (boundedBitrate < this->delayBasedEstimate && boundedBitrate < this->maxBitrate)
|
|
480
|
+
{
|
|
481
|
+
// A factor of zero means that no hold is ever entered, since each hold
|
|
482
|
+
// would last no time at all.
|
|
483
|
+
if (this->result.state != State::DECREASING && this->options.holdDurationFactor > 0.0)
|
|
484
|
+
{
|
|
485
|
+
MS_DEBUG_DEV(
|
|
486
|
+
"switching to hold [bitrate:%" PRIi64 ", durationMs:%" PRIi64 ", averageLoss:%f]",
|
|
487
|
+
boundedBitrate,
|
|
488
|
+
this->lastHoldInfo.durationUs / 1000,
|
|
489
|
+
this->averageReportedLossRatio);
|
|
490
|
+
|
|
491
|
+
this->lastHoldInfo = { .atUs = lastSendTimeUs + this->lastHoldInfo.durationUs,
|
|
492
|
+
.durationUs = std::min<int64_t>(
|
|
493
|
+
MaxHoldDurationUs,
|
|
494
|
+
std::llround(
|
|
495
|
+
static_cast<double>(this->lastHoldInfo.durationUs) *
|
|
496
|
+
this->options.holdDurationFactor)),
|
|
497
|
+
.bitrate = boundedBitrate };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
this->result.state = State::DECREASING;
|
|
501
|
+
}
|
|
502
|
+
else
|
|
503
|
+
{
|
|
504
|
+
// The delay based path is what limits us, so any hold that was in place
|
|
505
|
+
// would only keep the estimate low for no reason.
|
|
506
|
+
this->lastHoldInfo = { .atUs = 0,
|
|
507
|
+
.durationUs = InitHoldDurationUs,
|
|
508
|
+
.bitrate = Types::BitrateInfinite };
|
|
509
|
+
|
|
510
|
+
this->result.state = State::DELAY_BASED_ESTIMATE;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this->result.bitrate = boundedBitrate;
|
|
514
|
+
|
|
515
|
+
// Open a new window in which the estimate is not allowed to grow freely,
|
|
516
|
+
// so that a link that keeps losing packets is approached gradually.
|
|
517
|
+
if (
|
|
518
|
+
IsInLossLimitedState() &&
|
|
519
|
+
(!this->recoveringAfterLossAtUs.has_value() ||
|
|
520
|
+
this->recoveringAfterLossAtUs.value() + this->options.delayedIncreaseWindowUs <
|
|
521
|
+
lastSendTimeUs))
|
|
522
|
+
{
|
|
523
|
+
this->bitrateLimitInCurrentWindow = std::max<int64_t>(
|
|
524
|
+
Consts::BweMinBitrate,
|
|
525
|
+
BitrateUtils::ApplyBitrateFactor(
|
|
526
|
+
this->currentBestEstimate.lossLimitedBitrate, this->options.maxIncreaseFactor));
|
|
527
|
+
|
|
528
|
+
this->recoveringAfterLossAtUs = lastSendTimeUs;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
bool LossBasedController::PushBackObservation(const std::vector<Types::PacketResult>& packetResults)
|
|
533
|
+
{
|
|
534
|
+
MS_TRACE();
|
|
535
|
+
|
|
536
|
+
if (packetResults.empty())
|
|
537
|
+
{
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
int64_t firstSendTimeUs{ Types::TimeUsInfinite };
|
|
542
|
+
int64_t lastSendTimeUs{ 0 };
|
|
543
|
+
bool anySendTime{ false };
|
|
544
|
+
|
|
545
|
+
for (const auto& packetResult : packetResults)
|
|
546
|
+
{
|
|
547
|
+
const auto sendTimeUs = packetResult.sentPacket.sendTimeUs;
|
|
548
|
+
|
|
549
|
+
// A packet with no send time belongs to no span of send times, and its
|
|
550
|
+
// bytes cannot be counted either: the duration of the observation comes
|
|
551
|
+
// from the instants of the other packets, so adding its size would
|
|
552
|
+
// inflate the sending rate attributed to them.
|
|
553
|
+
if (sendTimeUs == Types::TimeUsInfinite)
|
|
554
|
+
{
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (packetResult.IsReceived())
|
|
559
|
+
{
|
|
560
|
+
this->partialObservation.lostPackets.erase(packetResult.sentPacket.sequenceNumber);
|
|
561
|
+
}
|
|
562
|
+
else
|
|
563
|
+
{
|
|
564
|
+
this->partialObservation.lostPackets.emplace(
|
|
565
|
+
packetResult.sentPacket.sequenceNumber,
|
|
566
|
+
static_cast<int64_t>(packetResult.sentPacket.size));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
this->partialObservation.numPackets += 1;
|
|
570
|
+
this->partialObservation.sizeBytes += static_cast<int64_t>(packetResult.sentPacket.size);
|
|
571
|
+
|
|
572
|
+
firstSendTimeUs = std::min(firstSendTimeUs, sendTimeUs);
|
|
573
|
+
lastSendTimeUs = std::max(lastSendTimeUs, sendTimeUs);
|
|
574
|
+
anySendTime = true;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (!anySendTime)
|
|
578
|
+
{
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// This is the first feedback ever received.
|
|
583
|
+
if (!this->lastSendTimeOfLatestObservationUs.has_value())
|
|
584
|
+
{
|
|
585
|
+
this->lastSendTimeOfLatestObservationUs = firstSendTimeUs;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const int64_t observationDurationUs =
|
|
589
|
+
lastSendTimeUs - this->lastSendTimeOfLatestObservationUs.value();
|
|
590
|
+
|
|
591
|
+
// Too short a span to tell anything.
|
|
592
|
+
if (observationDurationUs <= 0 || observationDurationUs < this->options.observationDurationLowerBoundUs)
|
|
593
|
+
{
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
this->lastSendTimeOfLatestObservationUs = lastSendTimeUs;
|
|
598
|
+
|
|
599
|
+
int64_t lostSizeBytes{ 0 };
|
|
600
|
+
|
|
601
|
+
for (const auto& kv : this->partialObservation.lostPackets)
|
|
602
|
+
{
|
|
603
|
+
lostSizeBytes += kv.second;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
Observation observation;
|
|
607
|
+
|
|
608
|
+
observation.numPackets = this->partialObservation.numPackets;
|
|
609
|
+
observation.numLostPackets = static_cast<int64_t>(this->partialObservation.lostPackets.size());
|
|
610
|
+
observation.numReceivedPackets = observation.numPackets - observation.numLostPackets;
|
|
611
|
+
observation.sizeBytes = this->partialObservation.sizeBytes;
|
|
612
|
+
observation.lostSizeBytes = lostSizeBytes;
|
|
613
|
+
observation.sendingRate =
|
|
614
|
+
GetSendingRate((this->partialObservation.sizeBytes * 8 * 1000000) / observationDurationUs);
|
|
615
|
+
observation.id = this->numObservations++;
|
|
616
|
+
|
|
617
|
+
this->observations[observation.id % this->options.observationWindowSize] = observation;
|
|
618
|
+
|
|
619
|
+
this->partialObservation = PartialObservation();
|
|
620
|
+
|
|
621
|
+
UpdateAverageReportedLossRatio();
|
|
622
|
+
CalculateImmediateUpperBoundBitrate();
|
|
623
|
+
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
std::vector<LossBasedController::ChannelParameters> LossBasedController::GetCandidates(bool inAlr) const
|
|
628
|
+
{
|
|
629
|
+
MS_TRACE();
|
|
630
|
+
|
|
631
|
+
std::vector<int64_t> bitrates;
|
|
632
|
+
|
|
633
|
+
// The factors plus the acknowledged bitrate, the delay based estimate and
|
|
634
|
+
// the bound that the observed loss puts on the estimate.
|
|
635
|
+
bitrates.reserve(this->options.candidateFactors.size() + 3);
|
|
636
|
+
|
|
637
|
+
for (const double candidateFactor : this->options.candidateFactors)
|
|
638
|
+
{
|
|
639
|
+
bitrates.push_back(
|
|
640
|
+
BitrateUtils::ApplyBitrateFactor(
|
|
641
|
+
this->currentBestEstimate.lossLimitedBitrate, candidateFactor));
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// While not sending enough to fill the link, what it delivers says nothing
|
|
645
|
+
// about what it could deliver, so it's no candidate.
|
|
646
|
+
if (
|
|
647
|
+
this->acknowledgedBitrate != Types::BitrateInfinite &&
|
|
648
|
+
this->options.appendAcknowledgedRateCandidate &&
|
|
649
|
+
!(this->options.notUseAckedRateInAlr && inAlr))
|
|
650
|
+
{
|
|
651
|
+
bitrates.push_back(
|
|
652
|
+
BitrateUtils::ApplyBitrateFactor(
|
|
653
|
+
this->acknowledgedBitrate, this->options.bitrateBackoffLowerBoundFactor));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (
|
|
657
|
+
this->delayBasedEstimate != Types::BitrateInfinite &&
|
|
658
|
+
this->options.appendDelayBasedEstimateCandidate &&
|
|
659
|
+
this->delayBasedEstimate > this->currentBestEstimate.lossLimitedBitrate)
|
|
660
|
+
{
|
|
661
|
+
bitrates.push_back(this->delayBasedEstimate);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Coming all the way down to that bound in one step rather than gradually
|
|
665
|
+
// only makes sense while not filling the link, since then the loss is not
|
|
666
|
+
// ours to fix by sending less.
|
|
667
|
+
if (
|
|
668
|
+
inAlr && this->options.appendUpperBoundCandidateInAlr &&
|
|
669
|
+
this->currentBestEstimate.lossLimitedBitrate > GetImmediateUpperBoundBitrate())
|
|
670
|
+
{
|
|
671
|
+
bitrates.push_back(GetImmediateUpperBoundBitrate());
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const int64_t candidateBitrateUpperBound = GetCandidateBitrateUpperBound();
|
|
675
|
+
|
|
676
|
+
std::vector<ChannelParameters> candidates;
|
|
677
|
+
|
|
678
|
+
candidates.reserve(bitrates.size());
|
|
679
|
+
|
|
680
|
+
for (const int64_t bitrate : bitrates)
|
|
681
|
+
{
|
|
682
|
+
ChannelParameters candidate = this->currentBestEstimate;
|
|
683
|
+
|
|
684
|
+
candidate.lossLimitedBitrate = std::min(
|
|
685
|
+
bitrate,
|
|
686
|
+
std::max(this->currentBestEstimate.lossLimitedBitrate, candidateBitrateUpperBound));
|
|
687
|
+
candidate.inherentLoss = GetFeasibleInherentLoss(candidate);
|
|
688
|
+
|
|
689
|
+
candidates.push_back(candidate);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return candidates;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
int64_t LossBasedController::GetCandidateBitrateUpperBound() const
|
|
696
|
+
{
|
|
697
|
+
MS_TRACE();
|
|
698
|
+
|
|
699
|
+
int64_t candidateBitrateUpperBound{ this->maxBitrate };
|
|
700
|
+
|
|
701
|
+
if (IsInLossLimitedState() && this->bitrateLimitInCurrentWindow != Types::BitrateInfinite)
|
|
702
|
+
{
|
|
703
|
+
candidateBitrateUpperBound = this->bitrateLimitInCurrentWindow;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (this->acknowledgedBitrate == Types::BitrateInfinite)
|
|
707
|
+
{
|
|
708
|
+
return candidateBitrateUpperBound;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// The longer it's been since the estimate was last reduced, the more it is
|
|
712
|
+
// allowed to grow beyond its usual bound.
|
|
713
|
+
if (this->options.rampupAccelerationMaxFactor > 0.0)
|
|
714
|
+
{
|
|
715
|
+
// Never having been reduced counts as the longest time possible, which
|
|
716
|
+
// is what gives the fullest acceleration.
|
|
717
|
+
const int64_t sinceBitrateReducedUs =
|
|
718
|
+
this->lastBitrateReducedAtUs.has_value() &&
|
|
719
|
+
this->lastSendTimeOfLatestObservationUs.has_value()
|
|
720
|
+
? std::min(
|
|
721
|
+
this->options.rampupAccelerationMaxoutTimeUs,
|
|
722
|
+
std::max<int64_t>(
|
|
723
|
+
this->lastSendTimeOfLatestObservationUs.value() -
|
|
724
|
+
this->lastBitrateReducedAtUs.value(),
|
|
725
|
+
0))
|
|
726
|
+
: this->options.rampupAccelerationMaxoutTimeUs;
|
|
727
|
+
const double rampupAcceleration =
|
|
728
|
+
this->options.rampupAccelerationMaxFactor * static_cast<double>(sinceBitrateReducedUs) /
|
|
729
|
+
static_cast<double>(this->options.rampupAccelerationMaxoutTimeUs);
|
|
730
|
+
|
|
731
|
+
candidateBitrateUpperBound = BitrateUtils::AddBitrates(
|
|
732
|
+
candidateBitrateUpperBound,
|
|
733
|
+
BitrateUtils::ApplyBitrateFactor(this->acknowledgedBitrate, rampupAcceleration));
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return candidateBitrateUpperBound;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
double LossBasedController::GetObjective(const ChannelParameters& channelParameters) const
|
|
740
|
+
{
|
|
741
|
+
MS_TRACE();
|
|
742
|
+
|
|
743
|
+
double objective{ 0.0 };
|
|
744
|
+
|
|
745
|
+
const double highBitrateBias = GetHighBitrateBias(channelParameters.lossLimitedBitrate);
|
|
746
|
+
|
|
747
|
+
for (const auto& observation : this->observations)
|
|
748
|
+
{
|
|
749
|
+
if (!observation.IsInitialized())
|
|
750
|
+
{
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const double lossProbability = getLossProbability(
|
|
755
|
+
channelParameters.inherentLoss,
|
|
756
|
+
channelParameters.lossLimitedBitrate,
|
|
757
|
+
observation.sendingRate);
|
|
758
|
+
|
|
759
|
+
const double temporalWeight =
|
|
760
|
+
this->temporalWeights[(this->numObservations - 1) - observation.id];
|
|
761
|
+
|
|
762
|
+
if (this->options.useByteLossRate)
|
|
763
|
+
{
|
|
764
|
+
objective +=
|
|
765
|
+
temporalWeight * ((toKiloBytes(observation.lostSizeBytes) * std::log(lossProbability)) +
|
|
766
|
+
(toKiloBytes(observation.sizeBytes - observation.lostSizeBytes) *
|
|
767
|
+
std::log(1.0 - lossProbability)));
|
|
768
|
+
objective += temporalWeight * highBitrateBias * toKiloBytes(observation.sizeBytes);
|
|
769
|
+
}
|
|
770
|
+
else
|
|
771
|
+
{
|
|
772
|
+
objective +=
|
|
773
|
+
temporalWeight *
|
|
774
|
+
((static_cast<double>(observation.numLostPackets) * std::log(lossProbability)) +
|
|
775
|
+
(static_cast<double>(observation.numReceivedPackets) * std::log(1.0 - lossProbability)));
|
|
776
|
+
objective += temporalWeight * highBitrateBias * static_cast<double>(observation.numPackets);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return objective;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
LossBasedController::Derivatives LossBasedController::GetDerivatives(
|
|
784
|
+
const ChannelParameters& channelParameters) const
|
|
785
|
+
{
|
|
786
|
+
MS_TRACE();
|
|
787
|
+
|
|
788
|
+
Derivatives derivatives;
|
|
789
|
+
|
|
790
|
+
for (const auto& observation : this->observations)
|
|
791
|
+
{
|
|
792
|
+
if (!observation.IsInitialized())
|
|
793
|
+
{
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const double lossProbability = getLossProbability(
|
|
798
|
+
channelParameters.inherentLoss,
|
|
799
|
+
channelParameters.lossLimitedBitrate,
|
|
800
|
+
observation.sendingRate);
|
|
801
|
+
|
|
802
|
+
const double temporalWeight =
|
|
803
|
+
this->temporalWeights[(this->numObservations - 1) - observation.id];
|
|
804
|
+
|
|
805
|
+
if (this->options.useByteLossRate)
|
|
806
|
+
{
|
|
807
|
+
derivatives.first +=
|
|
808
|
+
temporalWeight * ((toKiloBytes(observation.lostSizeBytes) / lossProbability) -
|
|
809
|
+
(toKiloBytes(observation.sizeBytes - observation.lostSizeBytes) /
|
|
810
|
+
(1.0 - lossProbability)));
|
|
811
|
+
derivatives.second -=
|
|
812
|
+
temporalWeight * ((toKiloBytes(observation.lostSizeBytes) / std::pow(lossProbability, 2)) +
|
|
813
|
+
(toKiloBytes(observation.sizeBytes - observation.lostSizeBytes) /
|
|
814
|
+
std::pow(1.0 - lossProbability, 2)));
|
|
815
|
+
}
|
|
816
|
+
else
|
|
817
|
+
{
|
|
818
|
+
derivatives.first +=
|
|
819
|
+
temporalWeight *
|
|
820
|
+
((static_cast<double>(observation.numLostPackets) / lossProbability) -
|
|
821
|
+
(static_cast<double>(observation.numReceivedPackets) / (1.0 - lossProbability)));
|
|
822
|
+
derivatives.second -=
|
|
823
|
+
temporalWeight *
|
|
824
|
+
((static_cast<double>(observation.numLostPackets) / std::pow(lossProbability, 2)) +
|
|
825
|
+
(static_cast<double>(observation.numReceivedPackets) /
|
|
826
|
+
std::pow(1.0 - lossProbability, 2)));
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// NOTE: The second derivative is negative by construction, so a value of
|
|
831
|
+
// zero can only come from rounding and would make the step below divide by
|
|
832
|
+
// zero.
|
|
833
|
+
if (derivatives.second >= 0.0)
|
|
834
|
+
{
|
|
835
|
+
derivatives.second = -1.0e-6;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return derivatives;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
void LossBasedController::ApplyNewtonsMethod(ChannelParameters& channelParameters) const
|
|
842
|
+
{
|
|
843
|
+
MS_TRACE();
|
|
844
|
+
|
|
845
|
+
if (this->numObservations <= 0)
|
|
846
|
+
{
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
for (int64_t i{ 0 }; i < this->options.newtonIterations; ++i)
|
|
851
|
+
{
|
|
852
|
+
const Derivatives derivatives = GetDerivatives(channelParameters);
|
|
853
|
+
|
|
854
|
+
channelParameters.inherentLoss -=
|
|
855
|
+
this->options.newtonStepSize * derivatives.first / derivatives.second;
|
|
856
|
+
channelParameters.inherentLoss = GetFeasibleInherentLoss(channelParameters);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
double LossBasedController::GetFeasibleInherentLoss(const ChannelParameters& channelParameters) const
|
|
861
|
+
{
|
|
862
|
+
MS_TRACE();
|
|
863
|
+
|
|
864
|
+
return std::min(
|
|
865
|
+
std::max(channelParameters.inherentLoss, this->options.inherentLossLowerBound),
|
|
866
|
+
GetInherentLossUpperBound(channelParameters.lossLimitedBitrate));
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
double LossBasedController::GetInherentLossUpperBound(int64_t bitrate) const
|
|
870
|
+
{
|
|
871
|
+
MS_TRACE();
|
|
872
|
+
|
|
873
|
+
if (bitrate == 0)
|
|
874
|
+
{
|
|
875
|
+
return 1.0;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// The lower the bitrate, the more loss a link is allowed to be said to
|
|
879
|
+
// have by itself, since at a low enough bitrate the traffic cannot be the
|
|
880
|
+
// cause.
|
|
881
|
+
const double inherentLossUpperBound =
|
|
882
|
+
this->options.inherentLossUpperBoundOffset +
|
|
883
|
+
(static_cast<double>(this->options.inherentLossUpperBoundBitrateBalance) /
|
|
884
|
+
static_cast<double>(bitrate));
|
|
885
|
+
|
|
886
|
+
return std::min(inherentLossUpperBound, 1.0);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
double LossBasedController::AdjustBiasFactor(double lossRate, double biasFactor) const
|
|
890
|
+
{
|
|
891
|
+
MS_TRACE();
|
|
892
|
+
|
|
893
|
+
return biasFactor * (this->options.lossThresholdOfHighBitratePreference - lossRate) /
|
|
894
|
+
(this->options.bitratePreferenceSmoothingFactor +
|
|
895
|
+
std::abs(this->options.lossThresholdOfHighBitratePreference - lossRate));
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
double LossBasedController::GetHighBitrateBias(int64_t bitrate) const
|
|
899
|
+
{
|
|
900
|
+
MS_TRACE();
|
|
901
|
+
|
|
902
|
+
if (bitrate <= 0)
|
|
903
|
+
{
|
|
904
|
+
return 0.0;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// NOTE: The bias is measured over a bitrate expressed with a resolution of
|
|
908
|
+
// 1 kbps, so that it doesn't move with a fraction of it.
|
|
909
|
+
const double bitrateKbps = std::round(static_cast<double>(bitrate) / 1000.0);
|
|
910
|
+
|
|
911
|
+
return (AdjustBiasFactor(this->averageReportedLossRatio, this->options.higherBitrateBiasFactor) *
|
|
912
|
+
bitrateKbps) +
|
|
913
|
+
(AdjustBiasFactor(
|
|
914
|
+
this->averageReportedLossRatio, this->options.higherLogBitrateBiasFactor) *
|
|
915
|
+
std::log(1.0 + bitrateKbps));
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
int64_t LossBasedController::GetSendingRate(int64_t instantSendingRate) const
|
|
919
|
+
{
|
|
920
|
+
MS_TRACE();
|
|
921
|
+
|
|
922
|
+
if (this->numObservations <= 0)
|
|
923
|
+
{
|
|
924
|
+
return instantSendingRate;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const int64_t latestObservationIdx =
|
|
928
|
+
(this->numObservations - 1) % this->options.observationWindowSize;
|
|
929
|
+
const int64_t previousSendingRate = this->observations[latestObservationIdx].sendingRate;
|
|
930
|
+
|
|
931
|
+
// Each share is rounded on its own before they are added, so that the
|
|
932
|
+
// smoothed rate doesn't depend on how the two of them happen to split.
|
|
933
|
+
return BitrateUtils::AddBitrates(
|
|
934
|
+
BitrateUtils::ApplyBitrateFactor(
|
|
935
|
+
previousSendingRate, this->options.sendingRateSmoothingFactor),
|
|
936
|
+
BitrateUtils::ApplyBitrateFactor(
|
|
937
|
+
instantSendingRate, 1.0 - this->options.sendingRateSmoothingFactor));
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
void LossBasedController::UpdateAverageReportedLossRatio()
|
|
941
|
+
{
|
|
942
|
+
MS_TRACE();
|
|
943
|
+
|
|
944
|
+
this->averageReportedLossRatio = this->options.useByteLossRate
|
|
945
|
+
? CalculateAverageReportedByteLossRatio()
|
|
946
|
+
: CalculateAverageReportedPacketLossRatio();
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
double LossBasedController::CalculateAverageReportedPacketLossRatio() const
|
|
950
|
+
{
|
|
951
|
+
MS_TRACE();
|
|
952
|
+
|
|
953
|
+
if (this->numObservations <= 0)
|
|
954
|
+
{
|
|
955
|
+
return 0.0;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
double numPackets{ 0.0 };
|
|
959
|
+
double numLostPackets{ 0.0 };
|
|
960
|
+
|
|
961
|
+
for (const auto& observation : this->observations)
|
|
962
|
+
{
|
|
963
|
+
if (!observation.IsInitialized())
|
|
964
|
+
{
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const double temporalWeight =
|
|
969
|
+
this->immediateUpperBoundTemporalWeights[(this->numObservations - 1) - observation.id];
|
|
970
|
+
|
|
971
|
+
numPackets += temporalWeight * static_cast<double>(observation.numPackets);
|
|
972
|
+
numLostPackets += temporalWeight * static_cast<double>(observation.numLostPackets);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// NOTE: Nothing was sent at all, so there is no loss to tell. Without this
|
|
976
|
+
// the division would give a NaN, which makes every comparison it takes
|
|
977
|
+
// part in false and silently freezes the estimate for good.
|
|
978
|
+
if (numPackets == 0.0)
|
|
979
|
+
{
|
|
980
|
+
return 0.0;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return numLostPackets / numPackets;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
double LossBasedController::CalculateAverageReportedByteLossRatio() const
|
|
987
|
+
{
|
|
988
|
+
MS_TRACE();
|
|
989
|
+
|
|
990
|
+
if (this->numObservations <= 0)
|
|
991
|
+
{
|
|
992
|
+
return 0.0;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// NOTE: The weighted sizes are counted in whole bytes, since a fraction of
|
|
996
|
+
// a byte is not something that was sent.
|
|
997
|
+
int64_t totalBytes{ 0 };
|
|
998
|
+
int64_t lostBytes{ 0 };
|
|
999
|
+
double minLossRate{ 1.0 };
|
|
1000
|
+
double maxLossRate{ 0.0 };
|
|
1001
|
+
int64_t minLostBytes{ 0 };
|
|
1002
|
+
int64_t maxLostBytes{ 0 };
|
|
1003
|
+
int64_t minBytesReceived{ 0 };
|
|
1004
|
+
int64_t maxBytesReceived{ 0 };
|
|
1005
|
+
int64_t sendingRateOfMaxLossObservation{ 0 };
|
|
1006
|
+
|
|
1007
|
+
for (const auto& observation : this->observations)
|
|
1008
|
+
{
|
|
1009
|
+
if (!observation.IsInitialized())
|
|
1010
|
+
{
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const double temporalWeight =
|
|
1015
|
+
this->immediateUpperBoundTemporalWeights[(this->numObservations - 1) - observation.id];
|
|
1016
|
+
|
|
1017
|
+
totalBytes += std::llround(temporalWeight * static_cast<double>(observation.sizeBytes));
|
|
1018
|
+
lostBytes += std::llround(temporalWeight * static_cast<double>(observation.lostSizeBytes));
|
|
1019
|
+
|
|
1020
|
+
const double lossRate = observation.sizeBytes != 0
|
|
1021
|
+
? static_cast<double>(observation.lostSizeBytes) /
|
|
1022
|
+
static_cast<double>(observation.sizeBytes)
|
|
1023
|
+
: 0.0;
|
|
1024
|
+
|
|
1025
|
+
// The highest and the lowest are left out only once there are enough
|
|
1026
|
+
// observations for that to still leave something.
|
|
1027
|
+
if (this->numObservations <= 3)
|
|
1028
|
+
{
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
if (lossRate > maxLossRate)
|
|
1033
|
+
{
|
|
1034
|
+
maxLossRate = lossRate;
|
|
1035
|
+
maxLostBytes =
|
|
1036
|
+
std::llround(temporalWeight * static_cast<double>(observation.lostSizeBytes));
|
|
1037
|
+
maxBytesReceived =
|
|
1038
|
+
std::llround(temporalWeight * static_cast<double>(observation.sizeBytes));
|
|
1039
|
+
sendingRateOfMaxLossObservation = observation.sendingRate;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if (lossRate < minLossRate)
|
|
1043
|
+
{
|
|
1044
|
+
minLossRate = lossRate;
|
|
1045
|
+
minLostBytes =
|
|
1046
|
+
std::llround(temporalWeight * static_cast<double>(observation.lostSizeBytes));
|
|
1047
|
+
minBytesReceived =
|
|
1048
|
+
std::llround(temporalWeight * static_cast<double>(observation.sizeBytes));
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// NOTE: Nothing was sent at all, so there is no loss to tell. Without this
|
|
1053
|
+
// the division below would give a NaN, which makes every comparison it
|
|
1054
|
+
// takes part in false and silently freezes the estimate for good.
|
|
1055
|
+
if (totalBytes == 0)
|
|
1056
|
+
{
|
|
1057
|
+
return 0.0;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// A sudden jump of the sending rate explains the loss of that observation
|
|
1061
|
+
// on its own, so it's not a spike to be filtered out.
|
|
1062
|
+
if (BitrateUtils::ApplyBitrateFactor(GetMedianSendingRate(), this->options.medianSendingRateFactor) <= sendingRateOfMaxLossObservation)
|
|
1063
|
+
{
|
|
1064
|
+
return static_cast<double>(lostBytes) / static_cast<double>(totalBytes);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// It could happen if the window was of two observations.
|
|
1068
|
+
if (totalBytes == maxBytesReceived + minBytesReceived)
|
|
1069
|
+
{
|
|
1070
|
+
return static_cast<double>(lostBytes) / static_cast<double>(totalBytes);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
return static_cast<double>(lostBytes - minLostBytes - maxLostBytes) /
|
|
1074
|
+
static_cast<double>(totalBytes - maxBytesReceived - minBytesReceived);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
int64_t LossBasedController::GetMedianSendingRate() const
|
|
1078
|
+
{
|
|
1079
|
+
MS_TRACE();
|
|
1080
|
+
|
|
1081
|
+
std::vector<int64_t> sendingRates;
|
|
1082
|
+
|
|
1083
|
+
sendingRates.reserve(this->observations.size());
|
|
1084
|
+
|
|
1085
|
+
for (const auto& observation : this->observations)
|
|
1086
|
+
{
|
|
1087
|
+
if (!observation.IsInitialized() || observation.sendingRate == 0)
|
|
1088
|
+
{
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
sendingRates.push_back(observation.sendingRate);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
if (sendingRates.empty())
|
|
1096
|
+
{
|
|
1097
|
+
return 0;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
std::ranges::sort(sendingRates);
|
|
1101
|
+
|
|
1102
|
+
if (sendingRates.size() % 2 == 0)
|
|
1103
|
+
{
|
|
1104
|
+
return (sendingRates[(sendingRates.size() / 2) - 1] + sendingRates[sendingRates.size() / 2]) /
|
|
1105
|
+
2;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
return sendingRates[sendingRates.size() / 2];
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
int64_t LossBasedController::GetImmediateUpperBoundBitrate() const
|
|
1112
|
+
{
|
|
1113
|
+
MS_TRACE();
|
|
1114
|
+
|
|
1115
|
+
return this->immediateUpperBoundBitrate.value_or(this->maxBitrate);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
void LossBasedController::CalculateImmediateUpperBoundBitrate()
|
|
1119
|
+
{
|
|
1120
|
+
MS_TRACE();
|
|
1121
|
+
|
|
1122
|
+
int64_t bitrate{ this->maxBitrate };
|
|
1123
|
+
|
|
1124
|
+
// Beyond a bit of loss, the more is observed the less the link is allowed
|
|
1125
|
+
// to be told to carry, without waiting for the estimate to converge.
|
|
1126
|
+
if (this->averageReportedLossRatio > this->options.immediateUpperBoundLossOffset)
|
|
1127
|
+
{
|
|
1128
|
+
const double boundBitrate =
|
|
1129
|
+
static_cast<double>(this->options.immediateUpperBoundBitrateBalance) /
|
|
1130
|
+
(this->averageReportedLossRatio - this->options.immediateUpperBoundLossOffset);
|
|
1131
|
+
|
|
1132
|
+
// NOTE: A loss barely above the offset gives a bound beyond what an
|
|
1133
|
+
// int64_t can hold, and converting such a value is undefined behaviour.
|
|
1134
|
+
// It bounds nothing anyway once it's over the maximum bitrate.
|
|
1135
|
+
bitrate = boundBitrate >= static_cast<double>(this->maxBitrate) ? this->maxBitrate
|
|
1136
|
+
: std::llround(boundBitrate);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
this->immediateUpperBoundBitrate = bitrate;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
int64_t LossBasedController::GetImmediateLowerBoundBitrate() const
|
|
1143
|
+
{
|
|
1144
|
+
MS_TRACE();
|
|
1145
|
+
|
|
1146
|
+
return this->immediateLowerBoundBitrate.value_or(0);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
void LossBasedController::CalculateImmediateLowerBoundBitrate()
|
|
1150
|
+
{
|
|
1151
|
+
MS_TRACE();
|
|
1152
|
+
|
|
1153
|
+
int64_t bitrate{ 0 };
|
|
1154
|
+
|
|
1155
|
+
// What the link is known to be delivering is never worth going below.
|
|
1156
|
+
if (this->acknowledgedBitrate != Types::BitrateInfinite)
|
|
1157
|
+
{
|
|
1158
|
+
bitrate = BitrateUtils::ApplyBitrateFactor(
|
|
1159
|
+
this->acknowledgedBitrate, this->options.lowerBoundByAckedRateFactor);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
this->immediateLowerBoundBitrate = std::max(bitrate, this->minBitrate);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
void LossBasedController::CalculateTemporalWeights()
|
|
1166
|
+
{
|
|
1167
|
+
MS_TRACE();
|
|
1168
|
+
|
|
1169
|
+
for (int64_t i{ 0 }; i < this->options.observationWindowSize; ++i)
|
|
1170
|
+
{
|
|
1171
|
+
this->temporalWeights[i] = std::pow(this->options.temporalWeightFactor, i);
|
|
1172
|
+
this->immediateUpperBoundTemporalWeights[i] =
|
|
1173
|
+
std::pow(this->options.immediateUpperBoundTemporalWeightFactor, i);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
bool LossBasedController::IsInLossLimitedState() const
|
|
1178
|
+
{
|
|
1179
|
+
MS_TRACE();
|
|
1180
|
+
|
|
1181
|
+
return this->result.state != State::DELAY_BASED_ESTIMATE;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
bool LossBasedController::IsEstimateIncreasingWhenLossLimited(
|
|
1185
|
+
int64_t oldEstimate, int64_t newEstimate) const
|
|
1186
|
+
{
|
|
1187
|
+
MS_TRACE();
|
|
1188
|
+
|
|
1189
|
+
return (oldEstimate < newEstimate ||
|
|
1190
|
+
(oldEstimate == newEstimate && this->result.state == State::INCREASING)) &&
|
|
1191
|
+
IsInLossLimitedState();
|
|
1192
|
+
}
|
|
1193
|
+
} // namespace BWE
|
|
1194
|
+
} // namespace RTC
|