mediasoup 3.24.2 → 3.25.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 +12 -0
- package/node/lib/Transport.d.ts.map +1 -1
- package/node/lib/Transport.js +7 -0
- package/node/lib/ortc.d.ts.map +1 -1
- package/node/lib/ortc.js +10 -0
- package/node/lib/test/test-PipeTransport.js +35 -30
- package/package.json +1 -1
- package/worker/include/DepLibUV.hpp +33 -4
- package/worker/include/RTC/Consumer.hpp +4 -1
- package/worker/include/RTC/Producer.hpp +34 -5
- package/worker/include/RTC/RTCP/Packet.hpp +7 -1
- package/worker/include/RTC/RTP/Packet.hpp +18 -0
- package/worker/include/RTC/RTP/RtpStream.hpp +76 -14
- package/worker/include/RTC/RTP/RtpStreamRecv.hpp +191 -7
- package/worker/include/RTC/RTP/RtpStreamSend.hpp +56 -10
- package/worker/include/RTC/RTP/RtxStream.hpp +34 -2
- package/worker/include/RTC/RemoteCaptureTimeEstimator.hpp +92 -0
- package/worker/include/RTC/RemoteClockOffsetEstimator.hpp +87 -0
- package/worker/include/RTC/Router.hpp +2 -5
- package/worker/include/RTC/SimulcastProducerStreamManager.hpp +3 -0
- package/worker/include/RTC/Transport.hpp +15 -7
- package/worker/include/Shared.hpp +5 -0
- package/worker/include/SharedInterface.hpp +6 -0
- package/worker/include/Utils.hpp +44 -3
- package/worker/meson.build +4 -0
- package/worker/mocks/include/MockShared.hpp +7 -0
- package/worker/src/DepLibUV.cpp +17 -0
- package/worker/src/RTC/Consumer.cpp +8 -5
- package/worker/src/RTC/Producer.cpp +85 -16
- package/worker/src/RTC/RTP/Packet.cpp +9 -1
- package/worker/src/RTC/RTP/RtpStream.cpp +14 -10
- package/worker/src/RTC/RTP/RtpStreamRecv.cpp +150 -23
- package/worker/src/RTC/RTP/RtpStreamSend.cpp +34 -19
- package/worker/src/RTC/RTP/RtxStream.cpp +12 -6
- package/worker/src/RTC/RemoteCaptureTimeEstimator.cpp +77 -0
- package/worker/src/RTC/RemoteClockOffsetEstimator.cpp +106 -0
- package/worker/src/RTC/Router.cpp +10 -8
- package/worker/src/RTC/RtpDictionaries/RtcpParameters.cpp +13 -3
- package/worker/src/RTC/SCTP/packet/chunks/SackChunk.cpp +6 -2
- package/worker/src/RTC/SimulcastProducerStreamManager.cpp +82 -40
- package/worker/src/RTC/Transport.cpp +96 -4
- package/worker/test/src/RTC/RTP/TestPacket.cpp +6 -0
- package/worker/test/src/RTC/RTP/TestRtpStreamRecv.cpp +2 -2
- package/worker/test/src/RTC/RTP/TestRtpStreamSend.cpp +119 -0
- package/worker/test/src/RTC/TestConsumer.cpp +4 -3
- package/worker/test/src/RTC/TestPortManager.cpp +1 -1
- package/worker/test/src/RTC/TestRemoteCaptureTimeEstimator.cpp +164 -0
- package/worker/test/src/RTC/TestRemoteClockOffsetEstimator.cpp +241 -0
- package/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +2 -2
- package/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +340 -15
- package/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +2 -2
- package/worker/test/src/RTC/TestTransportTuple.cpp +1 -1
- package/worker/test/src/Utils/TestTime.cpp +49 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#include "common.hpp"
|
|
2
|
+
#include "RTC/RemoteClockOffsetEstimator.hpp"
|
|
3
|
+
#include <catch2/catch_test_macros.hpp>
|
|
4
|
+
|
|
5
|
+
SCENARIO("RemoteClockOffsetEstimator", "[rtp][rtcp][remoteclockoffsetestimator]")
|
|
6
|
+
{
|
|
7
|
+
// Remote sender wall clock, as reported in the NTP field of its Sender Reports
|
|
8
|
+
// (milliseconds since 1900).
|
|
9
|
+
constexpr uint64_t RemoteBaseMs{ 3976000000000 };
|
|
10
|
+
// Our own monotonic clock.
|
|
11
|
+
constexpr uint64_t LocalBaseMs{ 1000000 };
|
|
12
|
+
// Offset expected when Sender Reports reach us with no delay at all.
|
|
13
|
+
constexpr int64_t BaseOffsetMs{ static_cast<int64_t>(LocalBaseMs) -
|
|
14
|
+
static_cast<int64_t>(RemoteBaseMs) };
|
|
15
|
+
|
|
16
|
+
const auto minSampleCount = RTC::RemoteClockOffsetEstimator::MinSampleCount;
|
|
17
|
+
const auto windowSize = RTC::RemoteClockOffsetEstimator::WindowSize;
|
|
18
|
+
|
|
19
|
+
// Feeds `count` Sender Reports one second apart, each one reaching us `owdMs`
|
|
20
|
+
// after having been generated by the sender.
|
|
21
|
+
auto feed = [](
|
|
22
|
+
RTC::RemoteClockOffsetEstimator& estimator,
|
|
23
|
+
uint64_t remoteNtpMs,
|
|
24
|
+
uint64_t localArrivalMs,
|
|
25
|
+
size_t count,
|
|
26
|
+
uint64_t owdMs,
|
|
27
|
+
uint32_t rttMs) -> void
|
|
28
|
+
{
|
|
29
|
+
for (size_t idx{ 0 }; idx < count; ++idx)
|
|
30
|
+
{
|
|
31
|
+
const uint64_t elapsedMs = static_cast<uint64_t>(idx) * 1000;
|
|
32
|
+
|
|
33
|
+
estimator.AddSenderReport(remoteNtpMs + elapsedMs, localArrivalMs + elapsedMs + owdMs, rttMs);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
RTC::RemoteClockOffsetEstimator estimator;
|
|
38
|
+
|
|
39
|
+
SECTION("no offset until MinSampleCount Sender Reports have been received")
|
|
40
|
+
{
|
|
41
|
+
feed(estimator, RemoteBaseMs, LocalBaseMs, minSampleCount - 1, 0, 0);
|
|
42
|
+
|
|
43
|
+
REQUIRE_FALSE(estimator.GetOffsetMs().has_value());
|
|
44
|
+
|
|
45
|
+
// One more Sender Report reaches the minimum.
|
|
46
|
+
const uint64_t elapsedMs = static_cast<uint64_t>(minSampleCount - 1) * 1000;
|
|
47
|
+
|
|
48
|
+
feed(estimator, RemoteBaseMs + elapsedMs, LocalBaseMs + elapsedMs, 1, 0, 0);
|
|
49
|
+
|
|
50
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
51
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
52
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
SECTION("the offset carries the Sender Report one way delay when the RTT is unknown")
|
|
56
|
+
{
|
|
57
|
+
feed(estimator, RemoteBaseMs, LocalBaseMs, minSampleCount, 40, 0);
|
|
58
|
+
|
|
59
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
60
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
61
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs + 40);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
SECTION("half of the RTT is removed from the offset")
|
|
65
|
+
{
|
|
66
|
+
feed(estimator, RemoteBaseMs, LocalBaseMs, minSampleCount, 40, 80);
|
|
67
|
+
|
|
68
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
69
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
70
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
SECTION("a single delayed Sender Report does not move the offset")
|
|
74
|
+
{
|
|
75
|
+
feed(estimator, RemoteBaseMs, LocalBaseMs, windowSize, 20, 0);
|
|
76
|
+
|
|
77
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
78
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
79
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs + 20);
|
|
80
|
+
|
|
81
|
+
// A Sender Report that took much longer to reach us must be rejected by the
|
|
82
|
+
// median.
|
|
83
|
+
const uint64_t elapsedMs = static_cast<uint64_t>(windowSize) * 1000;
|
|
84
|
+
|
|
85
|
+
feed(estimator, RemoteBaseMs + elapsedMs, LocalBaseMs + elapsedMs, 1, 500, 0);
|
|
86
|
+
|
|
87
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
88
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
89
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs + 20);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
SECTION("the offset follows a sustained change in delay")
|
|
93
|
+
{
|
|
94
|
+
feed(estimator, RemoteBaseMs, LocalBaseMs, windowSize, 0, 0);
|
|
95
|
+
|
|
96
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
97
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
98
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs);
|
|
99
|
+
|
|
100
|
+
// Once the whole window has been replaced the new delay is fully reflected.
|
|
101
|
+
const uint64_t elapsedMs = static_cast<uint64_t>(windowSize) * 1000;
|
|
102
|
+
|
|
103
|
+
feed(estimator, RemoteBaseMs + elapsedMs, LocalBaseMs + elapsedMs, windowSize, 100, 0);
|
|
104
|
+
|
|
105
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
106
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
107
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs + 100);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
SECTION("delayed Sender Reports move the offset once they are the majority")
|
|
111
|
+
{
|
|
112
|
+
feed(estimator, RemoteBaseMs, LocalBaseMs, windowSize, 0, 0);
|
|
113
|
+
|
|
114
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
115
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
116
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs);
|
|
117
|
+
|
|
118
|
+
// Less than half of the window delayed, so the median still falls on a
|
|
119
|
+
// Sender Report that reached us with no delay.
|
|
120
|
+
uint64_t elapsedMs = static_cast<uint64_t>(windowSize) * 1000;
|
|
121
|
+
|
|
122
|
+
feed(estimator, RemoteBaseMs + elapsedMs, LocalBaseMs + elapsedMs, windowSize / 2, 300, 0);
|
|
123
|
+
|
|
124
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
125
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
126
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs);
|
|
127
|
+
|
|
128
|
+
// One more delayed Sender Report makes them the majority.
|
|
129
|
+
elapsedMs += static_cast<uint64_t>(windowSize / 2) * 1000;
|
|
130
|
+
|
|
131
|
+
feed(estimator, RemoteBaseMs + elapsedMs, LocalBaseMs + elapsedMs, 1, 300, 0);
|
|
132
|
+
|
|
133
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
134
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
135
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs + 300);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
SECTION("the offset tracks a drifting remote clock")
|
|
139
|
+
{
|
|
140
|
+
// The remote wall clock runs one millisecond per second slower than ours, so
|
|
141
|
+
// each Sender Report yields an offset one millisecond higher than the
|
|
142
|
+
// previous one.
|
|
143
|
+
for (size_t idx{ 0 }; idx < windowSize; ++idx)
|
|
144
|
+
{
|
|
145
|
+
estimator.AddSenderReport(
|
|
146
|
+
RemoteBaseMs + (static_cast<uint64_t>(idx) * 1000),
|
|
147
|
+
LocalBaseMs + (static_cast<uint64_t>(idx) * 1001),
|
|
148
|
+
0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
152
|
+
// The samples in the window grow by one millisecond each, so the median is
|
|
153
|
+
// the middle of that ramp.
|
|
154
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
155
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs + static_cast<int64_t>(windowSize / 2));
|
|
156
|
+
|
|
157
|
+
// The drift keeps going, so the offset keeps up with it rather than sticking
|
|
158
|
+
// to the value it already had.
|
|
159
|
+
for (size_t idx{ windowSize }; idx < windowSize * 2; ++idx)
|
|
160
|
+
{
|
|
161
|
+
estimator.AddSenderReport(
|
|
162
|
+
RemoteBaseMs + (static_cast<uint64_t>(idx) * 1000),
|
|
163
|
+
LocalBaseMs + (static_cast<uint64_t>(idx) * 1001),
|
|
164
|
+
0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
168
|
+
REQUIRE(
|
|
169
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
170
|
+
estimator.GetOffsetMs().value() ==
|
|
171
|
+
BaseOffsetMs + static_cast<int64_t>(windowSize + (windowSize / 2)));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
SECTION("all the Sender Reports of a compound packet produce a single sample")
|
|
175
|
+
{
|
|
176
|
+
// Three Sender Reports of a same compound packet, hence a single arrival
|
|
177
|
+
// time.
|
|
178
|
+
estimator.AddSenderReport(RemoteBaseMs, LocalBaseMs, 0);
|
|
179
|
+
estimator.AddSenderReport(RemoteBaseMs + 1, LocalBaseMs, 0);
|
|
180
|
+
estimator.AddSenderReport(RemoteBaseMs + 2, LocalBaseMs, 0);
|
|
181
|
+
|
|
182
|
+
REQUIRE_FALSE(estimator.GetOffsetMs().has_value());
|
|
183
|
+
|
|
184
|
+
// Two more compound packets reach the minimum.
|
|
185
|
+
estimator.AddSenderReport(RemoteBaseMs + 1000, LocalBaseMs + 1000, 0);
|
|
186
|
+
estimator.AddSenderReport(RemoteBaseMs + 1001, LocalBaseMs + 1000, 0);
|
|
187
|
+
estimator.AddSenderReport(RemoteBaseMs + 2000, LocalBaseMs + 2000, 0);
|
|
188
|
+
|
|
189
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
190
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
191
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
SECTION("Sender Reports with no NTP timestamp are ignored")
|
|
195
|
+
{
|
|
196
|
+
// NOTE: Not using feed() here since it would advance the NTP timestamp.
|
|
197
|
+
estimator.AddSenderReport(0, LocalBaseMs, 0);
|
|
198
|
+
estimator.AddSenderReport(0, LocalBaseMs + 1000, 0);
|
|
199
|
+
estimator.AddSenderReport(0, LocalBaseMs + 2000, 0);
|
|
200
|
+
|
|
201
|
+
REQUIRE_FALSE(estimator.GetOffsetMs().has_value());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
SECTION("remote times are translated into our clock")
|
|
205
|
+
{
|
|
206
|
+
const RTC::RemoteClockOffsetEstimator untrainedEstimator;
|
|
207
|
+
|
|
208
|
+
REQUIRE_FALSE(untrainedEstimator.RemoteMsToLocalMs(RemoteBaseMs).has_value());
|
|
209
|
+
|
|
210
|
+
feed(estimator, RemoteBaseMs, LocalBaseMs, minSampleCount, 0, 0);
|
|
211
|
+
|
|
212
|
+
REQUIRE(estimator.RemoteMsToLocalMs(RemoteBaseMs).has_value());
|
|
213
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
214
|
+
REQUIRE(estimator.RemoteMsToLocalMs(RemoteBaseMs).value() == LocalBaseMs);
|
|
215
|
+
REQUIRE(estimator.RemoteMsToLocalMs(RemoteBaseMs + 500).has_value());
|
|
216
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
217
|
+
REQUIRE(estimator.RemoteMsToLocalMs(RemoteBaseMs + 500).value() == LocalBaseMs + 500);
|
|
218
|
+
|
|
219
|
+
// A remote time older than the origin of our clock cannot be translated.
|
|
220
|
+
REQUIRE_FALSE(estimator.RemoteMsToLocalMs(0).has_value());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
SECTION("Reset() clears the estimation")
|
|
224
|
+
{
|
|
225
|
+
feed(estimator, RemoteBaseMs, LocalBaseMs, minSampleCount, 0, 0);
|
|
226
|
+
|
|
227
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
228
|
+
|
|
229
|
+
estimator.Reset();
|
|
230
|
+
|
|
231
|
+
REQUIRE_FALSE(estimator.GetOffsetMs().has_value());
|
|
232
|
+
|
|
233
|
+
// The last arrival time is forgotten as well, so Sender Reports arriving at
|
|
234
|
+
// the very same times as before the reset are not taken as duplicates.
|
|
235
|
+
feed(estimator, RemoteBaseMs, LocalBaseMs, minSampleCount, 0, 0);
|
|
236
|
+
|
|
237
|
+
REQUIRE(estimator.GetOffsetMs().has_value());
|
|
238
|
+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
239
|
+
REQUIRE(estimator.GetOffsetMs().value() == BaseOffsetMs);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -61,9 +61,9 @@ namespace
|
|
|
61
61
|
{
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override
|
|
64
|
+
uint8_t OnRtpStreamNeedWorstRemoteFractionLost(RTC::RTP::RtpStreamRecv* /*rtpStream*/) override
|
|
66
65
|
{
|
|
66
|
+
return 0;
|
|
67
67
|
}
|
|
68
68
|
};
|
|
69
69
|
|
|
@@ -70,9 +70,9 @@ namespace
|
|
|
70
70
|
{
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override
|
|
73
|
+
uint8_t OnRtpStreamNeedWorstRemoteFractionLost(RTC::RTP::RtpStreamRecv* /*rtpStream*/) override
|
|
75
74
|
{
|
|
75
|
+
return 0;
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
|
|
@@ -598,7 +598,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
598
598
|
|
|
599
599
|
manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
|
|
600
600
|
|
|
601
|
-
// Give score > 0 so RecalculateTargetLayers keeps target at layer 0.
|
|
601
|
+
// Give score > 0 so RecalculateTargetLayers() keeps target at layer 0.
|
|
602
602
|
manager->ProducerRtpStreamScore(rtpStream0.get(), /*score*/ 7, /*previousScore*/ 0);
|
|
603
603
|
|
|
604
604
|
// Set target and sync on layer 0.
|
|
@@ -639,7 +639,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
639
639
|
manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
|
|
640
640
|
manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
|
|
641
641
|
|
|
642
|
-
// Both streams have score > 0 so RecalculateTargetLayers can pick layers.
|
|
642
|
+
// Both streams have score > 0 so RecalculateTargetLayers() can pick layers.
|
|
643
643
|
manager->ProducerRtpStreamScore(rtpStream0.get(), /*score*/ 7, /*previousScore*/ 0);
|
|
644
644
|
manager->ProducerRtpStreamScore(rtpStream1.get(), /*score*/ 7, /*previousScore*/ 0);
|
|
645
645
|
|
|
@@ -661,7 +661,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
661
661
|
|
|
662
662
|
auto keyFrameCountBefore = listener.keyFrameRequestCount;
|
|
663
663
|
|
|
664
|
-
// Reconnect: RecalculateTargetLayers picks preferred layer 1,
|
|
664
|
+
// Reconnect: RecalculateTargetLayers() picks preferred layer 1,
|
|
665
665
|
// which differs from current (-1), so a keyframe is requested.
|
|
666
666
|
manager->OnTransportConnected();
|
|
667
667
|
|
|
@@ -681,7 +681,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
681
681
|
REQUIRE(listener.keyFrameRequestCount == 0);
|
|
682
682
|
}
|
|
683
683
|
|
|
684
|
-
SECTION("RecalculateTargetLayers() skips spatial layer without
|
|
684
|
+
SECTION("RecalculateTargetLayers() skips spatial layer without capture instant")
|
|
685
685
|
{
|
|
686
686
|
MockListener listener;
|
|
687
687
|
auto manager = createManager(&listener, /*ssrcs*/ TwoSsrcs, /*preferredLayers*/ { 1, 0 });
|
|
@@ -708,9 +708,9 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
708
708
|
REQUIRE(manager->GetCurrentSpatialLayer() == 0);
|
|
709
709
|
REQUIRE(manager->GetTargetLayers().temporal == 0);
|
|
710
710
|
|
|
711
|
-
// Give both streams score > 0. RecalculateTargetLayers will be called
|
|
712
|
-
// but CanSwitchToSpatialLayer(1) returns false because
|
|
713
|
-
//
|
|
711
|
+
// Give both streams score > 0. RecalculateTargetLayers() will be called
|
|
712
|
+
// but CanSwitchToSpatialLayer(1) returns false because the capture instant of
|
|
713
|
+
// layer 1 cannot be told and tsReferenceSpatialLayer is 0 (not -1).
|
|
714
714
|
manager->ProducerRtpStreamScore(rtpStream0.get(), /*score*/ 7, /*previousScore*/ 0);
|
|
715
715
|
|
|
716
716
|
REQUIRE(manager->GetTargetLayers().spatial == 0);
|
|
@@ -733,7 +733,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
733
733
|
REQUIRE(result.tsOffset == 0u);
|
|
734
734
|
}
|
|
735
735
|
|
|
736
|
-
SECTION("RecalculateTargetLayers() switches to preferred layer
|
|
736
|
+
SECTION("RecalculateTargetLayers() switches to preferred layer once its capture instant is known")
|
|
737
737
|
{
|
|
738
738
|
MockListener listener;
|
|
739
739
|
auto manager = createManager(&listener, /*ssrcs*/ TwoSsrcs, /*preferredLayers*/ { 1, 0 });
|
|
@@ -757,9 +757,14 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
757
757
|
manager->ProcessRtpPacket(
|
|
758
758
|
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
759
759
|
|
|
760
|
-
// Target stays at 0 because layer 1
|
|
760
|
+
// Target stays at 0 because the capture instant of layer 1 cannot be told.
|
|
761
761
|
REQUIRE(manager->GetTargetLayers().spatial == 0);
|
|
762
762
|
|
|
763
|
+
// Both layers hold RTP timestamp 1000 and both were captured at the same instant,
|
|
764
|
+
// which is what makes them alignable to each other.
|
|
765
|
+
rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
|
|
766
|
+
rtpStream1->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
|
|
767
|
+
|
|
763
768
|
// Feed packets to both streams so ReceiveRtcpSenderReport's UpdateScore
|
|
764
769
|
// doesn't drop the score to 0.
|
|
765
770
|
packet->SetSsrc(MappedSsrc0);
|
|
@@ -768,7 +773,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
768
773
|
packet->SetSsrc(MappedSsrc1);
|
|
769
774
|
feedRtpStreamRecv(rtpStream1.get(), packet.get(), 10);
|
|
770
775
|
|
|
771
|
-
// Provide Sender Reports for both streams.
|
|
776
|
+
// Provide Sender Reports for both streams so that they get a score.
|
|
772
777
|
RTC::RTCP::SenderReport sr0;
|
|
773
778
|
sr0.SetSsrc(MappedSsrc0);
|
|
774
779
|
sr0.SetNtpSec(1000);
|
|
@@ -784,7 +789,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
784
789
|
rtpStream1->ReceiveRtcpSenderReport(&sr1);
|
|
785
790
|
|
|
786
791
|
// Notify the manager about the first Sender Report for layer 1.
|
|
787
|
-
// This triggers MayChangeLayers
|
|
792
|
+
// This triggers MayChangeLayers() -> RecalculateTargetLayers().
|
|
788
793
|
// Now CanSwitchToSpatialLayer(1) returns true, so preferred layer 1 is picked.
|
|
789
794
|
manager->ProducerRtcpSenderReport(rtpStream1.get(), /*first*/ true);
|
|
790
795
|
|
|
@@ -804,9 +809,329 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
804
809
|
REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
|
|
805
810
|
REQUIRE(result.isSyncPacket == true);
|
|
806
811
|
REQUIRE(result.spatialLayerSwitched == true);
|
|
807
|
-
// Both
|
|
812
|
+
// Both streams have the same capture instant and RTP timestamp, so tsOffset is 0.
|
|
813
|
+
REQUIRE(result.tsOffset == 0u);
|
|
814
|
+
REQUIRE(manager->GetCurrentSpatialLayer() == 1);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
SECTION("ProcessRtpPacket() calculates tsOffset from the capture instant of both layers")
|
|
818
|
+
{
|
|
819
|
+
MockListener listener;
|
|
820
|
+
auto manager = createManager(&listener, /*ssrcs*/ TwoSsrcs, /*preferredLayers*/ { 1, 0 });
|
|
821
|
+
auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
|
|
822
|
+
auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
|
|
823
|
+
|
|
824
|
+
manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
|
|
825
|
+
manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
|
|
826
|
+
|
|
827
|
+
// Set target layer to 0. This sets tsReferenceSpatialLayer = 0.
|
|
828
|
+
manager->UpdateTargetLayers(0, 0);
|
|
829
|
+
|
|
830
|
+
// Sync on layer 0 with a keyframe so current = 0.
|
|
831
|
+
packet->SetSsrc(MappedSsrc0);
|
|
832
|
+
packet->SetSequenceNumber(1);
|
|
833
|
+
|
|
834
|
+
auto* handler = new MockPayloadDescriptorHandler();
|
|
835
|
+
handler->isKeyFrame = true;
|
|
836
|
+
packet->SetPayloadDescriptorHandler(handler);
|
|
837
|
+
|
|
838
|
+
manager->ProcessRtpPacket(
|
|
839
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
840
|
+
|
|
841
|
+
// Both layers hold RTP timestamp 1000, but the one of layer 1 was captured 500 ms
|
|
842
|
+
// before the one of layer 0, so its RTP timeline runs 45000 ticks ahead.
|
|
843
|
+
rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
|
|
844
|
+
rtpStream1->SetCaptureMapping(/*captureMs*/ 500, /*ts*/ 1000);
|
|
845
|
+
|
|
846
|
+
packet->SetSsrc(MappedSsrc0);
|
|
847
|
+
feedRtpStreamRecv(rtpStream0.get(), packet.get(), 10);
|
|
848
|
+
|
|
849
|
+
packet->SetSsrc(MappedSsrc1);
|
|
850
|
+
feedRtpStreamRecv(rtpStream1.get(), packet.get(), 10);
|
|
851
|
+
|
|
852
|
+
// Provide Sender Reports for both streams so that they get a score.
|
|
853
|
+
RTC::RTCP::SenderReport sr0;
|
|
854
|
+
sr0.SetSsrc(MappedSsrc0);
|
|
855
|
+
sr0.SetNtpSec(1000);
|
|
856
|
+
sr0.SetNtpFrac(0);
|
|
857
|
+
sr0.SetRtpTs(90000);
|
|
858
|
+
rtpStream0->ReceiveRtcpSenderReport(&sr0);
|
|
859
|
+
|
|
860
|
+
RTC::RTCP::SenderReport sr1;
|
|
861
|
+
sr1.SetSsrc(MappedSsrc1);
|
|
862
|
+
sr1.SetNtpSec(1000);
|
|
863
|
+
sr1.SetNtpFrac(0);
|
|
864
|
+
sr1.SetRtpTs(90000);
|
|
865
|
+
rtpStream1->ReceiveRtcpSenderReport(&sr1);
|
|
866
|
+
|
|
867
|
+
manager->ProducerRtcpSenderReport(rtpStream1.get(), /*first*/ true);
|
|
868
|
+
|
|
869
|
+
REQUIRE(manager->GetTargetLayers().spatial == 1);
|
|
870
|
+
|
|
871
|
+
// Send a keyframe from layer 1 to complete the spatial switch.
|
|
872
|
+
packet->SetSsrc(MappedSsrc1);
|
|
873
|
+
packet->SetSequenceNumber(100);
|
|
874
|
+
packet->SetTimestamp(91000);
|
|
875
|
+
|
|
876
|
+
auto* handler1 = new MockPayloadDescriptorHandler();
|
|
877
|
+
handler1->isKeyFrame = true;
|
|
878
|
+
packet->SetPayloadDescriptorHandler(handler1);
|
|
879
|
+
|
|
880
|
+
auto result = manager->ProcessRtpPacket(
|
|
881
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
882
|
+
|
|
883
|
+
REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
|
|
884
|
+
REQUIRE(result.spatialLayerSwitched == true);
|
|
885
|
+
REQUIRE(result.tsOffset == 45000u);
|
|
886
|
+
REQUIRE(manager->GetCurrentSpatialLayer() == 1);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
SECTION(
|
|
890
|
+
"ProcessRtpPacket() discards the packet while its spatial layer cannot tell its capture instant")
|
|
891
|
+
{
|
|
892
|
+
MockListener listener;
|
|
893
|
+
auto manager = createManager(
|
|
894
|
+
&listener,
|
|
895
|
+
/*ssrcs*/ TwoSsrcs,
|
|
896
|
+
/*preferredLayers*/ { 1, 0 },
|
|
897
|
+
/*keyFrameSupported*/ false);
|
|
898
|
+
auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
|
|
899
|
+
auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
|
|
900
|
+
|
|
901
|
+
manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
|
|
902
|
+
manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
|
|
903
|
+
|
|
904
|
+
// Only layer 0 can tell its capture instant, so it remains the TS reference one and
|
|
905
|
+
// layer 1 cannot be aligned to it yet.
|
|
906
|
+
rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
|
|
907
|
+
|
|
908
|
+
// Set target layer to 0 and sync. This sets tsReferenceSpatialLayer = 0.
|
|
909
|
+
manager->UpdateTargetLayers(0, 0);
|
|
910
|
+
|
|
911
|
+
packet->SetSsrc(MappedSsrc0);
|
|
912
|
+
packet->SetSequenceNumber(1);
|
|
913
|
+
manager->ProcessRtpPacket(
|
|
914
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
915
|
+
|
|
916
|
+
REQUIRE(manager->GetCurrentSpatialLayer() == 0);
|
|
917
|
+
|
|
918
|
+
// Aim at layer 1, whose RTP timeline cannot be placed on the one of layer 0 yet.
|
|
919
|
+
manager->UpdateTargetLayers(1, 0);
|
|
920
|
+
|
|
921
|
+
const auto keyFrameRequestCountBefore = listener.keyFrameRequestCount;
|
|
922
|
+
|
|
923
|
+
packet->SetSsrc(MappedSsrc1);
|
|
924
|
+
packet->SetSequenceNumber(1);
|
|
925
|
+
|
|
926
|
+
auto result = manager->ProcessRtpPacket(
|
|
927
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
928
|
+
|
|
929
|
+
REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP);
|
|
930
|
+
REQUIRE(manager->GetCurrentSpatialLayer() == 0);
|
|
931
|
+
// The discarded packet is not a key frame, so no key frame is requested for it.
|
|
932
|
+
REQUIRE(listener.keyFrameRequestCount == keyFrameRequestCountBefore);
|
|
933
|
+
|
|
934
|
+
// Once layer 1 can tell its capture instant the switch completes with the proper
|
|
935
|
+
// offset.
|
|
936
|
+
rtpStream1->SetCaptureMapping(/*captureMs*/ 500, /*ts*/ 1000);
|
|
937
|
+
|
|
938
|
+
packet->SetSequenceNumber(2);
|
|
939
|
+
|
|
940
|
+
result = manager->ProcessRtpPacket(
|
|
941
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
942
|
+
|
|
943
|
+
REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
|
|
944
|
+
REQUIRE(result.spatialLayerSwitched == true);
|
|
945
|
+
REQUIRE(result.tsOffset == 45000u);
|
|
946
|
+
REQUIRE(manager->GetCurrentSpatialLayer() == 1);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
SECTION(
|
|
950
|
+
"ProcessRtpPacket() takes the spatial layer as TS reference when the TS reference one "
|
|
951
|
+
"cannot tell its capture instant")
|
|
952
|
+
{
|
|
953
|
+
MockListener listener;
|
|
954
|
+
auto manager = createManager(
|
|
955
|
+
&listener,
|
|
956
|
+
/*ssrcs*/ TwoSsrcs,
|
|
957
|
+
/*preferredLayers*/ { 1, 0 },
|
|
958
|
+
/*keyFrameSupported*/ false);
|
|
959
|
+
auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
|
|
960
|
+
auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
|
|
961
|
+
|
|
962
|
+
manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
|
|
963
|
+
manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
|
|
964
|
+
|
|
965
|
+
// No stream can tell its capture instant in this SECTION.
|
|
966
|
+
|
|
967
|
+
// Feed packets and a Sender Report to both streams so that they get a score and
|
|
968
|
+
// RecalculateTargetLayers() takes them into account.
|
|
969
|
+
for (auto* rtpStream : { rtpStream0.get(), rtpStream1.get() })
|
|
970
|
+
{
|
|
971
|
+
packet->SetSsrc(rtpStream->GetSsrc());
|
|
972
|
+
feedRtpStreamRecv(rtpStream, packet.get(), 10);
|
|
973
|
+
|
|
974
|
+
RTC::RTCP::SenderReport sr;
|
|
975
|
+
sr.SetSsrc(rtpStream->GetSsrc());
|
|
976
|
+
sr.SetNtpSec(1000);
|
|
977
|
+
sr.SetNtpFrac(0);
|
|
978
|
+
sr.SetRtpTs(90000);
|
|
979
|
+
rtpStream->ReceiveRtcpSenderReport(&sr);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Set target layer to 0 and sync. This sets tsReferenceSpatialLayer = 0.
|
|
983
|
+
manager->UpdateTargetLayers(0, 0);
|
|
984
|
+
|
|
985
|
+
packet->SetSsrc(MappedSsrc0);
|
|
986
|
+
packet->SetSequenceNumber(1);
|
|
987
|
+
manager->ProcessRtpPacket(
|
|
988
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
989
|
+
|
|
990
|
+
REQUIRE(manager->GetCurrentSpatialLayer() == 0);
|
|
991
|
+
|
|
992
|
+
// Aim at layer 1. Since layer 0 cannot tell its capture instant, layer 1 takes over
|
|
993
|
+
// as TS reference. A transport reconnection then asks for a resync of whatever
|
|
994
|
+
// layer arrives first.
|
|
995
|
+
manager->UpdateTargetLayers(1, 0);
|
|
996
|
+
manager->OnTransportConnected();
|
|
997
|
+
|
|
998
|
+
REQUIRE(manager->GetTargetLayers().spatial == 1);
|
|
999
|
+
|
|
1000
|
+
// A packet of the current layer 0 arrives. It is not the TS reference one and there
|
|
1001
|
+
// is no way to align it to layer 1, so layer 0 takes over as TS reference and its
|
|
1002
|
+
// RTP timestamps go untouched.
|
|
1003
|
+
packet->SetSsrc(MappedSsrc0);
|
|
1004
|
+
packet->SetSequenceNumber(2);
|
|
1005
|
+
|
|
1006
|
+
auto result = manager->ProcessRtpPacket(
|
|
1007
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
1008
|
+
|
|
1009
|
+
REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
|
|
1010
|
+
REQUIRE(result.isSyncPacket == true);
|
|
808
1011
|
REQUIRE(result.tsOffset == 0u);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
SECTION(
|
|
1015
|
+
"ProducerRtcpSenderReport() checks layers upon a Sender Report that is not the first "
|
|
1016
|
+
"one of its stream")
|
|
1017
|
+
{
|
|
1018
|
+
MockListener listener;
|
|
1019
|
+
auto manager = createManager(&listener, /*ssrcs*/ TwoSsrcs, /*preferredLayers*/ { 1, 0 });
|
|
1020
|
+
auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
|
|
1021
|
+
auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
|
|
1022
|
+
|
|
1023
|
+
manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
|
|
1024
|
+
manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
|
|
1025
|
+
|
|
1026
|
+
rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
|
|
1027
|
+
rtpStream1->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
|
|
1028
|
+
|
|
1029
|
+
// Feed packets and a Sender Report to both streams so that they get a score and
|
|
1030
|
+
// RecalculateTargetLayers() takes them into account.
|
|
1031
|
+
for (auto* rtpStream : { rtpStream0.get(), rtpStream1.get() })
|
|
1032
|
+
{
|
|
1033
|
+
packet->SetSsrc(rtpStream->GetSsrc());
|
|
1034
|
+
feedRtpStreamRecv(rtpStream, packet.get(), 10);
|
|
1035
|
+
|
|
1036
|
+
RTC::RTCP::SenderReport sr;
|
|
1037
|
+
sr.SetSsrc(rtpStream->GetSsrc());
|
|
1038
|
+
sr.SetNtpSec(1000);
|
|
1039
|
+
sr.SetNtpFrac(0);
|
|
1040
|
+
sr.SetRtpTs(90000);
|
|
1041
|
+
rtpStream->ReceiveRtcpSenderReport(&sr);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Set target layer to 0. This sets tsReferenceSpatialLayer = 0.
|
|
1045
|
+
manager->UpdateTargetLayers(0, 0);
|
|
1046
|
+
|
|
1047
|
+
REQUIRE(manager->GetTargetLayers().spatial == 0);
|
|
1048
|
+
|
|
1049
|
+
// A Sender Report that is not the first one of its stream may be the one that made
|
|
1050
|
+
// the capture instant of the TS reference stream known, so layers are checked and
|
|
1051
|
+
// the preferred layer 1 is picked.
|
|
1052
|
+
manager->ProducerRtcpSenderReport(rtpStream1.get(), /*first*/ false);
|
|
1053
|
+
|
|
1054
|
+
REQUIRE(manager->GetTargetLayers().spatial == 1);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
SECTION("ProcessRtpPacket() calculates tsOffset of the spatial layer of the packet")
|
|
1058
|
+
{
|
|
1059
|
+
MockListener listener;
|
|
1060
|
+
auto manager = createManager(
|
|
1061
|
+
&listener,
|
|
1062
|
+
/*ssrcs*/ ThreeSsrcs,
|
|
1063
|
+
/*preferredLayers*/ { 2, 0 },
|
|
1064
|
+
/*keyFrameSupported*/ false);
|
|
1065
|
+
auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
|
|
1066
|
+
auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
|
|
1067
|
+
auto rtpStream2 = createRtpStreamRecv(MappedSsrc2);
|
|
1068
|
+
|
|
1069
|
+
manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
|
|
1070
|
+
manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
|
|
1071
|
+
manager->ProducerRtpStream(rtpStream2.get(), MappedSsrc2);
|
|
1072
|
+
|
|
1073
|
+
// All layers hold RTP timestamp 1000. Layer 1 was captured 500 ms before layer 0
|
|
1074
|
+
// and layer 2 a whole second before it, so their offsets differ.
|
|
1075
|
+
rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
|
|
1076
|
+
rtpStream1->SetCaptureMapping(/*captureMs*/ 500, /*ts*/ 1000);
|
|
1077
|
+
rtpStream2->SetCaptureMapping(/*captureMs*/ 0, /*ts*/ 1000);
|
|
1078
|
+
|
|
1079
|
+
// Feed packets and a Sender Report to every stream so that they all get a score
|
|
1080
|
+
// and RecalculateTargetLayers() takes them into account.
|
|
1081
|
+
for (auto* rtpStream : { rtpStream0.get(), rtpStream1.get(), rtpStream2.get() })
|
|
1082
|
+
{
|
|
1083
|
+
packet->SetSsrc(rtpStream->GetSsrc());
|
|
1084
|
+
feedRtpStreamRecv(rtpStream, packet.get(), 10);
|
|
1085
|
+
|
|
1086
|
+
RTC::RTCP::SenderReport sr;
|
|
1087
|
+
sr.SetSsrc(rtpStream->GetSsrc());
|
|
1088
|
+
sr.SetNtpSec(1000);
|
|
1089
|
+
sr.SetNtpFrac(0);
|
|
1090
|
+
sr.SetRtpTs(90000);
|
|
1091
|
+
rtpStream->ReceiveRtcpSenderReport(&sr);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Set target layer to 0 and sync. This sets tsReferenceSpatialLayer = 0, which
|
|
1095
|
+
// stays there for the rest of the SECTION since it has a capture mapping.
|
|
1096
|
+
manager->UpdateTargetLayers(0, 0);
|
|
1097
|
+
|
|
1098
|
+
packet->SetSsrc(MappedSsrc0);
|
|
1099
|
+
packet->SetSequenceNumber(1);
|
|
1100
|
+
manager->ProcessRtpPacket(
|
|
1101
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
1102
|
+
|
|
1103
|
+
REQUIRE(manager->GetCurrentSpatialLayer() == 0);
|
|
1104
|
+
|
|
1105
|
+
// Switch to layer 1.
|
|
1106
|
+
manager->UpdateTargetLayers(1, 0);
|
|
1107
|
+
|
|
1108
|
+
packet->SetSsrc(MappedSsrc1);
|
|
1109
|
+
packet->SetSequenceNumber(1);
|
|
1110
|
+
|
|
1111
|
+
auto result = manager->ProcessRtpPacket(
|
|
1112
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
1113
|
+
|
|
1114
|
+
REQUIRE(result.spatialLayerSwitched == true);
|
|
1115
|
+
REQUIRE(result.tsOffset == 45000u);
|
|
809
1116
|
REQUIRE(manager->GetCurrentSpatialLayer() == 1);
|
|
1117
|
+
|
|
1118
|
+
// A transport reconnection asks for a resync of whatever layer arrives first, and
|
|
1119
|
+
// moves the target to the preferred layer 2.
|
|
1120
|
+
manager->OnTransportConnected();
|
|
1121
|
+
|
|
1122
|
+
REQUIRE(manager->GetTargetLayers().spatial == 2);
|
|
1123
|
+
|
|
1124
|
+
// A packet of the current layer 1 arrives while layer 2 is the target one, so the
|
|
1125
|
+
// offset must be the one of layer 1 and not the one of layer 2.
|
|
1126
|
+
packet->SetSsrc(MappedSsrc1);
|
|
1127
|
+
packet->SetSequenceNumber(2);
|
|
1128
|
+
|
|
1129
|
+
result = manager->ProcessRtpPacket(
|
|
1130
|
+
packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
|
|
1131
|
+
|
|
1132
|
+
REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
|
|
1133
|
+
REQUIRE(result.isSyncPacket == true);
|
|
1134
|
+
REQUIRE(result.tsOffset == 45000u);
|
|
810
1135
|
}
|
|
811
1136
|
|
|
812
1137
|
SECTION("OnResumed() sets syncRequired")
|
|
@@ -840,7 +1165,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
|
|
|
840
1165
|
// Call OnResumed — should set syncRequired.
|
|
841
1166
|
manager->OnResumed();
|
|
842
1167
|
|
|
843
|
-
// Reset target since OnResumed may have reset it via MayChangeLayers.
|
|
1168
|
+
// Reset target since OnResumed may have reset it via MayChangeLayers().
|
|
844
1169
|
manager->UpdateTargetLayers(0, 0);
|
|
845
1170
|
|
|
846
1171
|
// Prove syncRequired was set: next packet is a sync packet.
|