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:
Sebastian Jansson 2018-09-27 13:47:01 +02:00 committed by Commit Bot
parent 7988e5cbbf
commit 98b07e9180
25 changed files with 2967 additions and 0 deletions

View File

@ -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

View File

@ -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_

View File

@ -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",

View File

@ -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
View 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
View File

@ -0,0 +1 @@
srte@webrtc.org

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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
View 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
View 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_

View 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

View 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_

View 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" ]
}
}
}

View 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

View 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

View 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

View 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_