diff --git a/modules/congestion_controller/goog_cc/test/goog_cc_printer.cc b/modules/congestion_controller/goog_cc/test/goog_cc_printer.cc index 7eafece048..3264b37e6d 100644 --- a/modules/congestion_controller/goog_cc/test/goog_cc_printer.cc +++ b/modules/congestion_controller/goog_cc/test/goog_cc_printer.cc @@ -60,4 +60,18 @@ std::unique_ptr GoogCcDebugFactory::Create( return controller; } +GoogCcFeedbackDebugFactory::GoogCcFeedbackDebugFactory( + RtcEventLog* event_log, + GoogCcStatePrinter* printer) + : GoogCcFeedbackNetworkControllerFactory(event_log), printer_(printer) {} + +std::unique_ptr GoogCcFeedbackDebugFactory::Create( + NetworkControllerConfig config) { + RTC_CHECK(controller_ == nullptr); + auto controller = GoogCcFeedbackNetworkControllerFactory::Create(config); + controller_ = static_cast(controller.get()); + printer_->Attach(controller_); + return controller; +} + } // namespace webrtc diff --git a/modules/congestion_controller/goog_cc/test/goog_cc_printer.h b/modules/congestion_controller/goog_cc/test/goog_cc_printer.h index e5bd891c4f..4dfc059b47 100644 --- a/modules/congestion_controller/goog_cc/test/goog_cc_printer.h +++ b/modules/congestion_controller/goog_cc/test/goog_cc_printer.h @@ -43,6 +43,19 @@ class GoogCcDebugFactory : public GoogCcNetworkControllerFactory { GoogCcStatePrinter* printer_; GoogCcNetworkController* controller_ = nullptr; }; + +class GoogCcFeedbackDebugFactory + : public GoogCcFeedbackNetworkControllerFactory { + public: + GoogCcFeedbackDebugFactory(RtcEventLog* event_log, + GoogCcStatePrinter* printer); + std::unique_ptr Create( + NetworkControllerConfig config) override; + + private: + GoogCcStatePrinter* printer_; + GoogCcNetworkController* controller_ = nullptr; +}; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_TEST_GOOG_CC_PRINTER_H_ diff --git a/test/BUILD.gn b/test/BUILD.gn index 3fa25cf877..ec9e880bec 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -28,6 +28,7 @@ group("test") { deps += [ ":test_main", ":test_support_unittests", + "scenario/scenario_tests", ] } } @@ -316,6 +317,7 @@ if (rtc_include_tests) { "../modules/video_coding:simulcast_test_fixture_impl", "../rtc_base:rtc_base_approved", "../test:single_threaded_task_queue", + "scenario:scenario_unittests", "//testing/gmock", "//testing/gtest", "//third_party/abseil-cpp/absl/memory", diff --git a/test/DEPS b/test/DEPS index 80f47b2f28..110623082d 100644 --- a/test/DEPS +++ b/test/DEPS @@ -8,6 +8,7 @@ include_rules = [ "+media/base", "+media/engine", "+modules/audio_coding", + "+modules/congestion_controller", "+modules/audio_device", "+modules/audio_mixer", "+modules/audio_processing", diff --git a/test/scenario/BUILD.gn b/test/scenario/BUILD.gn new file mode 100644 index 0000000000..16f3a786a4 --- /dev/null +++ b/test/scenario/BUILD.gn @@ -0,0 +1,122 @@ +# Copyright (c) 2018 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_source_set("scenario") { + testonly = true + sources = [ + "audio_stream.cc", + "audio_stream.h", + "call_client.cc", + "call_client.h", + "column_printer.cc", + "column_printer.h", + "hardware_codecs.cc", + "hardware_codecs.h", + "network_node.cc", + "network_node.h", + "scenario.cc", + "scenario.h", + "scenario_config.cc", + "scenario_config.h", + "video_stream.cc", + "video_stream.h", + ] + deps = [ + "../:fake_video_codecs", + "../:fileutils", + "../:test_common", + "../:test_support", + "../:video_test_common", + "../..:webrtc_common", + "../../api:libjingle_peerconnection_api", + "../../api:transport_api", + "../../api/audio_codecs:builtin_audio_decoder_factory", + "../../api/audio_codecs:builtin_audio_encoder_factory", + "../../api/units:data_rate", + "../../api/units:time_delta", + "../../api/units:timestamp", + "../../api/video:video_frame", + "../../api/video:video_frame_i420", + "../../api/video_codecs:video_codecs_api", + "../../audio", + "../../call", + "../../call:call_interfaces", + "../../call:rtp_sender", + "../../call:simulated_network", + "../../call:video_stream_api", + "../../common_video", + "../../logging:rtc_event_log_api", + "../../logging:rtc_event_log_impl_base", + "../../logging:rtc_event_log_impl_output", + "../../media:rtc_audio_video", + "../../media:rtc_internal_video_codecs", + "../../media:rtc_media_base", + "../../modules/audio_device", + "../../modules/audio_device:audio_device_impl", + "../../modules/audio_device:mock_audio_device", + "../../modules/audio_mixer:audio_mixer_impl", + "../../modules/audio_processing", + "../../modules/congestion_controller:test_controller_printer", + "../../modules/congestion_controller/bbr:test_bbr_printer", + "../../modules/congestion_controller/goog_cc:test_goog_cc_printer", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:mock_rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../modules/video_coding:video_codec_interface", + "../../modules/video_coding:video_coding_utility", + "../../modules/video_coding:webrtc_h264", + "../../modules/video_coding:webrtc_multiplex", + "../../modules/video_coding:webrtc_vp8", + "../../modules/video_coding:webrtc_vp9", + "../../rtc_base:checks", + "../../rtc_base:rtc_base_approved", + "../../rtc_base:rtc_base_tests_utils", + "../../rtc_base:rtc_task_queue", + "../../rtc_base:sequenced_task_checker", + "../../rtc_base:stringutils", + "../../system_wrappers", + "../../system_wrappers:field_trial_api", + "../../system_wrappers:runtime_enabled_features_api", + "../../video", + "//third_party/abseil-cpp/absl/types:optional", + ] + if (is_android) { + deps += [ "../../modules/video_coding:android_codec_factory_helper" ] + } else if (is_ios || is_mac) { + deps += [ "../../modules/video_coding:objc_codec_factory_helper" ] + } + if (!build_with_chromium && is_clang) { + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + } + rtc_source_set("scenario_unittests") { + testonly = true + sources = [ + "scenario_unittest.cc", + ] + if (!build_with_chromium && is_clang) { + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + deps = [ + ":scenario", + "../../logging:mocks", + "../../rtc_base:checks", + "../../rtc_base:rtc_base_approved", + "../../rtc_base:rtc_base_tests_utils", + "../../system_wrappers", + "../../test:field_trial", + "../../test:test_support", + "//system_wrappers:field_trial_api", + "//testing/gmock", + "//third_party/abseil-cpp/absl/memory", + ] + } +} diff --git a/test/scenario/OWNERS b/test/scenario/OWNERS new file mode 100644 index 0000000000..53e076b20b --- /dev/null +++ b/test/scenario/OWNERS @@ -0,0 +1 @@ +srte@webrtc.org diff --git a/test/scenario/audio_stream.cc b/test/scenario/audio_stream.cc new file mode 100644 index 0000000000..e1b645e704 --- /dev/null +++ b/test/scenario/audio_stream.cc @@ -0,0 +1,171 @@ +/* + * Copyright 2018 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/scenario/audio_stream.h" + +#include "test/call_test.h" + +namespace webrtc { +namespace test { + +SendAudioStream::SendAudioStream( + CallClient* sender, + AudioStreamConfig config, + rtc::scoped_refptr encoder_factory, + Transport* send_transport) + : sender_(sender), config_(config) { + AudioSendStream::Config send_config(send_transport); + ssrc_ = sender->GetNextAudioSsrc(); + send_config.rtp.ssrc = ssrc_; + SdpAudioFormat::Parameters sdp_params; + if (config.source.channels == 2) + sdp_params["stereo"] = "1"; + if (config.encoder.initial_frame_length != TimeDelta::ms(20)) + sdp_params["ptime"] = + std::to_string(config.encoder.initial_frame_length.ms()); + + // SdpAudioFormat::num_channels indicates that the encoder is capable of + // stereo, but the actual channel count used is based on the "stereo" + // parameter. + send_config.send_codec_spec = AudioSendStream::Config::SendCodecSpec( + CallTest::kAudioSendPayloadType, {"opus", 48000, 2, sdp_params}); + RTC_DCHECK_LE(config.source.channels, 2); + send_config.encoder_factory = encoder_factory; + + if (config.encoder.fixed_rate) + send_config.send_codec_spec->target_bitrate_bps = + config.encoder.fixed_rate->bps(); + + if (config.encoder.allocate_bitrate || + config.stream.in_bandwidth_estimation) { + DataRate min_rate = DataRate::Infinity(); + DataRate max_rate = DataRate::Infinity(); + if (config.encoder.fixed_rate) { + min_rate = *config.encoder.fixed_rate; + max_rate = *config.encoder.fixed_rate; + } else { + min_rate = *config.encoder.min_rate; + max_rate = *config.encoder.max_rate; + } + if (field_trial::IsEnabled("WebRTC-SendSideBwe-WithOverhead")) { + TimeDelta frame_length = config.encoder.initial_frame_length; + DataSize rtp_overhead = DataSize::bytes(12); + DataSize total_overhead = config.stream.packet_overhead + rtp_overhead; + min_rate += total_overhead / frame_length; + max_rate += total_overhead / frame_length; + } + send_config.min_bitrate_bps = min_rate.bps(); + send_config.max_bitrate_bps = max_rate.bps(); + } + + if (config.stream.in_bandwidth_estimation) { + send_config.send_codec_spec->transport_cc_enabled = true; + send_config.rtp.extensions = { + {RtpExtension::kTransportSequenceNumberUri, 8}}; + } + + if (config.stream.rate_allocation_priority) { + send_config.track_id = sender->GetNextPriorityId(); + } + send_stream_ = sender_->call_->CreateAudioSendStream(send_config); + if (field_trial::IsEnabled("WebRTC-SendSideBwe-WithOverhead")) { + sender->call_->OnTransportOverheadChanged( + MediaType::AUDIO, config.stream.packet_overhead.bytes()); + } +} + +SendAudioStream::~SendAudioStream() { + sender_->call_->DestroyAudioSendStream(send_stream_); +} + +void SendAudioStream::Start() { + send_stream_->Start(); +} + +bool SendAudioStream::TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) { + // Removes added overhead before delivering RTCP packet to sender. + RTC_DCHECK_GE(packet.size(), config_.stream.packet_overhead.bytes()); + packet.SetSize(packet.size() - config_.stream.packet_overhead.bytes()); + sender_->DeliverPacket(MediaType::AUDIO, packet, at_time); + return true; +} +ReceiveAudioStream::ReceiveAudioStream( + CallClient* receiver, + AudioStreamConfig config, + SendAudioStream* send_stream, + rtc::scoped_refptr decoder_factory, + Transport* feedback_transport) + : receiver_(receiver), config_(config) { + AudioReceiveStream::Config recv_config; + recv_config.rtp.local_ssrc = CallTest::kReceiverLocalAudioSsrc; + recv_config.rtcp_send_transport = feedback_transport; + recv_config.rtp.remote_ssrc = send_stream->ssrc_; + if (config.stream.in_bandwidth_estimation) { + recv_config.rtp.transport_cc = true; + recv_config.rtp.extensions = { + {RtpExtension::kTransportSequenceNumberUri, 8}}; + } + recv_config.decoder_factory = decoder_factory; + recv_config.decoder_map = { + {CallTest::kAudioSendPayloadType, {"opus", 48000, 2}}}; + recv_config.sync_group = config.render.sync_group; + receive_stream_ = receiver_->call_->CreateAudioReceiveStream(recv_config); +} +ReceiveAudioStream::~ReceiveAudioStream() { + receiver_->call_->DestroyAudioReceiveStream(receive_stream_); +} + +bool ReceiveAudioStream::TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) { + RTC_DCHECK_GE(packet.size(), config_.stream.packet_overhead.bytes()); + packet.SetSize(packet.size() - config_.stream.packet_overhead.bytes()); + receiver_->DeliverPacket(MediaType::AUDIO, packet, at_time); + return true; +} + +AudioStreamPair::~AudioStreamPair() = default; + +AudioStreamPair::AudioStreamPair( + CallClient* sender, + std::vector send_link, + uint64_t send_receiver_id, + rtc::scoped_refptr encoder_factory, + CallClient* receiver, + std::vector return_link, + uint64_t return_receiver_id, + rtc::scoped_refptr decoder_factory, + AudioStreamConfig config) + : config_(config), + send_link_(send_link), + return_link_(return_link), + send_transport_(sender, + send_link.front(), + send_receiver_id, + config.stream.packet_overhead), + return_transport_(receiver, + return_link.front(), + return_receiver_id, + config.stream.packet_overhead), + send_stream_(sender, config, encoder_factory, &send_transport_), + receive_stream_(receiver, + config, + &send_stream_, + decoder_factory, + &return_transport_) { + NetworkNode::Route(send_transport_.ReceiverId(), send_link_, + &receive_stream_); + NetworkNode::Route(return_transport_.ReceiverId(), return_link_, + &send_stream_); +} + +} // namespace test +} // namespace webrtc diff --git a/test/scenario/audio_stream.h b/test/scenario/audio_stream.h new file mode 100644 index 0000000000..17b8bc370e --- /dev/null +++ b/test/scenario/audio_stream.h @@ -0,0 +1,110 @@ +/* + * Copyright 2018 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_SCENARIO_AUDIO_STREAM_H_ +#define TEST_SCENARIO_AUDIO_STREAM_H_ +#include +#include +#include + +#include "rtc_base/constructormagic.h" +#include "test/scenario/call_client.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/network_node.h" +#include "test/scenario/scenario_config.h" + +namespace webrtc { +namespace test { + +// SendAudioStream represents sending of audio. It can be used for starting the +// stream if neccessary. +class SendAudioStream : public NetworkReceiverInterface { + public: + RTC_DISALLOW_COPY_AND_ASSIGN(SendAudioStream); + ~SendAudioStream(); + void Start(); + + private: + friend class Scenario; + friend class AudioStreamPair; + friend class ReceiveAudioStream; + SendAudioStream(CallClient* sender, + AudioStreamConfig config, + rtc::scoped_refptr encoder_factory, + Transport* send_transport); + // Handles RTCP feedback for this stream. + bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) override; + + AudioSendStream* send_stream_ = nullptr; + CallClient* const sender_; + const AudioStreamConfig config_; + uint32_t ssrc_; +}; + +// ReceiveAudioStream represents an audio receiver. It can't be used directly. +class ReceiveAudioStream : public NetworkReceiverInterface { + public: + RTC_DISALLOW_COPY_AND_ASSIGN(ReceiveAudioStream); + ~ReceiveAudioStream(); + + private: + friend class Scenario; + friend class AudioStreamPair; + ReceiveAudioStream(CallClient* receiver, + AudioStreamConfig config, + SendAudioStream* send_stream, + rtc::scoped_refptr decoder_factory, + Transport* feedback_transport); + bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) override; + AudioReceiveStream* receive_stream_ = nullptr; + CallClient* const receiver_; + const AudioStreamConfig config_; +}; + +// AudioStreamPair represents an audio streaming session. It can be used to +// access underlying send and receive classes. It can also be used in calls to +// the Scenario class. +class AudioStreamPair { + public: + RTC_DISALLOW_COPY_AND_ASSIGN(AudioStreamPair); + ~AudioStreamPair(); + SendAudioStream* send() { return &send_stream_; } + ReceiveAudioStream* receive() { return &receive_stream_; } + + private: + friend class Scenario; + AudioStreamPair(CallClient* sender, + std::vector send_link, + uint64_t send_receiver_id, + rtc::scoped_refptr encoder_factory, + + CallClient* receiver, + std::vector return_link, + uint64_t return_receiver_id, + rtc::scoped_refptr decoder_factory, + AudioStreamConfig config); + + private: + const AudioStreamConfig config_; + std::vector send_link_; + std::vector return_link_; + NetworkNodeTransport send_transport_; + NetworkNodeTransport return_transport_; + + SendAudioStream send_stream_; + ReceiveAudioStream receive_stream_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_AUDIO_STREAM_H_ diff --git a/test/scenario/call_client.cc b/test/scenario/call_client.cc new file mode 100644 index 0000000000..47cc3bc57b --- /dev/null +++ b/test/scenario/call_client.cc @@ -0,0 +1,190 @@ +/* + * Copyright 2018 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/scenario/call_client.h" + +#include + +#include "logging/rtc_event_log/output/rtc_event_log_output_file.h" +#include "modules/audio_mixer/audio_mixer_impl.h" +#include "modules/congestion_controller/bbr/test/bbr_printer.h" +#include "modules/congestion_controller/goog_cc/test/goog_cc_printer.h" +#include "test/call_test.h" + +namespace webrtc { +namespace test { +namespace { +const char* kPriorityStreamId = "priority-track"; +} + +LoggingNetworkControllerFactory::LoggingNetworkControllerFactory( + std::string filename, + TransportControllerConfig config) { + if (filename.empty()) { + event_log_ = RtcEventLog::CreateNull(); + } else { + event_log_ = RtcEventLog::Create(RtcEventLog::EncodingType::Legacy); + bool success = event_log_->StartLogging( + absl::make_unique(filename + ".rtc.dat", + RtcEventLog::kUnlimitedOutput), + RtcEventLog::kImmediateOutput); + RTC_CHECK(success); + cc_out_ = fopen((filename + ".cc_state.txt").c_str(), "w"); + switch (config.cc) { + case TransportControllerConfig::CongestionController::kBbr: { + auto bbr_printer = absl::make_unique(); + cc_factory_.reset(new BbrDebugFactory(bbr_printer.get())); + cc_printer_.reset( + new ControlStatePrinter(cc_out_, std::move(bbr_printer))); + break; + } + case TransportControllerConfig::CongestionController::kGoogCc: { + auto goog_printer = absl::make_unique(); + cc_factory_.reset( + new GoogCcDebugFactory(event_log_.get(), goog_printer.get())); + cc_printer_.reset( + new ControlStatePrinter(cc_out_, std::move(goog_printer))); + break; + } + case TransportControllerConfig::CongestionController::kGoogCcFeedback: { + auto goog_printer = absl::make_unique(); + cc_factory_.reset(new GoogCcFeedbackDebugFactory(event_log_.get(), + goog_printer.get())); + cc_printer_.reset( + new ControlStatePrinter(cc_out_, std::move(goog_printer))); + break; + } + } + cc_printer_->PrintHeaders(); + } + if (!cc_factory_) { + switch (config.cc) { + case TransportControllerConfig::CongestionController::kBbr: + cc_factory_.reset(new BbrNetworkControllerFactory()); + break; + case TransportControllerConfig::CongestionController::kGoogCcFeedback: + cc_factory_.reset( + new GoogCcFeedbackNetworkControllerFactory(event_log_.get())); + break; + case TransportControllerConfig::CongestionController::kGoogCc: + cc_factory_.reset(new GoogCcNetworkControllerFactory(event_log_.get())); + break; + } + } +} + +LoggingNetworkControllerFactory::~LoggingNetworkControllerFactory() { + if (cc_out_) + fclose(cc_out_); +} + +void LoggingNetworkControllerFactory::LogCongestionControllerStats( + Timestamp at_time) { + if (cc_printer_) + cc_printer_->PrintState(at_time); +} + +RtcEventLog* LoggingNetworkControllerFactory::GetEventLog() const { + return event_log_.get(); +} + +std::unique_ptr +LoggingNetworkControllerFactory::Create(NetworkControllerConfig config) { + return cc_factory_->Create(config); +} + +TimeDelta LoggingNetworkControllerFactory::GetProcessInterval() const { + return cc_factory_->GetProcessInterval(); +} + +CallClient::CallClient(Clock* clock, + std::string log_filename, + CallClientConfig config) + : clock_(clock), + network_controller_factory_(log_filename, config.transport) { + CallConfig call_config(network_controller_factory_.GetEventLog()); + call_config.bitrate_config.max_bitrate_bps = + config.transport.rates.max_rate.bps_or(-1); + call_config.bitrate_config.min_bitrate_bps = + config.transport.rates.min_rate.bps(); + call_config.bitrate_config.start_bitrate_bps = + config.transport.rates.start_rate.bps(); + call_config.network_controller_factory = &network_controller_factory_; + call_config.audio_state = InitAudio(); + call_.reset(Call::Create(call_config)); + if (!config.priority_target_rate.IsZero() && + config.priority_target_rate.IsFinite()) { + call_->SetBitrateAllocationStrategy( + absl::make_unique( + kPriorityStreamId, config.priority_target_rate.bps())); + } +} // namespace test + +CallClient::~CallClient() {} + +void CallClient::DeliverPacket(MediaType media_type, + rtc::CopyOnWriteBuffer packet, + Timestamp at_time) { + call_->Receiver()->DeliverPacket(media_type, packet, at_time.us()); +} + +ColumnPrinter CallClient::StatsPrinter() { + return ColumnPrinter::Lambda( + "pacer_delay call_send_bw", + [this](rtc::SimpleStringBuilder& sb) { + Call::Stats call_stats = call_->GetStats(); + sb.AppendFormat("%.3lf %.0lf", call_stats.pacer_delay_ms / 1000.0, + call_stats.send_bandwidth_bps / 8.0); + }, + 64); +} + +Call::Stats CallClient::GetStats() { + return call_->GetStats(); +} + +uint32_t CallClient::GetNextVideoSsrc() { + RTC_CHECK_LT(next_video_ssrc_index_, CallTest::kNumSsrcs); + return CallTest::kVideoSendSsrcs[next_video_ssrc_index_++]; +} + +uint32_t CallClient::GetNextAudioSsrc() { + RTC_CHECK_LT(next_audio_ssrc_index_, 1); + next_audio_ssrc_index_++; + return CallTest::kAudioSendSsrc; +} + +uint32_t CallClient::GetNextRtxSsrc() { + RTC_CHECK_LT(next_rtx_ssrc_index_, CallTest::kNumSsrcs); + return CallTest::kSendRtxSsrcs[next_rtx_ssrc_index_++]; +} + +std::string CallClient::GetNextPriorityId() { + RTC_CHECK_LT(next_priority_index_++, 1); + return kPriorityStreamId; +} + +rtc::scoped_refptr CallClient::InitAudio() { + auto capturer = TestAudioDeviceModule::CreatePulsedNoiseCapturer(256, 48000); + auto renderer = TestAudioDeviceModule::CreateDiscardRenderer(48000); + fake_audio_device_ = TestAudioDeviceModule::CreateTestAudioDeviceModule( + std::move(capturer), std::move(renderer), 1.f); + apm_ = AudioProcessingBuilder().Create(); + fake_audio_device_->Init(); + AudioState::Config audio_state_config; + audio_state_config.audio_mixer = AudioMixerImpl::Create(); + audio_state_config.audio_processing = apm_; + audio_state_config.audio_device_module = fake_audio_device_; + auto audio_state = AudioState::Create(audio_state_config); + fake_audio_device_->RegisterAudioCallback(audio_state->audio_transport()); + return audio_state; +} + +} // namespace test +} // namespace webrtc diff --git a/test/scenario/call_client.h b/test/scenario/call_client.h new file mode 100644 index 0000000000..80e2faf162 --- /dev/null +++ b/test/scenario/call_client.h @@ -0,0 +1,94 @@ +/* + * Copyright 2018 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_SCENARIO_CALL_CLIENT_H_ +#define TEST_SCENARIO_CALL_CLIENT_H_ +#include +#include +#include + +#include "call/call.h" +#include "logging/rtc_event_log/rtc_event_log.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "modules/congestion_controller/test/controller_printer.h" +#include "rtc_base/constructormagic.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/scenario_config.h" + +namespace webrtc { + +namespace test { +class LoggingNetworkControllerFactory + : public NetworkControllerFactoryInterface { + public: + LoggingNetworkControllerFactory(std::string filename, + TransportControllerConfig config); + RTC_DISALLOW_COPY_AND_ASSIGN(LoggingNetworkControllerFactory); + ~LoggingNetworkControllerFactory(); + std::unique_ptr Create( + NetworkControllerConfig config) override; + TimeDelta GetProcessInterval() const override; + // TODO(srte): Consider using the Columnprinter interface for this. + void LogCongestionControllerStats(Timestamp at_time); + RtcEventLog* GetEventLog() const; + + private: + std::unique_ptr event_log_; + std::unique_ptr cc_factory_; + std::unique_ptr cc_printer_; + FILE* cc_out_ = nullptr; +}; + +// CallClient represents a participant in a call scenario. It is created by the +// Scenario class and is used as sender and receiver when setting up a media +// stream session. +class CallClient { + public: + CallClient(Clock* clock, std::string log_filename, CallClientConfig config); + RTC_DISALLOW_COPY_AND_ASSIGN(CallClient); + + ~CallClient(); + ColumnPrinter StatsPrinter(); + Call::Stats GetStats(); + + private: + friend class Scenario; + friend class SendVideoStream; + friend class ReceiveVideoStream; + friend class SendAudioStream; + friend class ReceiveAudioStream; + friend class NetworkNodeTransport; + // TODO(srte): Consider using the Columnprinter interface for this. + void DeliverPacket(MediaType media_type, + rtc::CopyOnWriteBuffer packet, + Timestamp at_time); + uint32_t GetNextVideoSsrc(); + uint32_t GetNextAudioSsrc(); + uint32_t GetNextRtxSsrc(); + std::string GetNextPriorityId(); + + Clock* clock_; + LoggingNetworkControllerFactory network_controller_factory_; + std::unique_ptr call_; + + rtc::scoped_refptr InitAudio(); + + rtc::scoped_refptr apm_; + rtc::scoped_refptr fake_audio_device_; + + std::unique_ptr fec_controller_factory_; + int next_video_ssrc_index_ = 0; + int next_rtx_ssrc_index_ = 0; + int next_audio_ssrc_index_ = 0; + int next_priority_index_ = 0; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_CALL_CLIENT_H_ diff --git a/test/scenario/column_printer.cc b/test/scenario/column_printer.cc new file mode 100644 index 0000000000..234c9199e7 --- /dev/null +++ b/test/scenario/column_printer.cc @@ -0,0 +1,85 @@ +/* + * Copyright 2018 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/scenario/column_printer.h" + +namespace webrtc { +namespace test { + +ColumnPrinter::ColumnPrinter(const ColumnPrinter&) = default; +ColumnPrinter::~ColumnPrinter() = default; + +ColumnPrinter::ColumnPrinter( + const char* headers, + std::function printer, + size_t max_length) + : headers_(headers), printer_(printer), max_length_(max_length) {} + +ColumnPrinter ColumnPrinter::Fixed(const char* headers, std::string fields) { + return ColumnPrinter(headers, + [fields](rtc::SimpleStringBuilder& sb) { sb << fields; }, + fields.size()); +} + +ColumnPrinter ColumnPrinter::Lambda( + const char* headers, + std::function printer, + size_t max_length) { + return ColumnPrinter(headers, printer, max_length); +} + +StatesPrinter::StatesPrinter(std::string filename, + std::vector printers) + : StatesPrinter(printers) { + if (!filename.empty()) { + output_file_ = fopen(filename.c_str(), "w"); + RTC_CHECK(output_file_); + output_ = output_file_; + } +} + +StatesPrinter::StatesPrinter(std::vector printers) + : printers_(printers) { + output_ = stdout; + RTC_CHECK(!printers_.empty()); + for (auto& printer : printers_) + buffer_size_ += printer.max_length_ + 1; + buffer_.resize(buffer_size_); +} + +StatesPrinter::~StatesPrinter() { + if (output_file_) + fclose(output_file_); +} + +void StatesPrinter::PrintHeaders() { + if (!output_file_) + return; + fprintf(output_, "%s", printers_[0].headers_); + for (size_t i = 1; i < printers_.size(); ++i) { + fprintf(output_, " %s", printers_[i].headers_); + } + fputs("\n", output_); +} + +void StatesPrinter::PrintRow() { + // Note that this is run for null output to preserve side effects, this allows + // setting break points etc. + rtc::SimpleStringBuilder sb(buffer_); + printers_[0].printer_(sb); + for (size_t i = 1; i < printers_.size(); ++i) { + sb << ' '; + printers_[i].printer_(sb); + } + sb << "\n\0"; + if (output_file_) + fputs(&buffer_.front(), output_); +} +} // namespace test +} // namespace webrtc diff --git a/test/scenario/column_printer.h b/test/scenario/column_printer.h new file mode 100644 index 0000000000..b1299a05ac --- /dev/null +++ b/test/scenario/column_printer.h @@ -0,0 +1,63 @@ +/* + * Copyright 2018 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_SCENARIO_COLUMN_PRINTER_H_ +#define TEST_SCENARIO_COLUMN_PRINTER_H_ +#include +#include +#include +#include + +#include "rtc_base/constructormagic.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { +namespace test { +class ColumnPrinter { + public: + ColumnPrinter(const ColumnPrinter&); + ~ColumnPrinter(); + static ColumnPrinter Fixed(const char* headers, std::string fields); + static ColumnPrinter Lambda( + const char* headers, + std::function printer, + size_t max_length = 256); + + protected: + friend class StatesPrinter; + const char* headers_; + std::function printer_; + size_t max_length_; + + private: + ColumnPrinter(const char* headers, + std::function printer, + size_t max_length); +}; + +class StatesPrinter { + public: + StatesPrinter(std::string filename, std::vector printers); + explicit StatesPrinter(std::vector printers); + RTC_DISALLOW_COPY_AND_ASSIGN(StatesPrinter); + ~StatesPrinter(); + void PrintHeaders(); + void PrintRow(); + + private: + const std::vector printers_; + size_t buffer_size_ = 0; + std::vector buffer_; + FILE* output_file_ = nullptr; + FILE* output_ = nullptr; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_COLUMN_PRINTER_H_ diff --git a/test/scenario/hardware_codecs.cc b/test/scenario/hardware_codecs.cc new file mode 100644 index 0000000000..16044de16e --- /dev/null +++ b/test/scenario/hardware_codecs.cc @@ -0,0 +1,49 @@ +/* + * Copyright 2018 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/scenario/hardware_codecs.h" +#include "rtc_base/checks.h" + +#ifdef WEBRTC_ANDROID +#include "modules/video_coding/codecs/test/android_codec_factory_helper.h" +#endif +#ifdef WEBRTC_MAC +#include "modules/video_coding/codecs/test/objc_codec_factory_helper.h" +#endif + +namespace webrtc { +namespace test { +std::unique_ptr CreateHardwareEncoderFactory() { +#ifdef WEBRTC_ANDROID + InitializeAndroidObjects(); + return CreateAndroidEncoderFactory(); +#else +#ifdef WEBRTC_MAC + return CreateObjCEncoderFactory(); +#else + RTC_NOTREACHED() << "Hardware encoder not implemented on this platform."; + return nullptr; +#endif +#endif +} +std::unique_ptr CreateHardwareDecoderFactory() { +#ifdef WEBRTC_ANDROID + InitializeAndroidObjects(); + return CreateAndroidDecoderFactory(); +#else +#ifdef WEBRTC_MAC + return CreateObjCDecoderFactory(); +#else + RTC_NOTREACHED() << "Hardware decoder not implemented on this platform."; + return nullptr; +#endif +#endif +} +} // namespace test +} // namespace webrtc diff --git a/test/scenario/hardware_codecs.h b/test/scenario/hardware_codecs.h new file mode 100644 index 0000000000..ae14a27d9e --- /dev/null +++ b/test/scenario/hardware_codecs.h @@ -0,0 +1,24 @@ +/* + * Copyright 2018 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_SCENARIO_HARDWARE_CODECS_H_ +#define TEST_SCENARIO_HARDWARE_CODECS_H_ + +#include + +#include "api/video_codecs/video_decoder_factory.h" +#include "api/video_codecs/video_encoder_factory.h" + +namespace webrtc { +namespace test { +std::unique_ptr CreateHardwareEncoderFactory(); +std::unique_ptr CreateHardwareDecoderFactory(); +} // namespace test +} // namespace webrtc +#endif // TEST_SCENARIO_HARDWARE_CODECS_H_ diff --git a/test/scenario/network_node.cc b/test/scenario/network_node.cc new file mode 100644 index 0000000000..eb215aa63c --- /dev/null +++ b/test/scenario/network_node.cc @@ -0,0 +1,257 @@ +/* + * Copyright 2018 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/scenario/network_node.h" + +#include +#include + +namespace webrtc { +namespace test { +namespace { +SimulatedNetwork::Config CreateSimulationConfig(NetworkNodeConfig config) { + SimulatedNetwork::Config sim_config; + sim_config.link_capacity_kbps = config.simulation.bandwidth.kbps_or(0); + sim_config.loss_percent = config.simulation.loss_rate * 100; + sim_config.queue_delay_ms = config.simulation.delay.ms(); + sim_config.delay_standard_deviation_ms = config.simulation.delay_std_dev.ms(); + return sim_config; +} +} // namespace + +bool NullReceiver::TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) { + return true; +} + +ActionReceiver::ActionReceiver(std::function action) + : action_(action) {} + +bool ActionReceiver::TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) { + action_(); + return true; +} + +NetworkNode::~NetworkNode() = default; + +NetworkNode::NetworkNode(NetworkNodeConfig config, + std::unique_ptr simulation) + : packet_overhead_(config.packet_overhead.bytes()), + simulation_(std::move(simulation)) {} + +void NetworkNode::SetRoute(uint64_t receiver, NetworkReceiverInterface* node) { + rtc::CritScope crit(&crit_sect_); + routing_[receiver] = node; +} + +void NetworkNode::ClearRoute(uint64_t receiver_id) { + rtc::CritScope crit(&crit_sect_); + auto it = routing_.find(receiver_id); + routing_.erase(it); +} + +bool NetworkNode::TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) { + rtc::CritScope crit(&crit_sect_); + if (routing_.find(receiver) == routing_.end()) + return false; + uint64_t packet_id = next_packet_id_++; + bool sent = simulation_->EnqueuePacket(PacketInFlightInfo( + packet.size() + packet_overhead_, at_time.us(), packet_id)); + if (sent) { + packets_.emplace_back(StoredPacket{packet, receiver, packet_id, false}); + } + return sent; +} + +void NetworkNode::Process(Timestamp at_time) { + std::vector delivery_infos; + { + rtc::CritScope crit(&crit_sect_); + absl::optional delivery_us = simulation_->NextDeliveryTimeUs(); + if (delivery_us && *delivery_us > at_time.us()) + return; + + delivery_infos = simulation_->DequeueDeliverablePackets(at_time.us()); + } + for (PacketDeliveryInfo& delivery_info : delivery_infos) { + StoredPacket* packet = nullptr; + NetworkReceiverInterface* receiver = nullptr; + { + rtc::CritScope crit(&crit_sect_); + for (StoredPacket& stored_packet : packets_) { + if (stored_packet.id == delivery_info.packet_id) { + packet = &stored_packet; + break; + } + } + RTC_CHECK(packet); + RTC_DCHECK(!packet->removed); + receiver = routing_[packet->receiver_id]; + packet->removed = true; + } + // We don't want to keep the lock here. Otherwise we would get a deadlock if + // the receiver tries to push a new packet. + receiver->TryDeliverPacket(packet->packet_data, packet->receiver_id, + at_time); + { + rtc::CritScope crit(&crit_sect_); + while (!packets_.empty() && packets_.front().removed) { + packets_.pop_front(); + } + } + } +} + +void NetworkNode::Route(int64_t receiver_id, + std::vector nodes, + NetworkReceiverInterface* receiver) { + RTC_CHECK(!nodes.empty()); + for (size_t i = 0; i + 1 < nodes.size(); ++i) + nodes[i]->SetRoute(receiver_id, nodes[i + 1]); + nodes.back()->SetRoute(receiver_id, receiver); +} + +void NetworkNode::ClearRoute(int64_t receiver_id, + std::vector nodes) { + for (NetworkNode* node : nodes) + node->ClearRoute(receiver_id); +} + +std::unique_ptr SimulationNode::Create( + NetworkNodeConfig config) { + RTC_DCHECK(config.mode == NetworkNodeConfig::TrafficMode::kSimulation); + SimulatedNetwork::Config sim_config = CreateSimulationConfig(config); + auto network = absl::make_unique(sim_config); + SimulatedNetwork* simulation_ptr = network.get(); + return std::unique_ptr( + new SimulationNode(config, std::move(network), simulation_ptr)); +} + +void SimulationNode::UpdateConfig( + std::function modifier) { + modifier(&config_); + SimulatedNetwork::Config sim_config = CreateSimulationConfig(config_); + simulated_network_->SetConfig(sim_config); +} + +void SimulationNode::PauseTransmissionUntil(Timestamp until) { + simulated_network_->PauseTransmissionUntil(until.us()); +} + +ColumnPrinter SimulationNode::ConfigPrinter() const { + return ColumnPrinter::Lambda("propagation_delay capacity loss_rate", + [this](rtc::SimpleStringBuilder& sb) { + sb.AppendFormat( + "%.3lf %.0lf %.2lf", + config_.simulation.delay.seconds(), + config_.simulation.bandwidth.bps() / 8.0, + config_.simulation.loss_rate); + }); +} + +SimulationNode::SimulationNode( + NetworkNodeConfig config, + std::unique_ptr behavior, + SimulatedNetwork* simulation) + : NetworkNode(config, std::move(behavior)), + simulated_network_(simulation), + config_(config) {} + +NetworkNodeTransport::NetworkNodeTransport(CallClient* sender, + NetworkNode* send_net, + uint64_t receiver, + DataSize packet_overhead) + : sender_(sender), + send_net_(send_net), + receiver_id_(receiver), + packet_overhead_(packet_overhead) {} + +NetworkNodeTransport::~NetworkNodeTransport() = default; + +bool NetworkNodeTransport::SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) { + sender_->call_->OnSentPacket(rtc::SentPacket( + options.packet_id, sender_->clock_->TimeInMilliseconds())); + Timestamp send_time = Timestamp::ms(sender_->clock_->TimeInMilliseconds()); + rtc::CopyOnWriteBuffer buffer(packet, length, + length + packet_overhead_.bytes()); + buffer.SetSize(length + packet_overhead_.bytes()); + return send_net_->TryDeliverPacket(buffer, receiver_id_, send_time); +} + +bool NetworkNodeTransport::SendRtcp(const uint8_t* packet, size_t length) { + rtc::CopyOnWriteBuffer buffer(packet, length); + Timestamp send_time = Timestamp::ms(sender_->clock_->TimeInMilliseconds()); + buffer.SetSize(length + packet_overhead_.bytes()); + return send_net_->TryDeliverPacket(buffer, receiver_id_, send_time); +} + +uint64_t NetworkNodeTransport::ReceiverId() const { + return receiver_id_; +} + +CrossTrafficSource::CrossTrafficSource(NetworkReceiverInterface* target, + uint64_t receiver_id, + CrossTrafficConfig config) + : target_(target), + receiver_id_(receiver_id), + config_(config), + random_(config.random_seed) {} + +CrossTrafficSource::~CrossTrafficSource() = default; + +DataRate CrossTrafficSource::TrafficRate() const { + return config_.peak_rate * intensity_; +} + +void CrossTrafficSource::Process(Timestamp at_time, TimeDelta delta) { + time_since_update_ += delta; + if (config_.mode == CrossTrafficConfig::Mode::kRandomWalk) { + if (time_since_update_ >= config_.random_walk.update_interval) { + intensity_ += random_.Gaussian(config_.random_walk.bias, + config_.random_walk.variance) * + time_since_update_.seconds(); + intensity_ = rtc::SafeClamp(intensity_, 0.0, 1.0); + time_since_update_ = TimeDelta::Zero(); + } + } else if (config_.mode == CrossTrafficConfig::Mode::kPulsedPeaks) { + if (intensity_ == 0 && time_since_update_ >= config_.pulsed.hold_duration) { + intensity_ = 1; + time_since_update_ = TimeDelta::Zero(); + } else if (intensity_ == 1 && + time_since_update_ >= config_.pulsed.send_duration) { + intensity_ = 0; + time_since_update_ = TimeDelta::Zero(); + } + } + pending_size_ += TrafficRate() * delta; + if (pending_size_ > config_.min_packet_size) { + target_->TryDeliverPacket(rtc::CopyOnWriteBuffer(pending_size_.bytes()), + receiver_id_, at_time); + pending_size_ = DataSize::Zero(); + } +} + +ColumnPrinter CrossTrafficSource::StatsPrinter() { + return ColumnPrinter::Lambda("cross_traffic_rate", + [this](rtc::SimpleStringBuilder& sb) { + sb.AppendFormat("%.0lf", + TrafficRate().bps() / 8.0); + }, + 32); +} + +} // namespace test +} // namespace webrtc diff --git a/test/scenario/network_node.h b/test/scenario/network_node.h new file mode 100644 index 0000000000..a94df1b34b --- /dev/null +++ b/test/scenario/network_node.h @@ -0,0 +1,167 @@ +/* + * Copyright 2018 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_SCENARIO_NETWORK_NODE_H_ +#define TEST_SCENARIO_NETWORK_NODE_H_ + +#include +#include +#include +#include +#include + +#include "api/call/transport.h" +#include "api/units/timestamp.h" +#include "call/simulated_network.h" +#include "rtc_base/constructormagic.h" +#include "rtc_base/copyonwritebuffer.h" +#include "test/scenario/call_client.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/scenario_config.h" + +namespace webrtc { +namespace test { + +class NetworkReceiverInterface { + public: + virtual bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) = 0; + virtual ~NetworkReceiverInterface() = default; +}; +class NullReceiver : public NetworkReceiverInterface { + public: + bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) override; +}; +class ActionReceiver : public NetworkReceiverInterface { + public: + explicit ActionReceiver(std::function action); + virtual ~ActionReceiver() = default; + bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) override; + + private: + std::function action_; +}; + +// NetworkNode represents one link in a simulated network. It is created by a +// scenario and can be used when setting up audio and video stream sessions. +class NetworkNode : public NetworkReceiverInterface { + public: + ~NetworkNode() override; + RTC_DISALLOW_COPY_AND_ASSIGN(NetworkNode); + + bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) override; + // Creates a route for the given receiver_id over all the given nodes to the + // given receiver. + static void Route(int64_t receiver_id, + std::vector nodes, + NetworkReceiverInterface* receiver); + + protected: + friend class Scenario; + friend class AudioStreamPair; + friend class VideoStreamPair; + + NetworkNode(NetworkNodeConfig config, + std::unique_ptr simulation); + static void ClearRoute(int64_t receiver_id, std::vector nodes); + void Process(Timestamp at_time); + + private: + struct StoredPacket { + rtc::CopyOnWriteBuffer packet_data; + uint64_t receiver_id; + uint64_t id; + bool removed; + }; + void SetRoute(uint64_t receiver, NetworkReceiverInterface* node); + void ClearRoute(uint64_t receiver_id); + rtc::CriticalSection crit_sect_; + size_t packet_overhead_ RTC_GUARDED_BY(crit_sect_); + const std::unique_ptr simulation_ + RTC_GUARDED_BY(crit_sect_); + std::map routing_ + RTC_GUARDED_BY(crit_sect_); + std::deque packets_ RTC_GUARDED_BY(crit_sect_); + + uint64_t next_packet_id_ RTC_GUARDED_BY(crit_sect_) = 1; +}; +// SimulationNode is a NetworkNode that expose an interface for changing run +// time behavior of the underlying simulation. +class SimulationNode : public NetworkNode { + public: + void UpdateConfig(std::function modifier); + void PauseTransmissionUntil(Timestamp until); + ColumnPrinter ConfigPrinter() const; + + private: + friend class Scenario; + + SimulationNode(NetworkNodeConfig config, + std::unique_ptr behavior, + SimulatedNetwork* simulation); + static std::unique_ptr Create(NetworkNodeConfig config); + SimulatedNetwork* const simulated_network_; + NetworkNodeConfig config_; +}; + +class NetworkNodeTransport : public Transport { + public: + NetworkNodeTransport(CallClient* sender, + NetworkNode* send_net, + uint64_t receiver, + DataSize packet_overhead); + ~NetworkNodeTransport() override; + + bool SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) override; + bool SendRtcp(const uint8_t* packet, size_t length) override; + uint64_t ReceiverId() const; + + private: + CallClient* const sender_; + NetworkNode* const send_net_; + const uint64_t receiver_id_; + const DataSize packet_overhead_; +}; + +// CrossTrafficSource is created by a Scenario and generates cross traffic. It +// provides methods to access and print internal state. +class CrossTrafficSource { + public: + DataRate TrafficRate() const; + ColumnPrinter StatsPrinter(); + ~CrossTrafficSource(); + + private: + friend class Scenario; + CrossTrafficSource(NetworkReceiverInterface* target, + uint64_t receiver_id, + CrossTrafficConfig config); + void Process(Timestamp at_time, TimeDelta delta); + + NetworkReceiverInterface* const target_; + const uint64_t receiver_id_; + CrossTrafficConfig config_; + webrtc::Random random_; + + TimeDelta time_since_update_ = TimeDelta::Zero(); + double intensity_ = 0; + DataSize pending_size_ = DataSize::Zero(); +}; +} // namespace test +} // namespace webrtc +#endif // TEST_SCENARIO_NETWORK_NODE_H_ diff --git a/test/scenario/scenario.cc b/test/scenario/scenario.cc new file mode 100644 index 0000000000..ab6ee6bca5 --- /dev/null +++ b/test/scenario/scenario.cc @@ -0,0 +1,341 @@ +/* + * Copyright 2018 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/scenario/scenario.h" + +#include + +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "rtc_base/flags.h" +#include "test/testsupport/fileutils.h" + +DEFINE_bool(scenario_logs, false, "Save logs from scenario framework."); + +namespace webrtc { +namespace test { +namespace { +int64_t kMicrosPerSec = 1000000; +} + +RepeatedActivity::RepeatedActivity(TimeDelta interval, + std::function function) + : interval_(interval), function_(function) {} + +void RepeatedActivity::Stop() { + interval_ = TimeDelta::PlusInfinity(); +} + +void RepeatedActivity::Poll(Timestamp time) { + RTC_DCHECK(last_update_.IsFinite()); + if (time >= last_update_ + interval_) { + function_(time - last_update_); + last_update_ = time; + } +} + +void RepeatedActivity::SetStartTime(Timestamp time) { + last_update_ = time; +} + +Timestamp RepeatedActivity::NextTime() { + RTC_DCHECK(last_update_.IsFinite()); + return last_update_ + interval_; +} + +Scenario::Scenario() : Scenario("", true) {} + +Scenario::Scenario(std::string file_name) : Scenario(file_name, true) {} + +Scenario::Scenario(std::string file_name, bool real_time) + : real_time_mode_(real_time), + sim_clock_(100000 * kMicrosPerSec), + clock_(real_time ? Clock::GetRealTimeClock() : &sim_clock_), + audio_decoder_factory_(CreateBuiltinAudioDecoderFactory()), + audio_encoder_factory_(CreateBuiltinAudioEncoderFactory()) { + if (FLAG_scenario_logs && !file_name.empty()) { + CreateDir(OutputPath() + "output_data"); + for (size_t i = 0; i < file_name.size(); ++i) { + if (file_name[i] == '/') + CreateDir(OutputPath() + "output_data/" + file_name.substr(0, i)); + } + base_filename_ = OutputPath() + "output_data/" + file_name; + RTC_LOG(LS_INFO) << "Saving scenario logs to: " << base_filename_; + } + if (!real_time_mode_) { + rtc::SetClockForTesting(&event_log_fake_clock_); + event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() * 1000); + } +} + +Scenario::~Scenario() { + if (!real_time_mode_) + rtc::SetClockForTesting(nullptr); +} + +ColumnPrinter Scenario::TimePrinter() { + return ColumnPrinter::Lambda("time", + [this](rtc::SimpleStringBuilder& sb) { + sb.AppendFormat("%.3lf", + Now().seconds()); + }, + 32); +} + +StatesPrinter* Scenario::CreatePrinter(std::string name, + TimeDelta interval, + std::vector printers) { + std::vector all_printers{TimePrinter()}; + for (auto& printer : printers) + all_printers.push_back(printer); + StatesPrinter* printer = + new StatesPrinter(GetFullPathOrEmpty(name), all_printers); + printers_.emplace_back(printer); + printer->PrintHeaders(); + if (interval.IsFinite()) + Every(interval, [printer] { printer->PrintRow(); }); + return printer; +} + +CallClient* Scenario::CreateClient(std::string name, CallClientConfig config) { + CallClient* client = new CallClient(clock_, GetFullPathOrEmpty(name), config); + if (config.transport.state_log_interval.IsFinite()) { + Every(config.transport.state_log_interval, [this, client]() { + client->network_controller_factory_.LogCongestionControllerStats(Now()); + }); + } + clients_.emplace_back(client); + return client; +} + +CallClient* Scenario::CreateClient( + std::string name, + std::function config_modifier) { + CallClientConfig config; + config_modifier(&config); + return CreateClient(name, config); +} + +SimulationNode* Scenario::CreateSimulationNode( + std::function config_modifier) { + NetworkNodeConfig config; + config_modifier(&config); + return CreateSimulationNode(config); +} + +SimulationNode* Scenario::CreateSimulationNode(NetworkNodeConfig config) { + RTC_DCHECK(config.mode == NetworkNodeConfig::TrafficMode::kSimulation); + auto network_node = SimulationNode::Create(config); + SimulationNode* sim_node = network_node.get(); + network_nodes_.emplace_back(std::move(network_node)); + Every(config.update_frequency, + [this, sim_node] { sim_node->Process(Now()); }); + return sim_node; +} + +NetworkNode* Scenario::CreateNetworkNode( + NetworkNodeConfig config, + std::unique_ptr simulation) { + RTC_DCHECK(config.mode == NetworkNodeConfig::TrafficMode::kCustom); + network_nodes_.emplace_back(new NetworkNode(config, std::move(simulation))); + NetworkNode* network_node = network_nodes_.back().get(); + Every(config.update_frequency, + [this, network_node] { network_node->Process(Now()); }); + return network_node; +} + +void Scenario::TriggerPacketBurst(std::vector over_nodes, + size_t num_packets, + size_t packet_size) { + int64_t receiver_id = next_receiver_id_++; + NetworkNode::Route(receiver_id, over_nodes, &null_receiver_); + for (size_t i = 0; i < num_packets; ++i) + over_nodes[0]->TryDeliverPacket(rtc::CopyOnWriteBuffer(packet_size), + receiver_id, Now()); +} + +void Scenario::NetworkDelayedAction(std::vector over_nodes, + size_t packet_size, + std::function action) { + int64_t receiver_id = next_receiver_id_++; + action_receivers_.emplace_back(new ActionReceiver(action)); + NetworkNode::Route(receiver_id, over_nodes, action_receivers_.back().get()); + over_nodes[0]->TryDeliverPacket(rtc::CopyOnWriteBuffer(packet_size), + receiver_id, Now()); +} + +CrossTrafficSource* Scenario::CreateCrossTraffic( + std::vector over_nodes, + std::function config_modifier) { + CrossTrafficConfig cross_config; + config_modifier(&cross_config); + return CreateCrossTraffic(over_nodes, cross_config); +} + +CrossTrafficSource* Scenario::CreateCrossTraffic( + std::vector over_nodes, + CrossTrafficConfig config) { + int64_t receiver_id = next_receiver_id_++; + cross_traffic_sources_.emplace_back( + new CrossTrafficSource(over_nodes.front(), receiver_id, config)); + CrossTrafficSource* node = cross_traffic_sources_.back().get(); + NetworkNode::Route(receiver_id, over_nodes, &null_receiver_); + Every(config.min_packet_interval, + [this, node](TimeDelta delta) { node->Process(Now(), delta); }); + return node; +} + +VideoStreamPair* Scenario::CreateVideoStream( + CallClient* sender, + std::vector send_link, + CallClient* receiver, + std::vector return_link, + std::function config_modifier) { + VideoStreamConfig config; + config_modifier(&config); + return CreateVideoStream(sender, send_link, receiver, return_link, config); +} + +VideoStreamPair* Scenario::CreateVideoStream( + CallClient* sender, + std::vector send_link, + CallClient* receiver, + std::vector return_link, + VideoStreamConfig config) { + uint64_t send_receiver_id = next_receiver_id_++; + uint64_t return_receiver_id = next_receiver_id_++; + + video_streams_.emplace_back( + new VideoStreamPair(sender, send_link, send_receiver_id, receiver, + return_link, return_receiver_id, config)); + return video_streams_.back().get(); +} + +AudioStreamPair* Scenario::CreateAudioStream( + CallClient* sender, + std::vector send_link, + CallClient* receiver, + std::vector return_link, + std::function config_modifier) { + AudioStreamConfig config; + config_modifier(&config); + return CreateAudioStream(sender, send_link, receiver, return_link, config); +} + +AudioStreamPair* Scenario::CreateAudioStream( + CallClient* sender, + std::vector send_link, + CallClient* receiver, + std::vector return_link, + AudioStreamConfig config) { + uint64_t send_receiver_id = next_receiver_id_++; + uint64_t return_receiver_id = next_receiver_id_++; + + audio_streams_.emplace_back(new AudioStreamPair( + sender, send_link, send_receiver_id, audio_encoder_factory_, receiver, + return_link, return_receiver_id, audio_decoder_factory_, config)); + return audio_streams_.back().get(); +} + +RepeatedActivity* Scenario::Every(TimeDelta interval, + std::function function) { + repeated_activities_.emplace_back(new RepeatedActivity(interval, function)); + return repeated_activities_.back().get(); +} + +RepeatedActivity* Scenario::Every(TimeDelta interval, + std::function function) { + auto function_with_argument = [function](TimeDelta) { function(); }; + repeated_activities_.emplace_back( + new RepeatedActivity(interval, function_with_argument)); + return repeated_activities_.back().get(); +} + +void Scenario::At(TimeDelta offset, std::function function) { + pending_activities_.emplace_back(new PendingActivity{offset, function}); +} + +void Scenario::RunFor(TimeDelta duration) { + RunUntil(duration, TimeDelta::PlusInfinity(), []() { return false; }); +} + +void Scenario::RunUntil(TimeDelta max_duration, + TimeDelta poll_interval, + std::function exit_function) { + start_time_ = Timestamp::us(clock_->TimeInMicroseconds()); + for (auto& activity : repeated_activities_) { + activity->SetStartTime(start_time_); + } + + for (auto& stream_pair : video_streams_) + stream_pair->receive()->receive_stream_->Start(); + for (auto& stream_pair : audio_streams_) + stream_pair->receive()->receive_stream_->Start(); + for (auto& stream_pair : video_streams_) { + if (stream_pair->config_.autostart) { + stream_pair->send()->Start(); + } + } + for (auto& stream_pair : audio_streams_) { + if (stream_pair->config_.autostart) { + stream_pair->send()->Start(); + } + } + for (auto& call : clients_) { + call->call_->SignalChannelNetworkState(MediaType::AUDIO, kNetworkUp); + call->call_->SignalChannelNetworkState(MediaType::VIDEO, kNetworkUp); + } + + rtc::Event done_(false, false); + while (!exit_function() && Duration() < max_duration) { + Timestamp current_time = Now(); + TimeDelta duration = current_time - start_time_; + Timestamp next_time = current_time + poll_interval; + for (auto& activity : repeated_activities_) { + activity->Poll(current_time); + next_time = std::min(next_time, activity->NextTime()); + } + for (auto activity = pending_activities_.begin(); + activity < pending_activities_.end(); activity++) { + if (duration > (*activity)->after_duration) { + (*activity)->function(); + pending_activities_.erase(activity); + } + } + TimeDelta wait_time = next_time - current_time; + if (real_time_mode_) { + done_.Wait(wait_time.ms()); + } else { + sim_clock_.AdvanceTimeMicroseconds(wait_time.us()); + event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() * + 1000); + } + } + for (auto& stream_pair : video_streams_) { + stream_pair->send()->video_capturer_->Stop(); + stream_pair->send()->send_stream_->Stop(); + } + for (auto& stream_pair : audio_streams_) + stream_pair->send()->send_stream_->Stop(); + for (auto& stream_pair : video_streams_) + stream_pair->receive()->receive_stream_->Stop(); + for (auto& stream_pair : audio_streams_) + stream_pair->receive()->receive_stream_->Stop(); +} + +Timestamp Scenario::Now() { + return Timestamp::us(clock_->TimeInMicroseconds()); +} + +TimeDelta Scenario::Duration() { + return Now() - start_time_; +} + +} // namespace test +} // namespace webrtc diff --git a/test/scenario/scenario.h b/test/scenario/scenario.h new file mode 100644 index 0000000000..2cdad3562d --- /dev/null +++ b/test/scenario/scenario.h @@ -0,0 +1,181 @@ +/* + * Copyright 2018 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_SCENARIO_SCENARIO_H_ +#define TEST_SCENARIO_SCENARIO_H_ +#include +#include +#include +#include + +#include "rtc_base/constructormagic.h" +#include "rtc_base/fakeclock.h" +#include "test/scenario/audio_stream.h" +#include "test/scenario/call_client.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/network_node.h" +#include "test/scenario/scenario_config.h" +#include "test/scenario/video_stream.h" + +namespace webrtc { +namespace test { +// RepeatedActivity is created by the Scenario class and can be used to stop a +// running activity at runtime. +class RepeatedActivity { + public: + void Stop(); + + private: + friend class Scenario; + RepeatedActivity(TimeDelta interval, std::function function); + + void Poll(Timestamp time); + void SetStartTime(Timestamp time); + Timestamp NextTime(); + + TimeDelta interval_; + std::function function_; + Timestamp last_update_ = Timestamp::MinusInfinity(); +}; + +struct PendingActivity { + TimeDelta after_duration; + std::function function; +}; + +// Scenario is a class owning everything for a test scenario. It creates and +// holds network nodes, call clients and media streams. It also provides methods +// for changing behavior at runtime. Since it always keeps ownership of the +// created components, it generally returns non-owning pointers. It maintains +// the life of its objects until it is destroyed. +// For methods accepting configuration structs, a modifier function interface is +// generally provided. This allows simple partial overriding of the default +// configuration. +class Scenario { + public: + Scenario(); + explicit Scenario(std::string file_name); + Scenario(std::string file_name, bool real_time); + RTC_DISALLOW_COPY_AND_ASSIGN(Scenario); + ~Scenario(); + + SimulationNode* CreateSimulationNode(NetworkNodeConfig config); + SimulationNode* CreateSimulationNode( + std::function config_modifier); + NetworkNode* CreateNetworkNode( + NetworkNodeConfig config, + std::unique_ptr simulation); + + CallClient* CreateClient(std::string name, CallClientConfig config); + CallClient* CreateClient( + std::string name, + std::function config_modifier); + + VideoStreamPair* CreateVideoStream( + CallClient* sender, + std::vector send_link, + CallClient* receiver, + std::vector return_link, + std::function config_modifier); + VideoStreamPair* CreateVideoStream(CallClient* sender, + std::vector send_link, + CallClient* receiver, + std::vector return_link, + VideoStreamConfig config); + + AudioStreamPair* CreateAudioStream( + CallClient* sender, + std::vector send_link, + CallClient* receiver, + std::vector return_link, + std::function config_modifier); + AudioStreamPair* CreateAudioStream(CallClient* sender, + std::vector send_link, + CallClient* receiver, + std::vector return_link, + AudioStreamConfig config); + + CrossTrafficSource* CreateCrossTraffic( + std::vector over_nodes, + std::function config_modifier); + CrossTrafficSource* CreateCrossTraffic(std::vector over_nodes, + CrossTrafficConfig config); + + // Runs the provided function with a fixed interval. + RepeatedActivity* Every(TimeDelta interval, + std::function function); + RepeatedActivity* Every(TimeDelta interval, std::function function); + + // Runs the provided function after given duration has passed in a session. + void At(TimeDelta offset, std::function function); + + // Sends a packet over the nodes and runs |action| when it has been delivered. + void NetworkDelayedAction(std::vector over_nodes, + size_t packet_size, + std::function action); + + // Runs the scenario for the given time or until the exit function returns + // true. + void RunFor(TimeDelta duration); + void RunUntil(TimeDelta max_duration, + TimeDelta probe_interval, + std::function exit_function); + + // Triggers sending of dummy packets over the given nodes. + void TriggerPacketBurst(std::vector over_nodes, + size_t num_packets, + size_t packet_size); + + ColumnPrinter TimePrinter(); + StatesPrinter* CreatePrinter(std::string name, + TimeDelta interval, + std::vector printers); + + // Returns the current time. + Timestamp Now(); + // Return the duration of the current session so far. + TimeDelta Duration(); + + std::string GetFullPathOrEmpty(std::string name) const { + if (base_filename_.empty() || name.empty()) + return std::string(); + return base_filename_ + "." + name; + } + + private: + NullReceiver null_receiver_; + std::string base_filename_; + const bool real_time_mode_; + SimulatedClock sim_clock_; + Clock* clock_; + // Event logs use a global clock instance, this is used to override that + // instance when not running in real time. + rtc::FakeClock event_log_fake_clock_; + + std::vector> clients_; + std::vector> network_nodes_; + std::vector> cross_traffic_sources_; + std::vector> video_streams_; + std::vector> audio_streams_; + + std::vector> repeated_activities_; + std::vector> action_receivers_; + std::vector> pending_activities_; + std::vector> printers_; + + int64_t next_receiver_id_ = 40000; + rtc::scoped_refptr audio_decoder_factory_; + rtc::scoped_refptr audio_encoder_factory_; + + Timestamp start_time_ = Timestamp::PlusInfinity(); +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_SCENARIO_H_ diff --git a/test/scenario/scenario_config.cc b/test/scenario/scenario_config.cc new file mode 100644 index 0000000000..1caac9f6cb --- /dev/null +++ b/test/scenario/scenario_config.cc @@ -0,0 +1,56 @@ +/* + * Copyright 2018 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/scenario/scenario_config.h" + +namespace webrtc { +namespace test { + +TransportControllerConfig::Rates::Rates() = default; +TransportControllerConfig::Rates::Rates( + const TransportControllerConfig::Rates&) = default; +TransportControllerConfig::Rates::~Rates() = default; + +VideoStreamConfig::Encoder::Encoder() = default; +VideoStreamConfig::Encoder::Encoder(const VideoStreamConfig::Encoder&) = + default; +VideoStreamConfig::Encoder::~Encoder() = default; + +VideoStreamConfig::Stream::Stream() = default; +VideoStreamConfig::Stream::Stream(const VideoStreamConfig::Stream&) = default; +VideoStreamConfig::Stream::~Stream() = default; + +AudioStreamConfig::AudioStreamConfig() = default; +AudioStreamConfig::AudioStreamConfig(const AudioStreamConfig&) = default; +AudioStreamConfig::~AudioStreamConfig() = default; + +AudioStreamConfig::Encoder::Encoder() = default; +AudioStreamConfig::Encoder::Encoder(const AudioStreamConfig::Encoder&) = + default; +AudioStreamConfig::Encoder::~Encoder() = default; + +AudioStreamConfig::Stream::Stream() = default; +AudioStreamConfig::Stream::Stream(const AudioStreamConfig::Stream&) = default; +AudioStreamConfig::Stream::~Stream() = default; + +NetworkNodeConfig::NetworkNodeConfig() = default; +NetworkNodeConfig::NetworkNodeConfig(const NetworkNodeConfig&) = default; +NetworkNodeConfig::~NetworkNodeConfig() = default; + +NetworkNodeConfig::Simulation::Simulation() = default; +NetworkNodeConfig::Simulation::Simulation( + const NetworkNodeConfig::Simulation&) = default; +NetworkNodeConfig::Simulation::~Simulation() = default; + +CrossTrafficConfig::CrossTrafficConfig() = default; +CrossTrafficConfig::CrossTrafficConfig(const CrossTrafficConfig&) = default; +CrossTrafficConfig::~CrossTrafficConfig() = default; + +} // namespace test +} // namespace webrtc diff --git a/test/scenario/scenario_config.h b/test/scenario/scenario_config.h new file mode 100644 index 0000000000..c4e8cc0152 --- /dev/null +++ b/test/scenario/scenario_config.h @@ -0,0 +1,182 @@ +/* + * Copyright 2018 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_SCENARIO_SCENARIO_CONFIG_H_ +#define TEST_SCENARIO_SCENARIO_CONFIG_H_ + +#include +#include +#include + +#include "absl/types/optional.h" +#include "api/rtpparameters.h" +#include "api/units/data_rate.h" +#include "api/units/time_delta.h" +#include "api/video_codecs/video_codec.h" +#include "test/frame_generator.h" + +namespace webrtc { +namespace test { +struct PacketOverhead { + static constexpr size_t kIpv4 = 20; + static constexpr size_t kIpv6 = 40; + static constexpr size_t kUdp = 8; + static constexpr size_t kSrtp = 10; + static constexpr size_t kTurn = 4; + static constexpr size_t kDefault = kIpv4 + kUdp + kSrtp; +}; +struct TransportControllerConfig { + struct Rates { + Rates(); + Rates(const Rates&); + ~Rates(); + DataRate min_rate = DataRate::kbps(30); + DataRate max_rate = DataRate::kbps(3000); + DataRate start_rate = DataRate::kbps(300); + } rates; + enum CongestionController { kBbr, kGoogCc, kGoogCcFeedback } cc = kGoogCc; + TimeDelta state_log_interval = TimeDelta::ms(100); +}; + +struct CallClientConfig { + TransportControllerConfig transport; + DataRate priority_target_rate = DataRate::Zero(); +}; + +struct VideoStreamConfig { + bool autostart = true; + struct Source { + enum Capture { + kGenerator, + kVideoFile, + // Support for still images and explicit frame triggers should be added + // here if needed. + } capture = Capture::kGenerator; + struct Generator { + using PixelFormat = FrameGenerator::OutputType; + PixelFormat pixel_format = PixelFormat::I420; + } generator; + struct VideoFile { + std::string name; + } video_file; + int width = 320; + int height = 180; + int framerate = 30; + } source; + struct Encoder { + Encoder(); + Encoder(const Encoder&); + ~Encoder(); + enum Implementation { kFake, kSoftware, kHardware } implementation = kFake; + struct Fake { + DataRate max_rate = DataRate::Infinity(); + } fake; + + using Codec = VideoCodecType; + Codec codec = Codec::kVideoCodecGeneric; + bool denoising = true; + absl::optional key_frame_interval = 3000; + + absl::optional max_data_rate; + size_t num_simulcast_streams = 1; + using DegradationPreference = DegradationPreference; + DegradationPreference degradation_preference = + DegradationPreference::MAINTAIN_FRAMERATE; + } encoder; + struct Stream { + Stream(); + Stream(const Stream&); + ~Stream(); + bool packet_feedback = true; + bool use_rtx = true; + TimeDelta nack_history_time = TimeDelta::ms(1000); + bool use_flexfec = false; + bool use_ulpfec = false; + DataSize packet_overhead = DataSize::bytes(PacketOverhead::kDefault); + } stream; + struct Renderer { + enum Type { kFake } type = kFake; + }; +}; + +struct AudioStreamConfig { + AudioStreamConfig(); + AudioStreamConfig(const AudioStreamConfig&); + ~AudioStreamConfig(); + bool autostart = true; + struct Source { + int channels = 1; + } source; + struct Encoder { + Encoder(); + Encoder(const Encoder&); + ~Encoder(); + bool allocate_bitrate = false; + absl::optional fixed_rate; + absl::optional min_rate; + absl::optional max_rate; + TimeDelta initial_frame_length = TimeDelta::ms(20); + } encoder; + struct Stream { + Stream(); + Stream(const Stream&); + ~Stream(); + bool in_bandwidth_estimation = false; + bool rate_allocation_priority = false; + DataSize packet_overhead = DataSize::bytes(PacketOverhead::kDefault); + } stream; + struct Render { + std::string sync_group; + } render; +}; + +struct NetworkNodeConfig { + NetworkNodeConfig(); + NetworkNodeConfig(const NetworkNodeConfig&); + ~NetworkNodeConfig(); + enum class TrafficMode { + kSimulation, + kCustom + } mode = TrafficMode::kSimulation; + struct Simulation { + Simulation(); + Simulation(const Simulation&); + ~Simulation(); + DataRate bandwidth = DataRate::Infinity(); + TimeDelta delay = TimeDelta::Zero(); + TimeDelta delay_std_dev = TimeDelta::Zero(); + double loss_rate = 0; + } simulation; + DataSize packet_overhead = DataSize::Zero(); + TimeDelta update_frequency = TimeDelta::ms(1); +}; + +struct CrossTrafficConfig { + CrossTrafficConfig(); + CrossTrafficConfig(const CrossTrafficConfig&); + ~CrossTrafficConfig(); + enum Mode { kRandomWalk, kPulsedPeaks } mode = kRandomWalk; + int random_seed = 1; + DataRate peak_rate = DataRate::kbps(100); + DataSize min_packet_size = DataSize::bytes(200); + TimeDelta min_packet_interval = TimeDelta::ms(1); + struct RandomWalk { + TimeDelta update_interval = TimeDelta::ms(200); + double variance = 0.6; + double bias = -0.1; + } random_walk; + struct PulsedPeaks { + TimeDelta send_duration = TimeDelta::ms(100); + TimeDelta hold_duration = TimeDelta::ms(2000); + } pulsed; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_SCENARIO_CONFIG_H_ diff --git a/test/scenario/scenario_tests/BUILD.gn b/test/scenario/scenario_tests/BUILD.gn new file mode 100644 index 0000000000..1810c2358e --- /dev/null +++ b/test/scenario/scenario_tests/BUILD.gn @@ -0,0 +1,33 @@ +# Copyright (c) 2018 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_test("scenario_tests") { + testonly = true + sources = [ + "bbr_performance.cc", + ] + deps = [ + "../:scenario", + "../..:test_main", + "../../:field_trial", + "../../:fileutils", + "../../:test_common", + "../../:test_support", + "../../../rtc_base:rtc_base_approved", + "../../../rtc_base:stringutils", + "../../../rtc_base/experiments:field_trial_parser", + "//testing/gtest", + ] + if (!build_with_chromium && is_clang) { + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + } +} diff --git a/test/scenario/scenario_tests/bbr_performance.cc b/test/scenario/scenario_tests/bbr_performance.cc new file mode 100644 index 0000000000..e87cc68dd3 --- /dev/null +++ b/test/scenario/scenario_tests/bbr_performance.cc @@ -0,0 +1,258 @@ +/* + * Copyright 2018 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 "rtc_base/random.h" + +#include "rtc_base/experiments/field_trial_parser.h" +#include "rtc_base/experiments/field_trial_units.h" +#include "test/field_trial.h" +#include "test/gtest.h" +#include "test/scenario/scenario.h" + +namespace webrtc { +namespace test { +namespace { +constexpr int64_t kRunTimeMs = 60000; + +using ::testing::Values; +using ::testing::Combine; +using ::testing::tuple; +using ::testing::make_tuple; + +using Codec = VideoStreamConfig::Encoder::Codec; +using CodecImpl = VideoStreamConfig::Encoder::Implementation; + +struct CallTestConfig { + struct Scenario { + FieldTrialParameter random_seed; + FieldTrialFlag return_traffic; + FieldTrialParameter capacity; + FieldTrialParameter propagation_delay; + FieldTrialParameter cross_traffic; + FieldTrialParameter delay_noise; + FieldTrialParameter loss_rate; + Scenario() + : random_seed("rs", 1), + return_traffic("ret"), + capacity("bw", DataRate::kbps(300)), + propagation_delay("dl", TimeDelta::ms(100)), + cross_traffic("ct", DataRate::Zero()), + delay_noise("dn", TimeDelta::Zero()), + loss_rate("pl", 0) {} + void Parse(std::string config_str) { + ParseFieldTrial( + {&random_seed, &return_traffic, &capacity, &propagation_delay, + &cross_traffic, &delay_noise, &loss_rate}, + config_str); + } + } scenario; + struct Tuning { + FieldTrialFlag use_bbr; + FieldTrialFlag bbr_no_target_rate; + FieldTrialOptional bbr_initial_window; + FieldTrialParameter bbr_encoder_gain; + Tuning() + : use_bbr("bbr"), + bbr_no_target_rate("notr"), + bbr_initial_window("iw", DataSize::bytes(8000)), + bbr_encoder_gain("eg", 0.8) {} + void Parse(std::string config_str) { + ParseFieldTrial( + { + &use_bbr, &bbr_no_target_rate, &bbr_initial_window, + &bbr_encoder_gain, + }, + config_str); + } + } tuning; + + void Parse(std::string scenario_string, std::string tuning_string) { + scenario.Parse(scenario_string); + tuning.Parse(tuning_string); + scenario_str = scenario_string; + tuning_str = tuning_string; + } + std::string scenario_str; + std::string tuning_str; + + std::string BbrTrial() const { + char trial_buf[1024]; + rtc::SimpleStringBuilder trial(trial_buf); + trial << "WebRTC-BweBbrConfig/"; + trial << "encoder_rate_gain_in_probe_rtt:0.5"; + trial.AppendFormat(",encoder_rate_gain:%.1lf", + tuning.bbr_encoder_gain.Get()); + if (tuning.bbr_no_target_rate) + trial << ",pacing_rate_as_target:1"; + if (tuning.bbr_initial_window) + trial << ",initial_cwin:" << tuning.bbr_initial_window->bytes(); + trial << "/"; + return trial.str(); + } + std::string FieldTrials() const { + std::string trials = "WebRTC-TaskQueueCongestionControl/Enabled/"; + if (tuning.use_bbr) { + trials += + "WebRTC-BweCongestionController/Enabled,BBR/" + "WebRTC-PacerPushbackExperiment/Enabled/" + "WebRTC-Pacer-DrainQueue/Disabled/" + "WebRTC-Pacer-PadInSilence/Enabled/" + "WebRTC-Pacer-BlockAudio/Disabled/" + "WebRTC-Audio-SendSideBwe/Enabled/" + "WebRTC-SendSideBwe-WithOverhead/Enabled/"; + trials += BbrTrial(); + } + return trials; + } + + std::string Name() const { + char raw_name[1024]; + rtc::SimpleStringBuilder name(raw_name); + for (char c : scenario_str + "__tun__" + tuning_str) { + if (c == ':') { + continue; + } else if (c == ',') { + name << "_"; + } else if (c == '%') { + name << "p"; + } else { + name << c; + } + } + return name.str(); + } +}; +} // namespace +class BbrScenarioTest + : public ::testing::Test, + public testing::WithParamInterface> { + public: + BbrScenarioTest() { + conf_.Parse(::testing::get<0>(GetParam()), ::testing::get<1>(GetParam())); + field_trial_.reset(new test::ScopedFieldTrials(conf_.FieldTrials())); + } + CallTestConfig conf_; + + private: + std::unique_ptr field_trial_; +}; + +TEST_P(BbrScenarioTest, ReceivesVideo) { + Scenario s("bbr_test_gen/bbr__" + conf_.Name()); + + CallClient* alice = s.CreateClient("send", [&](CallClientConfig* c) { + if (conf_.tuning.use_bbr) + c->transport.cc = TransportControllerConfig::CongestionController::kBbr; + c->transport.state_log_interval = TimeDelta::ms(100); + c->transport.rates.min_rate = DataRate::kbps(30); + c->transport.rates.max_rate = DataRate::kbps(1800); + }); + CallClient* bob = s.CreateClient("return", [&](CallClientConfig* c) { + if (conf_.tuning.use_bbr && conf_.scenario.return_traffic) + c->transport.cc = TransportControllerConfig::CongestionController::kBbr; + c->transport.state_log_interval = TimeDelta::ms(100); + c->transport.rates.min_rate = DataRate::kbps(30); + c->transport.rates.max_rate = DataRate::kbps(1800); + }); + NetworkNodeConfig net_conf; + net_conf.simulation.bandwidth = conf_.scenario.capacity; + net_conf.simulation.delay = conf_.scenario.propagation_delay; + net_conf.simulation.loss_rate = conf_.scenario.loss_rate; + net_conf.simulation.delay_std_dev = conf_.scenario.delay_noise; + SimulationNode* send_net = s.CreateSimulationNode(net_conf); + SimulationNode* ret_net = s.CreateSimulationNode(net_conf); + VideoStreamPair* alice_video = s.CreateVideoStream( + alice, {send_net}, bob, {ret_net}, [&](VideoStreamConfig* c) { + c->encoder.fake.max_rate = DataRate::kbps(1800); + }); + s.CreateAudioStream(alice, {send_net}, bob, {ret_net}, + [&](AudioStreamConfig* c) { + if (conf_.tuning.use_bbr) { + c->stream.in_bandwidth_estimation = true; + c->encoder.fixed_rate = DataRate::kbps(31); + } + }); + + VideoStreamPair* bob_video = nullptr; + if (conf_.scenario.return_traffic) { + bob_video = s.CreateVideoStream( + bob, {ret_net}, alice, {send_net}, [&](VideoStreamConfig* c) { + c->encoder.fake.max_rate = DataRate::kbps(1800); + }); + s.CreateAudioStream(bob, {ret_net}, alice, {send_net}, + [&](AudioStreamConfig* c) { + if (conf_.tuning.use_bbr) { + c->stream.in_bandwidth_estimation = true; + c->encoder.fixed_rate = DataRate::kbps(31); + } + }); + } + CrossTrafficConfig cross_config; + cross_config.peak_rate = conf_.scenario.cross_traffic; + cross_config.random_seed = conf_.scenario.random_seed; + CrossTrafficSource* cross_traffic = + s.CreateCrossTraffic({send_net}, cross_config); + + s.CreatePrinter("send.stats.txt", TimeDelta::ms(100), + {alice->StatsPrinter(), alice_video->send()->StatsPrinter(), + cross_traffic->StatsPrinter(), send_net->ConfigPrinter()}); + + std::vector return_printers{ + bob->StatsPrinter(), ColumnPrinter::Fixed("cross_traffic_rate", "0"), + ret_net->ConfigPrinter()}; + if (bob_video) + return_printers.push_back(bob_video->send()->StatsPrinter()); + s.CreatePrinter("return.stats.txt", TimeDelta::ms(100), return_printers); + + s.RunFor(TimeDelta::ms(kRunTimeMs)); +} + +INSTANTIATE_TEST_CASE_P(Selected, + BbrScenarioTest, + Values(make_tuple("rs:1,bw:150,dl:100,ct:100", "bbr"))); + +INSTANTIATE_TEST_CASE_P( + OneWayTuning, + BbrScenarioTest, + Values(make_tuple("bw:150,dl:100", "bbr,iw:,eg:100%,notr"), + make_tuple("bw:150,dl:100", "bbr,iw:8000,eg:100%,notr"), + make_tuple("bw:150,dl:100", "bbr,iw:8000,eg:100%"), + make_tuple("bw:150,dl:100", "bbr,iw:8000,eg:80%"))); + +INSTANTIATE_TEST_CASE_P(OneWayTuned, + BbrScenarioTest, + Values(make_tuple("bw:150,dl:100", "bbr"), + make_tuple("bw:150,dl:100", ""), + make_tuple("bw:800,dl:100", "bbr"), + make_tuple("bw:800,dl:100", ""))); + +INSTANTIATE_TEST_CASE_P(OneWayDegraded, + BbrScenarioTest, + Values(make_tuple("bw:150,dl:100,dn:30,pl:5%", "bbr"), + make_tuple("bw:150,dl:100,dn:30,pl:5%", ""), + + make_tuple("bw:150,ct:100,dl:100", "bbr"), + make_tuple("bw:150,ct:100,dl:100", ""), + + make_tuple("bw:800,dl:100,dn:30,pl:5%", "bbr"), + make_tuple("bw:800,dl:100,dn:30,pl:5%", ""), + + make_tuple("bw:800,ct:600,dl:100", "bbr"), + make_tuple("bw:800,ct:600,dl:100", ""))); + +INSTANTIATE_TEST_CASE_P(TwoWay, + BbrScenarioTest, + Values(make_tuple("ret,bw:150,dl:100", "bbr"), + make_tuple("ret,bw:150,dl:100", ""), + make_tuple("ret,bw:800,dl:100", "bbr"), + make_tuple("ret,bw:800,dl:100", ""), + make_tuple("ret,bw:150,dl:50", "bbr"), + make_tuple("ret,bw:150,dl:50", ""))); +} // namespace test +} // namespace webrtc diff --git a/test/scenario/scenario_unittest.cc b/test/scenario/scenario_unittest.cc new file mode 100644 index 0000000000..305f8660cc --- /dev/null +++ b/test/scenario/scenario_unittest.cc @@ -0,0 +1,53 @@ +/* + * Copyright 2018 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/scenario/scenario.h" +#include "test/gtest.h" +namespace webrtc { +namespace test { +TEST(ScenarioTest, StartsAndStopsWithoutErrors) { + Scenario s; + CallClientConfig call_client_config; + call_client_config.transport.rates.start_rate = DataRate::kbps(300); + auto* alice = s.CreateClient("alice", call_client_config); + auto* bob = s.CreateClient("bob", call_client_config); + NetworkNodeConfig network_config; + auto alice_net = s.CreateSimulationNode(network_config); + auto bob_net = s.CreateSimulationNode(network_config); + + VideoStreamConfig video_stream_config; + s.CreateVideoStream(alice, {alice_net}, bob, {bob_net}, video_stream_config); + s.CreateVideoStream(bob, {bob_net}, alice, {alice_net}, video_stream_config); + + AudioStreamConfig audio_stream_config; + s.CreateAudioStream(alice, {alice_net}, bob, {bob_net}, audio_stream_config); + s.CreateAudioStream(bob, {bob_net}, alice, {alice_net}, audio_stream_config); + + CrossTrafficConfig cross_traffic_config; + s.CreateCrossTraffic({alice_net}, cross_traffic_config); + + bool packet_received = false; + s.NetworkDelayedAction({alice_net, bob_net}, 100, + [&packet_received] { packet_received = true; }); + bool bitrate_changed = false; + s.Every(TimeDelta::ms(10), [alice, bob, &bitrate_changed] { + if (alice->GetStats().send_bandwidth_bps != 300000 && + bob->GetStats().send_bandwidth_bps != 300000) + bitrate_changed = true; + }); + s.RunUntil(TimeDelta::seconds(2), TimeDelta::ms(5), + [&bitrate_changed, &packet_received] { + return packet_received && bitrate_changed; + }); + EXPECT_TRUE(packet_received); + EXPECT_TRUE(bitrate_changed); +} +} // namespace test +} // namespace webrtc diff --git a/test/scenario/video_stream.cc b/test/scenario/video_stream.cc new file mode 100644 index 0000000000..fe87a6deee --- /dev/null +++ b/test/scenario/video_stream.cc @@ -0,0 +1,383 @@ +/* + * Copyright 2018 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/scenario/video_stream.h" + +#include +#include + +#include "media/base/mediaconstants.h" +#include "media/engine/internaldecoderfactory.h" +#include "media/engine/internalencoderfactory.h" +#include "media/engine/webrtcvideoengine.h" +#include "test/call_test.h" +#include "test/fake_encoder.h" +#include "test/function_video_encoder_factory.h" + +#include "test/scenario/hardware_codecs.h" +#include "test/testsupport/fileutils.h" + +namespace webrtc { +namespace test { +namespace { +constexpr int kDefaultMaxQp = cricket::WebRtcVideoChannel::kDefaultQpMax; +const int kVideoRotationRtpExtensionId = 4; +uint8_t CodecTypeToPayloadType(VideoCodecType codec_type) { + switch (codec_type) { + case VideoCodecType::kVideoCodecGeneric: + return CallTest::kFakeVideoSendPayloadType; + case VideoCodecType::kVideoCodecVP8: + return CallTest::kPayloadTypeVP8; + case VideoCodecType::kVideoCodecVP9: + return CallTest::kPayloadTypeVP9; + case VideoCodecType::kVideoCodecH264: + return CallTest::kPayloadTypeH264; + default: + RTC_NOTREACHED(); + } + return {}; +} +std::string CodecTypeToCodecName(VideoCodecType codec_type) { + switch (codec_type) { + case VideoCodecType::kVideoCodecGeneric: + return ""; + case VideoCodecType::kVideoCodecVP8: + return cricket::kVp8CodecName; + case VideoCodecType::kVideoCodecVP9: + return cricket::kVp9CodecName; + case VideoCodecType::kVideoCodecH264: + return cricket::kH264CodecName; + default: + RTC_NOTREACHED(); + } + return {}; +} +std::vector GetVideoRtpExtensions( + const VideoStreamConfig config) { + return {RtpExtension(RtpExtension::kTransportSequenceNumberUri, + kTransportSequenceNumberExtensionId), + RtpExtension(RtpExtension::kVideoContentTypeUri, + kVideoContentTypeExtensionId), + RtpExtension(RtpExtension::kVideoRotationUri, + kVideoRotationRtpExtensionId)}; +} + +VideoSendStream::Config CreateVideoSendStreamConfig(VideoStreamConfig config, + std::vector ssrcs, + Transport* send_transport) { + VideoSendStream::Config send_config(send_transport); + send_config.rtp.payload_name = CodecTypeToPayloadString(config.encoder.codec); + send_config.rtp.payload_type = CodecTypeToPayloadType(config.encoder.codec); + + send_config.rtp.ssrcs = ssrcs; + send_config.rtp.extensions = GetVideoRtpExtensions(config); + + if (config.stream.use_flexfec) { + send_config.rtp.flexfec.payload_type = CallTest::kFlexfecPayloadType; + send_config.rtp.flexfec.ssrc = CallTest::kFlexfecSendSsrc; + send_config.rtp.flexfec.protected_media_ssrcs = ssrcs; + } + if (config.stream.use_ulpfec) { + send_config.rtp.ulpfec.red_payload_type = CallTest::kRedPayloadType; + send_config.rtp.ulpfec.ulpfec_payload_type = CallTest::kUlpfecPayloadType; + send_config.rtp.ulpfec.red_rtx_payload_type = CallTest::kRtxRedPayloadType; + } + return send_config; +} +rtc::scoped_refptr +CreateEncoderSpecificSettings(VideoStreamConfig config) { + using Codec = VideoStreamConfig::Encoder::Codec; + switch (config.encoder.codec) { + case Codec::kVideoCodecH264: { + VideoCodecH264 h264_settings = VideoEncoder::GetDefaultH264Settings(); + h264_settings.frameDroppingOn = true; + h264_settings.keyFrameInterval = + config.encoder.key_frame_interval.value_or(0); + return new rtc::RefCountedObject< + VideoEncoderConfig::H264EncoderSpecificSettings>(h264_settings); + } + case Codec::kVideoCodecVP8: { + VideoCodecVP8 vp8_settings = VideoEncoder::GetDefaultVp8Settings(); + vp8_settings.frameDroppingOn = true; + vp8_settings.keyFrameInterval = + config.encoder.key_frame_interval.value_or(0); + vp8_settings.automaticResizeOn = true; + vp8_settings.denoisingOn = config.encoder.denoising; + return new rtc::RefCountedObject< + VideoEncoderConfig::Vp8EncoderSpecificSettings>(vp8_settings); + } + case Codec::kVideoCodecVP9: { + VideoCodecVP9 vp9_settings = VideoEncoder::GetDefaultVp9Settings(); + vp9_settings.frameDroppingOn = true; + vp9_settings.keyFrameInterval = + config.encoder.key_frame_interval.value_or(0); + vp9_settings.automaticResizeOn = true; + vp9_settings.denoisingOn = config.encoder.denoising; + vp9_settings.interLayerPred = InterLayerPredMode::kOnKeyPic; + return new rtc::RefCountedObject< + VideoEncoderConfig::Vp9EncoderSpecificSettings>(vp9_settings); + } + default: + return nullptr; + } +} + +VideoEncoderConfig CreateVideoEncoderConfig(VideoStreamConfig config) { + size_t num_streams = config.encoder.num_simulcast_streams; + VideoEncoderConfig encoder_config; + encoder_config.codec_type = config.encoder.codec; + encoder_config.content_type = VideoEncoderConfig::ContentType::kRealtimeVideo; + encoder_config.video_format = + SdpVideoFormat(CodecTypeToPayloadString(config.encoder.codec), {}); + encoder_config.number_of_streams = num_streams; + encoder_config.simulcast_layers = std::vector(num_streams); + + std::string cricket_codec = CodecTypeToCodecName(config.encoder.codec); + if (!cricket_codec.empty()) { + encoder_config.video_stream_factory = + new rtc::RefCountedObject( + cricket_codec, kDefaultMaxQp, false, false); + } else { + encoder_config.video_stream_factory = + new rtc::RefCountedObject(); + } + if (config.encoder.max_data_rate) { + encoder_config.max_bitrate_bps = config.encoder.max_data_rate->bps(); + } else { + encoder_config.max_bitrate_bps = 10000000; // 10 mbit + } + encoder_config.encoder_specific_settings = + CreateEncoderSpecificSettings(config); + return encoder_config; +} +} // namespace + +SendVideoStream::SendVideoStream(CallClient* sender, + VideoStreamConfig config, + Transport* send_transport) + : sender_(sender), config_(config) { + for (size_t i = 0; i < config.encoder.num_simulcast_streams; ++i) { + ssrcs_.push_back(sender->GetNextVideoSsrc()); + rtx_ssrcs_.push_back(sender->GetNextRtxSsrc()); + } + + using Capture = VideoStreamConfig::Source::Capture; + switch (config.source.capture) { + case Capture::kGenerator: + frame_generator_ = test::FrameGeneratorCapturer::Create( + config.source.width, config.source.height, + config.source.generator.pixel_format, absl::nullopt, + config.source.framerate, sender_->clock_); + video_capturer_.reset(frame_generator_); + break; + case Capture::kVideoFile: + frame_generator_ = test::FrameGeneratorCapturer::CreateFromYuvFile( + test::ResourcePath(config.source.video_file.name, "yuv"), + config.source.width, config.source.height, config.source.framerate, + sender_->clock_); + RTC_CHECK(frame_generator_) + << "Could not create capturer for " << config.source.video_file.name + << ".yuv. Is this resource file present?"; + video_capturer_.reset(frame_generator_); + break; + } + + using Encoder = VideoStreamConfig::Encoder; + using Codec = VideoStreamConfig::Encoder::Codec; + switch (config.encoder.implementation) { + case Encoder::Implementation::kFake: + if (config.encoder.codec == Codec::kVideoCodecGeneric) { + encoder_factory_ = + absl::make_unique([this, config]() { + auto encoder = + absl::make_unique(sender_->clock_); + if (config.encoder.fake.max_rate.IsFinite()) + encoder->SetMaxBitrate(config.encoder.fake.max_rate.kbps()); + return encoder; + }); + } else { + RTC_NOTREACHED(); + } + break; + case VideoStreamConfig::Encoder::Implementation::kSoftware: + encoder_factory_.reset(new InternalEncoderFactory()); + break; + case VideoStreamConfig::Encoder::Implementation::kHardware: + encoder_factory_ = CreateHardwareEncoderFactory(); + break; + } + RTC_CHECK(encoder_factory_); + + VideoSendStream::Config send_config = + CreateVideoSendStreamConfig(config, ssrcs_, send_transport); + send_config.encoder_settings.encoder_factory = encoder_factory_.get(); + VideoEncoderConfig encoder_config = CreateVideoEncoderConfig(config); + + send_stream_ = sender_->call_->CreateVideoSendStream( + std::move(send_config), std::move(encoder_config)); + + send_stream_->SetSource(video_capturer_.get(), + config.encoder.degradation_preference); +} + +SendVideoStream::~SendVideoStream() { + sender_->call_->DestroyVideoSendStream(send_stream_); +} + +void SendVideoStream::Start() { + send_stream_->Start(); + video_capturer_->Start(); +} + +bool SendVideoStream::TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) { + // Removes added overhead before delivering RTCP packet to sender. + RTC_DCHECK_GE(packet.size(), config_.stream.packet_overhead.bytes()); + packet.SetSize(packet.size() - config_.stream.packet_overhead.bytes()); + sender_->DeliverPacket(MediaType::VIDEO, packet, at_time); + return true; +} + +void SendVideoStream::SetCaptureFramerate(int framerate) { + RTC_CHECK(frame_generator_) + << "Framerate change only implemented for generators"; + frame_generator_->ChangeFramerate(framerate); +} + +void SendVideoStream::SetMaxFramerate(absl::optional max_framerate) { + VideoEncoderConfig encoder_config = CreateVideoEncoderConfig(config_); + RTC_DCHECK_EQ(encoder_config.simulcast_layers.size(), 1); + encoder_config.simulcast_layers[0].max_framerate = max_framerate.value_or(-1); + send_stream_->ReconfigureVideoEncoder(std::move(encoder_config)); +} + +VideoSendStream::Stats SendVideoStream::GetStats() const { + return send_stream_->GetStats(); +} + +ColumnPrinter SendVideoStream::StatsPrinter() { + return ColumnPrinter::Lambda( + "video_target_rate video_sent_rate width height", + [this](rtc::SimpleStringBuilder& sb) { + VideoSendStream::Stats video_stats = send_stream_->GetStats(); + int width = 0; + int height = 0; + for (auto stream_stat : video_stats.substreams) { + width = std::max(width, stream_stat.second.width); + height = std::max(height, stream_stat.second.height); + } + sb.AppendFormat("%.0lf %.0lf %i %i", + video_stats.target_media_bitrate_bps / 8.0, + video_stats.media_bitrate_bps / 8.0, width, height); + }, + 64); +} + +ReceiveVideoStream::ReceiveVideoStream(CallClient* receiver, + VideoStreamConfig config, + SendVideoStream* send_stream, + size_t chosen_stream, + Transport* feedback_transport) + : receiver_(receiver), + config_(config), + decoder_factory_(absl::make_unique()) { + renderer_ = absl::make_unique(); + VideoReceiveStream::Config recv_config(feedback_transport); + recv_config.rtp.remb = !config.stream.packet_feedback; + recv_config.rtp.transport_cc = config.stream.packet_feedback; + recv_config.rtp.local_ssrc = CallTest::kReceiverLocalVideoSsrc; + recv_config.rtp.extensions = GetVideoRtpExtensions(config); + RTC_DCHECK(!config.stream.use_rtx || + config.stream.nack_history_time > TimeDelta::Zero()); + recv_config.rtp.nack.rtp_history_ms = config.stream.nack_history_time.ms(); + recv_config.rtp.protected_by_flexfec = config.stream.use_flexfec; + recv_config.renderer = renderer_.get(); + if (config.stream.use_rtx) { + recv_config.rtp.rtx_ssrc = send_stream->rtx_ssrcs_[chosen_stream]; + recv_config.rtp + .rtx_associated_payload_types[CallTest::kSendRtxPayloadType] = + CodecTypeToPayloadType(config.encoder.codec); + } + recv_config.rtp.remote_ssrc = send_stream->ssrcs_[chosen_stream]; + VideoReceiveStream::Decoder decoder = + CreateMatchingDecoder(CodecTypeToPayloadType(config.encoder.codec), + CodecTypeToPayloadString(config.encoder.codec)); + decoder.decoder_factory = decoder_factory_.get(); + recv_config.decoders.push_back(decoder); + + if (config.stream.use_flexfec) { + RTC_CHECK_EQ(config.encoder.num_simulcast_streams, 1); + FlexfecReceiveStream::Config flexfec_config(feedback_transport); + flexfec_config.payload_type = CallTest::kFlexfecPayloadType; + flexfec_config.remote_ssrc = CallTest::kFlexfecSendSsrc; + flexfec_config.protected_media_ssrcs = send_stream->rtx_ssrcs_; + flexfec_config.local_ssrc = recv_config.rtp.local_ssrc; + flecfec_stream_ = + receiver_->call_->CreateFlexfecReceiveStream(flexfec_config); + } + if (config.stream.use_ulpfec) { + recv_config.rtp.red_payload_type = CallTest::kRedPayloadType; + recv_config.rtp.ulpfec_payload_type = CallTest::kUlpfecPayloadType; + recv_config.rtp.rtx_associated_payload_types[CallTest::kRtxRedPayloadType] = + CallTest::kRedPayloadType; + } + receive_stream_ = + receiver_->call_->CreateVideoReceiveStream(std::move(recv_config)); +} + +ReceiveVideoStream::~ReceiveVideoStream() { + receiver_->call_->DestroyVideoReceiveStream(receive_stream_); + if (flecfec_stream_) + receiver_->call_->DestroyFlexfecReceiveStream(flecfec_stream_); +} + +bool ReceiveVideoStream::TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) { + RTC_DCHECK_GE(packet.size(), config_.stream.packet_overhead.bytes()); + packet.SetSize(packet.size() - config_.stream.packet_overhead.bytes()); + receiver_->DeliverPacket(MediaType::VIDEO, packet, at_time); + return true; +} + +VideoStreamPair::~VideoStreamPair() = default; + +VideoStreamPair::VideoStreamPair(CallClient* sender, + std::vector send_link, + uint64_t send_receiver_id, + CallClient* receiver, + std::vector return_link, + uint64_t return_receiver_id, + VideoStreamConfig config) + : config_(config), + send_link_(send_link), + return_link_(return_link), + send_transport_(sender, + send_link.front(), + send_receiver_id, + config.stream.packet_overhead), + return_transport_(receiver, + return_link.front(), + return_receiver_id, + config.stream.packet_overhead), + send_stream_(sender, config, &send_transport_), + receive_stream_(receiver, + config, + &send_stream_, + /*chosen_stream=*/0, + &return_transport_) { + NetworkNode::Route(send_transport_.ReceiverId(), send_link_, + &receive_stream_); + NetworkNode::Route(return_transport_.ReceiverId(), return_link_, + &send_stream_); +} + +} // namespace test +} // namespace webrtc diff --git a/test/scenario/video_stream.h b/test/scenario/video_stream.h new file mode 100644 index 0000000000..e6696183a7 --- /dev/null +++ b/test/scenario/video_stream.h @@ -0,0 +1,117 @@ +/* + * Copyright 2018 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_SCENARIO_VIDEO_STREAM_H_ +#define TEST_SCENARIO_VIDEO_STREAM_H_ +#include +#include +#include + +#include "rtc_base/constructormagic.h" +#include "test/frame_generator_capturer.h" +#include "test/scenario/call_client.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/network_node.h" +#include "test/scenario/scenario_config.h" +#include "test/test_video_capturer.h" + +namespace webrtc { +namespace test { +// SendVideoStream provides an interface for changing parameters and retrieving +// states at run time. +class SendVideoStream : public NetworkReceiverInterface { + public: + RTC_DISALLOW_COPY_AND_ASSIGN(SendVideoStream); + ~SendVideoStream(); + void SetCaptureFramerate(int framerate); + void SetMaxFramerate(absl::optional max_framerate); + VideoSendStream::Stats GetStats() const; + ColumnPrinter StatsPrinter(); + void Start(); + + private: + friend class Scenario; + friend class VideoStreamPair; + friend class ReceiveVideoStream; + // Handles RTCP feedback for this stream. + SendVideoStream(CallClient* sender, + VideoStreamConfig config, + Transport* send_transport); + bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) override; + + std::vector ssrcs_; + std::vector rtx_ssrcs_; + VideoSendStream* send_stream_ = nullptr; + CallClient* const sender_; + const VideoStreamConfig config_; + std::unique_ptr encoder_factory_; + std::unique_ptr video_capturer_; + FrameGeneratorCapturer* frame_generator_ = nullptr; +}; + +// ReceiveVideoStream represents a video receiver. It can't be used directly. +class ReceiveVideoStream : public NetworkReceiverInterface { + public: + RTC_DISALLOW_COPY_AND_ASSIGN(ReceiveVideoStream); + ~ReceiveVideoStream(); + + private: + friend class Scenario; + friend class VideoStreamPair; + ReceiveVideoStream(CallClient* receiver, + VideoStreamConfig config, + SendVideoStream* send_stream, + size_t chosen_stream, + Transport* feedback_transport); + bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) override; + VideoReceiveStream* receive_stream_ = nullptr; + FlexfecReceiveStream* flecfec_stream_ = nullptr; + std::unique_ptr> renderer_; + CallClient* const receiver_; + const VideoStreamConfig config_; + std::unique_ptr decoder_factory_; +}; + +// VideoStreamPair represents a video streaming session. It can be used to +// access underlying send and receive classes. It can also be used in calls to +// the Scenario class. +class VideoStreamPair { + public: + RTC_DISALLOW_COPY_AND_ASSIGN(VideoStreamPair); + ~VideoStreamPair(); + SendVideoStream* send() { return &send_stream_; } + ReceiveVideoStream* receive() { return &receive_stream_; } + + private: + friend class Scenario; + VideoStreamPair(CallClient* sender, + std::vector send_link, + uint64_t send_receiver_id, + CallClient* receiver, + std::vector return_link, + uint64_t return_receiver_id, + VideoStreamConfig config); + + const VideoStreamConfig config_; + std::vector send_link_; + std::vector return_link_; + NetworkNodeTransport send_transport_; + NetworkNodeTransport return_transport_; + + SendVideoStream send_stream_; + ReceiveVideoStream receive_stream_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_VIDEO_STREAM_H_