From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- third_party/libwebrtc/test/peer_scenario/BUILD.gn | 77 ++++ third_party/libwebrtc/test/peer_scenario/DEPS | 5 + .../libwebrtc/test/peer_scenario/peer_scenario.cc | 127 ++++++ .../libwebrtc/test/peer_scenario/peer_scenario.h | 122 ++++++ .../test/peer_scenario/peer_scenario_client.cc | 444 +++++++++++++++++++++ .../test/peer_scenario/peer_scenario_client.h | 180 +++++++++ .../test/peer_scenario/scenario_connection.cc | 242 +++++++++++ .../test/peer_scenario/scenario_connection.h | 66 +++ .../test/peer_scenario/signaling_route.cc | 114 ++++++ .../libwebrtc/test/peer_scenario/signaling_route.h | 67 ++++ .../libwebrtc/test/peer_scenario/tests/BUILD.gn | 35 ++ .../test/peer_scenario/tests/bwe_ramp_up_test.cc | 128 ++++++ .../tests/peer_scenario_quality_test.cc | 46 +++ .../peer_scenario/tests/remote_estimate_test.cc | 113 ++++++ .../peer_scenario/tests/unsignaled_stream_test.cc | 270 +++++++++++++ 15 files changed, 2036 insertions(+) create mode 100644 third_party/libwebrtc/test/peer_scenario/BUILD.gn create mode 100644 third_party/libwebrtc/test/peer_scenario/DEPS create mode 100644 third_party/libwebrtc/test/peer_scenario/peer_scenario.cc create mode 100644 third_party/libwebrtc/test/peer_scenario/peer_scenario.h create mode 100644 third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc create mode 100644 third_party/libwebrtc/test/peer_scenario/peer_scenario_client.h create mode 100644 third_party/libwebrtc/test/peer_scenario/scenario_connection.cc create mode 100644 third_party/libwebrtc/test/peer_scenario/scenario_connection.h create mode 100644 third_party/libwebrtc/test/peer_scenario/signaling_route.cc create mode 100644 third_party/libwebrtc/test/peer_scenario/signaling_route.h create mode 100644 third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn create mode 100644 third_party/libwebrtc/test/peer_scenario/tests/bwe_ramp_up_test.cc create mode 100644 third_party/libwebrtc/test/peer_scenario/tests/peer_scenario_quality_test.cc create mode 100644 third_party/libwebrtc/test/peer_scenario/tests/remote_estimate_test.cc create mode 100644 third_party/libwebrtc/test/peer_scenario/tests/unsignaled_stream_test.cc (limited to 'third_party/libwebrtc/test/peer_scenario') 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..18f81a56e6 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/BUILD.gn @@ -0,0 +1,77 @@ +# 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 = [ + "..:create_frame_generator_capturer", + "..:fake_video_codecs", + "..:fileutils", + "..:frame_generator_capturer", + "..:test_support", + "../../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:video_decoder_factory_template", + "../../api/video_codecs:video_decoder_factory_template_dav1d_adapter", + "../../api/video_codecs:video_decoder_factory_template_libvpx_vp8_adapter", + "../../api/video_codecs:video_decoder_factory_template_libvpx_vp9_adapter", + "../../api/video_codecs:video_decoder_factory_template_open_h264_adapter", + "../../api/video_codecs:video_encoder_factory_template", + "../../api/video_codecs:video_encoder_factory_template_libaom_av1_adapter", + "../../api/video_codecs:video_encoder_factory_template_libvpx_vp8_adapter", + "../../api/video_codecs:video_encoder_factory_template_libvpx_vp9_adapter", + "../../api/video_codecs:video_encoder_factory_template_open_h264_adapter", + "../../media:rtc_audio_video", + "../../media:rtc_media_base", + "../../media:rtp_utils", + "../../modules/audio_device:test_audio_device_module", + "../../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 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(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 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 send_link, + std::vector ret_link) { + return SignalingRoute(caller, callee, net_.CreateCrossTrafficRoute(send_link), + net_.CreateCrossTrafficRoute(ret_link)); +} + +void PeerScenario::SimpleConnection( + PeerScenarioClient* caller, + PeerScenarioClient* callee, + std::vector send_link, + std::vector 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 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* 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 PeerScenario::GetLogWriterFactory( + std::string name) { + if (!log_writer_manager_ || name.empty()) + return nullptr; + return std::make_unique(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 +#include + +#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 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 send_link, + std::vector 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 send_link, + std::vector 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* 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 GetLogWriterFactory( + std::string name); + + const std::unique_ptr log_writer_manager_; + NetworkEmulationManagerImpl net_; + rtc::Thread* const signaling_thread_; + std::list video_quality_pairs_; + std::list 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..697bf055a7 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc @@ -0,0 +1,444 @@ +/* + * 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 +#include +#include + +#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/video_decoder_factory_template.h" +#include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h" +#include "api/video_codecs/video_encoder_factory_template.h" +#include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.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/create_frame_generator_capturer.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 CreateEndpoints( + NetworkEmulationManager* net, + std::map endpoint_configs) { + std::map 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 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& candidates) override { + for (const auto& handler : handlers_->on_ice_candidates_removed) + handler(candidates); + } + void OnAddTrack(rtc::scoped_refptr receiver, + const std::vector>& + streams) override { + for (const auto& handler : handlers_->on_add_track) + handler(receiver, streams); + } + void OnTrack( + rtc::scoped_refptr transceiver) override { + for (const auto& handler : handlers_->on_track) + handler(transceiver); + } + void OnRemoveTrack( + rtc::scoped_refptr 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 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 desc)> + on_success_; +}; + +class LambdaSetLocalDescriptionObserver + : public SetLocalDescriptionObserverInterface { + public: + explicit LambdaSetLocalDescriptionObserver( + std::function on_complete) + : on_complete_(on_complete) {} + void OnSetLocalDescriptionComplete(RTCError error) override { + on_complete_(error); + } + + private: + std::function on_complete_; +}; + +class LambdaSetRemoteDescriptionObserver + : public SetRemoteDescriptionObserverInterface { + public: + explicit LambdaSetRemoteDescriptionObserver( + std::function on_complete) + : on_complete_(on_complete) {} + void OnSetRemoteDescriptionComplete(RTCError error) override { + on_complete_(error); + } + + private: + std::function on_complete_; +}; + +class FakeVideoEncoderFactory : public VideoEncoderFactory { + public: + FakeVideoEncoderFactory(Clock* clock) : clock_(clock) {} + std::vector GetSupportedFormats() const override { + return {SdpVideoFormat("VP8")}; + } + std::unique_ptr CreateVideoEncoder( + const SdpVideoFormat& format) override { + RTC_CHECK_EQ(format.name, "VP8"); + return std::make_unique(clock_); + } + + private: + Clock* const clock_; +}; +class FakeVideoDecoderFactory : public VideoDecoderFactory { + public: + std::vector GetSupportedFormats() const override { + return {SdpVideoFormat("VP8")}; + } + std::unique_ptr CreateVideoDecoder( + const SdpVideoFormat& format) override { + return std::make_unique(); + } +}; +} // namespace + +PeerScenarioClient::PeerScenarioClient( + NetworkEmulationManager* net, + rtc::Thread* signaling_thread, + std::unique_ptr 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 transceiver) { + auto track = transceiver->receiver()->track().get(); + if (track->kind() == MediaStreamTrackInterface::kVideoKind) { + auto* video = static_cast(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 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(task_queue_factory_); + pcf_deps.trials = std::make_unique(); + + 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::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( + net->time_controller()->GetClock()); + media_deps.video_decoder_factory = + std::make_unique(); + } else { + media_deps.video_encoder_factory = + std::make_unique>(); + media_deps.video_decoder_factory = + std::make_unique>(); + } + 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( + 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 = CreateFrameGeneratorCapturer(clock(), *task_queue_factory_, + config.generator); + res.capturer = capturer.get(); + capturer->Init(); + res.source = rtc::make_ref_counted( + std::move(capturer), config.screencast); + res.source->Start(); + auto track = pc_factory_->CreateVideoTrack(res.source, track_id); + res.track = track.get(); + res.sender = + peer_connection_->AddTrack(track, {kCommonStreamId}).MoveValue().get(); + return res; +} + +void PeerScenarioClient::AddVideoReceiveSink( + std::string track_id, + rtc::VideoSinkInterface* video_sink) { + RTC_DCHECK_RUN_ON(signaling_thread_); + track_id_to_video_sinks_[track_id].push_back(video_sink); +} + +void PeerScenarioClient::CreateAndSetSdp( + std::function munge_offer, + std::function offer_handler) { + RTC_DCHECK_RUN_ON(signaling_thread_); + peer_connection_->CreateOffer( + rtc::make_ref_counted( + [=](std::unique_ptr 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( + [sdp_offer, offer_handler](RTCError) { + offer_handler(sdp_offer); + })); + }) + .get(), + PeerConnectionInterface::RTCOfferAnswerOptions()); +} + +void PeerScenarioClient::SetSdpOfferAndGetAnswer( + std::string remote_offer, + std::function 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([=](RTCError) { + RTC_DCHECK_RUN_ON(signaling_thread_); + peer_connection_->CreateAnswer( + rtc::make_ref_counted( + [=](std::unique_ptr 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( + [answer_handler, sdp_answer](RTCError) { + answer_handler(sdp_answer); + })); + }) + .get(), + PeerConnectionInterface::RTCOfferAnswerOptions()); + })); +} + +void PeerScenarioClient::SetSdpAnswer( + std::string remote_answer, + std::function 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( + [remote_answer, done_handler](RTCError) { + auto answer = + CreateSessionDescription(SdpType::kAnswer, remote_answer); + done_handler(*answer); + })); +} + +void PeerScenarioClient::AddIceCandidate( + std::unique_ptr 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..e863757759 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.h @@ -0,0 +1,180 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#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/create_frame_generator_capturer.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> + on_signaling_change; + std::vector)>> + on_data_channel; + std::vector> on_renegotiation_needed; + std::vector< + std::function> + on_standardized_ice_connection_change; + std::vector< + std::function> + on_connection_change; + std::vector> + on_ice_gathering_change; + std::vector> + on_ice_candidate; + std::vector> + on_ice_candidate_error; + std::vector&)>> + on_ice_candidates_removed; + std::vector, + const std::vector>&)>> + on_add_track; + std::vector< + std::function)>> + on_track; + std::vector)>> + 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 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 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 track; + rtc::scoped_refptr sender; + }; + + struct VideoSendTrack { + // Raw pointer to the capturer owned by `source`. + FrameGeneratorCapturer* capturer; + rtc::scoped_refptr source; + rtc::scoped_refptr track; + rtc::scoped_refptr sender; + }; + + PeerScenarioClient( + NetworkEmulationManager* net, + rtc::Thread* signaling_thread, + std::unique_ptr 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* 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 munge_offer, + std::function offer_handler); + void SetSdpOfferAndGetAnswer(std::string remote_offer, + std::function answer_handler); + void SetSdpAnswer( + std::string remote_answer, + std::function + done_handler); + + // Adds the given ice candidate when the peer connection is ready. + void AddIceCandidate(std::unique_ptr candidate); + + private: + const std::map endpoints_; + TaskQueueFactory* const task_queue_factory_; + rtc::Thread* const signaling_thread_; + const std::unique_ptr log_writer_factory_; + const std::unique_ptr worker_thread_; + CallbackHandlers handlers_ RTC_GUARDED_BY(signaling_thread_); + const std::unique_ptr observer_; + std::map*>> + track_id_to_video_sinks_ RTC_GUARDED_BY(signaling_thread_); + std::list> pending_ice_candidates_ + RTC_GUARDED_BY(signaling_thread_); + + rtc::scoped_refptr pc_factory_; + rtc::scoped_refptr 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 packet_view) override; + void SendRtcpPacket(rtc::ArrayView 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 dtls_transport, + DataChannelTransportInterface* data_channel_transport) override; + + void OnRtpPacket(const RtpPacketReceived& packet) override; + void OnCandidates(const std::string& mid, + const std::vector& candidates); + + IceConnectionObserver* const observer_; + EmulatedEndpoint* const endpoint_; + EmulatedNetworkManagerInterface* const manager_; + rtc::Thread* const signaling_thread_; + rtc::Thread* const network_thread_; + rtc::scoped_refptr const certificate_ + RTC_GUARDED_BY(network_thread_); + cricket::TransportDescription const transport_description_ + RTC_GUARDED_BY(signaling_thread_); + std::unique_ptr port_allocator_ + RTC_GUARDED_BY(network_thread_); + std::unique_ptr jsep_controller_; + RtpTransportInternal* rtp_transport_ RTC_GUARDED_BY(network_thread_) = + nullptr; + std::unique_ptr remote_description_ + RTC_GUARDED_BY(signaling_thread_); + std::unique_ptr local_description_ + RTC_GUARDED_BY(signaling_thread_); +}; + +std::unique_ptr ScenarioIceConnection::Create( + webrtc::test::NetworkEmulationManagerImpl* net, + IceConnectionObserver* observer) { + return std::make_unique(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 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 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& 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 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& 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 +#include +#include +#include + +#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& candidates) = 0; + + protected: + ~IceConnectionObserver() = default; + }; + static std::unique_ptr Create( + test::NetworkEmulationManagerImpl* net, + IceConnectionObserver* observer); + + virtual ~ScenarioIceConnection() = default; + + // Posts tasks to send packets to network thread. + virtual void SendRtpPacket(rtc::ArrayView packet_view) = 0; + virtual void SendRtcpPacket(rtc::ArrayView 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 + +#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 AsCandidate() const { + SdpParseError err; + std::unique_ptr 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 munge_offer, + std::function modify_offer, + std::function 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 munge_offer, + std::function modify_offer, + std::function exchange_finished) { + StartSdpNegotiation(caller_, callee_, send_route_, ret_route_, munge_offer, + modify_offer, exchange_finished); +} + +void SignalingRoute::NegotiateSdp( + std::function modify_offer, + std::function exchange_finished) { + NegotiateSdp({}, modify_offer, exchange_finished); +} + +void SignalingRoute::NegotiateSdp( + std::function 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 +#include + +#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 munge_offer, + std::function modify_offer, + std::function + exchange_finished); + void NegotiateSdp( + std::function modify_offer, + std::function + exchange_finished); + void NegotiateSdp( + std::function + 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..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 GetStatsAndProcess( + PeerScenario& s, + PeerScenarioClient* client) { + auto stats_collector = + rtc::make_ref_counted(); + 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& report) { + auto stats = report->GetStatsOfType(); + 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 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(&mid)) { + if (mid == video_mid) { + parsed_packet.SetExtension("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(); + 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 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 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 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()); + 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 info) { + switch (info.param) { + case MidTestConfiguration::kMidNotNegotiated: + return "MidNotNegotiated"; + case MidTestConfiguration::kMidNegotiatedButMissingFromPackets: + return "MidNegotiatedButMissingFromPackets"; + case MidTestConfiguration::kMidNegotiatedAndPresentInPackets: + return "MidNegotiatedAndPresentInPackets"; + } +} + +class FrameObserver : public rtc::VideoSinkInterface { + public: + FrameObserver() : frame_observed_(false) {} + void OnFrame(const VideoFrame&) override { frame_observed_ = true; } + + std::atomic 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 {}; + +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 offer_exchange_done(false); + std::atomic 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 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 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::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 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()); + break; + case MidTestConfiguration::kMidNegotiatedButMissingFromPackets: + EXPECT_TRUE(parsed_packet.HasExtension()); + ASSERT_TRUE(parsed_packet.RemoveExtension(RtpMid::kId)); + break; + case MidTestConfiguration::kMidNegotiatedAndPresentInPackets: + EXPECT_TRUE(parsed_packet.HasExtension()); + // 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("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 -- cgit v1.2.3