diff options
Diffstat (limited to 'third_party/libwebrtc/test/peer_scenario/tests')
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 |