diff --git a/test/DEPS b/test/DEPS index d3b03e036f..f2bded5f3c 100644 --- a/test/DEPS +++ b/test/DEPS @@ -45,4 +45,8 @@ specific_include_rules = { ".*stun_validator_fuzzer\.cc": [ "+p2p/base/stun.h", ], + ".*test_peer\.(h|cc)": [ + "+pc", + "+p2p", + ], } diff --git a/test/pc/e2e/BUILD.gn b/test/pc/e2e/BUILD.gn index 6aa5412afb..994d61f3f4 100644 --- a/test/pc/e2e/BUILD.gn +++ b/test/pc/e2e/BUILD.gn @@ -21,7 +21,10 @@ group("e2e") { ":single_process_encoded_image_id_injector", ] if (rtc_include_tests) { - deps += [ ":video_quality_analyzer_injection_helper" ] + deps += [ + ":test_peer", + ":video_quality_analyzer_injection_helper", + ] } } @@ -151,6 +154,47 @@ if (rtc_include_tests) { ] } + rtc_source_set("test_peer") { + visibility = [ "*" ] + testonly = true + sources = [ + "test_peer.cc", + "test_peer.h", + ] + deps = [ + ":default_encoded_image_id_injector", + ":encoded_image_id_injector_api", + ":example_video_quality_analyzer", + ":video_quality_analyzer_injection_helper", + "../../../api:array_view", + "../../../api:scoped_refptr", + "../../../api/audio_codecs:builtin_audio_decoder_factory", + "../../../api/audio_codecs:builtin_audio_encoder_factory", + "../../../api/video_codecs:builtin_video_decoder_factory", + "../../../api/video_codecs:builtin_video_encoder_factory", + "../../../logging:rtc_event_log_impl_base", + "../../../media:rtc_audio_video", + "../../../media:rtc_media_base", + "../../../modules/audio_device:audio_device_api", + "../../../modules/audio_device:audio_device_impl", + "../../../modules/audio_processing:api", + "../../../p2p:rtc_p2p", + "../../../pc:pc_test_utils", + "../../../pc:peerconnection_wrapper", + "../../../rtc_base:rtc_base", + "../../../rtc_base:rtc_base_approved", + "../../../test:copy_to_file_audio_capturer", + "../../../test:video_test_common", + "api:peer_connection_quality_test_fixture_api", + "//third_party/abseil-cpp/absl/memory:memory", + "//third_party/abseil-cpp/absl/types:optional", + ] + if (!build_with_chromium && is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + } + rtc_source_set("single_process_encoded_image_id_injector_unittest") { testonly = true sources = [ diff --git a/test/pc/e2e/test_peer.cc b/test/pc/e2e/test_peer.cc new file mode 100644 index 0000000000..62bbc1d46c --- /dev/null +++ b/test/pc/e2e/test_peer.cc @@ -0,0 +1,306 @@ +/* + * 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/pc/e2e/test_peer.h" + +#include + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/scoped_refptr.h" +#include "api/video_codecs/builtin_video_decoder_factory.h" +#include "api/video_codecs/builtin_video_encoder_factory.h" +#include "logging/rtc_event_log/rtc_event_log_factory.h" +#include "media/engine/webrtc_media_engine.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "p2p/client/basic_port_allocator.h" +#include "pc/test/mock_peer_connection_observers.h" +#include "rtc_base/bind.h" +#include "rtc_base/location.h" +#include "rtc_base/network.h" +#include "test/frame_generator_capturer.h" +#include "test/pc/e2e/analyzer/video/default_encoded_image_id_injector.h" +#include "test/pc/e2e/analyzer/video/example_video_quality_analyzer.h" +#include "test/testsupport/copy_to_file_audio_capturer.h" + +namespace webrtc { +namespace test { +namespace { + +constexpr int16_t kGeneratedAudioMaxAmplitude = 32000; +constexpr int kSamplingFrequencyInHz = 48000; + +using Params = PeerConnectionE2EQualityTestFixture::Params; +using InjectableComponents = + PeerConnectionE2EQualityTestFixture::InjectableComponents; +using PeerConnectionFactoryComponents = + PeerConnectionE2EQualityTestFixture::PeerConnectionFactoryComponents; +using PeerConnectionComponents = + PeerConnectionE2EQualityTestFixture::PeerConnectionComponents; +using AudioConfig = PeerConnectionE2EQualityTestFixture::AudioConfig; + +// Sets mandatory entities in injectable components like |pcf_dependencies| +// and |pc_dependencies| if they are omitted. Also setup required +// dependencies, that won't be specially provided by factory and will be just +// transferred to peer connection creation code. +void SetMandatoryEntities(InjectableComponents* components) { + if (components->pcf_dependencies == nullptr) { + components->pcf_dependencies = + absl::make_unique(); + } + if (components->pc_dependencies == nullptr) { + components->pc_dependencies = absl::make_unique(); + } + + // Setup required peer connection factory dependencies. + if (components->pcf_dependencies->call_factory == nullptr) { + components->pcf_dependencies->call_factory = webrtc::CreateCallFactory(); + } + if (components->pcf_dependencies->event_log_factory == nullptr) { + components->pcf_dependencies->event_log_factory = + webrtc::CreateRtcEventLogFactory(); + } +} + +std::unique_ptr CreateAudioCapturer( + AudioConfig audio_config) { + if (audio_config.mode == AudioConfig::Mode::kGenerated) { + return TestAudioDeviceModule::CreatePulsedNoiseCapturer( + kGeneratedAudioMaxAmplitude, kSamplingFrequencyInHz); + } else if (audio_config.mode == AudioConfig::Mode::kFile) { + RTC_DCHECK(audio_config.input_file_name); + return TestAudioDeviceModule::CreateWavFileReader( + audio_config.input_file_name.value()); + } else { + RTC_NOTREACHED() << "Unknown audio_config->mode"; + return nullptr; + } +} + +rtc::scoped_refptr CreateAudioDeviceModule( + absl::optional audio_config, + absl::optional audio_output_file_name) { + std::unique_ptr capturer; + if (audio_config) { + capturer = CreateAudioCapturer(audio_config.value()); + } else { + // If we have no audio config we still need to provide some audio device. + // In such case use generated capturer. Despite of we provided audio here, + // in test media setup audio stream won't be added into peer connection. + capturer = TestAudioDeviceModule::CreatePulsedNoiseCapturer( + kGeneratedAudioMaxAmplitude, kSamplingFrequencyInHz); + } + RTC_DCHECK(capturer); + + if (audio_config && audio_config->input_dump_file_name) { + capturer = absl::make_unique( + std::move(capturer), audio_config->input_dump_file_name.value()); + } + + std::unique_ptr renderer; + if (audio_output_file_name) { + renderer = TestAudioDeviceModule::CreateBoundedWavFileWriter( + audio_output_file_name.value(), kSamplingFrequencyInHz); + } else { + renderer = + TestAudioDeviceModule::CreateDiscardRenderer(kSamplingFrequencyInHz); + } + + return TestAudioDeviceModule::CreateTestAudioDeviceModule( + std::move(capturer), std::move(renderer), /*speed=*/1.f); +} + +std::unique_ptr CreateVideoEncoderFactory( + PeerConnectionFactoryComponents* pcf_dependencies, + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper) { + std::unique_ptr video_encoder_factory; + if (pcf_dependencies->video_encoder_factory != nullptr) { + video_encoder_factory = std::move(pcf_dependencies->video_encoder_factory); + } else { + video_encoder_factory = CreateBuiltinVideoEncoderFactory(); + } + return video_analyzer_helper->WrapVideoEncoderFactory( + std::move(video_encoder_factory)); +} + +std::unique_ptr CreateVideoDecoderFactory( + PeerConnectionFactoryComponents* pcf_dependencies, + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper) { + std::unique_ptr video_decoder_factory; + if (pcf_dependencies->video_decoder_factory != nullptr) { + video_decoder_factory = std::move(pcf_dependencies->video_decoder_factory); + } else { + video_decoder_factory = CreateBuiltinVideoDecoderFactory(); + } + return video_analyzer_helper->WrapVideoDecoderFactory( + std::move(video_decoder_factory)); +} + +std::unique_ptr CreateMediaEngine( + PeerConnectionFactoryComponents* pcf_dependencies, + absl::optional audio_config, + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper, + absl::optional audio_output_file_name) { + rtc::scoped_refptr adm = CreateAudioDeviceModule( + std::move(audio_config), std::move(audio_output_file_name)); + + std::unique_ptr video_encoder_factory = + CreateVideoEncoderFactory(pcf_dependencies, video_analyzer_helper); + std::unique_ptr video_decoder_factory = + CreateVideoDecoderFactory(pcf_dependencies, video_analyzer_helper); + + return cricket::WebRtcMediaEngineFactory::Create( + adm, webrtc::CreateBuiltinAudioEncoderFactory(), + webrtc::CreateBuiltinAudioDecoderFactory(), + std::move(video_encoder_factory), std::move(video_decoder_factory), + /*audio_mixer=*/nullptr, webrtc::AudioProcessingBuilder().Create()); +} + +// Creates PeerConnectionFactoryDependencies objects, providing entities +// from InjectableComponents::PeerConnectionFactoryComponents and also +// creating entities, that are required for correct injection of media quality +// analyzers. +PeerConnectionFactoryDependencies CreatePCFDependencies( + std::unique_ptr pcf_dependencies, + absl::optional audio_config, + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper, + rtc::Thread* network_thread, + rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + absl::optional audio_output_file_name) { + PeerConnectionFactoryDependencies pcf_deps; + pcf_deps.network_thread = network_thread; + pcf_deps.signaling_thread = signaling_thread; + pcf_deps.worker_thread = worker_thread; + pcf_deps.media_engine = CreateMediaEngine( + pcf_dependencies.get(), std::move(audio_config), video_analyzer_helper, + std::move(audio_output_file_name)); + + pcf_deps.call_factory = std::move(pcf_dependencies->call_factory); + pcf_deps.event_log_factory = std::move(pcf_dependencies->event_log_factory); + + if (pcf_dependencies->fec_controller_factory != nullptr) { + pcf_deps.fec_controller_factory = + std::move(pcf_dependencies->fec_controller_factory); + } + if (pcf_dependencies->network_controller_factory != nullptr) { + pcf_deps.network_controller_factory = + std::move(pcf_dependencies->network_controller_factory); + } + if (pcf_dependencies->media_transport_factory != nullptr) { + pcf_deps.media_transport_factory = + std::move(pcf_dependencies->media_transport_factory); + } + + return pcf_deps; +} + +// Creates PeerConnectionDependencies objects, providing entities +// from InjectableComponents::PeerConnectionComponents. +PeerConnectionDependencies CreatePCDependencies( + PeerConnectionComponents* pc_dependencies, + PeerConnectionObserver* observer) { + PeerConnectionDependencies pc_deps(observer); + + // We need to create network manager, because it is required for port + // allocator. TestPeer will take ownership of this object and will store it + // until the end of the test. + if (pc_dependencies->network_manager == nullptr) { + pc_dependencies->network_manager = + // TODO(titovartem) have network manager integrated with emulated + // network layer. + absl::make_unique(); + } + auto port_allocator = absl::make_unique( + pc_dependencies->network_manager.get()); + + // This test does not support TCP + int flags = cricket::PORTALLOCATOR_DISABLE_TCP; + port_allocator->set_flags(port_allocator->flags() | flags); + + pc_deps.allocator = std::move(port_allocator); + + if (pc_dependencies->async_resolver_factory != nullptr) { + pc_deps.async_resolver_factory = + std::move(pc_dependencies->async_resolver_factory); + } + if (pc_dependencies->cert_generator != nullptr) { + pc_deps.cert_generator = std::move(pc_dependencies->cert_generator); + } + if (pc_dependencies->tls_cert_verifier != nullptr) { + pc_deps.tls_cert_verifier = std::move(pc_dependencies->tls_cert_verifier); + } + return pc_deps; +} + +} // namespace + +std::unique_ptr TestPeer::CreateTestPeer( + std::unique_ptr components, + std::unique_ptr params, + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper, + rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + absl::optional audio_output_file_name) { + RTC_DCHECK(components); + RTC_DCHECK(params); + SetMandatoryEntities(components.get()); + params->rtc_configuration.sdp_semantics = SdpSemantics::kUnifiedPlan; + + std::unique_ptr observer = + absl::make_unique(); + + // Create peer connection factory. + PeerConnectionFactoryDependencies pcf_deps = CreatePCFDependencies( + std::move(components->pcf_dependencies), params->audio_config, + video_analyzer_helper, components->network_thread, signaling_thread, + worker_thread, std::move(audio_output_file_name)); + rtc::scoped_refptr pcf = + CreateModularPeerConnectionFactory(std::move(pcf_deps)); + + // Create peer connection. + PeerConnectionDependencies pc_deps = + CreatePCDependencies(components->pc_dependencies.get(), observer.get()); + rtc::scoped_refptr pc = + pcf->CreatePeerConnection(params->rtc_configuration, std::move(pc_deps)); + + return absl::WrapUnique( + new TestPeer(pcf, pc, std::move(observer), std::move(params), + std::move(components->pc_dependencies->network_manager))); +} + +bool TestPeer::AddIceCandidates( + rtc::ArrayView candidates) { + bool success = true; + for (const auto* candidate : candidates) { + if (!pc()->AddIceCandidate(candidate)) { + success = false; + } + } + return success; +} + +TestPeer::TestPeer( + rtc::scoped_refptr pc_factory, + rtc::scoped_refptr pc, + std::unique_ptr observer, + std::unique_ptr params, + std::unique_ptr network_manager) + : PeerConnectionWrapper::PeerConnectionWrapper(pc_factory, + pc, + std::move(observer)), + params_(std::move(params)), + network_manager_(std::move(network_manager)) {} + +} // namespace test +} // namespace webrtc diff --git a/test/pc/e2e/test_peer.h b/test/pc/e2e/test_peer.h new file mode 100644 index 0000000000..afd6f0bbbf --- /dev/null +++ b/test/pc/e2e/test_peer.h @@ -0,0 +1,86 @@ +/* + * 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_PC_E2E_TEST_PEER_H_ +#define TEST_PC_E2E_TEST_PEER_H_ + +#include +#include + +#include "absl/memory/memory.h" +#include "api/array_view.h" +#include "media/base/media_engine.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "pc/peer_connection_wrapper.h" +#include "pc/test/mock_peer_connection_observers.h" +#include "rtc_base/network.h" +#include "rtc_base/thread.h" +#include "test/pc/e2e/analyzer/video/encoded_image_id_injector.h" +#include "test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h" +#include "test/pc/e2e/api/peerconnection_quality_test_fixture.h" + +namespace webrtc { +namespace test { + +// Describes a single participant in the call. +class TestPeer final : public PeerConnectionWrapper { + public: + using PeerConnectionWrapper::PeerConnectionWrapper; + using Params = PeerConnectionE2EQualityTestFixture::Params; + using VideoConfig = PeerConnectionE2EQualityTestFixture::VideoConfig; + using AudioConfig = PeerConnectionE2EQualityTestFixture::AudioConfig; + using InjectableComponents = + PeerConnectionE2EQualityTestFixture::InjectableComponents; + + // Setups all components, that should be provided to WebRTC + // PeerConnectionFactory and PeerConnection creation methods, + // also will setup dependencies, that are required for media analyzers + // injection. + // + // We require |worker_thread| here, because TestPeer can't own worker thread, + // because in such case it will be destroyed before peer connection. + // |signaling_thread| will be provided by test fixture implementation. + // |params| - describes current peer paramters, like current peer video + // streams and audio streams + // |audio_outpu_file_name| - the name of output file, where incoming audio + // stream should be written. It should be provided from remote peer + // |params.audio_config.output_file_name| + static std::unique_ptr CreateTestPeer( + std::unique_ptr components, + std::unique_ptr params, + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper, + rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + absl::optional audio_output_file_name); + + Params* params() const { return params_.get(); } + + // Adds provided |candidates| to the owned peer connection. + bool AddIceCandidates( + rtc::ArrayView candidates); + + private: + TestPeer(rtc::scoped_refptr pc_factory, + rtc::scoped_refptr pc, + std::unique_ptr observer, + std::unique_ptr params, + std::unique_ptr network_manager); + + std::unique_ptr params_; + // Test peer will take ownership of network manager and keep it during the + // call. Network manager will be deleted before peer connection, but + // connection will be closed before destruction, so it should be ok. + std::unique_ptr network_manager_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_PC_E2E_TEST_PEER_H_