summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/test/peer_scenario/tests
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/test/peer_scenario/tests')
-rw-r--r--third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn35
-rw-r--r--third_party/libwebrtc/test/peer_scenario/tests/bwe_ramp_up_test.cc128
-rw-r--r--third_party/libwebrtc/test/peer_scenario/tests/peer_scenario_quality_test.cc46
-rw-r--r--third_party/libwebrtc/test/peer_scenario/tests/remote_estimate_test.cc113
-rw-r--r--third_party/libwebrtc/test/peer_scenario/tests/unsignaled_stream_test.cc270
5 files changed, 592 insertions, 0 deletions
diff --git a/third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn b/third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn
new file mode 100644
index 0000000000..fb2948922a
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+
+import("../../../webrtc.gni")
+
+if (rtc_include_tests) {
+ rtc_library("tests") {
+ testonly = true
+ sources = [
+ "bwe_ramp_up_test.cc",
+ "peer_scenario_quality_test.cc",
+ "remote_estimate_test.cc",
+ "unsignaled_stream_test.cc",
+ ]
+ deps = [
+ "..:peer_scenario",
+ "../../:field_trial",
+ "../../:test_support",
+ "../../../api:rtc_stats_api",
+ "../../../api/units:data_rate",
+ "../../../api/units:time_delta",
+ "../../../media:rtc_media_base",
+ "../../../media:stream_params",
+ "../../../modules/rtp_rtcp:rtp_rtcp_format",
+ "../../../pc:media_session",
+ "../../../pc:pc_test_utils",
+ "../../../pc:session_description",
+ ]
+ }
+}
diff --git a/third_party/libwebrtc/test/peer_scenario/tests/bwe_ramp_up_test.cc b/third_party/libwebrtc/test/peer_scenario/tests/bwe_ramp_up_test.cc
new file mode 100644
index 0000000000..a7a17bbfd1
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/tests/bwe_ramp_up_test.cc
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/stats/rtcstats_objects.h"
+#include "api/units/data_rate.h"
+#include "api/units/time_delta.h"
+#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/rtp_rtcp/source/rtp_util.h"
+#include "pc/media_session.h"
+#include "pc/test/mock_peer_connection_observers.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/peer_scenario/peer_scenario.h"
+#include "test/peer_scenario/peer_scenario_client.h"
+
+namespace webrtc {
+namespace test {
+
+using ::testing::SizeIs;
+
+rtc::scoped_refptr<const RTCStatsReport> GetStatsAndProcess(
+ PeerScenario& s,
+ PeerScenarioClient* client) {
+ auto stats_collector =
+ rtc::make_ref_counted<webrtc::MockRTCStatsCollectorCallback>();
+ client->pc()->GetStats(stats_collector.get());
+ s.ProcessMessages(TimeDelta::Millis(0));
+ RTC_CHECK(stats_collector->called());
+ return stats_collector->report();
+}
+
+DataRate GetAvailableSendBitrate(
+ const rtc::scoped_refptr<const RTCStatsReport>& report) {
+ auto stats = report->GetStatsOfType<RTCIceCandidatePairStats>();
+ if (stats.empty()) {
+ return DataRate::Zero();
+ }
+ return DataRate::BitsPerSec(*stats[0]->available_outgoing_bitrate);
+}
+
+// Test that caller BWE can rampup even if callee can not demux incoming RTP
+// packets.
+TEST(BweRampupTest, RampUpWithUndemuxableRtpPackets) {
+ PeerScenario s(*test_info_);
+
+ PeerScenarioClient::Config config = PeerScenarioClient::Config();
+ config.disable_encryption = true;
+ PeerScenarioClient* caller = s.CreateClient(config);
+ PeerScenarioClient* callee = s.CreateClient(config);
+
+ auto send_node = s.net()->NodeBuilder().Build().node;
+ auto ret_node = s.net()->NodeBuilder().Build().node;
+
+ s.net()->CreateRoute(caller->endpoint(), {send_node}, callee->endpoint());
+ s.net()->CreateRoute(callee->endpoint(), {ret_node}, caller->endpoint());
+
+ auto signaling = s.ConnectSignaling(caller, callee, {send_node}, {ret_node});
+ PeerScenarioClient::VideoSendTrackConfig video_conf;
+ video_conf.generator.squares_video->framerate = 15;
+
+ PeerScenarioClient::VideoSendTrack track =
+ caller->CreateVideo("VIDEO", video_conf);
+
+ signaling.StartIceSignaling();
+
+ std::atomic<bool> offer_exchange_done(false);
+ signaling.NegotiateSdp(
+ [&](SessionDescriptionInterface* offer) {
+ RtpHeaderExtensionMap extension_map(
+ cricket::GetFirstVideoContentDescription(offer->description())
+ ->rtp_header_extensions());
+ ASSERT_TRUE(extension_map.IsRegistered(kRtpExtensionMid));
+ const std::string video_mid =
+ cricket::GetFirstVideoContent(offer->description())->mid();
+ send_node->router()->SetFilter([extension_map, video_mid, &send_node](
+ const EmulatedIpPacket& packet) {
+ if (IsRtpPacket(packet.data)) {
+ // Replace Mid with another. This should lead to that packets
+ // can not be demuxed by the callee, but BWE should still
+ // function.
+ RtpPacket parsed_packet;
+ parsed_packet.IdentifyExtensions(extension_map);
+ EXPECT_TRUE(parsed_packet.Parse(packet.data));
+ std::string mid;
+ if (parsed_packet.GetExtension<RtpMid>(&mid)) {
+ if (mid == video_mid) {
+ parsed_packet.SetExtension<RtpMid>("x");
+ EmulatedIpPacket updated_packet(packet.from, packet.to,
+ parsed_packet.Buffer(),
+ packet.arrival_time);
+ send_node->OnPacketReceived(std::move(updated_packet));
+ return false;
+ }
+ }
+ }
+ return true;
+ });
+ },
+ [&](const SessionDescriptionInterface& answer) {
+ offer_exchange_done = true;
+ });
+ // Wait for SDP negotiation and the packet filter to be setup.
+ s.WaitAndProcess(&offer_exchange_done);
+
+ DataRate initial_bwe = GetAvailableSendBitrate(GetStatsAndProcess(s, caller));
+ s.ProcessMessages(TimeDelta::Seconds(2));
+
+ auto callee_inbound_stats =
+ GetStatsAndProcess(s, callee)->GetStatsOfType<RTCInboundRtpStreamStats>();
+ ASSERT_THAT(callee_inbound_stats, SizeIs(1));
+ ASSERT_EQ(*callee_inbound_stats[0]->frames_received, 0u);
+
+ DataRate final_bwe = GetAvailableSendBitrate(GetStatsAndProcess(s, caller));
+ // Ensure BWE has increased from the initial BWE. BWE will not increase unless
+ // RTCP feedback is recevied. The increase is just an arbitrary value to
+ // ensure BWE has increased beyond noise levels.
+ EXPECT_GT(final_bwe, initial_bwe + DataRate::KilobitsPerSec(345));
+}
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/peer_scenario/tests/peer_scenario_quality_test.cc b/third_party/libwebrtc/test/peer_scenario/tests/peer_scenario_quality_test.cc
new file mode 100644
index 0000000000..911a68720f
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/tests/peer_scenario_quality_test.cc
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "test/gtest.h"
+#include "test/peer_scenario/peer_scenario.h"
+#include "test/peer_scenario/peer_scenario_client.h"
+
+namespace webrtc {
+namespace test {
+#if defined(WEBRTC_WIN)
+#define MAYBE_PsnrIsCollected DISABLED_PsnrIsCollected
+#else
+#define MAYBE_PsnrIsCollected PsnrIsCollected
+#endif
+TEST(PeerScenarioQualityTest, MAYBE_PsnrIsCollected) {
+ VideoQualityAnalyzer analyzer;
+ {
+ PeerScenario s(*test_info_);
+ auto caller = s.CreateClient(PeerScenarioClient::Config());
+ auto callee = s.CreateClient(PeerScenarioClient::Config());
+ PeerScenarioClient::VideoSendTrackConfig video_conf;
+ video_conf.generator.squares_video->framerate = 20;
+ auto video = caller->CreateVideo("VIDEO", video_conf);
+ auto link_builder = s.net()->NodeBuilder().delay_ms(100).capacity_kbps(600);
+ s.AttachVideoQualityAnalyzer(&analyzer, video.track.get(), callee);
+ s.SimpleConnection(caller, callee, {link_builder.Build().node},
+ {link_builder.Build().node});
+ s.ProcessMessages(TimeDelta::Seconds(2));
+ // Exit scope to ensure that there's no pending tasks reporting to analyzer.
+ }
+
+ // We expect ca 40 frames to be produced, but to avoid flakiness on slow
+ // machines we only test for 10.
+ EXPECT_GT(analyzer.stats().render.count, 10);
+ EXPECT_GT(analyzer.stats().psnr_with_freeze.Mean(), 20);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/peer_scenario/tests/remote_estimate_test.cc b/third_party/libwebrtc/test/peer_scenario/tests/remote_estimate_test.cc
new file mode 100644
index 0000000000..fa343a7fdf
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/tests/remote_estimate_test.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
+#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
+#include "modules/rtp_rtcp/source/rtp_packet.h"
+#include "modules/rtp_rtcp/source/rtp_util.h"
+#include "pc/media_session.h"
+#include "pc/session_description.h"
+#include "test/field_trial.h"
+#include "test/gtest.h"
+#include "test/peer_scenario/peer_scenario.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+RtpHeaderExtensionMap AudioExtensions(
+ const SessionDescriptionInterface& session) {
+ auto* audio_desc =
+ cricket::GetFirstAudioContentDescription(session.description());
+ return RtpHeaderExtensionMap(audio_desc->rtp_header_extensions());
+}
+
+} // namespace
+
+TEST(RemoteEstimateEndToEnd, OfferedCapabilityIsInAnswer) {
+ PeerScenario s(*test_info_);
+
+ auto* caller = s.CreateClient(PeerScenarioClient::Config());
+ auto* callee = s.CreateClient(PeerScenarioClient::Config());
+
+ auto send_link = {s.net()->NodeBuilder().Build().node};
+ auto ret_link = {s.net()->NodeBuilder().Build().node};
+
+ s.net()->CreateRoute(caller->endpoint(), send_link, callee->endpoint());
+ s.net()->CreateRoute(callee->endpoint(), ret_link, caller->endpoint());
+
+ auto signaling = s.ConnectSignaling(caller, callee, send_link, ret_link);
+ caller->CreateVideo("VIDEO", PeerScenarioClient::VideoSendTrackConfig());
+ std::atomic<bool> offer_exchange_done(false);
+ signaling.NegotiateSdp(
+ [](SessionDescriptionInterface* offer) {
+ for (auto& cont : offer->description()->contents()) {
+ cont.media_description()->set_remote_estimate(true);
+ }
+ },
+ [&](const SessionDescriptionInterface& answer) {
+ for (auto& cont : answer.description()->contents()) {
+ EXPECT_TRUE(cont.media_description()->remote_estimate());
+ }
+ offer_exchange_done = true;
+ });
+ RTC_CHECK(s.WaitAndProcess(&offer_exchange_done));
+}
+
+TEST(RemoteEstimateEndToEnd, AudioUsesAbsSendTimeExtension) {
+ // Defined before PeerScenario so it gets destructed after, to avoid use after
+ // free.
+ std::atomic<bool> received_abs_send_time(false);
+ PeerScenario s(*test_info_);
+
+ auto* caller = s.CreateClient(PeerScenarioClient::Config());
+ auto* callee = s.CreateClient(PeerScenarioClient::Config());
+
+ auto send_node = s.net()->NodeBuilder().Build().node;
+ auto ret_node = s.net()->NodeBuilder().Build().node;
+
+ s.net()->CreateRoute(caller->endpoint(), {send_node}, callee->endpoint());
+ s.net()->CreateRoute(callee->endpoint(), {ret_node}, caller->endpoint());
+
+ auto signaling = s.ConnectSignaling(caller, callee, {send_node}, {ret_node});
+ caller->CreateAudio("AUDIO", cricket::AudioOptions());
+ signaling.StartIceSignaling();
+ RtpHeaderExtensionMap extension_map;
+ std::atomic<bool> offer_exchange_done(false);
+ signaling.NegotiateSdp(
+ [&extension_map](SessionDescriptionInterface* offer) {
+ extension_map = AudioExtensions(*offer);
+ EXPECT_TRUE(extension_map.IsRegistered(kRtpExtensionAbsoluteSendTime));
+ },
+ [&](const SessionDescriptionInterface& answer) {
+ EXPECT_TRUE(AudioExtensions(answer).IsRegistered(
+ kRtpExtensionAbsoluteSendTime));
+ offer_exchange_done = true;
+ });
+ RTC_CHECK(s.WaitAndProcess(&offer_exchange_done));
+ send_node->router()->SetWatcher(
+ [extension_map, &received_abs_send_time](const EmulatedIpPacket& packet) {
+ // The dummy packets used by the fake signaling are filled with 0. We
+ // want to ignore those and we can do that on the basis that the first
+ // byte of RTP packets are guaranteed to not be 0.
+ RtpPacket rtp_packet(&extension_map);
+ // TODO(bugs.webrtc.org/14525): Look why there are RTP packets with
+ // payload 72 or 73 (these don't have the RTP AbsoluteSendTime
+ // Extension).
+ if (rtp_packet.Parse(packet.data) && rtp_packet.PayloadType() == 111) {
+ EXPECT_TRUE(rtp_packet.HasExtension<AbsoluteSendTime>());
+ received_abs_send_time = true;
+ }
+ });
+ RTC_CHECK(s.WaitAndProcess(&received_abs_send_time));
+ caller->pc()->Close();
+ callee->pc()->Close();
+}
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/peer_scenario/tests/unsignaled_stream_test.cc b/third_party/libwebrtc/test/peer_scenario/tests/unsignaled_stream_test.cc
new file mode 100644
index 0000000000..4f478b4b2a
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/tests/unsignaled_stream_test.cc
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "media/base/stream_params.h"
+#include "modules/rtp_rtcp/source/byte_io.h"
+#include "modules/rtp_rtcp/source/rtp_util.h"
+#include "pc/media_session.h"
+#include "pc/session_description.h"
+#include "test/field_trial.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/peer_scenario/peer_scenario.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+enum class MidTestConfiguration {
+ // Legacy endpoint setup where PT demuxing is used.
+ kMidNotNegotiated,
+ // MID is negotiated but missing from packets. PT demuxing is disabled, so
+ // SSRCs have to be added to the SDP for WebRTC to forward packets correctly.
+ // Happens when client is spec compliant but the SFU isn't. Popular legacy.
+ kMidNegotiatedButMissingFromPackets,
+ // Fully spec-compliant: MID is present so we can safely drop packets with
+ // unknown MIDs.
+ kMidNegotiatedAndPresentInPackets,
+};
+
+// Gives the parameterized test a readable suffix.
+std::string TestParametersMidTestConfigurationToString(
+ testing::TestParamInfo<MidTestConfiguration> info) {
+ switch (info.param) {
+ case MidTestConfiguration::kMidNotNegotiated:
+ return "MidNotNegotiated";
+ case MidTestConfiguration::kMidNegotiatedButMissingFromPackets:
+ return "MidNegotiatedButMissingFromPackets";
+ case MidTestConfiguration::kMidNegotiatedAndPresentInPackets:
+ return "MidNegotiatedAndPresentInPackets";
+ }
+}
+
+class FrameObserver : public rtc::VideoSinkInterface<VideoFrame> {
+ public:
+ FrameObserver() : frame_observed_(false) {}
+ void OnFrame(const VideoFrame&) override { frame_observed_ = true; }
+
+ std::atomic<bool> frame_observed_;
+};
+
+uint32_t get_ssrc(SessionDescriptionInterface* offer, size_t track_index) {
+ EXPECT_LT(track_index, offer->description()->contents().size());
+ return offer->description()
+ ->contents()[track_index]
+ .media_description()
+ ->streams()[0]
+ .ssrcs[0];
+}
+
+void set_ssrc(SessionDescriptionInterface* offer, size_t index, uint32_t ssrc) {
+ EXPECT_LT(index, offer->description()->contents().size());
+ cricket::StreamParams& new_stream_params = offer->description()
+ ->contents()[index]
+ .media_description()
+ ->mutable_streams()[0];
+ new_stream_params.ssrcs[0] = ssrc;
+ new_stream_params.ssrc_groups[0].ssrcs[0] = ssrc;
+}
+
+} // namespace
+
+class UnsignaledStreamTest
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<MidTestConfiguration> {};
+
+TEST_P(UnsignaledStreamTest, ReplacesUnsignaledStreamOnCompletedSignaling) {
+ // This test covers a scenario that might occur if a remote client starts
+ // sending media packets before negotiation has completed. Depending on setup,
+ // these packets either get dropped or trigger an unsignalled default stream
+ // to be created, and connects that to a default video sink.
+ // In some edge cases using Unified Plan and PT demuxing, the default stream
+ // is create in a different transceiver to where the media SSRC will actually
+ // be used. This test verifies that the default stream is removed properly,
+ // and that packets are demuxed and video frames reach the desired sink.
+ const MidTestConfiguration kMidTestConfiguration = GetParam();
+
+ // Defined before PeerScenario so it gets destructed after, to avoid use after
+ // free.
+ PeerScenario s(*::testing::UnitTest::GetInstance()->current_test_info());
+
+ PeerScenarioClient::Config config = PeerScenarioClient::Config();
+ // Disable encryption so that we can inject a fake early media packet without
+ // triggering srtp failures.
+ config.disable_encryption = true;
+ auto* caller = s.CreateClient(config);
+ auto* callee = s.CreateClient(config);
+
+ auto send_node = s.net()->NodeBuilder().Build().node;
+ auto ret_node = s.net()->NodeBuilder().Build().node;
+
+ s.net()->CreateRoute(caller->endpoint(), {send_node}, callee->endpoint());
+ s.net()->CreateRoute(callee->endpoint(), {ret_node}, caller->endpoint());
+
+ auto signaling = s.ConnectSignaling(caller, callee, {send_node}, {ret_node});
+ PeerScenarioClient::VideoSendTrackConfig video_conf;
+ video_conf.generator.squares_video->framerate = 15;
+
+ auto first_track = caller->CreateVideo("VIDEO", video_conf);
+ FrameObserver first_sink;
+ callee->AddVideoReceiveSink(first_track.track->id(), &first_sink);
+
+ signaling.StartIceSignaling();
+ std::atomic<bool> offer_exchange_done(false);
+ std::atomic<bool> got_unsignaled_packet(false);
+
+ // We will capture the media ssrc of the first added stream, and preemptively
+ // inject a new media packet using a different ssrc. What happens depends on
+ // the test configuration.
+ //
+ // MidTestConfiguration::kMidNotNegotiated:
+ // - MID is not negotiated which means PT-based demuxing is enabled. Because
+ // the packets have no MID, the second ssrc packet gets forwarded to the
+ // first m= section. This will create a "default stream" for the second ssrc
+ // and connect it to the default video sink (not set in this test). The test
+ // verifies we can recover from this when we later get packets for the first
+ // ssrc.
+ //
+ // MidTestConfiguration::kMidNegotiatedButMissingFromPackets:
+ // - MID is negotiated wich means PT-based demuxing is disabled. Because we
+ // modify the packets not to contain the MID anyway (simulating a legacy SFU
+ // that does not negotiate properly) unknown SSRCs are dropped but do not
+ // otherwise cause any issues.
+ //
+ // MidTestConfiguration::kMidNegotiatedAndPresentInPackets:
+ // - MID is negotiated which means PT-based demuxing is enabled. In this case
+ // the packets have the MID so they either get forwarded or dropped
+ // depending on if the MID is known. The spec-compliant way is also the most
+ // straight-forward one.
+
+ uint32_t first_ssrc = 0;
+ uint32_t second_ssrc = 0;
+ absl::optional<int> mid_header_extension_id = absl::nullopt;
+
+ signaling.NegotiateSdp(
+ /* munge_sdp = */
+ [&](SessionDescriptionInterface* offer) {
+ // Obtain the MID header extension ID and if we want the
+ // MidTestConfiguration::kMidNotNegotiated setup then we remove the MID
+ // header extension through SDP munging (otherwise SDP is not modified).
+ for (cricket::ContentInfo& content_info :
+ offer->description()->contents()) {
+ std::vector<RtpExtension> header_extensions =
+ content_info.media_description()->rtp_header_extensions();
+ for (auto it = header_extensions.begin();
+ it != header_extensions.end(); ++it) {
+ if (it->uri == RtpExtension::kMidUri) {
+ // MID header extension found!
+ mid_header_extension_id = it->id;
+ if (kMidTestConfiguration ==
+ MidTestConfiguration::kMidNotNegotiated) {
+ // Munge away the extension.
+ header_extensions.erase(it);
+ }
+ break;
+ }
+ }
+ content_info.media_description()->set_rtp_header_extensions(
+ std::move(header_extensions));
+ }
+ ASSERT_TRUE(mid_header_extension_id.has_value());
+ },
+ /* modify_sdp = */
+ [&](SessionDescriptionInterface* offer) {
+ first_ssrc = get_ssrc(offer, 0);
+ second_ssrc = first_ssrc + 1;
+
+ send_node->router()->SetWatcher([&](const EmulatedIpPacket& packet) {
+ if (IsRtpPacket(packet.data) &&
+ ByteReader<uint32_t>::ReadBigEndian(&(packet.cdata()[8])) ==
+ first_ssrc &&
+ !got_unsignaled_packet) {
+ // Parse packet and modify the SSRC to simulate a second m=
+ // section that has not been negotiated yet.
+ std::vector<RtpExtension> extensions;
+ extensions.emplace_back(RtpExtension::kMidUri,
+ mid_header_extension_id.value());
+ RtpHeaderExtensionMap extensions_map(extensions);
+ RtpPacket parsed_packet;
+ parsed_packet.IdentifyExtensions(extensions_map);
+ ASSERT_TRUE(parsed_packet.Parse(packet.data));
+ parsed_packet.SetSsrc(second_ssrc);
+ // The MID extension is present if and only if it was negotiated.
+ // If present, we either want to remove it or modify it depending
+ // on setup.
+ switch (kMidTestConfiguration) {
+ case MidTestConfiguration::kMidNotNegotiated:
+ EXPECT_FALSE(parsed_packet.HasExtension<RtpMid>());
+ break;
+ case MidTestConfiguration::kMidNegotiatedButMissingFromPackets:
+ EXPECT_TRUE(parsed_packet.HasExtension<RtpMid>());
+ ASSERT_TRUE(parsed_packet.RemoveExtension(RtpMid::kId));
+ break;
+ case MidTestConfiguration::kMidNegotiatedAndPresentInPackets:
+ EXPECT_TRUE(parsed_packet.HasExtension<RtpMid>());
+ // The simulated second m= section would have a different MID.
+ // If we don't modify it here then `second_ssrc` would end up
+ // being mapped to the first m= section which would cause SSRC
+ // conflicts if we later add the same SSRC to a second m=
+ // section. Hidden assumption: first m= section does not use
+ // MID:1.
+ ASSERT_TRUE(parsed_packet.SetExtension<RtpMid>("1"));
+ break;
+ }
+ // Inject the modified packet.
+ rtc::CopyOnWriteBuffer updated_buffer = parsed_packet.Buffer();
+ EmulatedIpPacket updated_packet(
+ packet.from, packet.to, updated_buffer, packet.arrival_time);
+ send_node->OnPacketReceived(std::move(updated_packet));
+ got_unsignaled_packet = true;
+ }
+ });
+ },
+ [&](const SessionDescriptionInterface& answer) {
+ EXPECT_EQ(answer.description()->contents().size(), 1u);
+ offer_exchange_done = true;
+ });
+ EXPECT_TRUE(s.WaitAndProcess(&offer_exchange_done));
+ EXPECT_TRUE(s.WaitAndProcess(&got_unsignaled_packet));
+ EXPECT_TRUE(s.WaitAndProcess(&first_sink.frame_observed_));
+
+ auto second_track = caller->CreateVideo("VIDEO2", video_conf);
+ FrameObserver second_sink;
+ callee->AddVideoReceiveSink(second_track.track->id(), &second_sink);
+
+ // Create a second video stream, munge the sdp to force it to use our fake
+ // early media ssrc.
+ offer_exchange_done = false;
+ signaling.NegotiateSdp(
+ /* munge_sdp = */
+ [&](SessionDescriptionInterface* offer) {
+ set_ssrc(offer, 1, second_ssrc);
+ },
+ /* modify_sdp = */ {},
+ [&](const SessionDescriptionInterface& answer) {
+ EXPECT_EQ(answer.description()->contents().size(), 2u);
+ offer_exchange_done = true;
+ });
+ EXPECT_TRUE(s.WaitAndProcess(&offer_exchange_done));
+ EXPECT_TRUE(s.WaitAndProcess(&second_sink.frame_observed_));
+ caller->pc()->Close();
+ callee->pc()->Close();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ All,
+ UnsignaledStreamTest,
+ ::testing::Values(MidTestConfiguration::kMidNotNegotiated,
+ MidTestConfiguration::kMidNegotiatedButMissingFromPackets,
+ MidTestConfiguration::kMidNegotiatedAndPresentInPackets),
+ TestParametersMidTestConfigurationToString);
+
+} // namespace test
+} // namespace webrtc