Adds scenario test framework.
Bug: webrtc:9510
Change-Id: I387aab4211f520a1c54832f82032ee724479e89e
Reviewed-on: https://webrtc-review.googlesource.com/89342
Commit-Queue: Sebastian Jansson <srte@webrtc.org>
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#24864}
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 7eafece..3264b37 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 @@
return controller;
}
+GoogCcFeedbackDebugFactory::GoogCcFeedbackDebugFactory(
+ RtcEventLog* event_log,
+ GoogCcStatePrinter* printer)
+ : GoogCcFeedbackNetworkControllerFactory(event_log), printer_(printer) {}
+
+std::unique_ptr<NetworkControllerInterface> GoogCcFeedbackDebugFactory::Create(
+ NetworkControllerConfig config) {
+ RTC_CHECK(controller_ == nullptr);
+ auto controller = GoogCcFeedbackNetworkControllerFactory::Create(config);
+ controller_ = static_cast<GoogCcNetworkController*>(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 e5bd891..4dfc059 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 @@
GoogCcStatePrinter* printer_;
GoogCcNetworkController* controller_ = nullptr;
};
+
+class GoogCcFeedbackDebugFactory
+ : public GoogCcFeedbackNetworkControllerFactory {
+ public:
+ GoogCcFeedbackDebugFactory(RtcEventLog* event_log,
+ GoogCcStatePrinter* printer);
+ std::unique_ptr<NetworkControllerInterface> 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 3fa25cf..ec9e880 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -28,6 +28,7 @@
deps += [
":test_main",
":test_support_unittests",
+ "scenario/scenario_tests",
]
}
}
@@ -316,6 +317,7 @@
"../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 80f47b2..1106230 100644
--- a/test/DEPS
+++ b/test/DEPS
@@ -8,6 +8,7 @@
"+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 0000000..16f3a78
--- /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 0000000..53e076b
--- /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 0000000..e1b645e
--- /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<AudioEncoderFactory> 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<AudioDecoderFactory> 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<NetworkNode*> send_link,
+ uint64_t send_receiver_id,
+ rtc::scoped_refptr<AudioEncoderFactory> encoder_factory,
+ CallClient* receiver,
+ std::vector<NetworkNode*> return_link,
+ uint64_t return_receiver_id,
+ rtc::scoped_refptr<AudioDecoderFactory> 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 0000000..17b8bc3
--- /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 <memory>
+#include <string>
+#include <vector>
+
+#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<AudioEncoderFactory> 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<AudioDecoderFactory> 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<NetworkNode*> send_link,
+ uint64_t send_receiver_id,
+ rtc::scoped_refptr<AudioEncoderFactory> encoder_factory,
+
+ CallClient* receiver,
+ std::vector<NetworkNode*> return_link,
+ uint64_t return_receiver_id,
+ rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
+ AudioStreamConfig config);
+
+ private:
+ const AudioStreamConfig config_;
+ std::vector<NetworkNode*> send_link_;
+ std::vector<NetworkNode*> 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 0000000..47cc3bc
--- /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 <utility>
+
+#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<RtcEventLogOutputFile>(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<BbrStatePrinter>();
+ 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<GoogCcStatePrinter>();
+ 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<GoogCcStatePrinter>();
+ 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<NetworkControllerInterface>
+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<rtc::AudioPriorityBitrateAllocationStrategy>(
+ 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<AudioState> 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 0000000..80e2faf
--- /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 <memory>
+#include <string>
+#include <vector>
+
+#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<NetworkControllerInterface> 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<RtcEventLog> event_log_;
+ std::unique_ptr<NetworkControllerFactoryInterface> cc_factory_;
+ std::unique_ptr<ControlStatePrinter> 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> call_;
+
+ rtc::scoped_refptr<AudioState> InitAudio();
+
+ rtc::scoped_refptr<AudioProcessing> apm_;
+ rtc::scoped_refptr<TestAudioDeviceModule> fake_audio_device_;
+
+ std::unique_ptr<FecControllerFactoryInterface> 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 0000000..234c919
--- /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<void(rtc::SimpleStringBuilder&)> 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<void(rtc::SimpleStringBuilder&)> printer,
+ size_t max_length) {
+ return ColumnPrinter(headers, printer, max_length);
+}
+
+StatesPrinter::StatesPrinter(std::string filename,
+ std::vector<ColumnPrinter> printers)
+ : StatesPrinter(printers) {
+ if (!filename.empty()) {
+ output_file_ = fopen(filename.c_str(), "w");
+ RTC_CHECK(output_file_);
+ output_ = output_file_;
+ }
+}
+
+StatesPrinter::StatesPrinter(std::vector<ColumnPrinter> 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 0000000..b1299a0
--- /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 <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#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<void(rtc::SimpleStringBuilder&)> printer,
+ size_t max_length = 256);
+
+ protected:
+ friend class StatesPrinter;
+ const char* headers_;
+ std::function<void(rtc::SimpleStringBuilder&)> printer_;
+ size_t max_length_;
+
+ private:
+ ColumnPrinter(const char* headers,
+ std::function<void(rtc::SimpleStringBuilder&)> printer,
+ size_t max_length);
+};
+
+class StatesPrinter {
+ public:
+ StatesPrinter(std::string filename, std::vector<ColumnPrinter> printers);
+ explicit StatesPrinter(std::vector<ColumnPrinter> printers);
+ RTC_DISALLOW_COPY_AND_ASSIGN(StatesPrinter);
+ ~StatesPrinter();
+ void PrintHeaders();
+ void PrintRow();
+
+ private:
+ const std::vector<ColumnPrinter> printers_;
+ size_t buffer_size_ = 0;
+ std::vector<char> 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 0000000..16044de
--- /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<VideoEncoderFactory> 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<VideoDecoderFactory> 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 0000000..ae14a27
--- /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 <memory>
+
+#include "api/video_codecs/video_decoder_factory.h"
+#include "api/video_codecs/video_encoder_factory.h"
+
+namespace webrtc {
+namespace test {
+std::unique_ptr<VideoEncoderFactory> CreateHardwareEncoderFactory();
+std::unique_ptr<VideoDecoderFactory> 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 0000000..eb215aa
--- /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 <algorithm>
+#include <vector>
+
+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<void()> 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<NetworkSimulationInterface> 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<PacketDeliveryInfo> delivery_infos;
+ {
+ rtc::CritScope crit(&crit_sect_);
+ absl::optional<int64_t> 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<NetworkNode*> 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<NetworkNode*> nodes) {
+ for (NetworkNode* node : nodes)
+ node->ClearRoute(receiver_id);
+}
+
+std::unique_ptr<SimulationNode> SimulationNode::Create(
+ NetworkNodeConfig config) {
+ RTC_DCHECK(config.mode == NetworkNodeConfig::TrafficMode::kSimulation);
+ SimulatedNetwork::Config sim_config = CreateSimulationConfig(config);
+ auto network = absl::make_unique<SimulatedNetwork>(sim_config);
+ SimulatedNetwork* simulation_ptr = network.get();
+ return std::unique_ptr<SimulationNode>(
+ new SimulationNode(config, std::move(network), simulation_ptr));
+}
+
+void SimulationNode::UpdateConfig(
+ std::function<void(NetworkNodeConfig*)> 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<double>(),
+ config_.simulation.bandwidth.bps() / 8.0,
+ config_.simulation.loss_rate);
+ });
+}
+
+SimulationNode::SimulationNode(
+ NetworkNodeConfig config,
+ std::unique_ptr<NetworkSimulationInterface> 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<double>();
+ 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 0000000..a94df1b
--- /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 <deque>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#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<void()> action);
+ virtual ~ActionReceiver() = default;
+ bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet,
+ uint64_t receiver,
+ Timestamp at_time) override;
+
+ private:
+ std::function<void()> 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<NetworkNode*> nodes,
+ NetworkReceiverInterface* receiver);
+
+ protected:
+ friend class Scenario;
+ friend class AudioStreamPair;
+ friend class VideoStreamPair;
+
+ NetworkNode(NetworkNodeConfig config,
+ std::unique_ptr<NetworkSimulationInterface> simulation);
+ static void ClearRoute(int64_t receiver_id, std::vector<NetworkNode*> 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<NetworkSimulationInterface> simulation_
+ RTC_GUARDED_BY(crit_sect_);
+ std::map<uint64_t, NetworkReceiverInterface*> routing_
+ RTC_GUARDED_BY(crit_sect_);
+ std::deque<StoredPacket> 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<void(NetworkNodeConfig*)> modifier);
+ void PauseTransmissionUntil(Timestamp until);
+ ColumnPrinter ConfigPrinter() const;
+
+ private:
+ friend class Scenario;
+
+ SimulationNode(NetworkNodeConfig config,
+ std::unique_ptr<NetworkSimulationInterface> behavior,
+ SimulatedNetwork* simulation);
+ static std::unique_ptr<SimulationNode> 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 0000000..ab6ee6b
--- /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 <algorithm>
+
+#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<void(TimeDelta)> 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<double>());
+ },
+ 32);
+}
+
+StatesPrinter* Scenario::CreatePrinter(std::string name,
+ TimeDelta interval,
+ std::vector<ColumnPrinter> printers) {
+ std::vector<ColumnPrinter> 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<void(CallClientConfig*)> config_modifier) {
+ CallClientConfig config;
+ config_modifier(&config);
+ return CreateClient(name, config);
+}
+
+SimulationNode* Scenario::CreateSimulationNode(
+ std::function<void(NetworkNodeConfig*)> 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<NetworkSimulationInterface> 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<NetworkNode*> 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<NetworkNode*> over_nodes,
+ size_t packet_size,
+ std::function<void()> 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<NetworkNode*> over_nodes,
+ std::function<void(CrossTrafficConfig*)> config_modifier) {
+ CrossTrafficConfig cross_config;
+ config_modifier(&cross_config);
+ return CreateCrossTraffic(over_nodes, cross_config);
+}
+
+CrossTrafficSource* Scenario::CreateCrossTraffic(
+ std::vector<NetworkNode*> 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<NetworkNode*> send_link,
+ CallClient* receiver,
+ std::vector<NetworkNode*> return_link,
+ std::function<void(VideoStreamConfig*)> config_modifier) {
+ VideoStreamConfig config;
+ config_modifier(&config);
+ return CreateVideoStream(sender, send_link, receiver, return_link, config);
+}
+
+VideoStreamPair* Scenario::CreateVideoStream(
+ CallClient* sender,
+ std::vector<NetworkNode*> send_link,
+ CallClient* receiver,
+ std::vector<NetworkNode*> 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<NetworkNode*> send_link,
+ CallClient* receiver,
+ std::vector<NetworkNode*> return_link,
+ std::function<void(AudioStreamConfig*)> config_modifier) {
+ AudioStreamConfig config;
+ config_modifier(&config);
+ return CreateAudioStream(sender, send_link, receiver, return_link, config);
+}
+
+AudioStreamPair* Scenario::CreateAudioStream(
+ CallClient* sender,
+ std::vector<NetworkNode*> send_link,
+ CallClient* receiver,
+ std::vector<NetworkNode*> 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<void(TimeDelta)> function) {
+ repeated_activities_.emplace_back(new RepeatedActivity(interval, function));
+ return repeated_activities_.back().get();
+}
+
+RepeatedActivity* Scenario::Every(TimeDelta interval,
+ std::function<void()> 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<void()> 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<bool()> 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<int>());
+ } 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 0000000..2cdad35
--- /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 <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#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<void(TimeDelta)> function);
+
+ void Poll(Timestamp time);
+ void SetStartTime(Timestamp time);
+ Timestamp NextTime();
+
+ TimeDelta interval_;
+ std::function<void(TimeDelta)> function_;
+ Timestamp last_update_ = Timestamp::MinusInfinity();
+};
+
+struct PendingActivity {
+ TimeDelta after_duration;
+ std::function<void()> 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<void(NetworkNodeConfig*)> config_modifier);
+ NetworkNode* CreateNetworkNode(
+ NetworkNodeConfig config,
+ std::unique_ptr<NetworkSimulationInterface> simulation);
+
+ CallClient* CreateClient(std::string name, CallClientConfig config);
+ CallClient* CreateClient(
+ std::string name,
+ std::function<void(CallClientConfig*)> config_modifier);
+
+ VideoStreamPair* CreateVideoStream(
+ CallClient* sender,
+ std::vector<NetworkNode*> send_link,
+ CallClient* receiver,
+ std::vector<NetworkNode*> return_link,
+ std::function<void(VideoStreamConfig*)> config_modifier);
+ VideoStreamPair* CreateVideoStream(CallClient* sender,
+ std::vector<NetworkNode*> send_link,
+ CallClient* receiver,
+ std::vector<NetworkNode*> return_link,
+ VideoStreamConfig config);
+
+ AudioStreamPair* CreateAudioStream(
+ CallClient* sender,
+ std::vector<NetworkNode*> send_link,
+ CallClient* receiver,
+ std::vector<NetworkNode*> return_link,
+ std::function<void(AudioStreamConfig*)> config_modifier);
+ AudioStreamPair* CreateAudioStream(CallClient* sender,
+ std::vector<NetworkNode*> send_link,
+ CallClient* receiver,
+ std::vector<NetworkNode*> return_link,
+ AudioStreamConfig config);
+
+ CrossTrafficSource* CreateCrossTraffic(
+ std::vector<NetworkNode*> over_nodes,
+ std::function<void(CrossTrafficConfig*)> config_modifier);
+ CrossTrafficSource* CreateCrossTraffic(std::vector<NetworkNode*> over_nodes,
+ CrossTrafficConfig config);
+
+ // Runs the provided function with a fixed interval.
+ RepeatedActivity* Every(TimeDelta interval,
+ std::function<void(TimeDelta)> function);
+ RepeatedActivity* Every(TimeDelta interval, std::function<void()> function);
+
+ // Runs the provided function after given duration has passed in a session.
+ void At(TimeDelta offset, std::function<void()> function);
+
+ // Sends a packet over the nodes and runs |action| when it has been delivered.
+ void NetworkDelayedAction(std::vector<NetworkNode*> over_nodes,
+ size_t packet_size,
+ std::function<void()> 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<bool()> exit_function);
+
+ // Triggers sending of dummy packets over the given nodes.
+ void TriggerPacketBurst(std::vector<NetworkNode*> over_nodes,
+ size_t num_packets,
+ size_t packet_size);
+
+ ColumnPrinter TimePrinter();
+ StatesPrinter* CreatePrinter(std::string name,
+ TimeDelta interval,
+ std::vector<ColumnPrinter> 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<std::unique_ptr<CallClient>> clients_;
+ std::vector<std::unique_ptr<NetworkNode>> network_nodes_;
+ std::vector<std::unique_ptr<CrossTrafficSource>> cross_traffic_sources_;
+ std::vector<std::unique_ptr<VideoStreamPair>> video_streams_;
+ std::vector<std::unique_ptr<AudioStreamPair>> audio_streams_;
+
+ std::vector<std::unique_ptr<RepeatedActivity>> repeated_activities_;
+ std::vector<std::unique_ptr<ActionReceiver>> action_receivers_;
+ std::vector<std::unique_ptr<PendingActivity>> pending_activities_;
+ std::vector<std::unique_ptr<StatesPrinter>> printers_;
+
+ int64_t next_receiver_id_ = 40000;
+ rtc::scoped_refptr<AudioDecoderFactory> audio_decoder_factory_;
+ rtc::scoped_refptr<AudioEncoderFactory> 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 0000000..1caac9f
--- /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 0000000..c4e8cc0
--- /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 <memory>
+#include <string>
+#include <vector>
+
+#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<int> key_frame_interval = 3000;
+
+ absl::optional<DataRate> 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<DataRate> fixed_rate;
+ absl::optional<DataRate> min_rate;
+ absl::optional<DataRate> 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 0000000..1810c23
--- /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 0000000..e87cc68
--- /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<int> random_seed;
+ FieldTrialFlag return_traffic;
+ FieldTrialParameter<DataRate> capacity;
+ FieldTrialParameter<TimeDelta> propagation_delay;
+ FieldTrialParameter<DataRate> cross_traffic;
+ FieldTrialParameter<TimeDelta> delay_noise;
+ FieldTrialParameter<double> 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<DataSize> bbr_initial_window;
+ FieldTrialParameter<double> 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<tuple<std::string, std::string>> {
+ 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<test::ScopedFieldTrials> 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<ColumnPrinter> 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 0000000..305f866
--- /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 0000000..fe87a6d
--- /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 <algorithm>
+#include <utility>
+
+#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<RtpExtension> GetVideoRtpExtensions(
+ const VideoStreamConfig config) {
+ return {RtpExtension(RtpExtension::kTransportSequenceNumberUri,
+ kTransportSequenceNumberExtensionId),
+ RtpExtension(RtpExtension::kVideoContentTypeUri,
+ kVideoContentTypeExtensionId),
+ RtpExtension(RtpExtension::kVideoRotationUri,
+ kVideoRotationRtpExtensionId)};
+}
+
+VideoSendStream::Config CreateVideoSendStreamConfig(VideoStreamConfig config,
+ std::vector<uint32_t> 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<VideoEncoderConfig::EncoderSpecificSettings>
+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<VideoStream>(num_streams);
+
+ std::string cricket_codec = CodecTypeToCodecName(config.encoder.codec);
+ if (!cricket_codec.empty()) {
+ encoder_config.video_stream_factory =
+ new rtc::RefCountedObject<cricket::EncoderStreamFactory>(
+ cricket_codec, kDefaultMaxQp, false, false);
+ } else {
+ encoder_config.video_stream_factory =
+ new rtc::RefCountedObject<DefaultVideoStreamFactory>();
+ }
+ 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<FunctionVideoEncoderFactory>([this, config]() {
+ auto encoder =
+ absl::make_unique<test::FakeEncoder>(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<int> 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<InternalDecoderFactory>()) {
+ renderer_ = absl::make_unique<FakeVideoRenderer>();
+ 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<NetworkNode*> send_link,
+ uint64_t send_receiver_id,
+ CallClient* receiver,
+ std::vector<NetworkNode*> 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 0000000..e669618
--- /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 <memory>
+#include <string>
+#include <vector>
+
+#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<int> 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<uint32_t> ssrcs_;
+ std::vector<uint32_t> rtx_ssrcs_;
+ VideoSendStream* send_stream_ = nullptr;
+ CallClient* const sender_;
+ const VideoStreamConfig config_;
+ std::unique_ptr<VideoEncoderFactory> encoder_factory_;
+ std::unique_ptr<TestVideoCapturer> 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<rtc::VideoSinkInterface<webrtc::VideoFrame>> renderer_;
+ CallClient* const receiver_;
+ const VideoStreamConfig config_;
+ std::unique_ptr<VideoDecoderFactory> 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<NetworkNode*> send_link,
+ uint64_t send_receiver_id,
+ CallClient* receiver,
+ std::vector<NetworkNode*> return_link,
+ uint64_t return_receiver_id,
+ VideoStreamConfig config);
+
+ const VideoStreamConfig config_;
+ std::vector<NetworkNode*> send_link_;
+ std::vector<NetworkNode*> return_link_;
+ NetworkNodeTransport send_transport_;
+ NetworkNodeTransport return_transport_;
+
+ SendVideoStream send_stream_;
+ ReceiveVideoStream receive_stream_;
+};
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_SCENARIO_VIDEO_STREAM_H_