diff --git a/modules/congestion_controller/goog_cc/BUILD.gn b/modules/congestion_controller/goog_cc/BUILD.gn index 334fd22a89..b12690b817 100644 --- a/modules/congestion_controller/goog_cc/BUILD.gn +++ b/modules/congestion_controller/goog_cc/BUILD.gn @@ -166,6 +166,9 @@ if (rtc_include_tests) { "probe_controller_unittest.cc", "trendline_estimator_unittest.cc", ] + if (!build_with_chromium && is_clang) { + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } deps = [ ":alr_detector", ":delay_based_bwe", @@ -183,6 +186,7 @@ if (rtc_include_tests) { "../../../system_wrappers:field_trial", "../../../test:field_trial", "../../../test:test_support", + "../../../test/scenario", "../../pacing", "../../remote_bitrate_estimator", "../../rtp_rtcp:rtp_rtcp_format", diff --git a/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc b/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc index 484b0e694c..ea561a65fa 100644 --- a/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc +++ b/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc @@ -11,6 +11,7 @@ #include "api/transport/test/network_control_tester.h" #include "logging/rtc_event_log/mock/mock_rtc_event_log.h" #include "modules/congestion_controller/goog_cc/include/goog_cc_factory.h" +#include "test/scenario/scenario.h" #include "test/gtest.h" @@ -21,7 +22,6 @@ using testing::Property; using testing::_; namespace webrtc { -namespace webrtc_cc { namespace test { namespace { @@ -285,9 +285,8 @@ TEST_F(GoogCcNetworkControllerTest, UpdatesDelayBasedEstimate) { TEST_F(GoogCcNetworkControllerTest, FeedbackVersionUpdatesTargetSendRateBasedOnFeedback) { GoogCcFeedbackNetworkControllerFactory factory(&event_log_); - webrtc::test::NetworkControllerTester tester(&factory, - InitialConfig(60, 0, 600)); - auto packet_producer = &webrtc::test::SimpleTargetRateProducer::ProduceNext; + NetworkControllerTester tester(&factory, InitialConfig(60, 0, 600)); + auto packet_producer = &SimpleTargetRateProducer::ProduceNext; tester.RunSimulation(TimeDelta::seconds(10), TimeDelta::ms(10), DataRate::kbps(300), TimeDelta::ms(100), @@ -308,6 +307,56 @@ TEST_F(GoogCcNetworkControllerTest, 20); } +TEST_F(GoogCcNetworkControllerTest, ScenarioQuickTest) { + Scenario s("googcc_unit/scenario_quick", false); + SimulatedTimeClientConfig config; + config.transport.cc = + TransportControllerConfig::CongestionController::kGoogCcFeedback; + config.transport.rates.min_rate = DataRate::kbps(10); + config.transport.rates.max_rate = DataRate::kbps(1500); + config.transport.rates.start_rate = DataRate::kbps(300); + NetworkNodeConfig net_conf; + auto send_net = s.CreateSimulationNode([](NetworkNodeConfig* c) { + c->simulation.bandwidth = DataRate::kbps(500); + c->simulation.delay = TimeDelta::ms(100); + c->update_frequency = TimeDelta::ms(5); + }); + auto ret_net = s.CreateSimulationNode([](NetworkNodeConfig* c) { + c->simulation.delay = TimeDelta::ms(100); + c->update_frequency = TimeDelta::ms(5); + }); + StatesPrinter* truth = s.CreatePrinter( + "send.truth.txt", TimeDelta::PlusInfinity(), {send_net->ConfigPrinter()}); + SimulatedTimeClient* client = s.CreateSimulatedTimeClient( + "send", config, {PacketStreamConfig()}, {send_net}, {ret_net}); + + truth->PrintRow(); + s.RunFor(TimeDelta::seconds(25)); + truth->PrintRow(); + EXPECT_NEAR(client->target_rate_kbps(), 450, 100); + + send_net->UpdateConfig([](NetworkNodeConfig* c) { + c->simulation.bandwidth = DataRate::kbps(800); + c->simulation.delay = TimeDelta::ms(100); + }); + + truth->PrintRow(); + s.RunFor(TimeDelta::seconds(20)); + truth->PrintRow(); + EXPECT_NEAR(client->target_rate_kbps(), 750, 150); + + send_net->UpdateConfig([](NetworkNodeConfig* c) { + c->simulation.bandwidth = DataRate::kbps(100); + c->simulation.delay = TimeDelta::ms(200); + }); + ret_net->UpdateConfig( + [](NetworkNodeConfig* c) { c->simulation.delay = TimeDelta::ms(200); }); + + truth->PrintRow(); + s.RunFor(TimeDelta::seconds(30)); + truth->PrintRow(); + EXPECT_NEAR(client->target_rate_kbps(), 90, 20); +} + } // namespace test -} // namespace webrtc_cc } // namespace webrtc diff --git a/test/scenario/BUILD.gn b/test/scenario/BUILD.gn index 47f3844212..ef98b715e6 100644 --- a/test/scenario/BUILD.gn +++ b/test/scenario/BUILD.gn @@ -26,6 +26,8 @@ if (rtc_include_tests) { "scenario.h", "scenario_config.cc", "scenario_config.h", + "simulated_time.cc", + "simulated_time.h", "video_stream.cc", "video_stream.h", ] diff --git a/test/scenario/scenario.cc b/test/scenario/scenario.cc index ab6ee6bca5..107365f42f 100644 --- a/test/scenario/scenario.cc +++ b/test/scenario/scenario.cc @@ -68,7 +68,7 @@ Scenario::Scenario(std::string file_name, bool real_time) base_filename_ = OutputPath() + "output_data/" + file_name; RTC_LOG(LS_INFO) << "Saving scenario logs to: " << base_filename_; } - if (!real_time_mode_) { + if (!real_time_mode_ && !base_filename_.empty()) { rtc::SetClockForTesting(&event_log_fake_clock_); event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() * 1000); } @@ -104,6 +104,7 @@ StatesPrinter* Scenario::CreatePrinter(std::string name, } CallClient* Scenario::CreateClient(std::string name, CallClientConfig config) { + RTC_DCHECK(real_time_mode_); CallClient* client = new CallClient(clock_, GetFullPathOrEmpty(name), config); if (config.transport.state_log_interval.IsFinite()) { Every(config.transport.state_log_interval, [this, client]() { @@ -122,6 +123,31 @@ CallClient* Scenario::CreateClient( return CreateClient(name, config); } +SimulatedTimeClient* Scenario::CreateSimulatedTimeClient( + std::string name, + SimulatedTimeClientConfig config, + std::vector stream_configs, + std::vector send_link, + std::vector return_link) { + uint64_t send_id = next_receiver_id_++; + uint64_t return_id = next_receiver_id_++; + SimulatedTimeClient* client = new SimulatedTimeClient( + GetFullPathOrEmpty(name), config, stream_configs, send_link, return_link, + send_id, return_id, Now()); + if (!base_filename_.empty() && !name.empty() && + config.transport.state_log_interval.IsFinite()) { + Every(config.transport.state_log_interval, [this, client]() { + client->network_controller_factory_.LogCongestionControllerStats(Now()); + }); + } + + Every(client->GetNetworkControllerProcessInterval(), + [this, client] { client->CongestionProcess(Now()); }); + Every(TimeDelta::ms(5), [this, client] { client->PacerProcess(Now()); }); + simulated_time_clients_.emplace_back(client); + return client; +} + SimulationNode* Scenario::CreateSimulationNode( std::function config_modifier) { NetworkNodeConfig config; @@ -313,8 +339,11 @@ void Scenario::RunUntil(TimeDelta max_duration, done_.Wait(wait_time.ms()); } else { sim_clock_.AdvanceTimeMicroseconds(wait_time.us()); - event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() * - 1000); + // The fake clock is quite slow to update, we only update it if logging is + // turned on to save time. + if (!base_filename_.empty()) + event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() * + 1000); } } for (auto& stream_pair : video_streams_) { diff --git a/test/scenario/scenario.h b/test/scenario/scenario.h index 2cdad3562d..9390b0670e 100644 --- a/test/scenario/scenario.h +++ b/test/scenario/scenario.h @@ -21,6 +21,7 @@ #include "test/scenario/column_printer.h" #include "test/scenario/network_node.h" #include "test/scenario/scenario_config.h" +#include "test/scenario/simulated_time.h" #include "test/scenario/video_stream.h" namespace webrtc { @@ -77,6 +78,13 @@ class Scenario { std::string name, std::function config_modifier); + SimulatedTimeClient* CreateSimulatedTimeClient( + std::string name, + SimulatedTimeClientConfig config, + std::vector stream_configs, + std::vector send_link, + std::vector return_link); + VideoStreamPair* CreateVideoStream( CallClient* sender, std::vector send_link, @@ -164,6 +172,8 @@ class Scenario { std::vector> video_streams_; std::vector> audio_streams_; + std::vector> simulated_time_clients_; + std::vector> repeated_activities_; std::vector> action_receivers_; std::vector> pending_activities_; diff --git a/test/scenario/scenario_config.cc b/test/scenario/scenario_config.cc index 1caac9f6cb..223b2a07b9 100644 --- a/test/scenario/scenario_config.cc +++ b/test/scenario/scenario_config.cc @@ -17,6 +17,10 @@ TransportControllerConfig::Rates::Rates( const TransportControllerConfig::Rates&) = default; TransportControllerConfig::Rates::~Rates() = default; +PacketStreamConfig::PacketStreamConfig() = default; +PacketStreamConfig::PacketStreamConfig(const PacketStreamConfig&) = default; +PacketStreamConfig::~PacketStreamConfig() = default; + VideoStreamConfig::Encoder::Encoder() = default; VideoStreamConfig::Encoder::Encoder(const VideoStreamConfig::Encoder&) = default; diff --git a/test/scenario/scenario_config.h b/test/scenario/scenario_config.h index c4e8cc0152..e652e2ce39 100644 --- a/test/scenario/scenario_config.h +++ b/test/scenario/scenario_config.h @@ -49,6 +49,25 @@ struct CallClientConfig { DataRate priority_target_rate = DataRate::Zero(); }; +struct SimulatedTimeClientConfig { + TransportControllerConfig transport; + struct Feedback { + TimeDelta interval = TimeDelta::ms(100); + } feedback; +}; + +struct PacketStreamConfig { + PacketStreamConfig(); + PacketStreamConfig(const PacketStreamConfig&); + ~PacketStreamConfig(); + int frame_rate = 30; + DataRate max_data_rate = DataRate::Infinity(); + DataSize max_packet_size = DataSize::bytes(1400); + DataSize min_frame_size = DataSize::bytes(100); + double keyframe_multiplier = 1; + DataSize packet_overhead = DataSize::bytes(PacketOverhead::kDefault); +}; + struct VideoStreamConfig { bool autostart = true; struct Source { diff --git a/test/scenario/simulated_time.cc b/test/scenario/simulated_time.cc new file mode 100644 index 0000000000..f29a82c455 --- /dev/null +++ b/test/scenario/simulated_time.cc @@ -0,0 +1,348 @@ +/* + * 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/simulated_time.h" + +#include + +#include "rtc_base/format_macros.h" + +namespace webrtc { +namespace test { +namespace { +struct RawFeedbackReportPacket { + static constexpr int MAX_FEEDBACKS = 10; + struct Feedback { + int16_t seq_offset; + int32_t recv_offset_ms; + }; + uint8_t count; + int64_t first_seq_num; + int64_t first_recv_time_ms; + Feedback feedbacks[MAX_FEEDBACKS - 1]; +}; +} // namespace + +PacketStream::PacketStream(PacketStreamConfig config) : config_(config) {} + +std::vector PacketStream::PullPackets(Timestamp at_time) { + if (next_frame_time_.IsInfinite()) + next_frame_time_ = at_time; + + TimeDelta frame_interval = TimeDelta::seconds(1) / config_.frame_rate; + int64_t frame_allowance = (frame_interval * target_rate_).bytes(); + + if (next_frame_is_keyframe_) { + frame_allowance *= config_.keyframe_multiplier; + next_frame_is_keyframe_ = false; + } + + std::vector packets; + while (at_time >= next_frame_time_) { + next_frame_time_ += frame_interval; + + int64_t frame_size = budget_ + frame_allowance; + frame_size = std::max(frame_size, config_.min_frame_size.bytes()); + budget_ += frame_allowance - frame_size; + + int64_t packet_budget = frame_size; + int64_t max_packet_size = config_.max_packet_size.bytes(); + while (packet_budget > max_packet_size) { + packets.push_back(max_packet_size); + packet_budget -= max_packet_size; + } + packets.push_back(packet_budget); + } + for (int64_t& packet : packets) + packet += config_.packet_overhead.bytes(); + return packets; +} + +void PacketStream::OnTargetRateUpdate(DataRate target_rate) { + target_rate_ = std::min(target_rate, config_.max_data_rate); +} + +SimpleFeedbackReportPacket FeedbackFromBuffer( + rtc::CopyOnWriteBuffer raw_buffer) { + RTC_CHECK_LE(sizeof(RawFeedbackReportPacket), raw_buffer.size()); + const RawFeedbackReportPacket& raw_packet = + *reinterpret_cast(raw_buffer.cdata()); + RTC_CHECK_GE(raw_packet.count, 1); + SimpleFeedbackReportPacket packet; + packet.receive_times.emplace_back(SimpleFeedbackReportPacket::ReceiveInfo{ + raw_packet.first_seq_num, Timestamp::ms(raw_packet.first_recv_time_ms)}); + for (int i = 1; i < raw_packet.count; ++i) + packet.receive_times.emplace_back(SimpleFeedbackReportPacket::ReceiveInfo{ + raw_packet.first_seq_num + raw_packet.feedbacks[i - 1].seq_offset, + Timestamp::ms(raw_packet.first_recv_time_ms + + raw_packet.feedbacks[i - 1].recv_offset_ms)}); + return packet; +} + +rtc::CopyOnWriteBuffer FeedbackToBuffer( + const SimpleFeedbackReportPacket packet) { + RTC_CHECK_LE(packet.receive_times.size(), + RawFeedbackReportPacket::MAX_FEEDBACKS); + RawFeedbackReportPacket report; + report.count = packet.receive_times.size(); + RTC_CHECK(!packet.receive_times.empty()); + report.first_seq_num = packet.receive_times.front().sequence_number; + report.first_recv_time_ms = packet.receive_times.front().receive_time.ms(); + + for (int i = 1; i < report.count; ++i) { + report.feedbacks[i - 1].seq_offset = static_cast( + packet.receive_times[i].sequence_number - report.first_seq_num); + report.feedbacks[i - 1].recv_offset_ms = static_cast( + packet.receive_times[i].receive_time.ms() - report.first_recv_time_ms); + } + return rtc::CopyOnWriteBuffer(reinterpret_cast(&report), + sizeof(RawFeedbackReportPacket)); +} + +SimulatedSender::SimulatedSender(NetworkNode* send_node, + uint64_t send_receiver_id) + : send_node_(send_node), send_receiver_id_(send_receiver_id) {} + +SimulatedSender::~SimulatedSender() {} + +TransportPacketsFeedback SimulatedSender::PullFeedbackReport( + SimpleFeedbackReportPacket packet, + Timestamp at_time) { + TransportPacketsFeedback report; + report.prior_in_flight = data_in_flight_; + report.feedback_time = at_time; + + for (auto& receive_info : packet.receive_times) { + // Look up sender side information for all packets up to and including each + // packet with feedback in the report. + for (; next_feedback_seq_num_ <= receive_info.sequence_number; + ++next_feedback_seq_num_) { + PacketResult feedback; + if (next_feedback_seq_num_ == receive_info.sequence_number) { + feedback.receive_time = receive_info.receive_time; + } else { + // If we did not get any feedback for this packet, mark it as lost by + // setting receive time to infinity. Note that this can also happen due + // to reordering, we will newer send feedback out of order. In this case + // the packet was not really lost, but we don't have that information. + feedback.receive_time = Timestamp::PlusInfinity(); + } + + // Looking up send side information. + for (auto it = sent_packets_.begin(); it != sent_packets_.end(); ++it) { + if (it->sequence_number == next_feedback_seq_num_) { + feedback.sent_packet = *it; + if (feedback.receive_time.IsFinite()) + sent_packets_.erase(it); + break; + } + } + + data_in_flight_ -= feedback.sent_packet->size; + report.packet_feedbacks.push_back(feedback); + } + } + report.data_in_flight = data_in_flight_; + return report; +} + +// Applies pacing and congetsion window based on the configuration from the +// congestion controller. This is not a complete implementation of the real +// pacer but useful for unit tests since it isn't limited to real time. +std::vector +SimulatedSender::PaceAndPullSendPackets(Timestamp at_time) { + // TODO(srte): Extract the behavior of PacedSender to a threading and time + // independent component and use that here to allow a truthful simulation. + if (last_update_.IsInfinite()) { + pacing_budget_ = 0; + } else { + TimeDelta delta = at_time - last_update_; + pacing_budget_ += (delta * pacer_config_.data_rate()).bytes(); + } + std::vector to_send; + while (data_in_flight_ <= max_in_flight_ && pacing_budget_ >= 0 && + !packet_queue_.empty()) { + PendingPacket pending = packet_queue_.front(); + pacing_budget_ -= pending.size; + packet_queue_.pop_front(); + SentPacket sent; + sent.sequence_number = next_sequence_number_++; + sent.size = DataSize::bytes(pending.size); + data_in_flight_ += sent.size; + sent.data_in_flight = data_in_flight_; + sent.pacing_info = PacedPacketInfo(); + sent.send_time = at_time; + sent_packets_.push_back(sent); + rtc::CopyOnWriteBuffer packet( + std::max(pending.size, sizeof(sent.sequence_number))); + memcpy(packet.data(), &sent.sequence_number, sizeof(sent.sequence_number)); + to_send.emplace_back(PacketReadyToSend{sent, packet}); + } + pacing_budget_ = std::min(pacing_budget_, 0); + last_update_ = at_time; + return to_send; +} + +void SimulatedSender::Update(NetworkControlUpdate update) { + if (update.pacer_config) + pacer_config_ = *update.pacer_config; + if (update.congestion_window) + max_in_flight_ = *update.congestion_window; +} + +SimulatedFeedback::SimulatedFeedback(SimulatedTimeClientConfig config, + uint64_t return_receiver_id, + NetworkNode* return_node) + : config_(config), + return_receiver_id_(return_receiver_id), + return_node_(return_node) {} + +// Polls receiver side for a feedback report and sends it to the stream sender +// via return_node_, +bool SimulatedFeedback::TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) { + int64_t sequence_number; + memcpy(&sequence_number, packet.cdata(), sizeof(sequence_number)); + receive_times_.insert({sequence_number, at_time}); + if (last_feedback_time_.IsInfinite()) + last_feedback_time_ = at_time; + if (at_time >= last_feedback_time_ + config_.feedback.interval) { + SimpleFeedbackReportPacket report; + for (; next_feedback_seq_num_ <= sequence_number; + ++next_feedback_seq_num_) { + auto it = receive_times_.find(next_feedback_seq_num_); + if (it != receive_times_.end()) { + report.receive_times.emplace_back( + SimpleFeedbackReportPacket::ReceiveInfo{next_feedback_seq_num_, + it->second}); + receive_times_.erase(it); + } + if (receive_times_.size() >= RawFeedbackReportPacket::MAX_FEEDBACKS) { + return_node_->TryDeliverPacket(FeedbackToBuffer(report), + return_receiver_id_, at_time); + report = SimpleFeedbackReportPacket(); + } + } + if (!report.receive_times.empty()) + return_node_->TryDeliverPacket(FeedbackToBuffer(report), + return_receiver_id_, at_time); + last_feedback_time_ = at_time; + } + return true; +} + +SimulatedTimeClient::SimulatedTimeClient( + std::string log_filename, + SimulatedTimeClientConfig config, + std::vector stream_configs, + std::vector send_link, + std::vector return_link, + uint64_t send_receiver_id, + uint64_t return_receiver_id, + Timestamp at_time) + : network_controller_factory_(log_filename, config.transport), + send_link_(send_link), + return_link_(return_link), + sender_(send_link.front(), send_receiver_id), + feedback_(config, return_receiver_id, return_link.front()) { + NetworkControllerConfig initial_config; + initial_config.constraints.at_time = at_time; + initial_config.constraints.starting_rate = config.transport.rates.start_rate; + initial_config.constraints.min_data_rate = config.transport.rates.min_rate; + initial_config.constraints.max_data_rate = config.transport.rates.max_rate; + congestion_controller_ = network_controller_factory_.Create(initial_config); + for (auto& stream_config : stream_configs) + packet_streams_.emplace_back(new PacketStream(stream_config)); + NetworkNode::Route(send_receiver_id, send_link, &feedback_); + NetworkNode::Route(return_receiver_id, return_link, this); + + CongestionProcess(at_time); + network_controller_factory_.LogCongestionControllerStats(at_time); + if (!log_filename.empty()) { + std::string packet_log_name = log_filename + ".packets.txt"; + packet_log_ = fopen(packet_log_name.c_str(), "w"); + fprintf(packet_log_, + "transport_seq packet_size send_time recv_time feed_time\n"); + } +} + +// Pulls feedback reports from sender side based on the recieved feedback +// packet. Updates congestion controller with the resulting report. +bool SimulatedTimeClient::TryDeliverPacket(rtc::CopyOnWriteBuffer raw_buffer, + uint64_t receiver, + Timestamp at_time) { + auto report = + sender_.PullFeedbackReport(FeedbackFromBuffer(raw_buffer), at_time); + for (PacketResult& feedback : report.packet_feedbacks) { + if (packet_log_) + fprintf(packet_log_, "%" PRId64 " %" PRId64 " %.3lf %.3lf %.3lf\n", + feedback.sent_packet->sequence_number, + feedback.sent_packet->size.bytes(), + feedback.sent_packet->send_time.seconds(), + feedback.receive_time.seconds(), + at_time.seconds()); + } + Update(congestion_controller_->OnTransportPacketsFeedback(report)); + return true; +} +SimulatedTimeClient::~SimulatedTimeClient() { + if (packet_log_) + fclose(packet_log_); +} + +void SimulatedTimeClient::Update(NetworkControlUpdate update) { + sender_.Update(update); + if (update.target_rate) { + // TODO(srte): Implement more realistic distribution of bandwidths between + // streams. Either using BitrateAllocationStrategy directly or using + // BitrateAllocation. + double ratio_per_stream = 1.0 / packet_streams_.size(); + DataRate rate_per_stream = + update.target_rate->target_rate * ratio_per_stream; + target_rate_ = update.target_rate->target_rate; + for (auto& stream : packet_streams_) + stream->OnTargetRateUpdate(rate_per_stream); + } +} + +void SimulatedTimeClient::CongestionProcess(Timestamp at_time) { + ProcessInterval msg; + msg.at_time = at_time; + Update(congestion_controller_->OnProcessInterval(msg)); +} + +void SimulatedTimeClient::PacerProcess(Timestamp at_time) { + ProcessFrames(at_time); + for (auto to_send : sender_.PaceAndPullSendPackets(at_time)) { + sender_.send_node_->TryDeliverPacket(to_send.data, + sender_.send_receiver_id_, at_time); + Update(congestion_controller_->OnSentPacket(to_send.send_info)); + } +} + +void SimulatedTimeClient::ProcessFrames(Timestamp at_time) { + for (auto& stream : packet_streams_) { + for (int64_t packet_size : stream->PullPackets(at_time)) { + sender_.packet_queue_.push_back( + SimulatedSender::PendingPacket{packet_size}); + } + } +} + +TimeDelta SimulatedTimeClient::GetNetworkControllerProcessInterval() const { + return network_controller_factory_.GetProcessInterval(); +} + +double SimulatedTimeClient::target_rate_kbps() const { + return target_rate_.kbps(); +} + +} // namespace test +} // namespace webrtc diff --git a/test/scenario/simulated_time.h b/test/scenario/simulated_time.h new file mode 100644 index 0000000000..b9c6de124d --- /dev/null +++ b/test/scenario/simulated_time.h @@ -0,0 +1,155 @@ +/* + * 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_SIMULATED_TIME_H_ +#define TEST_SCENARIO_SIMULATED_TIME_H_ + +#include +#include +#include +#include +#include +#include +#include + +#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 { +class PacketStream { + public: + explicit PacketStream(PacketStreamConfig config); + + private: + std::vector PullPackets(Timestamp at_time); + void OnTargetRateUpdate(DataRate target_rate); + + friend class SimulatedTimeClient; + PacketStreamConfig config_; + bool next_frame_is_keyframe_ = true; + Timestamp next_frame_time_ = Timestamp::MinusInfinity(); + DataRate target_rate_ = DataRate::Zero(); + int64_t budget_ = 0; +}; + +class SimulatedFeedback : NetworkReceiverInterface { + public: + SimulatedFeedback(SimulatedTimeClientConfig config, + uint64_t return_receiver_id, + NetworkNode* return_node); + bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) override; + + private: + friend class SimulatedTimeClient; + const SimulatedTimeClientConfig config_; + const uint64_t return_receiver_id_; + NetworkNode* return_node_; + Timestamp last_feedback_time_ = Timestamp::MinusInfinity(); + int32_t next_feedback_seq_num_ = 1; + std::map receive_times_; +}; + +struct SimpleFeedbackReportPacket { + struct ReceiveInfo { + int64_t sequence_number; + Timestamp receive_time; + }; + std::vector receive_times; +}; + +SimpleFeedbackReportPacket FeedbackFromBuffer( + rtc::CopyOnWriteBuffer raw_buffer); +rtc::CopyOnWriteBuffer FeedbackToBuffer( + const SimpleFeedbackReportPacket packet); + +class SimulatedSender { + public: + struct PacketReadyToSend { + SentPacket send_info; + rtc::CopyOnWriteBuffer data; + }; + struct PendingPacket { + int64_t size; + }; + + SimulatedSender(NetworkNode* send_node, uint64_t send_receiver_id); + SimulatedSender(const SimulatedSender&) = delete; + ~SimulatedSender(); + TransportPacketsFeedback PullFeedbackReport(SimpleFeedbackReportPacket report, + Timestamp at_time); + std::vector PaceAndPullSendPackets(Timestamp at_time); + void Update(NetworkControlUpdate update); + + private: + friend class SimulatedTimeClient; + NetworkNode* send_node_; + uint64_t send_receiver_id_; + PacerConfig pacer_config_; + DataSize max_in_flight_ = DataSize::Infinity(); + + std::deque packet_queue_; + std::vector sent_packets_; + + Timestamp last_update_ = Timestamp::MinusInfinity(); + int64_t pacing_budget_ = 0; + int64_t next_sequence_number_ = 1; + int64_t next_feedback_seq_num_ = 1; + DataSize data_in_flight_ = DataSize::Zero(); +}; + +// SimulatedTimeClient emulates core parts of the behavior of WebRTC from the +// perspective of congestion controllers. This is intended for use in functional +// unit tests to ensure that congestion controllers behave in a reasonable way. +// It does not, however, completely simulate the actual behavior of WebRTC. For +// a more accurate simulation, use the real time only CallClient. +class SimulatedTimeClient : NetworkReceiverInterface { + public: + SimulatedTimeClient(std::string log_filename, + SimulatedTimeClientConfig config, + std::vector stream_configs, + std::vector send_link, + std::vector return_link, + uint64_t send_receiver_id, + uint64_t return_receiver_id, + Timestamp at_time); + SimulatedTimeClient(const SimulatedTimeClient&) = delete; + ~SimulatedTimeClient(); + void Update(NetworkControlUpdate update); + void CongestionProcess(Timestamp at_time); + void PacerProcess(Timestamp at_time); + void ProcessFrames(Timestamp at_time); + TimeDelta GetNetworkControllerProcessInterval() const; + double target_rate_kbps() const; + + bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet, + uint64_t receiver, + Timestamp at_time) override; + + private: + friend class Scenario; + LoggingNetworkControllerFactory network_controller_factory_; + std::unique_ptr congestion_controller_; + std::vector send_link_; + std::vector return_link_; + SimulatedSender sender_; + SimulatedFeedback feedback_; + DataRate target_rate_ = DataRate::Infinity(); + FILE* packet_log_ = nullptr; + + std::vector> packet_streams_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_SIMULATED_TIME_H_