xref: /aosp_15_r20/external/webrtc/test/peer_scenario/tests/unsignaled_stream_test.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "media/base/stream_params.h"
12 #include "modules/rtp_rtcp/source/byte_io.h"
13 #include "modules/rtp_rtcp/source/rtp_util.h"
14 #include "pc/media_session.h"
15 #include "pc/session_description.h"
16 #include "test/field_trial.h"
17 #include "test/gmock.h"
18 #include "test/gtest.h"
19 #include "test/peer_scenario/peer_scenario.h"
20 
21 namespace webrtc {
22 namespace test {
23 namespace {
24 
25 enum class MidTestConfiguration {
26   // Legacy endpoint setup where PT demuxing is used.
27   kMidNotNegotiated,
28   // MID is negotiated but missing from packets. PT demuxing is disabled, so
29   // SSRCs have to be added to the SDP for WebRTC to forward packets correctly.
30   // Happens when client is spec compliant but the SFU isn't. Popular legacy.
31   kMidNegotiatedButMissingFromPackets,
32   // Fully spec-compliant: MID is present so we can safely drop packets with
33   // unknown MIDs.
34   kMidNegotiatedAndPresentInPackets,
35 };
36 
37 // Gives the parameterized test a readable suffix.
TestParametersMidTestConfigurationToString(testing::TestParamInfo<MidTestConfiguration> info)38 std::string TestParametersMidTestConfigurationToString(
39     testing::TestParamInfo<MidTestConfiguration> info) {
40   switch (info.param) {
41     case MidTestConfiguration::kMidNotNegotiated:
42       return "MidNotNegotiated";
43     case MidTestConfiguration::kMidNegotiatedButMissingFromPackets:
44       return "MidNegotiatedButMissingFromPackets";
45     case MidTestConfiguration::kMidNegotiatedAndPresentInPackets:
46       return "MidNegotiatedAndPresentInPackets";
47   }
48 }
49 
50 class FrameObserver : public rtc::VideoSinkInterface<VideoFrame> {
51  public:
FrameObserver()52   FrameObserver() : frame_observed_(false) {}
OnFrame(const VideoFrame &)53   void OnFrame(const VideoFrame&) override { frame_observed_ = true; }
54 
55   std::atomic<bool> frame_observed_;
56 };
57 
get_ssrc(SessionDescriptionInterface * offer,size_t track_index)58 uint32_t get_ssrc(SessionDescriptionInterface* offer, size_t track_index) {
59   EXPECT_LT(track_index, offer->description()->contents().size());
60   return offer->description()
61       ->contents()[track_index]
62       .media_description()
63       ->streams()[0]
64       .ssrcs[0];
65 }
66 
set_ssrc(SessionDescriptionInterface * offer,size_t index,uint32_t ssrc)67 void set_ssrc(SessionDescriptionInterface* offer, size_t index, uint32_t ssrc) {
68   EXPECT_LT(index, offer->description()->contents().size());
69   cricket::StreamParams& new_stream_params = offer->description()
70                                                  ->contents()[index]
71                                                  .media_description()
72                                                  ->mutable_streams()[0];
73   new_stream_params.ssrcs[0] = ssrc;
74   new_stream_params.ssrc_groups[0].ssrcs[0] = ssrc;
75 }
76 
77 }  // namespace
78 
79 class UnsignaledStreamTest
80     : public ::testing::Test,
81       public ::testing::WithParamInterface<MidTestConfiguration> {};
82 
TEST_P(UnsignaledStreamTest,ReplacesUnsignaledStreamOnCompletedSignaling)83 TEST_P(UnsignaledStreamTest, ReplacesUnsignaledStreamOnCompletedSignaling) {
84   // This test covers a scenario that might occur if a remote client starts
85   // sending media packets before negotiation has completed. Depending on setup,
86   // these packets either get dropped or trigger an unsignalled default stream
87   // to be created, and connects that to a default video sink.
88   // In some edge cases using Unified Plan and PT demuxing, the default stream
89   // is create in a different transceiver to where the media SSRC will actually
90   // be used. This test verifies that the default stream is removed properly,
91   // and that packets are demuxed and video frames reach the desired sink.
92   const MidTestConfiguration kMidTestConfiguration = GetParam();
93 
94   // Defined before PeerScenario so it gets destructed after, to avoid use after
95   // free.
96   PeerScenario s(*::testing::UnitTest::GetInstance()->current_test_info());
97 
98   PeerScenarioClient::Config config = PeerScenarioClient::Config();
99   // Disable encryption so that we can inject a fake early media packet without
100   // triggering srtp failures.
101   config.disable_encryption = true;
102   auto* caller = s.CreateClient(config);
103   auto* callee = s.CreateClient(config);
104 
105   auto send_node = s.net()->NodeBuilder().Build().node;
106   auto ret_node = s.net()->NodeBuilder().Build().node;
107 
108   s.net()->CreateRoute(caller->endpoint(), {send_node}, callee->endpoint());
109   s.net()->CreateRoute(callee->endpoint(), {ret_node}, caller->endpoint());
110 
111   auto signaling = s.ConnectSignaling(caller, callee, {send_node}, {ret_node});
112   PeerScenarioClient::VideoSendTrackConfig video_conf;
113   video_conf.generator.squares_video->framerate = 15;
114 
115   auto first_track = caller->CreateVideo("VIDEO", video_conf);
116   FrameObserver first_sink;
117   callee->AddVideoReceiveSink(first_track.track->id(), &first_sink);
118 
119   signaling.StartIceSignaling();
120   std::atomic<bool> offer_exchange_done(false);
121   std::atomic<bool> got_unsignaled_packet(false);
122 
123   // We will capture the media ssrc of the first added stream, and preemptively
124   // inject a new media packet using a different ssrc. What happens depends on
125   // the test configuration.
126   //
127   // MidTestConfiguration::kMidNotNegotiated:
128   // - MID is not negotiated which means PT-based demuxing is enabled. Because
129   //   the packets have no MID, the second ssrc packet gets forwarded to the
130   //   first m= section. This will create a "default stream" for the second ssrc
131   //   and connect it to the default video sink (not set in this test). The test
132   //   verifies we can recover from this when we later get packets for the first
133   //   ssrc.
134   //
135   // MidTestConfiguration::kMidNegotiatedButMissingFromPackets:
136   // - MID is negotiated wich means PT-based demuxing is disabled. Because we
137   //   modify the packets not to contain the MID anyway (simulating a legacy SFU
138   //   that does not negotiate properly) unknown SSRCs are dropped but do not
139   //   otherwise cause any issues.
140   //
141   // MidTestConfiguration::kMidNegotiatedAndPresentInPackets:
142   // - MID is negotiated which means PT-based demuxing is enabled. In this case
143   //   the packets have the MID so they either get forwarded or dropped
144   //   depending on if the MID is known. The spec-compliant way is also the most
145   //   straight-forward one.
146 
147   uint32_t first_ssrc = 0;
148   uint32_t second_ssrc = 0;
149   absl::optional<int> mid_header_extension_id = absl::nullopt;
150 
151   signaling.NegotiateSdp(
152       /* munge_sdp = */
153       [&](SessionDescriptionInterface* offer) {
154         // Obtain the MID header extension ID and if we want the
155         // MidTestConfiguration::kMidNotNegotiated setup then we remove the MID
156         // header extension through SDP munging (otherwise SDP is not modified).
157         for (cricket::ContentInfo& content_info :
158              offer->description()->contents()) {
159           std::vector<RtpExtension> header_extensions =
160               content_info.media_description()->rtp_header_extensions();
161           for (auto it = header_extensions.begin();
162                it != header_extensions.end(); ++it) {
163             if (it->uri == RtpExtension::kMidUri) {
164               // MID header extension found!
165               mid_header_extension_id = it->id;
166               if (kMidTestConfiguration ==
167                   MidTestConfiguration::kMidNotNegotiated) {
168                 // Munge away the extension.
169                 header_extensions.erase(it);
170               }
171               break;
172             }
173           }
174           content_info.media_description()->set_rtp_header_extensions(
175               std::move(header_extensions));
176         }
177         ASSERT_TRUE(mid_header_extension_id.has_value());
178       },
179       /* modify_sdp = */
180       [&](SessionDescriptionInterface* offer) {
181         first_ssrc = get_ssrc(offer, 0);
182         second_ssrc = first_ssrc + 1;
183 
184         send_node->router()->SetWatcher([&](const EmulatedIpPacket& packet) {
185           if (IsRtpPacket(packet.data) &&
186               ByteReader<uint32_t>::ReadBigEndian(&(packet.cdata()[8])) ==
187                   first_ssrc &&
188               !got_unsignaled_packet) {
189             // Parse packet and modify the SSRC to simulate a second m=
190             // section that has not been negotiated yet.
191             std::vector<RtpExtension> extensions;
192             extensions.emplace_back(RtpExtension::kMidUri,
193                                     mid_header_extension_id.value());
194             RtpHeaderExtensionMap extensions_map(extensions);
195             RtpPacket parsed_packet;
196             parsed_packet.IdentifyExtensions(extensions_map);
197             ASSERT_TRUE(parsed_packet.Parse(packet.data));
198             parsed_packet.SetSsrc(second_ssrc);
199             // The MID extension is present if and only if it was negotiated.
200             // If present, we either want to remove it or modify it depending
201             // on setup.
202             switch (kMidTestConfiguration) {
203               case MidTestConfiguration::kMidNotNegotiated:
204                 EXPECT_FALSE(parsed_packet.HasExtension<RtpMid>());
205                 break;
206               case MidTestConfiguration::kMidNegotiatedButMissingFromPackets:
207                 EXPECT_TRUE(parsed_packet.HasExtension<RtpMid>());
208                 ASSERT_TRUE(parsed_packet.RemoveExtension(RtpMid::kId));
209                 break;
210               case MidTestConfiguration::kMidNegotiatedAndPresentInPackets:
211                 EXPECT_TRUE(parsed_packet.HasExtension<RtpMid>());
212                 // The simulated second m= section would have a different MID.
213                 // If we don't modify it here then `second_ssrc` would end up
214                 // being mapped to the first m= section which would cause SSRC
215                 // conflicts if we later add the same SSRC to a second m=
216                 // section. Hidden assumption: first m= section does not use
217                 // MID:1.
218                 ASSERT_TRUE(parsed_packet.SetExtension<RtpMid>("1"));
219                 break;
220             }
221             // Inject the modified packet.
222             rtc::CopyOnWriteBuffer updated_buffer = parsed_packet.Buffer();
223             EmulatedIpPacket updated_packet(
224                 packet.from, packet.to, updated_buffer, packet.arrival_time);
225             send_node->OnPacketReceived(std::move(updated_packet));
226             got_unsignaled_packet = true;
227           }
228         });
229       },
230       [&](const SessionDescriptionInterface& answer) {
231         EXPECT_EQ(answer.description()->contents().size(), 1u);
232         offer_exchange_done = true;
233       });
234   EXPECT_TRUE(s.WaitAndProcess(&offer_exchange_done));
235   EXPECT_TRUE(s.WaitAndProcess(&got_unsignaled_packet));
236   EXPECT_TRUE(s.WaitAndProcess(&first_sink.frame_observed_));
237 
238   auto second_track = caller->CreateVideo("VIDEO2", video_conf);
239   FrameObserver second_sink;
240   callee->AddVideoReceiveSink(second_track.track->id(), &second_sink);
241 
242   // Create a second video stream, munge the sdp to force it to use our fake
243   // early media ssrc.
244   offer_exchange_done = false;
245   signaling.NegotiateSdp(
246       /* munge_sdp = */
247       [&](SessionDescriptionInterface* offer) {
248         set_ssrc(offer, 1, second_ssrc);
249       },
250       /* modify_sdp = */ {},
251       [&](const SessionDescriptionInterface& answer) {
252         EXPECT_EQ(answer.description()->contents().size(), 2u);
253         offer_exchange_done = true;
254       });
255   EXPECT_TRUE(s.WaitAndProcess(&offer_exchange_done));
256   EXPECT_TRUE(s.WaitAndProcess(&second_sink.frame_observed_));
257   caller->pc()->Close();
258   callee->pc()->Close();
259 }
260 
261 INSTANTIATE_TEST_SUITE_P(
262     All,
263     UnsignaledStreamTest,
264     ::testing::Values(MidTestConfiguration::kMidNotNegotiated,
265                       MidTestConfiguration::kMidNegotiatedButMissingFromPackets,
266                       MidTestConfiguration::kMidNegotiatedAndPresentInPackets),
267     TestParametersMidTestConfigurationToString);
268 
269 }  // namespace test
270 }  // namespace webrtc
271