summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/test/peer_scenario
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/libwebrtc/test/peer_scenario
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/test/peer_scenario')
-rw-r--r--third_party/libwebrtc/test/peer_scenario/BUILD.gn68
-rw-r--r--third_party/libwebrtc/test/peer_scenario/DEPS5
-rw-r--r--third_party/libwebrtc/test/peer_scenario/peer_scenario.cc127
-rw-r--r--third_party/libwebrtc/test/peer_scenario/peer_scenario.h122
-rw-r--r--third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc428
-rw-r--r--third_party/libwebrtc/test/peer_scenario/peer_scenario_client.h179
-rw-r--r--third_party/libwebrtc/test/peer_scenario/scenario_connection.cc242
-rw-r--r--third_party/libwebrtc/test/peer_scenario/scenario_connection.h66
-rw-r--r--third_party/libwebrtc/test/peer_scenario/signaling_route.cc114
-rw-r--r--third_party/libwebrtc/test/peer_scenario/signaling_route.h67
-rw-r--r--third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn30
-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.cc112
-rw-r--r--third_party/libwebrtc/test/peer_scenario/tests/unsignaled_stream_test.cc270
14 files changed, 1876 insertions, 0 deletions
diff --git a/third_party/libwebrtc/test/peer_scenario/BUILD.gn b/third_party/libwebrtc/test/peer_scenario/BUILD.gn
new file mode 100644
index 0000000000..00492a18a9
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/BUILD.gn
@@ -0,0 +1,68 @@
+# 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("peer_scenario") {
+ testonly = true
+ sources = [
+ "peer_scenario.cc",
+ "peer_scenario.h",
+ "peer_scenario_client.cc",
+ "peer_scenario_client.h",
+ "scenario_connection.cc",
+ "scenario_connection.h",
+ "signaling_route.cc",
+ "signaling_route.h",
+ ]
+ deps = [
+ "..:fake_video_codecs",
+ "..:fileutils",
+ "..:test_support",
+ "../:video_test_common",
+ "../../api:candidate",
+ "../../api:create_time_controller",
+ "../../api:libjingle_peerconnection_api",
+ "../../api:network_emulation_manager_api",
+ "../../api:rtc_stats_api",
+ "../../api:time_controller",
+ "../../api/audio_codecs:builtin_audio_decoder_factory",
+ "../../api/audio_codecs:builtin_audio_encoder_factory",
+ "../../api/rtc_event_log:rtc_event_log_factory",
+ "../../api/task_queue:default_task_queue_factory",
+ "../../api/transport:field_trial_based_config",
+ "../../api/video_codecs:builtin_video_decoder_factory",
+ "../../api/video_codecs:builtin_video_encoder_factory",
+ "../../media:rtc_audio_video",
+ "../../media:rtc_media_base",
+ "../../media:rtp_utils",
+ "../../modules/audio_device:audio_device_impl",
+ "../../modules/rtp_rtcp:rtp_rtcp_format",
+ "../../p2p:rtc_p2p",
+ "../../pc:channel",
+ "../../pc:jsep_transport_controller",
+ "../../pc:pc_test_utils",
+ "../../pc:rtp_transport_internal",
+ "../../pc:session_description",
+ "../../rtc_base:null_socket_server",
+ "../../rtc_base:stringutils",
+ "../../rtc_base:task_queue_for_test",
+ "../../test:explicit_key_value_config",
+ "../../test:scoped_key_value_config",
+ "../logging:log_writer",
+ "../network:emulated_network",
+ "../scenario",
+ "../time_controller",
+ ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/flags:flag",
+ "//third_party/abseil-cpp/absl/memory",
+ ]
+ }
+}
diff --git a/third_party/libwebrtc/test/peer_scenario/DEPS b/third_party/libwebrtc/test/peer_scenario/DEPS
new file mode 100644
index 0000000000..68e9f46087
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+pc",
+ "+p2p",
+]
+
diff --git a/third_party/libwebrtc/test/peer_scenario/peer_scenario.cc b/third_party/libwebrtc/test/peer_scenario/peer_scenario.cc
new file mode 100644
index 0000000000..485e33f67f
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario.cc
@@ -0,0 +1,127 @@
+/*
+ * 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/peer_scenario/peer_scenario.h"
+
+#include "absl/flags/flag.h"
+#include "absl/memory/memory.h"
+#include "rtc_base/null_socket_server.h"
+#include "rtc_base/string_encode.h"
+#include "rtc_base/strings/string_builder.h"
+#include "test/logging/file_log_writer.h"
+#include "test/testsupport/file_utils.h"
+#include "test/time_controller/real_time_controller.h"
+#include "test/time_controller/simulated_time_controller.h"
+
+ABSL_FLAG(bool, peer_logs, false, "Save logs from peer scenario framework.");
+ABSL_FLAG(std::string,
+ peer_logs_root,
+ "",
+ "Output root path, based on project root if unset.");
+
+namespace webrtc {
+namespace test {
+namespace {
+std::unique_ptr<FileLogWriterFactory> GetPeerScenarioLogManager(
+ std::string file_name) {
+ if (absl::GetFlag(FLAGS_peer_logs) && !file_name.empty()) {
+ std::string output_root = absl::GetFlag(FLAGS_peer_logs_root);
+ if (output_root.empty())
+ output_root = OutputPath() + "output_data/";
+
+ auto base_filename = output_root + file_name + ".";
+ RTC_LOG(LS_INFO) << "Saving peer scenario logs to: " << base_filename;
+ return std::make_unique<FileLogWriterFactory>(base_filename);
+ }
+ return nullptr;
+}
+} // namespace
+
+PeerScenario::PeerScenario(const testing::TestInfo& test_info, TimeMode mode)
+ : PeerScenario(
+ std::string(test_info.test_suite_name()) + "/" + test_info.name(),
+ mode) {}
+
+PeerScenario::PeerScenario(std::string file_name, TimeMode mode)
+ : PeerScenario(GetPeerScenarioLogManager(file_name), mode) {}
+
+PeerScenario::PeerScenario(
+ std::unique_ptr<LogWriterFactoryInterface> log_writer_manager,
+ TimeMode mode)
+ : log_writer_manager_(std::move(log_writer_manager)),
+ net_(mode, EmulatedNetworkStatsGatheringMode::kDefault),
+ signaling_thread_(net_.time_controller()->GetMainThread()) {}
+
+PeerScenarioClient* PeerScenario::CreateClient(
+ PeerScenarioClient::Config config) {
+ return CreateClient(
+ std::string("client_") + rtc::ToString(peer_clients_.size() + 1), config);
+}
+
+PeerScenarioClient* PeerScenario::CreateClient(
+ std::string name,
+ PeerScenarioClient::Config config) {
+ peer_clients_.emplace_back(net(), signaling_thread_,
+ GetLogWriterFactory(name), config);
+ return &peer_clients_.back();
+}
+
+SignalingRoute PeerScenario::ConnectSignaling(
+ PeerScenarioClient* caller,
+ PeerScenarioClient* callee,
+ std::vector<EmulatedNetworkNode*> send_link,
+ std::vector<EmulatedNetworkNode*> ret_link) {
+ return SignalingRoute(caller, callee, net_.CreateCrossTrafficRoute(send_link),
+ net_.CreateCrossTrafficRoute(ret_link));
+}
+
+void PeerScenario::SimpleConnection(
+ PeerScenarioClient* caller,
+ PeerScenarioClient* callee,
+ std::vector<EmulatedNetworkNode*> send_link,
+ std::vector<EmulatedNetworkNode*> ret_link) {
+ net()->CreateRoute(caller->endpoint(), send_link, callee->endpoint());
+ net()->CreateRoute(callee->endpoint(), ret_link, caller->endpoint());
+ auto signaling = ConnectSignaling(caller, callee, send_link, ret_link);
+ signaling.StartIceSignaling();
+ std::atomic<bool> done(false);
+ signaling.NegotiateSdp(
+ [&](const SessionDescriptionInterface&) { done = true; });
+ RTC_CHECK(WaitAndProcess(&done));
+}
+
+void PeerScenario::AttachVideoQualityAnalyzer(VideoQualityAnalyzer* analyzer,
+ VideoTrackInterface* send_track,
+ PeerScenarioClient* receiver) {
+ video_quality_pairs_.emplace_back(clock(), analyzer);
+ auto pair = &video_quality_pairs_.back();
+ send_track->AddOrUpdateSink(&pair->capture_tap_, rtc::VideoSinkWants());
+ receiver->AddVideoReceiveSink(send_track->id(), &pair->decode_tap_);
+}
+
+bool PeerScenario::WaitAndProcess(std::atomic<bool>* event,
+ TimeDelta max_duration) {
+ return net_.time_controller()->Wait([event] { return event->load(); },
+ max_duration);
+}
+
+void PeerScenario::ProcessMessages(TimeDelta duration) {
+ net_.time_controller()->AdvanceTime(duration);
+}
+
+std::unique_ptr<LogWriterFactoryInterface> PeerScenario::GetLogWriterFactory(
+ std::string name) {
+ if (!log_writer_manager_ || name.empty())
+ return nullptr;
+ return std::make_unique<LogWriterFactoryAddPrefix>(log_writer_manager_.get(),
+ name);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/peer_scenario/peer_scenario.h b/third_party/libwebrtc/test/peer_scenario/peer_scenario.h
new file mode 100644
index 0000000000..a177eeaac6
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario.h
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+#ifndef TEST_PEER_SCENARIO_PEER_SCENARIO_H_
+#define TEST_PEER_SCENARIO_PEER_SCENARIO_H_
+
+// The peer connection scenario test framework enables writing end to end unit
+// tests on the peer connection level. It's similar to the Scenario test but
+// uses the full stack, including SDP and ICE negotiation. This ensures that
+// features work end to end. It's also diffferent from the other tests on peer
+// connection level in that it does not rely on any mocks or fakes other than
+// for media input and networking. Additionally it provides direct access to the
+// underlying peer connection class.
+
+#include <list>
+#include <vector>
+
+#include "api/test/time_controller.h"
+#include "test/gtest.h"
+#include "test/logging/log_writer.h"
+#include "test/network/network_emulation_manager.h"
+#include "test/peer_scenario/peer_scenario_client.h"
+#include "test/peer_scenario/signaling_route.h"
+#include "test/scenario/stats_collection.h"
+#include "test/scenario/video_frame_matcher.h"
+
+namespace webrtc {
+namespace test {
+// The PeerScenario class represents a PeerConnection simulation scenario. The
+// main purpose is to maintain ownership and ensure safe destruction order of
+// clients and network emulation. Additionally it reduces the amount of boiler
+// plate requited for some actions. For example usage see the existing tests
+// using this class. Note that it should be used from a single calling thread.
+// This thread will also be assigned as the signaling thread for all peer
+// connections that are created. This means that the process methods must be
+// used when waiting to ensure that messages are processed on the signaling
+// thread.
+class PeerScenario {
+ public:
+ // The name is used for log output when those are enabled by the --peer_logs
+ // command line flag. Optionally, the TestInfo struct available in gtest can
+ // be used to automatically generate a path based on the test name.
+ explicit PeerScenario(const testing::TestInfo& test_info,
+ TimeMode mode = TimeMode::kSimulated);
+ explicit PeerScenario(std::string file_name,
+ TimeMode mode = TimeMode::kSimulated);
+ explicit PeerScenario(
+ std::unique_ptr<LogWriterFactoryInterface> log_writer_manager,
+ TimeMode mode = TimeMode::kSimulated);
+
+ NetworkEmulationManagerImpl* net() { return &net_; }
+
+ // Creates a client wrapping a peer connection conforming to the given config.
+ // The client will share the signaling thread with the scenario. To maintain
+ // control of destruction order, ownership is kept within the scenario.
+ PeerScenarioClient* CreateClient(PeerScenarioClient::Config config);
+ PeerScenarioClient* CreateClient(std::string name,
+ PeerScenarioClient::Config config);
+
+ // Sets up a signaling route that can be used for SDP and ICE.
+ SignalingRoute ConnectSignaling(PeerScenarioClient* caller,
+ PeerScenarioClient* callee,
+ std::vector<EmulatedNetworkNode*> send_link,
+ std::vector<EmulatedNetworkNode*> ret_link);
+
+ // Connects two clients over given links. This will also start ICE signaling
+ // and SDP negotiation with default behavior. For customized behavior,
+ // ConnectSignaling should be used to allow more detailed control, for
+ // instance to allow different signaling and media routes.
+ void SimpleConnection(PeerScenarioClient* caller,
+ PeerScenarioClient* callee,
+ std::vector<EmulatedNetworkNode*> send_link,
+ std::vector<EmulatedNetworkNode*> ret_link);
+
+ // Starts feeding the results of comparing captured frames from `send_track`
+ // with decoded frames on `receiver` to `analyzer`.
+ // TODO(srte): Provide a way to detach to allow removal of tracks.
+ void AttachVideoQualityAnalyzer(VideoQualityAnalyzer* analyzer,
+ VideoTrackInterface* send_track,
+ PeerScenarioClient* receiver);
+
+ // Waits on `event` while processing messages on the signaling thread.
+ bool WaitAndProcess(std::atomic<bool>* event,
+ TimeDelta max_duration = TimeDelta::Seconds(5));
+
+ // Process messages on the signaling thread for the given duration.
+ void ProcessMessages(TimeDelta duration);
+
+ private:
+ // Helper struct to maintain ownership of the matcher and taps.
+ struct PeerVideoQualityPair {
+ public:
+ PeerVideoQualityPair(Clock* capture_clock, VideoQualityAnalyzer* analyzer)
+ : matcher_({analyzer->Handler()}),
+ capture_tap_(capture_clock, &matcher_),
+ decode_tap_(capture_clock, &matcher_, 0) {}
+ VideoFrameMatcher matcher_;
+ CapturedFrameTap capture_tap_;
+ DecodedFrameTap decode_tap_;
+ };
+
+ Clock* clock() { return Clock::GetRealTimeClock(); }
+
+ std::unique_ptr<LogWriterFactoryInterface> GetLogWriterFactory(
+ std::string name);
+
+ const std::unique_ptr<LogWriterFactoryInterface> log_writer_manager_;
+ NetworkEmulationManagerImpl net_;
+ rtc::Thread* const signaling_thread_;
+ std::list<PeerVideoQualityPair> video_quality_pairs_;
+ std::list<PeerScenarioClient> peer_clients_;
+};
+
+} // namespace test
+} // namespace webrtc
+#endif // TEST_PEER_SCENARIO_PEER_SCENARIO_H_
diff --git a/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc
new file mode 100644
index 0000000000..5d77f17561
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc
@@ -0,0 +1,428 @@
+/*
+ * 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/peer_scenario/peer_scenario_client.h"
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "api/audio_codecs/builtin_audio_decoder_factory.h"
+#include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "api/rtc_event_log/rtc_event_log_factory.h"
+#include "api/task_queue/default_task_queue_factory.h"
+#include "api/test/create_time_controller.h"
+#include "api/transport/field_trial_based_config.h"
+#include "api/video_codecs/builtin_video_decoder_factory.h"
+#include "api/video_codecs/builtin_video_encoder_factory.h"
+#include "media/engine/webrtc_media_engine.h"
+#include "modules/audio_device/include/test_audio_device.h"
+#include "p2p/client/basic_port_allocator.h"
+#include "test/fake_decoder.h"
+#include "test/fake_vp8_encoder.h"
+#include "test/frame_generator_capturer.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+
+constexpr char kCommonStreamId[] = "stream_id";
+
+std::map<int, EmulatedEndpoint*> CreateEndpoints(
+ NetworkEmulationManager* net,
+ std::map<int, EmulatedEndpointConfig> endpoint_configs) {
+ std::map<int, EmulatedEndpoint*> endpoints;
+ for (const auto& kv : endpoint_configs)
+ endpoints[kv.first] = net->CreateEndpoint(kv.second);
+ return endpoints;
+}
+
+class LambdaPeerConnectionObserver final : public PeerConnectionObserver {
+ public:
+ explicit LambdaPeerConnectionObserver(
+ PeerScenarioClient::CallbackHandlers* handlers)
+ : handlers_(handlers) {}
+ void OnSignalingChange(
+ PeerConnectionInterface::SignalingState new_state) override {
+ for (const auto& handler : handlers_->on_signaling_change)
+ handler(new_state);
+ }
+ void OnDataChannel(
+ rtc::scoped_refptr<DataChannelInterface> data_channel) override {
+ for (const auto& handler : handlers_->on_data_channel)
+ handler(data_channel);
+ }
+ void OnRenegotiationNeeded() override {
+ for (const auto& handler : handlers_->on_renegotiation_needed)
+ handler();
+ }
+ void OnStandardizedIceConnectionChange(
+ PeerConnectionInterface::IceConnectionState new_state) override {
+ for (const auto& handler : handlers_->on_standardized_ice_connection_change)
+ handler(new_state);
+ }
+ void OnConnectionChange(
+ PeerConnectionInterface::PeerConnectionState new_state) override {
+ for (const auto& handler : handlers_->on_connection_change)
+ handler(new_state);
+ }
+ void OnIceGatheringChange(
+ PeerConnectionInterface::IceGatheringState new_state) override {
+ for (const auto& handler : handlers_->on_ice_gathering_change)
+ handler(new_state);
+ }
+ void OnIceCandidate(const IceCandidateInterface* candidate) override {
+ for (const auto& handler : handlers_->on_ice_candidate)
+ handler(candidate);
+ }
+ void OnIceCandidateError(const std::string& address,
+ int port,
+ const std::string& url,
+ int error_code,
+ const std::string& error_text) override {
+ for (const auto& handler : handlers_->on_ice_candidate_error)
+ handler(address, port, url, error_code, error_text);
+ }
+ void OnIceCandidatesRemoved(
+ const std::vector<cricket::Candidate>& candidates) override {
+ for (const auto& handler : handlers_->on_ice_candidates_removed)
+ handler(candidates);
+ }
+ void OnAddTrack(rtc::scoped_refptr<RtpReceiverInterface> receiver,
+ const std::vector<rtc::scoped_refptr<MediaStreamInterface> >&
+ streams) override {
+ for (const auto& handler : handlers_->on_add_track)
+ handler(receiver, streams);
+ }
+ void OnTrack(
+ rtc::scoped_refptr<RtpTransceiverInterface> transceiver) override {
+ for (const auto& handler : handlers_->on_track)
+ handler(transceiver);
+ }
+ void OnRemoveTrack(
+ rtc::scoped_refptr<RtpReceiverInterface> receiver) override {
+ for (const auto& handler : handlers_->on_remove_track)
+ handler(receiver);
+ }
+
+ private:
+ PeerScenarioClient::CallbackHandlers* handlers_;
+};
+
+class LambdaCreateSessionDescriptionObserver
+ : public CreateSessionDescriptionObserver {
+ public:
+ explicit LambdaCreateSessionDescriptionObserver(
+ std::function<void(std::unique_ptr<SessionDescriptionInterface> desc)>
+ on_success)
+ : on_success_(on_success) {}
+ void OnSuccess(SessionDescriptionInterface* desc) override {
+ // Takes ownership of answer, according to CreateSessionDescriptionObserver
+ // convention.
+ on_success_(absl::WrapUnique(desc));
+ }
+ void OnFailure(RTCError error) override {
+ RTC_DCHECK_NOTREACHED() << error.message();
+ }
+
+ private:
+ std::function<void(std::unique_ptr<SessionDescriptionInterface> desc)>
+ on_success_;
+};
+
+class LambdaSetLocalDescriptionObserver
+ : public SetLocalDescriptionObserverInterface {
+ public:
+ explicit LambdaSetLocalDescriptionObserver(
+ std::function<void(RTCError)> on_complete)
+ : on_complete_(on_complete) {}
+ void OnSetLocalDescriptionComplete(RTCError error) override {
+ on_complete_(error);
+ }
+
+ private:
+ std::function<void(RTCError)> on_complete_;
+};
+
+class LambdaSetRemoteDescriptionObserver
+ : public SetRemoteDescriptionObserverInterface {
+ public:
+ explicit LambdaSetRemoteDescriptionObserver(
+ std::function<void(RTCError)> on_complete)
+ : on_complete_(on_complete) {}
+ void OnSetRemoteDescriptionComplete(RTCError error) override {
+ on_complete_(error);
+ }
+
+ private:
+ std::function<void(RTCError)> on_complete_;
+};
+
+class FakeVideoEncoderFactory : public VideoEncoderFactory {
+ public:
+ FakeVideoEncoderFactory(Clock* clock) : clock_(clock) {}
+ std::vector<SdpVideoFormat> GetSupportedFormats() const override {
+ return {SdpVideoFormat("VP8")};
+ }
+ std::unique_ptr<VideoEncoder> CreateVideoEncoder(
+ const SdpVideoFormat& format) override {
+ RTC_CHECK_EQ(format.name, "VP8");
+ return std::make_unique<FakeVp8Encoder>(clock_);
+ }
+
+ private:
+ Clock* const clock_;
+};
+class FakeVideoDecoderFactory : public VideoDecoderFactory {
+ public:
+ std::vector<SdpVideoFormat> GetSupportedFormats() const override {
+ return {SdpVideoFormat("VP8")};
+ }
+ std::unique_ptr<VideoDecoder> CreateVideoDecoder(
+ const SdpVideoFormat& format) override {
+ return std::make_unique<FakeDecoder>();
+ }
+};
+} // namespace
+
+PeerScenarioClient::PeerScenarioClient(
+ NetworkEmulationManager* net,
+ rtc::Thread* signaling_thread,
+ std::unique_ptr<LogWriterFactoryInterface> log_writer_factory,
+ PeerScenarioClient::Config config)
+ : endpoints_(CreateEndpoints(net, config.endpoints)),
+ task_queue_factory_(net->time_controller()->GetTaskQueueFactory()),
+ signaling_thread_(signaling_thread),
+ log_writer_factory_(std::move(log_writer_factory)),
+ worker_thread_(net->time_controller()->CreateThread("worker")),
+ handlers_(config.handlers),
+ observer_(new LambdaPeerConnectionObserver(&handlers_)) {
+ handlers_.on_track.push_back(
+ [this](rtc::scoped_refptr<RtpTransceiverInterface> transceiver) {
+ auto track = transceiver->receiver()->track().get();
+ if (track->kind() == MediaStreamTrackInterface::kVideoKind) {
+ auto* video = static_cast<VideoTrackInterface*>(track);
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ for (auto* sink : track_id_to_video_sinks_[track->id()]) {
+ video->AddOrUpdateSink(sink, rtc::VideoSinkWants());
+ }
+ }
+ });
+ handlers_.on_signaling_change.push_back(
+ [this](PeerConnectionInterface::SignalingState state) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ if (state == PeerConnectionInterface::SignalingState::kStable &&
+ peer_connection_->current_remote_description()) {
+ for (const auto& candidate : pending_ice_candidates_) {
+ RTC_CHECK(peer_connection_->AddIceCandidate(candidate.get()));
+ }
+ pending_ice_candidates_.clear();
+ }
+ });
+
+ std::vector<EmulatedEndpoint*> endpoints_vector;
+ for (const auto& kv : endpoints_)
+ endpoints_vector.push_back(kv.second);
+ auto* manager = net->CreateEmulatedNetworkManagerInterface(endpoints_vector);
+
+ PeerConnectionFactoryDependencies pcf_deps;
+ pcf_deps.network_thread = manager->network_thread();
+ pcf_deps.signaling_thread = signaling_thread_;
+ pcf_deps.worker_thread = worker_thread_.get();
+ pcf_deps.call_factory =
+ CreateTimeControllerBasedCallFactory(net->time_controller());
+ pcf_deps.task_queue_factory =
+ net->time_controller()->CreateTaskQueueFactory();
+ pcf_deps.event_log_factory =
+ std::make_unique<RtcEventLogFactory>(task_queue_factory_);
+ pcf_deps.trials = std::make_unique<FieldTrialBasedConfig>();
+
+ cricket::MediaEngineDependencies media_deps;
+ media_deps.task_queue_factory = task_queue_factory_;
+ media_deps.adm = TestAudioDeviceModule::Create(
+ task_queue_factory_,
+ TestAudioDeviceModule::CreatePulsedNoiseCapturer(
+ config.audio.pulsed_noise->amplitude *
+ std::numeric_limits<int16_t>::max(),
+ config.audio.sample_rate, config.audio.channels),
+ TestAudioDeviceModule::CreateDiscardRenderer(config.audio.sample_rate));
+
+ media_deps.audio_processing = AudioProcessingBuilder().Create();
+ if (config.video.use_fake_codecs) {
+ media_deps.video_encoder_factory =
+ std::make_unique<FakeVideoEncoderFactory>(
+ net->time_controller()->GetClock());
+ media_deps.video_decoder_factory =
+ std::make_unique<FakeVideoDecoderFactory>();
+ } else {
+ media_deps.video_encoder_factory = CreateBuiltinVideoEncoderFactory();
+ media_deps.video_decoder_factory = CreateBuiltinVideoDecoderFactory();
+ }
+ media_deps.audio_encoder_factory = CreateBuiltinAudioEncoderFactory();
+ media_deps.audio_decoder_factory = CreateBuiltinAudioDecoderFactory();
+ media_deps.trials = pcf_deps.trials.get();
+
+ pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps));
+ pcf_deps.fec_controller_factory = nullptr;
+ pcf_deps.network_controller_factory = nullptr;
+ pcf_deps.network_state_predictor_factory = nullptr;
+
+ pc_factory_ = CreateModularPeerConnectionFactory(std::move(pcf_deps));
+ PeerConnectionFactoryInterface::Options pc_options;
+ pc_options.disable_encryption = config.disable_encryption;
+ pc_factory_->SetOptions(pc_options);
+
+ PeerConnectionDependencies pc_deps(observer_.get());
+ pc_deps.allocator = std::make_unique<cricket::BasicPortAllocator>(
+ manager->network_manager(), manager->packet_socket_factory());
+ pc_deps.allocator->set_flags(pc_deps.allocator->flags() |
+ cricket::PORTALLOCATOR_DISABLE_TCP);
+ peer_connection_ =
+ pc_factory_
+ ->CreatePeerConnectionOrError(config.rtc_config, std::move(pc_deps))
+ .MoveValue();
+ if (log_writer_factory_) {
+ peer_connection_->StartRtcEventLog(log_writer_factory_->Create(".rtc.dat"),
+ /*output_period_ms=*/1000);
+ }
+}
+
+EmulatedEndpoint* PeerScenarioClient::endpoint(int index) {
+ RTC_CHECK_GT(endpoints_.size(), index);
+ return endpoints_.at(index);
+}
+
+PeerScenarioClient::AudioSendTrack PeerScenarioClient::CreateAudio(
+ std::string track_id,
+ cricket::AudioOptions options) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ AudioSendTrack res;
+ auto source = pc_factory_->CreateAudioSource(options);
+ auto track = pc_factory_->CreateAudioTrack(track_id, source.get());
+ res.track = track;
+ res.sender = peer_connection_->AddTrack(track, {kCommonStreamId}).value();
+ return res;
+}
+
+PeerScenarioClient::VideoSendTrack PeerScenarioClient::CreateVideo(
+ std::string track_id,
+ VideoSendTrackConfig config) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ VideoSendTrack res;
+ auto capturer = FrameGeneratorCapturer::Create(clock(), *task_queue_factory_,
+ config.generator);
+ res.capturer = capturer.get();
+ capturer->Init();
+ res.source = rtc::make_ref_counted<FrameGeneratorCapturerVideoTrackSource>(
+ std::move(capturer), config.screencast);
+ auto track = pc_factory_->CreateVideoTrack(track_id, res.source.get());
+ res.track = track.get();
+ res.sender =
+ peer_connection_->AddTrack(track, {kCommonStreamId}).MoveValue().get();
+ return res;
+}
+
+void PeerScenarioClient::AddVideoReceiveSink(
+ std::string track_id,
+ rtc::VideoSinkInterface<VideoFrame>* video_sink) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ track_id_to_video_sinks_[track_id].push_back(video_sink);
+}
+
+void PeerScenarioClient::CreateAndSetSdp(
+ std::function<void(SessionDescriptionInterface*)> munge_offer,
+ std::function<void(std::string)> offer_handler) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ peer_connection_->CreateOffer(
+ rtc::make_ref_counted<LambdaCreateSessionDescriptionObserver>(
+ [=](std::unique_ptr<SessionDescriptionInterface> offer) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ if (munge_offer) {
+ munge_offer(offer.get());
+ }
+ std::string sdp_offer;
+ RTC_CHECK(offer->ToString(&sdp_offer));
+ peer_connection_->SetLocalDescription(
+ std::move(offer),
+ rtc::make_ref_counted<LambdaSetLocalDescriptionObserver>(
+ [sdp_offer, offer_handler](RTCError) {
+ offer_handler(sdp_offer);
+ }));
+ })
+ .get(),
+ PeerConnectionInterface::RTCOfferAnswerOptions());
+}
+
+void PeerScenarioClient::SetSdpOfferAndGetAnswer(
+ std::string remote_offer,
+ std::function<void(std::string)> answer_handler) {
+ if (!signaling_thread_->IsCurrent()) {
+ signaling_thread_->PostTask(
+ [=] { SetSdpOfferAndGetAnswer(remote_offer, answer_handler); });
+ return;
+ }
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ peer_connection_->SetRemoteDescription(
+ CreateSessionDescription(SdpType::kOffer, remote_offer),
+ rtc::make_ref_counted<LambdaSetRemoteDescriptionObserver>([=](RTCError) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ peer_connection_->CreateAnswer(
+ rtc::make_ref_counted<LambdaCreateSessionDescriptionObserver>(
+ [=](std::unique_ptr<SessionDescriptionInterface> answer) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ std::string sdp_answer;
+ answer->ToString(&sdp_answer);
+ RTC_LOG(LS_INFO) << sdp_answer;
+ peer_connection_->SetLocalDescription(
+ std::move(answer),
+ rtc::make_ref_counted<LambdaSetLocalDescriptionObserver>(
+ [answer_handler, sdp_answer](RTCError) {
+ answer_handler(sdp_answer);
+ }));
+ })
+ .get(),
+ PeerConnectionInterface::RTCOfferAnswerOptions());
+ }));
+}
+
+void PeerScenarioClient::SetSdpAnswer(
+ std::string remote_answer,
+ std::function<void(const SessionDescriptionInterface&)> done_handler) {
+ if (!signaling_thread_->IsCurrent()) {
+ signaling_thread_->PostTask(
+ [=] { SetSdpAnswer(remote_answer, done_handler); });
+ return;
+ }
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ peer_connection_->SetRemoteDescription(
+ CreateSessionDescription(SdpType::kAnswer, remote_answer),
+ rtc::make_ref_counted<LambdaSetRemoteDescriptionObserver>(
+ [remote_answer, done_handler](RTCError) {
+ auto answer =
+ CreateSessionDescription(SdpType::kAnswer, remote_answer);
+ done_handler(*answer);
+ }));
+}
+
+void PeerScenarioClient::AddIceCandidate(
+ std::unique_ptr<IceCandidateInterface> candidate) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ if (peer_connection_->signaling_state() ==
+ PeerConnectionInterface::SignalingState::kStable &&
+ peer_connection_->current_remote_description()) {
+ RTC_CHECK(peer_connection_->AddIceCandidate(candidate.get()));
+ } else {
+ pending_ice_candidates_.push_back(std::move(candidate));
+ }
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.h b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.h
new file mode 100644
index 0000000000..ab6aac9cf8
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.h
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+#ifndef TEST_PEER_SCENARIO_PEER_SCENARIO_CLIENT_H_
+#define TEST_PEER_SCENARIO_PEER_SCENARIO_CLIENT_H_
+
+#include <functional>
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "api/peer_connection_interface.h"
+#include "api/test/network_emulation_manager.h"
+#include "api/test/time_controller.h"
+#include "pc/test/frame_generator_capturer_video_track_source.h"
+#include "test/logging/log_writer.h"
+
+namespace webrtc {
+namespace test {
+
+// Wrapper for a PeerConnection for use in PeerScenario tests. It's intended to
+// be a minimal wrapper for a peer connection that's simple to use in testing.
+// In particular the constructor hides a lot of the required setup for a peer
+// connection.
+class PeerScenarioClient {
+ public:
+ struct CallbackHandlers {
+ std::vector<std::function<void(PeerConnectionInterface::SignalingState)>>
+ on_signaling_change;
+ std::vector<std::function<void(rtc::scoped_refptr<DataChannelInterface>)>>
+ on_data_channel;
+ std::vector<std::function<void()>> on_renegotiation_needed;
+ std::vector<
+ std::function<void(PeerConnectionInterface::IceConnectionState)>>
+ on_standardized_ice_connection_change;
+ std::vector<
+ std::function<void(PeerConnectionInterface::PeerConnectionState)>>
+ on_connection_change;
+ std::vector<std::function<void(PeerConnectionInterface::IceGatheringState)>>
+ on_ice_gathering_change;
+ std::vector<std::function<void(const IceCandidateInterface*)>>
+ on_ice_candidate;
+ std::vector<std::function<void(const std::string&,
+ int,
+ const std::string&,
+ int,
+ const std::string&)>>
+ on_ice_candidate_error;
+ std::vector<std::function<void(const std::vector<cricket::Candidate>&)>>
+ on_ice_candidates_removed;
+ std::vector<std::function<void(
+ rtc::scoped_refptr<RtpReceiverInterface>,
+ const std::vector<rtc::scoped_refptr<MediaStreamInterface>>&)>>
+ on_add_track;
+ std::vector<
+ std::function<void(rtc::scoped_refptr<RtpTransceiverInterface>)>>
+ on_track;
+ std::vector<std::function<void(rtc::scoped_refptr<RtpReceiverInterface>)>>
+ on_remove_track;
+ };
+ struct Config {
+ // WebRTC only support one audio device that is setup up on construction, so
+ // we provide the audio generator configuration here rather than on creation
+ // of the tracks. This is unlike video, where multiple capture sources can
+ // be used at the same time.
+ struct AudioSource {
+ int sample_rate = 48000;
+ int channels = 1;
+ struct PulsedNoise {
+ double amplitude = 0.1;
+ };
+ absl::optional<PulsedNoise> pulsed_noise = PulsedNoise();
+ } audio;
+ struct Video {
+ bool use_fake_codecs = false;
+ } video;
+ // The created endpoints can be accessed using the map key as `index` in
+ // PeerScenarioClient::endpoint(index).
+ std::map<int, EmulatedEndpointConfig> endpoints = {
+ {0, EmulatedEndpointConfig()}};
+ CallbackHandlers handlers;
+ PeerConnectionInterface::RTCConfiguration rtc_config;
+ bool disable_encryption = false;
+ Config() { rtc_config.sdp_semantics = SdpSemantics::kUnifiedPlan; }
+ };
+
+ struct VideoSendTrackConfig {
+ FrameGeneratorCapturerConfig generator;
+ bool screencast = false;
+ };
+
+ struct AudioSendTrack {
+ rtc::scoped_refptr<AudioTrackInterface> track;
+ rtc::scoped_refptr<RtpSenderInterface> sender;
+ };
+
+ struct VideoSendTrack {
+ // Raw pointer to the capturer owned by `source`.
+ FrameGeneratorCapturer* capturer;
+ rtc::scoped_refptr<FrameGeneratorCapturerVideoTrackSource> source;
+ rtc::scoped_refptr<VideoTrackInterface> track;
+ rtc::scoped_refptr<RtpSenderInterface> sender;
+ };
+
+ PeerScenarioClient(
+ NetworkEmulationManager* net,
+ rtc::Thread* signaling_thread,
+ std::unique_ptr<LogWriterFactoryInterface> log_writer_factory,
+ Config config);
+
+ PeerConnectionFactoryInterface* factory() { return pc_factory_.get(); }
+ PeerConnectionInterface* pc() {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ return peer_connection_.get();
+ }
+ rtc::Thread* thread() { return signaling_thread_; }
+ Clock* clock() { return Clock::GetRealTimeClock(); }
+
+ // Returns the endpoint created from the EmulatedEndpointConfig with the same
+ // index in PeerScenarioClient::config.
+ EmulatedEndpoint* endpoint(int index = 0);
+
+ AudioSendTrack CreateAudio(std::string track_id,
+ cricket::AudioOptions options);
+ VideoSendTrack CreateVideo(std::string track_id, VideoSendTrackConfig config);
+
+ void AddVideoReceiveSink(std::string track_id,
+ rtc::VideoSinkInterface<VideoFrame>* video_sink);
+
+ CallbackHandlers* handlers() { return &handlers_; }
+
+ // The `munge_offer` function can be used to munge the SDP, i.e. modify a
+ // local description afer creating it but before setting it. Note that this is
+ // legacy behavior. It's added here only to be able to have test coverage for
+ // scenarios even if they are not spec compliant.
+ void CreateAndSetSdp(
+ std::function<void(SessionDescriptionInterface*)> munge_offer,
+ std::function<void(std::string)> offer_handler);
+ void SetSdpOfferAndGetAnswer(std::string remote_offer,
+ std::function<void(std::string)> answer_handler);
+ void SetSdpAnswer(
+ std::string remote_answer,
+ std::function<void(const SessionDescriptionInterface& answer)>
+ done_handler);
+
+ // Adds the given ice candidate when the peer connection is ready.
+ void AddIceCandidate(std::unique_ptr<IceCandidateInterface> candidate);
+
+ private:
+ const std::map<int, EmulatedEndpoint*> endpoints_;
+ TaskQueueFactory* const task_queue_factory_;
+ rtc::Thread* const signaling_thread_;
+ const std::unique_ptr<LogWriterFactoryInterface> log_writer_factory_;
+ const std::unique_ptr<rtc::Thread> worker_thread_;
+ CallbackHandlers handlers_ RTC_GUARDED_BY(signaling_thread_);
+ const std::unique_ptr<PeerConnectionObserver> observer_;
+ std::map<std::string, std::vector<rtc::VideoSinkInterface<VideoFrame>*>>
+ track_id_to_video_sinks_ RTC_GUARDED_BY(signaling_thread_);
+ std::list<std::unique_ptr<IceCandidateInterface>> pending_ice_candidates_
+ RTC_GUARDED_BY(signaling_thread_);
+
+ rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
+ rtc::scoped_refptr<PeerConnectionInterface> peer_connection_
+ RTC_GUARDED_BY(signaling_thread_);
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_PEER_SCENARIO_PEER_SCENARIO_CLIENT_H_
diff --git a/third_party/libwebrtc/test/peer_scenario/scenario_connection.cc b/third_party/libwebrtc/test/peer_scenario/scenario_connection.cc
new file mode 100644
index 0000000000..66eca275d1
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/scenario_connection.cc
@@ -0,0 +1,242 @@
+/*
+ * 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/peer_scenario/scenario_connection.h"
+
+#include "absl/memory/memory.h"
+#include "media/base/rtp_utils.h"
+#include "modules/rtp_rtcp/source/rtp_packet_received.h"
+#include "p2p/client/basic_port_allocator.h"
+#include "pc/channel.h"
+#include "pc/jsep_transport_controller.h"
+#include "pc/rtp_transport_internal.h"
+#include "pc/session_description.h"
+#include "rtc_base/task_queue_for_test.h"
+
+namespace webrtc {
+class ScenarioIceConnectionImpl : public ScenarioIceConnection,
+ public sigslot::has_slots<>,
+ private JsepTransportController::Observer,
+ private RtpPacketSinkInterface {
+ public:
+ ScenarioIceConnectionImpl(test::NetworkEmulationManagerImpl* net,
+ IceConnectionObserver* observer);
+ ~ScenarioIceConnectionImpl() override;
+
+ void SendRtpPacket(rtc::ArrayView<const uint8_t> packet_view) override;
+ void SendRtcpPacket(rtc::ArrayView<const uint8_t> packet_view) override;
+
+ void SetRemoteSdp(SdpType type, const std::string& remote_sdp) override;
+ void SetLocalSdp(SdpType type, const std::string& local_sdp) override;
+
+ EmulatedEndpoint* endpoint() override { return endpoint_; }
+ const cricket::TransportDescription& transport_description() const override {
+ return transport_description_;
+ }
+
+ private:
+ JsepTransportController::Config CreateJsepConfig();
+ bool OnTransportChanged(
+ const std::string& mid,
+ RtpTransportInternal* rtp_transport,
+ rtc::scoped_refptr<DtlsTransport> dtls_transport,
+ DataChannelTransportInterface* data_channel_transport) override;
+
+ void OnRtpPacket(const RtpPacketReceived& packet) override;
+ void OnCandidates(const std::string& mid,
+ const std::vector<cricket::Candidate>& candidates);
+
+ IceConnectionObserver* const observer_;
+ EmulatedEndpoint* const endpoint_;
+ EmulatedNetworkManagerInterface* const manager_;
+ rtc::Thread* const signaling_thread_;
+ rtc::Thread* const network_thread_;
+ rtc::scoped_refptr<rtc::RTCCertificate> const certificate_
+ RTC_GUARDED_BY(network_thread_);
+ cricket::TransportDescription const transport_description_
+ RTC_GUARDED_BY(signaling_thread_);
+ std::unique_ptr<cricket::BasicPortAllocator> port_allocator_
+ RTC_GUARDED_BY(network_thread_);
+ std::unique_ptr<JsepTransportController> jsep_controller_;
+ RtpTransportInternal* rtp_transport_ RTC_GUARDED_BY(network_thread_) =
+ nullptr;
+ std::unique_ptr<SessionDescriptionInterface> remote_description_
+ RTC_GUARDED_BY(signaling_thread_);
+ std::unique_ptr<SessionDescriptionInterface> local_description_
+ RTC_GUARDED_BY(signaling_thread_);
+};
+
+std::unique_ptr<ScenarioIceConnection> ScenarioIceConnection::Create(
+ webrtc::test::NetworkEmulationManagerImpl* net,
+ IceConnectionObserver* observer) {
+ return std::make_unique<ScenarioIceConnectionImpl>(net, observer);
+}
+
+ScenarioIceConnectionImpl::ScenarioIceConnectionImpl(
+ test::NetworkEmulationManagerImpl* net,
+ IceConnectionObserver* observer)
+ : observer_(observer),
+ endpoint_(net->CreateEndpoint(EmulatedEndpointConfig())),
+ manager_(net->CreateEmulatedNetworkManagerInterface({endpoint_})),
+ signaling_thread_(rtc::Thread::Current()),
+ network_thread_(manager_->network_thread()),
+ certificate_(rtc::RTCCertificate::Create(
+ rtc::SSLIdentity::Create("", ::rtc::KT_DEFAULT))),
+ transport_description_(
+ /*transport_options*/ {},
+ rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH),
+ rtc::CreateRandomString(cricket::ICE_PWD_LENGTH),
+ cricket::IceMode::ICEMODE_FULL,
+ cricket::ConnectionRole::CONNECTIONROLE_PASSIVE,
+ rtc::SSLFingerprint::CreateFromCertificate(*certificate_.get())
+ .get()),
+ port_allocator_(
+ new cricket::BasicPortAllocator(manager_->network_manager(),
+ manager_->packet_socket_factory())),
+ jsep_controller_(
+ new JsepTransportController(network_thread_,
+ port_allocator_.get(),
+ /*async_resolver_factory*/ nullptr,
+ CreateJsepConfig())) {
+ SendTask(network_thread_, [this] {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ uint32_t flags = cricket::PORTALLOCATOR_DISABLE_TCP;
+ port_allocator_->set_flags(port_allocator_->flags() | flags);
+ port_allocator_->Initialize();
+ RTC_CHECK(port_allocator_->SetConfiguration(/*stun_servers*/ {},
+ /*turn_servers*/ {}, 0,
+ webrtc::NO_PRUNE));
+ jsep_controller_->SetLocalCertificate(certificate_);
+ });
+}
+
+ScenarioIceConnectionImpl::~ScenarioIceConnectionImpl() {
+ SendTask(network_thread_, [this] {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ jsep_controller_.reset();
+ port_allocator_.reset();
+ rtp_transport_ = nullptr;
+ });
+}
+
+JsepTransportController::Config ScenarioIceConnectionImpl::CreateJsepConfig() {
+ JsepTransportController::Config config;
+ config.transport_observer = this;
+ config.bundle_policy =
+ PeerConnectionInterface::BundlePolicy::kBundlePolicyMaxBundle;
+ config.rtcp_handler = [this](const rtc::CopyOnWriteBuffer& packet,
+ int64_t packet_time_us) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ observer_->OnPacketReceived(packet);
+ };
+ config.field_trials = &field_trials;
+ return config;
+}
+
+void ScenarioIceConnectionImpl::SendRtpPacket(
+ rtc::ArrayView<const uint8_t> packet_view) {
+ rtc::CopyOnWriteBuffer packet(packet_view.data(), packet_view.size(),
+ ::cricket::kMaxRtpPacketLen);
+ network_thread_->PostTask([this, packet = std::move(packet)]() mutable {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (rtp_transport_ != nullptr)
+ rtp_transport_->SendRtpPacket(&packet, rtc::PacketOptions(),
+ cricket::PF_SRTP_BYPASS);
+ });
+}
+
+void ScenarioIceConnectionImpl::SendRtcpPacket(
+ rtc::ArrayView<const uint8_t> packet_view) {
+ rtc::CopyOnWriteBuffer packet(packet_view.data(), packet_view.size(),
+ ::cricket::kMaxRtpPacketLen);
+ network_thread_->PostTask([this, packet = std::move(packet)]() mutable {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (rtp_transport_ != nullptr)
+ rtp_transport_->SendRtcpPacket(&packet, rtc::PacketOptions(),
+ cricket::PF_SRTP_BYPASS);
+ });
+}
+void ScenarioIceConnectionImpl::SetRemoteSdp(SdpType type,
+ const std::string& remote_sdp) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ remote_description_ = webrtc::CreateSessionDescription(type, remote_sdp);
+ jsep_controller_->SubscribeIceCandidateGathered(
+ [this](const std::string& transport,
+ const std::vector<cricket::Candidate>& candidate) {
+ ScenarioIceConnectionImpl::OnCandidates(transport, candidate);
+ });
+
+ auto res = jsep_controller_->SetRemoteDescription(
+ remote_description_->GetType(), remote_description_->description());
+ RTC_CHECK(res.ok()) << res.message();
+ RtpDemuxerCriteria criteria;
+ for (const auto& content : remote_description_->description()->contents()) {
+ if (content.media_description()->as_audio()) {
+ for (const auto& codec :
+ content.media_description()->as_audio()->codecs()) {
+ criteria.payload_types().insert(codec.id);
+ }
+ }
+ if (content.media_description()->as_video()) {
+ for (const auto& codec :
+ content.media_description()->as_video()->codecs()) {
+ criteria.payload_types().insert(codec.id);
+ }
+ }
+ }
+
+ network_thread_->PostTask([this, criteria]() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(rtp_transport_);
+ rtp_transport_->RegisterRtpDemuxerSink(criteria, this);
+ });
+}
+
+void ScenarioIceConnectionImpl::SetLocalSdp(SdpType type,
+ const std::string& local_sdp) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ local_description_ = webrtc::CreateSessionDescription(type, local_sdp);
+ auto res = jsep_controller_->SetLocalDescription(
+ local_description_->GetType(), local_description_->description());
+ RTC_CHECK(res.ok()) << res.message();
+ jsep_controller_->MaybeStartGathering();
+}
+
+bool ScenarioIceConnectionImpl::OnTransportChanged(
+ const std::string& mid,
+ RtpTransportInternal* rtp_transport,
+ rtc::scoped_refptr<DtlsTransport> dtls_transport,
+ DataChannelTransportInterface* data_channel_transport) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (rtp_transport == nullptr) {
+ rtp_transport_->UnregisterRtpDemuxerSink(this);
+ } else {
+ RTC_DCHECK(rtp_transport_ == nullptr || rtp_transport_ == rtp_transport);
+ if (rtp_transport_ != rtp_transport) {
+ rtp_transport_ = rtp_transport;
+ }
+ RtpDemuxerCriteria criteria(mid);
+ rtp_transport_->RegisterRtpDemuxerSink(criteria, this);
+ }
+ return true;
+}
+
+void ScenarioIceConnectionImpl::OnRtpPacket(const RtpPacketReceived& packet) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ observer_->OnPacketReceived(packet.Buffer());
+}
+
+void ScenarioIceConnectionImpl::OnCandidates(
+ const std::string& mid,
+ const std::vector<cricket::Candidate>& candidates) {
+ RTC_DCHECK_RUN_ON(signaling_thread_);
+ observer_->OnIceCandidates(mid, candidates);
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/peer_scenario/scenario_connection.h b/third_party/libwebrtc/test/peer_scenario/scenario_connection.h
new file mode 100644
index 0000000000..e8cef527c5
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/scenario_connection.h
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+#ifndef TEST_PEER_SCENARIO_SCENARIO_CONNECTION_H_
+#define TEST_PEER_SCENARIO_SCENARIO_CONNECTION_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "api/candidate.h"
+#include "api/jsep.h"
+#include "p2p/base/transport_description.h"
+#include "test/network/network_emulation_manager.h"
+#include "test/scoped_key_value_config.h"
+
+namespace webrtc {
+
+// ScenarioIceConnection provides the transport level functionality of a
+// PeerConnection for use in peer connection scenario tests. This allows
+// implementing custom server side behavior in tests.
+class ScenarioIceConnection {
+ public:
+ class IceConnectionObserver {
+ public:
+ // Called on network thread.
+ virtual void OnPacketReceived(rtc::CopyOnWriteBuffer packet) = 0;
+ // Called on signaling thread.
+ virtual void OnIceCandidates(
+ const std::string& mid,
+ const std::vector<cricket::Candidate>& candidates) = 0;
+
+ protected:
+ ~IceConnectionObserver() = default;
+ };
+ static std::unique_ptr<ScenarioIceConnection> Create(
+ test::NetworkEmulationManagerImpl* net,
+ IceConnectionObserver* observer);
+
+ virtual ~ScenarioIceConnection() = default;
+
+ // Posts tasks to send packets to network thread.
+ virtual void SendRtpPacket(rtc::ArrayView<const uint8_t> packet_view) = 0;
+ virtual void SendRtcpPacket(rtc::ArrayView<const uint8_t> packet_view) = 0;
+
+ // Used for ICE configuration, called on signaling thread.
+ virtual void SetRemoteSdp(SdpType type, const std::string& remote_sdp) = 0;
+ virtual void SetLocalSdp(SdpType type, const std::string& local_sdp) = 0;
+
+ virtual EmulatedEndpoint* endpoint() = 0;
+ virtual const cricket::TransportDescription& transport_description()
+ const = 0;
+
+ webrtc::test::ScopedKeyValueConfig field_trials;
+};
+
+} // namespace webrtc
+
+#endif // TEST_PEER_SCENARIO_SCENARIO_CONNECTION_H_
diff --git a/third_party/libwebrtc/test/peer_scenario/signaling_route.cc b/third_party/libwebrtc/test/peer_scenario/signaling_route.cc
new file mode 100644
index 0000000000..eeec7c8657
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/signaling_route.cc
@@ -0,0 +1,114 @@
+/*
+ * 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/peer_scenario/signaling_route.h"
+
+#include <memory>
+
+#include "test/network/network_emulation_manager.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+constexpr size_t kIcePacketSize = 400;
+constexpr size_t kSdpPacketSize = 1200;
+
+struct IceMessage {
+ IceMessage() = default;
+ explicit IceMessage(const IceCandidateInterface* candidate)
+ : sdp_mid(candidate->sdp_mid()),
+ sdp_mline_index(candidate->sdp_mline_index()) {
+ RTC_CHECK(candidate->ToString(&sdp_line));
+ }
+ std::unique_ptr<IceCandidateInterface> AsCandidate() const {
+ SdpParseError err;
+ std::unique_ptr<IceCandidateInterface> candidate(
+ CreateIceCandidate(sdp_mid, sdp_mline_index, sdp_line, &err));
+ RTC_CHECK(candidate) << "Failed to parse: \"" << err.line
+ << "\". Reason: " << err.description;
+ return candidate;
+ }
+ std::string sdp_mid;
+ int sdp_mline_index;
+ std::string sdp_line;
+};
+
+void StartIceSignalingForRoute(PeerScenarioClient* caller,
+ PeerScenarioClient* callee,
+ CrossTrafficRoute* send_route) {
+ caller->handlers()->on_ice_candidate.push_back(
+ [=](const IceCandidateInterface* candidate) {
+ IceMessage msg(candidate);
+ send_route->NetworkDelayedAction(kIcePacketSize, [callee, msg]() {
+ callee->thread()->PostTask(
+ [callee, msg]() { callee->AddIceCandidate(msg.AsCandidate()); });
+ });
+ });
+}
+
+void StartSdpNegotiation(
+ PeerScenarioClient* caller,
+ PeerScenarioClient* callee,
+ CrossTrafficRoute* send_route,
+ CrossTrafficRoute* ret_route,
+ std::function<void(SessionDescriptionInterface* offer)> munge_offer,
+ std::function<void(SessionDescriptionInterface*)> modify_offer,
+ std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
+ caller->CreateAndSetSdp(munge_offer, [=](std::string sdp_offer) {
+ if (modify_offer) {
+ auto offer = CreateSessionDescription(SdpType::kOffer, sdp_offer);
+ modify_offer(offer.get());
+ RTC_CHECK(offer->ToString(&sdp_offer));
+ }
+ send_route->NetworkDelayedAction(kSdpPacketSize, [=] {
+ callee->SetSdpOfferAndGetAnswer(sdp_offer, [=](std::string answer) {
+ ret_route->NetworkDelayedAction(kSdpPacketSize, [=] {
+ caller->SetSdpAnswer(std::move(answer), std::move(exchange_finished));
+ });
+ });
+ });
+ });
+}
+} // namespace
+
+SignalingRoute::SignalingRoute(PeerScenarioClient* caller,
+ PeerScenarioClient* callee,
+ CrossTrafficRoute* send_route,
+ CrossTrafficRoute* ret_route)
+ : caller_(caller),
+ callee_(callee),
+ send_route_(send_route),
+ ret_route_(ret_route) {}
+
+void SignalingRoute::StartIceSignaling() {
+ StartIceSignalingForRoute(caller_, callee_, send_route_);
+ StartIceSignalingForRoute(callee_, caller_, ret_route_);
+}
+
+void SignalingRoute::NegotiateSdp(
+ std::function<void(SessionDescriptionInterface*)> munge_offer,
+ std::function<void(SessionDescriptionInterface*)> modify_offer,
+ std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
+ StartSdpNegotiation(caller_, callee_, send_route_, ret_route_, munge_offer,
+ modify_offer, exchange_finished);
+}
+
+void SignalingRoute::NegotiateSdp(
+ std::function<void(SessionDescriptionInterface*)> modify_offer,
+ std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
+ NegotiateSdp({}, modify_offer, exchange_finished);
+}
+
+void SignalingRoute::NegotiateSdp(
+ std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
+ NegotiateSdp({}, {}, exchange_finished);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/peer_scenario/signaling_route.h b/third_party/libwebrtc/test/peer_scenario/signaling_route.h
new file mode 100644
index 0000000000..a95ae5c9f7
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/signaling_route.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+#ifndef TEST_PEER_SCENARIO_SIGNALING_ROUTE_H_
+#define TEST_PEER_SCENARIO_SIGNALING_ROUTE_H_
+
+#include <string>
+#include <utility>
+
+#include "test/network/network_emulation_manager.h"
+#include "test/peer_scenario/peer_scenario_client.h"
+
+namespace webrtc {
+namespace test {
+
+// Helper class to reduce the amount of boilerplate required for ICE signalling
+// ad SDP negotiation.
+class SignalingRoute {
+ public:
+ SignalingRoute(PeerScenarioClient* caller,
+ PeerScenarioClient* callee,
+ CrossTrafficRoute* send_route,
+ CrossTrafficRoute* ret_route);
+
+ void StartIceSignaling();
+
+ // The `modify_offer` callback is used to modify an offer after the local
+ // description has been set. This is legal (but odd) behavior.
+ // The `munge_offer` callback is used to modify an offer between its creation
+ // and set local description. This behavior is forbidden according to the spec
+ // but available here in order to allow test coverage on corner cases.
+ // The `exchange_finished` callback is called with the answer produced after
+ // SDP negotations has completed.
+ // TODO(srte): Handle lossy links.
+ void NegotiateSdp(
+ std::function<void(SessionDescriptionInterface* offer)> munge_offer,
+ std::function<void(SessionDescriptionInterface* offer)> modify_offer,
+ std::function<void(const SessionDescriptionInterface& answer)>
+ exchange_finished);
+ void NegotiateSdp(
+ std::function<void(SessionDescriptionInterface* offer)> modify_offer,
+ std::function<void(const SessionDescriptionInterface& answer)>
+ exchange_finished);
+ void NegotiateSdp(
+ std::function<void(const SessionDescriptionInterface& answer)>
+ exchange_finished);
+ SignalingRoute reverse() {
+ return SignalingRoute(callee_, caller_, ret_route_, send_route_);
+ }
+
+ private:
+ PeerScenarioClient* const caller_;
+ PeerScenarioClient* const callee_;
+ CrossTrafficRoute* const send_route_;
+ CrossTrafficRoute* const ret_route_;
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_PEER_SCENARIO_SIGNALING_ROUTE_H_
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..ba6ec20e84
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn
@@ -0,0 +1,30 @@
+# 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 = [
+ "peer_scenario_quality_test.cc",
+ "remote_estimate_test.cc",
+ "unsignaled_stream_test.cc",
+ ]
+ deps = [
+ "..:peer_scenario",
+ "../../:field_trial",
+ "../../:test_support",
+ "../../../media:rtc_media_base",
+ "../../../media:stream_params",
+ "../../../modules/rtp_rtcp:rtp_rtcp_format",
+ "../../../pc:media_session",
+ "../../../pc:session_description",
+ ]
+ }
+}
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..2dfbfdd3c9
--- /dev/null
+++ b/third_party/libwebrtc/test/peer_scenario/tests/remote_estimate_test.cc
@@ -0,0 +1,112 @@
+/*
+ * 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