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.
Files changed (213) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/node/lib/Consumer.d.ts.map +1 -1
  3. package/node/lib/Consumer.js +4 -51
  4. package/node/lib/Producer.d.ts.map +1 -1
  5. package/node/lib/Producer.js +4 -8
  6. package/node/lib/ProducerTypes.d.ts +4 -2
  7. package/node/lib/ProducerTypes.d.ts.map +1 -1
  8. package/node/lib/Router.d.ts.map +1 -1
  9. package/node/lib/Router.js +4 -0
  10. package/node/lib/Transport.d.ts.map +1 -1
  11. package/node/lib/Transport.js +7 -10
  12. package/node/lib/fbs/rtp-parameters/rtp-header-extension-uri.d.ts +3 -4
  13. package/node/lib/fbs/rtp-parameters/rtp-header-extension-uri.d.ts.map +1 -1
  14. package/node/lib/fbs/rtp-parameters/rtp-header-extension-uri.js +3 -4
  15. package/node/lib/ortc.d.ts +0 -2
  16. package/node/lib/ortc.d.ts.map +1 -1
  17. package/node/lib/ortc.js +11 -21
  18. package/node/lib/rtpMappingFbsUtils.d.ts +6 -0
  19. package/node/lib/rtpMappingFbsUtils.d.ts.map +1 -0
  20. package/node/lib/rtpMappingFbsUtils.js +65 -0
  21. package/node/lib/rtpParametersFbsUtils.d.ts.map +1 -1
  22. package/node/lib/rtpParametersFbsUtils.js +0 -6
  23. package/node/lib/rtpParametersTypes.d.ts +1 -1
  24. package/node/lib/rtpParametersTypes.d.ts.map +1 -1
  25. package/node/lib/rtpStreamFbsUtils.d.ts +4 -0
  26. package/node/lib/rtpStreamFbsUtils.d.ts.map +1 -0
  27. package/node/lib/rtpStreamFbsUtils.js +51 -0
  28. package/node/lib/supportedRtpCapabilities.d.ts.map +1 -1
  29. package/node/lib/supportedRtpCapabilities.js +6 -13
  30. package/node/lib/test/test-Consumer.js +1 -13
  31. package/node/lib/test/test-DataConsumer.js +9 -0
  32. package/node/lib/test/test-DataProducer.js +38 -0
  33. package/node/lib/test/test-PipeTransport.js +27 -29
  34. package/node/lib/test/test-Producer.js +97 -0
  35. package/node/lib/test/test-ortc.js +34 -14
  36. package/package.json +10 -10
  37. package/worker/fbs/FBS/meson.build +25 -0
  38. package/worker/fbs/meson.build +15 -18
  39. package/worker/fbs/rtpParameters.fbs +0 -1
  40. package/worker/fuzzer/src/FuzzerUtils.cpp +5 -3
  41. package/worker/fuzzer/src/RTC/RTP/FuzzerPacket.cpp +16 -2
  42. package/worker/include/DepLibUV.hpp +13 -0
  43. package/worker/include/RTC/BWE/AlrDetector.hpp +97 -0
  44. package/worker/include/RTC/BWE/BitrateProber.hpp +217 -0
  45. package/worker/include/RTC/BWE/BitrateUtils.hpp +39 -0
  46. package/worker/include/RTC/BWE/BweTypes.hpp +171 -3
  47. package/worker/include/RTC/BWE/FeedbackAdapter.hpp +0 -14
  48. package/worker/include/RTC/BWE/InterArrival.hpp +117 -0
  49. package/worker/include/RTC/BWE/IntervalBudget.hpp +81 -0
  50. package/worker/include/RTC/BWE/LossBasedController.hpp +625 -0
  51. package/worker/include/RTC/BWE/OveruseDetector.hpp +71 -0
  52. package/worker/include/RTC/BWE/OveruseEstimator.hpp +115 -0
  53. package/worker/include/RTC/BWE/ProbeBitrateEstimator.hpp +84 -0
  54. package/worker/include/RTC/BWE/ProbeController.hpp +379 -0
  55. package/worker/include/RTC/BWE/ProbePacketGenerator.hpp +130 -0
  56. package/worker/include/RTC/BWE/ProbingScheduler.hpp +187 -0
  57. package/worker/include/RTC/BWE/RembGenerator.hpp +96 -0
  58. package/worker/include/RTC/BWE/RemoteBitrateEstimatorAbsSendTime.hpp +237 -0
  59. package/worker/include/RTC/BWE/RobustThroughputEstimator.hpp +26 -0
  60. package/worker/include/RTC/BWE/SendPacketHistory.hpp +161 -25
  61. package/worker/include/RTC/BWE/TargetRateController.hpp +306 -0
  62. package/worker/include/RTC/BWE/TransportWideCcFeedbackGenerator.hpp +146 -0
  63. package/worker/include/RTC/BWE/TrendlineEstimator.hpp +73 -16
  64. package/worker/include/RTC/Consts.hpp +70 -0
  65. package/worker/include/RTC/DataConsumer.hpp +3 -5
  66. package/worker/include/RTC/DirectTransport.hpp +4 -5
  67. package/worker/include/RTC/PipeTransport.hpp +4 -5
  68. package/worker/include/RTC/PlainTransport.hpp +4 -5
  69. package/worker/include/RTC/RTCP/FeedbackPsRemb.hpp +6 -7
  70. package/worker/include/RTC/RTCP/FeedbackRtpTransport.hpp +3 -1
  71. package/worker/include/RTC/RTP/Codecs/Tools.hpp +1 -1
  72. package/worker/include/RTC/RTP/HeaderExtensionIds.hpp +0 -1
  73. package/worker/include/RTC/RTP/Packet.hpp +16 -4
  74. package/worker/include/RTC/RTP/ProbationGenerator.hpp +0 -8
  75. package/worker/include/RTC/RTP/RtpStream.hpp +12 -9
  76. package/worker/include/RTC/RTP/RtpStreamRecv.hpp +22 -4
  77. package/worker/include/RTC/RTP/RtpStreamSend.hpp +4 -2
  78. package/worker/include/RTC/RTP/RtxStream.hpp +10 -4
  79. package/worker/include/RTC/RateCalculator.hpp +103 -24
  80. package/worker/include/RTC/RemoteCaptureTimeEstimator.hpp +33 -3
  81. package/worker/include/RTC/{RtcLogger.hpp → RtcLogger/RtpPacket.hpp} +5 -3
  82. package/worker/include/RTC/RtcLogger/SendBurst.hpp +88 -0
  83. package/worker/include/RTC/RtpDictionaries.hpp +3 -4
  84. package/worker/include/RTC/SCTP/association/Association.hpp +1 -1
  85. package/worker/include/RTC/SCTP/association/StreamResetHandler.hpp +36 -0
  86. package/worker/include/RTC/SCTP/public/AssociationMetrics.hpp +0 -1
  87. package/worker/include/RTC/SCTP/public/SctpTypes.hpp +9 -9
  88. package/worker/include/RTC/SCTP/rx/DataTracker.hpp +1 -1
  89. package/worker/include/RTC/SCTP/rx/ReassemblyQueue.hpp +11 -1
  90. package/worker/include/RTC/SctpDictionaries.hpp +2 -2
  91. package/worker/include/RTC/TcpConnection.hpp +2 -1
  92. package/worker/include/RTC/Transport.hpp +23 -8
  93. package/worker/include/RTC/TransportTuple.hpp +61 -6
  94. package/worker/include/RTC/WebRtcTransport.hpp +4 -5
  95. package/worker/include/Shared.hpp +5 -0
  96. package/worker/include/SharedInterface.hpp +10 -0
  97. package/worker/include/Utils.hpp +72 -3
  98. package/worker/include/common.hpp +12 -4
  99. package/worker/include/handles/SendCallbacks.hpp +34 -0
  100. package/worker/include/handles/TcpConnectionHandle.hpp +4 -12
  101. package/worker/include/handles/UdpSocketHandle.hpp +4 -8
  102. package/worker/meson.build +45 -1
  103. package/worker/meson_options.txt +1 -0
  104. package/worker/mocks/include/MockShared.hpp +16 -2
  105. package/worker/mocks/include/handles/MockBackoffTimerHandle.hpp +1 -1
  106. package/worker/scripts/package-lock.json +6 -6
  107. package/worker/src/RTC/BWE/AimdRateControl.cpp +4 -5
  108. package/worker/src/RTC/BWE/AlrDetector.cpp +80 -0
  109. package/worker/src/RTC/BWE/BitrateProber.cpp +256 -0
  110. package/worker/src/RTC/BWE/BitrateUtils.cpp +54 -0
  111. package/worker/src/RTC/BWE/DelayBasedBwe.cpp +2 -2
  112. package/worker/src/RTC/BWE/FeedbackAdapter.cpp +20 -37
  113. package/worker/src/RTC/BWE/InterArrival.cpp +216 -0
  114. package/worker/src/RTC/BWE/IntervalBudget.cpp +93 -0
  115. package/worker/src/RTC/BWE/LossBasedController.cpp +1194 -0
  116. package/worker/src/RTC/BWE/OveruseDetector.cpp +128 -0
  117. package/worker/src/RTC/BWE/OveruseEstimator.cpp +166 -0
  118. package/worker/src/RTC/BWE/ProbeBitrateEstimator.cpp +222 -0
  119. package/worker/src/RTC/BWE/ProbeController.cpp +645 -0
  120. package/worker/src/RTC/BWE/ProbePacketGenerator.cpp +140 -0
  121. package/worker/src/RTC/BWE/ProbingScheduler.cpp +196 -0
  122. package/worker/src/RTC/BWE/RembGenerator.cpp +169 -0
  123. package/worker/src/RTC/BWE/RemoteBitrateEstimatorAbsSendTime.cpp +497 -0
  124. package/worker/src/RTC/BWE/RobustThroughputEstimator.cpp +47 -0
  125. package/worker/src/RTC/BWE/SendPacketHistory.cpp +173 -50
  126. package/worker/src/RTC/BWE/TargetRateController.cpp +433 -0
  127. package/worker/src/RTC/BWE/TransportWideCcFeedbackGenerator.cpp +377 -0
  128. package/worker/src/RTC/BWE/TrendlineEstimator.cpp +102 -5
  129. package/worker/src/RTC/Consumer.cpp +7 -5
  130. package/worker/src/RTC/DataConsumer.cpp +8 -10
  131. package/worker/src/RTC/DirectTransport.cpp +14 -8
  132. package/worker/src/RTC/DtlsTransport.cpp +8 -8
  133. package/worker/src/RTC/ICE/IceCandidate.cpp +4 -4
  134. package/worker/src/RTC/ICE/IceServer.cpp +1 -1
  135. package/worker/src/RTC/PipeTransport.cpp +13 -8
  136. package/worker/src/RTC/PlainTransport.cpp +13 -8
  137. package/worker/src/RTC/Producer.cpp +14 -31
  138. package/worker/src/RTC/RTCP/Bye.cpp +1 -1
  139. package/worker/src/RTC/RTCP/FeedbackPsRemb.cpp +18 -0
  140. package/worker/src/RTC/RTCP/FeedbackPsRpsi.cpp +2 -2
  141. package/worker/src/RTC/RTCP/FeedbackPsVbcm.cpp +2 -1
  142. package/worker/src/RTC/RTCP/FeedbackRtpTransport.cpp +2 -2
  143. package/worker/src/RTC/RTCP/Sdes.cpp +1 -1
  144. package/worker/src/RTC/RTP/Codecs/DependencyDescriptor.cpp +2 -1
  145. package/worker/src/RTC/RTP/Packet.cpp +26 -11
  146. package/worker/src/RTC/RTP/ProbationGenerator.cpp +5 -6
  147. package/worker/src/RTC/RTP/RtpStream.cpp +1 -1
  148. package/worker/src/RTC/RTP/RtpStreamRecv.cpp +69 -48
  149. package/worker/src/RTC/RTP/RtpStreamSend.cpp +31 -31
  150. package/worker/src/RTC/RTP/RtxStream.cpp +22 -7
  151. package/worker/src/RTC/RateCalculator.cpp +133 -28
  152. package/worker/src/RTC/RemoteCaptureTimeEstimator.cpp +56 -1
  153. package/worker/src/RTC/{RtcLogger.cpp → RtcLogger/RtpPacket.cpp} +2 -2
  154. package/worker/src/RTC/RtcLogger/SendBurst.cpp +124 -0
  155. package/worker/src/RTC/RtpDictionaries/RtpHeaderExtensionUri.cpp +2 -12
  156. package/worker/src/RTC/RtpDictionaries/RtpParameters.cpp +1 -1
  157. package/worker/src/RTC/SCTP/association/Association.cpp +16 -2
  158. package/worker/src/RTC/SCTP/association/StreamResetHandler.cpp +106 -1
  159. package/worker/src/RTC/SCTP/public/AssociationMetrics.cpp +1 -1
  160. package/worker/src/RTC/SCTP/rx/InterleavedReassemblyStreams.cpp +3 -3
  161. package/worker/src/RTC/SCTP/rx/ReassemblyQueue.cpp +2 -1
  162. package/worker/src/RTC/SCTP/rx/TraditionalReassemblyStreams.cpp +4 -4
  163. package/worker/src/RTC/SCTP/tx/RetransmissionTimeout.cpp +4 -4
  164. package/worker/src/RTC/SctpDictionaries/SctpStreamParameters.cpp +6 -10
  165. package/worker/src/RTC/SimulcastProducerStreamManager.cpp +2 -2
  166. package/worker/src/RTC/SrtpSession.cpp +2 -2
  167. package/worker/src/RTC/SvcProducerStreamManager.cpp +2 -2
  168. package/worker/src/RTC/TcpConnection.cpp +2 -2
  169. package/worker/src/RTC/Transport.cpp +112 -75
  170. package/worker/src/RTC/TransportTuple.cpp +4 -4
  171. package/worker/src/RTC/WebRtcServer.cpp +4 -3
  172. package/worker/src/RTC/WebRtcTransport.cpp +20 -13
  173. package/worker/src/Utils/BitStream.cpp +24 -0
  174. package/worker/src/handles/BackoffTimerHandle.cpp +1 -1
  175. package/worker/src/handles/TcpConnectionHandle.cpp +13 -23
  176. package/worker/src/handles/UdpSocketHandle.cpp +14 -21
  177. package/worker/tasks.py +43 -18
  178. package/worker/test/include/RTC/BWE/helpers/LinkSimulator.hpp +68 -14
  179. package/worker/test/include/RTC/BWE/helpers/RemoteBitrateEstimatorAbsSendTimeTestHelper.hpp +125 -0
  180. package/worker/test/src/RTC/BWE/TestAlrDetector.cpp +110 -0
  181. package/worker/test/src/RTC/BWE/TestBitrateProber.cpp +418 -0
  182. package/worker/test/src/RTC/BWE/TestBitrateUtils.cpp +61 -0
  183. package/worker/test/src/RTC/BWE/TestDelayBasedBwe.cpp +4 -4
  184. package/worker/test/src/RTC/BWE/TestFeedbackAdapter.cpp +53 -37
  185. package/worker/test/src/RTC/BWE/TestInterArrival.cpp +643 -0
  186. package/worker/test/src/RTC/BWE/TestIntervalBudget.cpp +130 -0
  187. package/worker/test/src/RTC/BWE/TestLossBasedController.cpp +1199 -0
  188. package/worker/test/src/RTC/BWE/TestOveruseDetector.cpp +519 -0
  189. package/worker/test/src/RTC/BWE/TestProbeBitrateEstimator.cpp +266 -0
  190. package/worker/test/src/RTC/BWE/TestProbeController.cpp +1682 -0
  191. package/worker/test/src/RTC/BWE/TestProbePacketGenerator.cpp +265 -0
  192. package/worker/test/src/RTC/BWE/TestProbingScheduler.cpp +260 -0
  193. package/worker/test/src/RTC/BWE/TestRembGenerator.cpp +285 -0
  194. package/worker/test/src/RTC/BWE/TestRemoteBitrateEstimatorAbsSendTime.cpp +414 -0
  195. package/worker/test/src/RTC/BWE/TestSendPacketHistory.cpp +296 -77
  196. package/worker/test/src/RTC/BWE/TestTargetRateController.cpp +337 -0
  197. package/worker/test/src/RTC/BWE/TestTransportWideCcFeedbackGenerator.cpp +578 -0
  198. package/worker/test/src/RTC/BWE/TestTrendlineEstimator.cpp +45 -0
  199. package/worker/test/src/RTC/BWE/helpers/LinkSimulator.cpp +133 -29
  200. package/worker/test/src/RTC/BWE/helpers/RemoteBitrateEstimatorAbsSendTimeTestHelper.cpp +625 -0
  201. package/worker/test/src/RTC/RTCP/TestFeedbackPsRemb.cpp +19 -0
  202. package/worker/test/src/RTC/RTP/TestPacket.cpp +58 -4
  203. package/worker/test/src/RTC/RTP/TestProbationGenerator.cpp +3 -2
  204. package/worker/test/src/RTC/RTP/TestRtpStreamRecv.cpp +50 -0
  205. package/worker/test/src/RTC/RTP/TestRtpStreamSend.cpp +59 -0
  206. package/worker/test/src/RTC/SCTP/association/TestStreamResetHandler.cpp +111 -0
  207. package/worker/test/src/RTC/TestRateCalculator.cpp +322 -60
  208. package/worker/test/src/RTC/TestRemoteCaptureTimeEstimator.cpp +95 -7
  209. package/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +34 -16
  210. package/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +31 -13
  211. package/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +37 -19
  212. package/worker/test/src/RTC/TestTransportCongestionControlServer.cpp +10 -10
  213. 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