| /* |
| * Copyright 2012 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 "pc/test/integration_test_helpers.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/functional/any_invocable.h" |
| #include "api/audio/builtin_audio_processing_builder.h" |
| #include "api/enable_media_with_defaults.h" |
| #include "api/jsep.h" |
| #include "api/peer_connection_interface.h" |
| #include "api/rtc_event_log/rtc_event_log_factory.h" |
| #include "api/sequence_checker.h" |
| #include "api/stats/rtcstats_objects.h" |
| #include "api/task_queue/default_task_queue_factory.h" |
| #include "api/task_queue/pending_task_safety_flag.h" |
| #include "api/task_queue/task_queue_base.h" |
| #include "api/transport/field_trial_based_config.h" |
| #include "api/units/time_delta.h" |
| #include "logging/rtc_event_log/fake_rtc_event_log_factory.h" |
| #include "p2p/base/basic_packet_socket_factory.h" |
| #include "p2p/base/port_allocator.h" |
| #include "p2p/client/basic_port_allocator.h" |
| #include "pc/peer_connection_factory.h" |
| #include "pc/test/fake_audio_capture_module.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/fake_network.h" |
| #include "rtc_base/socket_server.h" |
| #include "rtc_base/thread.h" |
| #include "test/gtest.h" |
| |
| namespace webrtc { |
| |
| PeerConnectionInterface::RTCOfferAnswerOptions IceRestartOfferAnswerOptions() { |
| PeerConnectionInterface::RTCOfferAnswerOptions options; |
| options.ice_restart = true; |
| return options; |
| } |
| |
| void RemoveSsrcsAndMsids(std::unique_ptr<SessionDescriptionInterface>& sdp) { |
| for (ContentInfo& content : sdp->description()->contents()) { |
| content.media_description()->mutable_streams().clear(); |
| } |
| sdp->description()->set_msid_signaling(0); |
| } |
| |
| void RemoveSsrcsAndKeepMsids( |
| std::unique_ptr<SessionDescriptionInterface>& sdp) { |
| for (ContentInfo& content : sdp->description()->contents()) { |
| std::string track_id; |
| std::vector<std::string> stream_ids; |
| if (!content.media_description()->streams().empty()) { |
| const StreamParams& first_stream = |
| content.media_description()->streams()[0]; |
| track_id = first_stream.id; |
| stream_ids = first_stream.stream_ids(); |
| } |
| content.media_description()->mutable_streams().clear(); |
| StreamParams new_stream; |
| new_stream.id = track_id; |
| new_stream.set_stream_ids(stream_ids); |
| content.media_description()->AddStream(new_stream); |
| } |
| } |
| |
| void SetSdpType(std::unique_ptr<SessionDescriptionInterface>& sdp, |
| SdpType sdpType) { |
| std::string str; |
| sdp->ToString(&str); |
| sdp = CreateSessionDescription(sdpType, str); |
| } |
| |
| int FindFirstMediaStatsIndexByKind( |
| const std::string& kind, |
| const std::vector<const RTCInboundRtpStreamStats*>& inbound_rtps) { |
| for (size_t i = 0; i < inbound_rtps.size(); i++) { |
| if (*inbound_rtps[i]->kind == kind) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| void ReplaceFirstSsrc(StreamParams& stream, uint32_t ssrc) { |
| stream.ssrcs[0] = ssrc; |
| for (auto& group : stream.ssrc_groups) { |
| group.ssrcs[0] = ssrc; |
| } |
| } |
| |
| TaskQueueMetronome::TaskQueueMetronome(TimeDelta tick_period) |
| : tick_period_(tick_period) {} |
| |
| TaskQueueMetronome::~TaskQueueMetronome() { |
| RTC_DCHECK_RUN_ON(&sequence_checker_); |
| } |
| void TaskQueueMetronome::RequestCallOnNextTick( |
| absl::AnyInvocable<void() &&> callback) { |
| RTC_DCHECK_RUN_ON(&sequence_checker_); |
| callbacks_.push_back(std::move(callback)); |
| // Only schedule a tick callback for the first `callback` addition. |
| // Schedule on the current task queue to comply with RequestCallOnNextTick |
| // requirements. |
| if (callbacks_.size() == 1) { |
| TaskQueueBase::Current()->PostDelayedTask( |
| SafeTask(safety_.flag(), |
| [this] { |
| RTC_DCHECK_RUN_ON(&sequence_checker_); |
| std::vector<absl::AnyInvocable<void() &&>> callbacks; |
| callbacks_.swap(callbacks); |
| for (auto& callback : callbacks) |
| std::move(callback)(); |
| }), |
| tick_period_); |
| } |
| } |
| |
| TimeDelta TaskQueueMetronome::TickPeriod() const { |
| RTC_DCHECK_RUN_ON(&sequence_checker_); |
| return tick_period_; |
| } |
| |
| // Implementation of PeerConnectionIntegrationWrapper functions |
| void PeerConnectionIntegrationWrapper::StartWatchingDelayStats() { |
| // Get the baseline numbers for audio_packets and audio_delay. |
| auto received_stats = NewGetStats(); |
| auto rtp_stats = |
| received_stats->GetStatsOfType<RTCInboundRtpStreamStats>()[0]; |
| ASSERT_TRUE(rtp_stats->relative_packet_arrival_delay.has_value()); |
| ASSERT_TRUE(rtp_stats->packets_received.has_value()); |
| rtp_stats_id_ = rtp_stats->id(); |
| audio_packets_stat_ = *rtp_stats->packets_received; |
| audio_delay_stat_ = *rtp_stats->relative_packet_arrival_delay; |
| audio_samples_stat_ = *rtp_stats->total_samples_received; |
| audio_concealed_stat_ = *rtp_stats->concealed_samples; |
| } |
| |
| void PeerConnectionIntegrationWrapper::UpdateDelayStats(std::string tag, |
| int desc_size) { |
| auto report = NewGetStats(); |
| auto rtp_stats = report->GetAs<RTCInboundRtpStreamStats>(rtp_stats_id_); |
| ASSERT_TRUE(rtp_stats); |
| auto delta_packets = *rtp_stats->packets_received - audio_packets_stat_; |
| auto delta_rpad = |
| *rtp_stats->relative_packet_arrival_delay - audio_delay_stat_; |
| auto recent_delay = delta_packets > 0 ? delta_rpad / delta_packets : -1; |
| // The purpose of these checks is to sound the alarm early if we introduce |
| // serious regressions. The numbers are not acceptable for production, but |
| // occur on slow bots. |
| // |
| // An average relative packet arrival delay over the renegotiation of |
| // > 100 ms indicates that something is dramatically wrong, and will impact |
| // quality for sure. |
| // Worst bots: |
| // linux_x86_dbg at 0.206 |
| #if !defined(NDEBUG) |
| EXPECT_GT(0.25, recent_delay) << tag << " size " << desc_size; |
| #else |
| EXPECT_GT(0.1, recent_delay) << tag << " size " << desc_size; |
| #endif |
| auto delta_samples = *rtp_stats->total_samples_received - audio_samples_stat_; |
| auto delta_concealed = *rtp_stats->concealed_samples - audio_concealed_stat_; |
| // These limits should be adjusted down as we improve: |
| // |
| // Concealing more than 4000 samples during a renegotiation is unacceptable. |
| // But some bots are slow. |
| |
| // Worst bots: |
| // linux_more_configs bot at conceal count 5184 |
| // android_arm_rel at conceal count 9241 |
| // linux_x86_dbg at 15174 |
| #if !defined(NDEBUG) |
| EXPECT_GT(18000U, delta_concealed) << "Concealed " << delta_concealed |
| << " of " << delta_samples << " samples"; |
| #else |
| EXPECT_GT(15000U, delta_concealed) << "Concealed " << delta_concealed |
| << " of " << delta_samples << " samples"; |
| #endif |
| // Concealing more than 20% of samples during a renegotiation is |
| // unacceptable. |
| // Worst bots: |
| // Nondebug: Linux32 Release at conceal rate 0.606597 (CI run) |
| // Debug: linux_x86_dbg bot at conceal rate 0.854 |
| // internal bot at conceal rate 0.967 (b/294020344) |
| // TODO(https://crbug.com/webrtc/15393): Improve audio quality during |
| // renegotiation so that we can reduce these thresholds, 99% is not even |
| // close to the 20% deemed unacceptable above or the 0% that would be ideal. |
| if (delta_samples > 0) { |
| #if !defined(NDEBUG) |
| EXPECT_LT(1.0 * delta_concealed / delta_samples, 0.99) |
| << "Concealed " << delta_concealed << " of " << delta_samples |
| << " samples"; |
| #else |
| EXPECT_LT(1.0 * delta_concealed / delta_samples, 0.7) |
| << "Concealed " << delta_concealed << " of " << delta_samples |
| << " samples"; |
| #endif |
| } |
| // Increment trailing counters |
| audio_packets_stat_ = *rtp_stats->packets_received; |
| audio_delay_stat_ = *rtp_stats->relative_packet_arrival_delay; |
| audio_samples_stat_ = *rtp_stats->total_samples_received; |
| audio_concealed_stat_ = *rtp_stats->concealed_samples; |
| } |
| |
| bool PeerConnectionIntegrationWrapper::Init( |
| const PeerConnectionFactory::Options* options, |
| const PeerConnectionInterface::RTCConfiguration* config, |
| PeerConnectionDependencies dependencies, |
| rtc::SocketServer* socket_server, |
| rtc::Thread* network_thread, |
| rtc::Thread* worker_thread, |
| std::unique_ptr<FakeRtcEventLogFactory> event_log_factory, |
| bool reset_encoder_factory, |
| bool reset_decoder_factory, |
| bool create_media_engine) { |
| // There's an error in this test code if Init ends up being called twice. |
| RTC_DCHECK(!peer_connection_); |
| RTC_DCHECK(!peer_connection_factory_); |
| |
| fake_network_manager_.reset(new rtc::FakeNetworkManager()); |
| fake_network_manager_->AddInterface(kDefaultLocalAddress); |
| |
| socket_factory_.reset(new rtc::BasicPacketSocketFactory(socket_server)); |
| |
| std::unique_ptr<cricket::PortAllocator> port_allocator( |
| new cricket::BasicPortAllocator(fake_network_manager_.get(), |
| socket_factory_.get())); |
| port_allocator_ = port_allocator.get(); |
| fake_audio_capture_module_ = FakeAudioCaptureModule::Create(); |
| if (!fake_audio_capture_module_) { |
| return false; |
| } |
| rtc::Thread* const signaling_thread = rtc::Thread::Current(); |
| |
| PeerConnectionFactoryDependencies pc_factory_dependencies; |
| pc_factory_dependencies.network_thread = network_thread; |
| pc_factory_dependencies.worker_thread = worker_thread; |
| pc_factory_dependencies.signaling_thread = signaling_thread; |
| pc_factory_dependencies.task_queue_factory = CreateDefaultTaskQueueFactory(); |
| pc_factory_dependencies.trials = std::make_unique<FieldTrialBasedConfig>(); |
| pc_factory_dependencies.decode_metronome = |
| std::make_unique<TaskQueueMetronome>(TimeDelta::Millis(8)); |
| |
| pc_factory_dependencies.adm = fake_audio_capture_module_; |
| if (create_media_engine) { |
| // Standard creation method for APM may return a null pointer when |
| // AudioProcessing is disabled with a build flag. Bypass that flag by |
| // explicitly injecting the factory. |
| pc_factory_dependencies.audio_processing_builder = |
| std::make_unique<BuiltinAudioProcessingBuilder>(); |
| EnableMediaWithDefaults(pc_factory_dependencies); |
| } |
| |
| if (reset_encoder_factory) { |
| pc_factory_dependencies.video_encoder_factory.reset(); |
| } |
| if (reset_decoder_factory) { |
| pc_factory_dependencies.video_decoder_factory.reset(); |
| } |
| |
| if (event_log_factory) { |
| event_log_factory_ = event_log_factory.get(); |
| pc_factory_dependencies.event_log_factory = std::move(event_log_factory); |
| } else { |
| pc_factory_dependencies.event_log_factory = |
| std::make_unique<RtcEventLogFactory>(); |
| } |
| peer_connection_factory_ = |
| CreateModularPeerConnectionFactory(std::move(pc_factory_dependencies)); |
| |
| if (!peer_connection_factory_) { |
| return false; |
| } |
| if (options) { |
| peer_connection_factory_->SetOptions(*options); |
| } |
| if (config) { |
| sdp_semantics_ = config->sdp_semantics; |
| } |
| |
| dependencies.allocator = std::move(port_allocator); |
| peer_connection_ = CreatePeerConnection(config, std::move(dependencies)); |
| return peer_connection_.get() != nullptr; |
| } |
| |
| } // namespace webrtc |