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}
This commit is contained in:
parent
7988e5cbbf
commit
98b07e9180
@ -60,4 +60,18 @@ std::unique_ptr<NetworkControllerInterface> GoogCcDebugFactory::Create(
|
||||
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
|
||||
|
||||
@ -43,6 +43,19 @@ class GoogCcDebugFactory : public GoogCcNetworkControllerFactory {
|
||||
GoogCcStatePrinter* printer_;
|
||||
GoogCcNetworkController* controller_ = nullptr;
|
||||
};
|
||||
|
||||
class GoogCcFeedbackDebugFactory
|
||||
: public GoogCcFeedbackNetworkControllerFactory {
|
||||
public:
|
||||
GoogCcFeedbackDebugFactory(RtcEventLog* event_log,
|
||||
GoogCcStatePrinter* printer);
|
||||
std::unique_ptr<NetworkControllerInterface> Create(
|
||||
NetworkControllerConfig config) override;
|
||||
|
||||
private:
|
||||
GoogCcStatePrinter* printer_;
|
||||
GoogCcNetworkController* controller_ = nullptr;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_TEST_GOOG_CC_PRINTER_H_
|
||||
|
||||
@ -28,6 +28,7 @@ group("test") {
|
||||
deps += [
|
||||
":test_main",
|
||||
":test_support_unittests",
|
||||
"scenario/scenario_tests",
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -316,6 +317,7 @@ if (rtc_include_tests) {
|
||||
"../modules/video_coding:simulcast_test_fixture_impl",
|
||||
"../rtc_base:rtc_base_approved",
|
||||
"../test:single_threaded_task_queue",
|
||||
"scenario:scenario_unittests",
|
||||
"//testing/gmock",
|
||||
"//testing/gtest",
|
||||
"//third_party/abseil-cpp/absl/memory",
|
||||
|
||||
@ -8,6 +8,7 @@ include_rules = [
|
||||
"+media/base",
|
||||
"+media/engine",
|
||||
"+modules/audio_coding",
|
||||
"+modules/congestion_controller",
|
||||
"+modules/audio_device",
|
||||
"+modules/audio_mixer",
|
||||
"+modules/audio_processing",
|
||||
|
||||
122
test/scenario/BUILD.gn
Normal file
122
test/scenario/BUILD.gn
Normal file
@ -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",
|
||||
]
|
||||
}
|
||||
}
|
||||
1
test/scenario/OWNERS
Normal file
1
test/scenario/OWNERS
Normal file
@ -0,0 +1 @@
|
||||
srte@webrtc.org
|
||||
171
test/scenario/audio_stream.cc
Normal file
171
test/scenario/audio_stream.cc
Normal file
@ -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
|
||||
110
test/scenario/audio_stream.h
Normal file
110
test/scenario/audio_stream.h
Normal file
@ -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_
|
||||
190
test/scenario/call_client.cc
Normal file
190
test/scenario/call_client.cc
Normal file
@ -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
|
||||
94
test/scenario/call_client.h
Normal file
94
test/scenario/call_client.h
Normal file
@ -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_
|
||||
85
test/scenario/column_printer.cc
Normal file
85
test/scenario/column_printer.cc
Normal file
@ -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
|
||||
63
test/scenario/column_printer.h
Normal file
63
test/scenario/column_printer.h
Normal file
@ -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_
|
||||
49
test/scenario/hardware_codecs.cc
Normal file
49
test/scenario/hardware_codecs.cc
Normal file
@ -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
|
||||
24
test/scenario/hardware_codecs.h
Normal file
24
test/scenario/hardware_codecs.h
Normal file
@ -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_
|
||||
257
test/scenario/network_node.cc
Normal file
257
test/scenario/network_node.cc
Normal file
@ -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
|
||||
167
test/scenario/network_node.h
Normal file
167
test/scenario/network_node.h
Normal file
@ -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_
|
||||
341
test/scenario/scenario.cc
Normal file
341
test/scenario/scenario.cc
Normal file
@ -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
|
||||
181
test/scenario/scenario.h
Normal file
181
test/scenario/scenario.h
Normal file
@ -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_
|
||||
56
test/scenario/scenario_config.cc
Normal file
56
test/scenario/scenario_config.cc
Normal file
@ -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
|
||||
182
test/scenario/scenario_config.h
Normal file
182
test/scenario/scenario_config.h
Normal file
@ -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_
|
||||
33
test/scenario/scenario_tests/BUILD.gn
Normal file
33
test/scenario/scenario_tests/BUILD.gn
Normal file
@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
258
test/scenario/scenario_tests/bbr_performance.cc
Normal file
258
test/scenario/scenario_tests/bbr_performance.cc
Normal file
@ -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
|
||||
53
test/scenario/scenario_unittest.cc
Normal file
53
test/scenario/scenario_unittest.cc
Normal file
@ -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
|
||||
383
test/scenario/video_stream.cc
Normal file
383
test/scenario/video_stream.cc
Normal file
@ -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
|
||||
117
test/scenario/video_stream.h
Normal file
117
test/scenario/video_stream.h
Normal file
@ -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_
|
||||
Loading…
x
Reference in New Issue
Block a user