/* * Copyright (c) 2016 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 "modules/congestion_controller/delay_based_bwe_unittest_helper.h" #include #include #include #include "modules/congestion_controller/delay_based_bwe.h" #include "rtc_base/checks.h" #include "rtc_base/ptr_util.h" namespace webrtc { constexpr size_t kMtu = 1200; constexpr uint32_t kAcceptedBitrateErrorBps = 50000; // Number of packets needed before we have a valid estimate. constexpr int kNumInitialPackets = 2; constexpr int kInitialProbingPackets = 5; namespace test { void TestBitrateObserver::OnReceiveBitrateChanged( const std::vector& ssrcs, uint32_t bitrate) { latest_bitrate_ = bitrate; updated_ = true; } RtpStream::RtpStream(int fps, int bitrate_bps) : fps_(fps), bitrate_bps_(bitrate_bps), next_rtp_time_(0), sequence_number_(0) { RTC_CHECK_GT(fps_, 0); } // Generates a new frame for this stream. If called too soon after the // previous frame, no frame will be generated. The frame is split into // packets. int64_t RtpStream::GenerateFrame(int64_t time_now_us, std::vector* packets) { if (time_now_us < next_rtp_time_) { return next_rtp_time_; } RTC_CHECK(packets != NULL); size_t bits_per_frame = (bitrate_bps_ + fps_ / 2) / fps_; size_t n_packets = std::max((bits_per_frame + 4 * kMtu) / (8 * kMtu), 1u); size_t payload_size = (bits_per_frame + 4 * n_packets) / (8 * n_packets); for (size_t i = 0; i < n_packets; ++i) { PacketFeedback packet(-1, sequence_number_++); packet.send_time_ms = (time_now_us + kSendSideOffsetUs) / 1000; packet.payload_size = payload_size; packets->push_back(packet); } next_rtp_time_ = time_now_us + (1000000 + fps_ / 2) / fps_; return next_rtp_time_; } // The send-side time when the next frame can be generated. int64_t RtpStream::next_rtp_time() const { return next_rtp_time_; } void RtpStream::set_bitrate_bps(int bitrate_bps) { ASSERT_GE(bitrate_bps, 0); bitrate_bps_ = bitrate_bps; } int RtpStream::bitrate_bps() const { return bitrate_bps_; } bool RtpStream::Compare(const std::unique_ptr& lhs, const std::unique_ptr& rhs) { return lhs->next_rtp_time_ < rhs->next_rtp_time_; } StreamGenerator::StreamGenerator(int capacity, int64_t time_now) : capacity_(capacity), prev_arrival_time_us_(time_now) {} // Add a new stream. void StreamGenerator::AddStream(RtpStream* stream) { streams_.push_back(std::unique_ptr(stream)); } // Set the link capacity. void StreamGenerator::set_capacity_bps(int capacity_bps) { ASSERT_GT(capacity_bps, 0); capacity_ = capacity_bps; } // Divides |bitrate_bps| among all streams. The allocated bitrate per stream // is decided by the current allocation ratios. void StreamGenerator::SetBitrateBps(int bitrate_bps) { ASSERT_GE(streams_.size(), 0u); int total_bitrate_before = 0; for (const auto& stream : streams_) { total_bitrate_before += stream->bitrate_bps(); } int64_t bitrate_before = 0; int total_bitrate_after = 0; for (const auto& stream : streams_) { bitrate_before += stream->bitrate_bps(); int64_t bitrate_after = (bitrate_before * bitrate_bps + total_bitrate_before / 2) / total_bitrate_before; stream->set_bitrate_bps(bitrate_after - total_bitrate_after); total_bitrate_after += stream->bitrate_bps(); } ASSERT_EQ(bitrate_before, total_bitrate_before); EXPECT_EQ(total_bitrate_after, bitrate_bps); } // TODO(holmer): Break out the channel simulation part from this class to make // it possible to simulate different types of channels. int64_t StreamGenerator::GenerateFrame(std::vector* packets, int64_t time_now_us) { RTC_CHECK(packets != NULL); RTC_CHECK(packets->empty()); RTC_CHECK_GT(capacity_, 0); auto it = std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare); (*it)->GenerateFrame(time_now_us, packets); int i = 0; for (PacketFeedback& packet : *packets) { int capacity_bpus = capacity_ / 1000; int64_t required_network_time_us = (8 * 1000 * packet.payload_size + capacity_bpus / 2) / capacity_bpus; prev_arrival_time_us_ = std::max(time_now_us + required_network_time_us, prev_arrival_time_us_ + required_network_time_us); packet.arrival_time_ms = prev_arrival_time_us_ / 1000; ++i; } it = std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare); return std::max((*it)->next_rtp_time(), time_now_us); } } // namespace test LegacyDelayBasedBweTest::LegacyDelayBasedBweTest() : clock_(100000000), acknowledged_bitrate_estimator_( rtc::MakeUnique()), bitrate_estimator_(new DelayBasedBwe(nullptr, &clock_)), stream_generator_(new test::StreamGenerator(1e6, // Capacity. clock_.TimeInMicroseconds())), arrival_time_offset_ms_(0), first_update_(true) {} LegacyDelayBasedBweTest::~LegacyDelayBasedBweTest() {} void LegacyDelayBasedBweTest::AddDefaultStream() { stream_generator_->AddStream(new test::RtpStream(30, 3e5)); } const uint32_t LegacyDelayBasedBweTest::kDefaultSsrc = 0; void LegacyDelayBasedBweTest::IncomingFeedback(int64_t arrival_time_ms, int64_t send_time_ms, uint16_t sequence_number, size_t payload_size) { IncomingFeedback(arrival_time_ms, send_time_ms, sequence_number, payload_size, PacedPacketInfo()); } void LegacyDelayBasedBweTest::IncomingFeedback( int64_t arrival_time_ms, int64_t send_time_ms, uint16_t sequence_number, size_t payload_size, const PacedPacketInfo& pacing_info) { RTC_CHECK_GE(arrival_time_ms + arrival_time_offset_ms_, 0); PacketFeedback packet(arrival_time_ms + arrival_time_offset_ms_, send_time_ms, sequence_number, payload_size, pacing_info); std::vector packets; packets.push_back(packet); acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(packets); DelayBasedBwe::Result result = bitrate_estimator_->IncomingPacketFeedbackVector( packets, acknowledged_bitrate_estimator_->bitrate_bps()); const uint32_t kDummySsrc = 0; if (result.updated) { bitrate_observer_.OnReceiveBitrateChanged({kDummySsrc}, result.target_bitrate_bps); } } // Generates a frame of packets belonging to a stream at a given bitrate and // with a given ssrc. The stream is pushed through a very simple simulated // network, and is then given to the receive-side bandwidth estimator. // Returns true if an over-use was seen, false otherwise. // The StreamGenerator::updated() should be used to check for any changes in // target bitrate after the call to this function. bool LegacyDelayBasedBweTest::GenerateAndProcessFrame(uint32_t ssrc, uint32_t bitrate_bps) { stream_generator_->SetBitrateBps(bitrate_bps); std::vector packets; int64_t next_time_us = stream_generator_->GenerateFrame(&packets, clock_.TimeInMicroseconds()); if (packets.empty()) return false; bool overuse = false; bitrate_observer_.Reset(); clock_.AdvanceTimeMicroseconds(1000 * packets.back().arrival_time_ms - clock_.TimeInMicroseconds()); for (auto& packet : packets) { RTC_CHECK_GE(packet.arrival_time_ms + arrival_time_offset_ms_, 0); packet.arrival_time_ms += arrival_time_offset_ms_; } acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(packets); DelayBasedBwe::Result result = bitrate_estimator_->IncomingPacketFeedbackVector( packets, acknowledged_bitrate_estimator_->bitrate_bps()); const uint32_t kDummySsrc = 0; if (result.updated) { bitrate_observer_.OnReceiveBitrateChanged({kDummySsrc}, result.target_bitrate_bps); if (!first_update_ && result.target_bitrate_bps < bitrate_bps) overuse = true; first_update_ = false; } clock_.AdvanceTimeMicroseconds(next_time_us - clock_.TimeInMicroseconds()); return overuse; } // Run the bandwidth estimator with a stream of |number_of_frames| frames, or // until it reaches |target_bitrate|. // Can for instance be used to run the estimator for some time to get it // into a steady state. uint32_t LegacyDelayBasedBweTest::SteadyStateRun(uint32_t ssrc, int max_number_of_frames, uint32_t start_bitrate, uint32_t min_bitrate, uint32_t max_bitrate, uint32_t target_bitrate) { uint32_t bitrate_bps = start_bitrate; bool bitrate_update_seen = false; // Produce |number_of_frames| frames and give them to the estimator. for (int i = 0; i < max_number_of_frames; ++i) { bool overuse = GenerateAndProcessFrame(ssrc, bitrate_bps); if (overuse) { EXPECT_LT(bitrate_observer_.latest_bitrate(), max_bitrate); EXPECT_GT(bitrate_observer_.latest_bitrate(), min_bitrate); bitrate_bps = bitrate_observer_.latest_bitrate(); bitrate_update_seen = true; } else if (bitrate_observer_.updated()) { bitrate_bps = bitrate_observer_.latest_bitrate(); bitrate_observer_.Reset(); } if (bitrate_update_seen && bitrate_bps > target_bitrate) { break; } } EXPECT_TRUE(bitrate_update_seen); return bitrate_bps; } void LegacyDelayBasedBweTest::InitialBehaviorTestHelper( uint32_t expected_converge_bitrate) { const int kFramerate = 50; // 50 fps to avoid rounding errors. const int kFrameIntervalMs = 1000 / kFramerate; const PacedPacketInfo kPacingInfo(0, 5, 5000); uint32_t bitrate_bps = 0; int64_t send_time_ms = 0; uint16_t sequence_number = 0; std::vector ssrcs; EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps)); EXPECT_EQ(0u, ssrcs.size()); clock_.AdvanceTimeMilliseconds(1000); EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps)); EXPECT_FALSE(bitrate_observer_.updated()); bitrate_observer_.Reset(); clock_.AdvanceTimeMilliseconds(1000); // Inserting packets for 5 seconds to get a valid estimate. for (int i = 0; i < 5 * kFramerate + 1 + kNumInitialPackets; ++i) { // NOTE!!! If the following line is moved under the if case then this test // wont work on windows realease bots. PacedPacketInfo pacing_info = i < kInitialProbingPackets ? kPacingInfo : PacedPacketInfo(); if (i == kNumInitialPackets) { EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps)); EXPECT_EQ(0u, ssrcs.size()); EXPECT_FALSE(bitrate_observer_.updated()); bitrate_observer_.Reset(); } IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, sequence_number++, kMtu, pacing_info); clock_.AdvanceTimeMilliseconds(1000 / kFramerate); send_time_ms += kFrameIntervalMs; } EXPECT_TRUE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps)); ASSERT_EQ(1u, ssrcs.size()); EXPECT_EQ(kDefaultSsrc, ssrcs.front()); EXPECT_NEAR(expected_converge_bitrate, bitrate_bps, kAcceptedBitrateErrorBps); EXPECT_TRUE(bitrate_observer_.updated()); bitrate_observer_.Reset(); EXPECT_EQ(bitrate_observer_.latest_bitrate(), bitrate_bps); } void LegacyDelayBasedBweTest::RateIncreaseReorderingTestHelper( uint32_t expected_bitrate_bps) { const int kFramerate = 50; // 50 fps to avoid rounding errors. const int kFrameIntervalMs = 1000 / kFramerate; const PacedPacketInfo kPacingInfo(0, 5, 5000); int64_t send_time_ms = 0; uint16_t sequence_number = 0; // Inserting packets for five seconds to get a valid estimate. for (int i = 0; i < 5 * kFramerate + 1 + kNumInitialPackets; ++i) { // NOTE!!! If the following line is moved under the if case then this test // wont work on windows realease bots. PacedPacketInfo pacing_info = i < kInitialProbingPackets ? kPacingInfo : PacedPacketInfo(); // TODO(sprang): Remove this hack once the single stream estimator is gone, // as it doesn't do anything in Process(). if (i == kNumInitialPackets) { // Process after we have enough frames to get a valid input rate estimate. EXPECT_FALSE(bitrate_observer_.updated()); // No valid estimate. } IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, sequence_number++, kMtu, pacing_info); clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); send_time_ms += kFrameIntervalMs; } EXPECT_TRUE(bitrate_observer_.updated()); EXPECT_NEAR(expected_bitrate_bps, bitrate_observer_.latest_bitrate(), kAcceptedBitrateErrorBps); for (int i = 0; i < 10; ++i) { clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs); send_time_ms += 2 * kFrameIntervalMs; IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, sequence_number + 2, 1000); IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms - kFrameIntervalMs, sequence_number + 1, 1000); sequence_number += 2; } EXPECT_TRUE(bitrate_observer_.updated()); EXPECT_NEAR(expected_bitrate_bps, bitrate_observer_.latest_bitrate(), kAcceptedBitrateErrorBps); } // Make sure we initially increase the bitrate as expected. void LegacyDelayBasedBweTest::RateIncreaseRtpTimestampsTestHelper( int expected_iterations) { // This threshold corresponds approximately to increasing linearly with // bitrate(i) = 1.04 * bitrate(i-1) + 1000 // until bitrate(i) > 500000, with bitrate(1) ~= 30000. uint32_t bitrate_bps = 30000; int iterations = 0; AddDefaultStream(); // Feed the estimator with a stream of packets and verify that it reaches // 500 kbps at the expected time. while (bitrate_bps < 5e5) { bool overuse = GenerateAndProcessFrame(kDefaultSsrc, bitrate_bps); if (overuse) { EXPECT_GT(bitrate_observer_.latest_bitrate(), bitrate_bps); bitrate_bps = bitrate_observer_.latest_bitrate(); bitrate_observer_.Reset(); } else if (bitrate_observer_.updated()) { bitrate_bps = bitrate_observer_.latest_bitrate(); bitrate_observer_.Reset(); } ++iterations; } ASSERT_EQ(expected_iterations, iterations); } void LegacyDelayBasedBweTest::CapacityDropTestHelper( int number_of_streams, bool wrap_time_stamp, uint32_t expected_bitrate_drop_delta, int64_t receiver_clock_offset_change_ms) { const int kFramerate = 30; const int kStartBitrate = 900e3; const int kMinExpectedBitrate = 800e3; const int kMaxExpectedBitrate = 1100e3; const uint32_t kInitialCapacityBps = 1000e3; const uint32_t kReducedCapacityBps = 500e3; int steady_state_time = 0; if (number_of_streams <= 1) { steady_state_time = 10; AddDefaultStream(); } else { steady_state_time = 10 * number_of_streams; int bitrate_sum = 0; int kBitrateDenom = number_of_streams * (number_of_streams - 1); for (int i = 0; i < number_of_streams; i++) { // First stream gets half available bitrate, while the rest share the // remaining half i.e.: 1/2 = Sum[n/(N*(N-1))] for n=1..N-1 (rounded up) int bitrate = kStartBitrate / 2; if (i > 0) { bitrate = (kStartBitrate * i + kBitrateDenom / 2) / kBitrateDenom; } stream_generator_->AddStream(new test::RtpStream(kFramerate, bitrate)); bitrate_sum += bitrate; } ASSERT_EQ(bitrate_sum, kStartBitrate); } // Run in steady state to make the estimator converge. stream_generator_->set_capacity_bps(kInitialCapacityBps); uint32_t bitrate_bps = SteadyStateRun( kDefaultSsrc, steady_state_time * kFramerate, kStartBitrate, kMinExpectedBitrate, kMaxExpectedBitrate, kInitialCapacityBps); EXPECT_NEAR(kInitialCapacityBps, bitrate_bps, 180000u); bitrate_observer_.Reset(); // Add an offset to make sure the BWE can handle it. arrival_time_offset_ms_ += receiver_clock_offset_change_ms; // Reduce the capacity and verify the decrease time. stream_generator_->set_capacity_bps(kReducedCapacityBps); int64_t overuse_start_time = clock_.TimeInMilliseconds(); int64_t bitrate_drop_time = -1; for (int i = 0; i < 100 * number_of_streams; ++i) { GenerateAndProcessFrame(kDefaultSsrc, bitrate_bps); if (bitrate_drop_time == -1 && bitrate_observer_.latest_bitrate() <= kReducedCapacityBps) { bitrate_drop_time = clock_.TimeInMilliseconds(); } if (bitrate_observer_.updated()) bitrate_bps = bitrate_observer_.latest_bitrate(); } EXPECT_NEAR(expected_bitrate_drop_delta, bitrate_drop_time - overuse_start_time, 33); } void LegacyDelayBasedBweTest::TestTimestampGroupingTestHelper() { const int kFramerate = 50; // 50 fps to avoid rounding errors. const int kFrameIntervalMs = 1000 / kFramerate; int64_t send_time_ms = 0; uint16_t sequence_number = 0; // Initial set of frames to increase the bitrate. 6 seconds to have enough // time for the first estimate to be generated and for Process() to be called. for (int i = 0; i <= 6 * kFramerate; ++i) { IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, sequence_number++, 1000); clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); send_time_ms += kFrameIntervalMs; } EXPECT_TRUE(bitrate_observer_.updated()); EXPECT_GE(bitrate_observer_.latest_bitrate(), 400000u); // Insert batches of frames which were sent very close in time. Also simulate // capacity over-use to see that we back off correctly. const int kTimestampGroupLength = 15; for (int i = 0; i < 100; ++i) { for (int j = 0; j < kTimestampGroupLength; ++j) { // Insert |kTimestampGroupLength| frames with just 1 timestamp ticks in // between. Should be treated as part of the same group by the estimator. IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, sequence_number++, 100); clock_.AdvanceTimeMilliseconds(kFrameIntervalMs / kTimestampGroupLength); send_time_ms += 1; } // Increase time until next batch to simulate over-use. clock_.AdvanceTimeMilliseconds(10); send_time_ms += kFrameIntervalMs - kTimestampGroupLength; } EXPECT_TRUE(bitrate_observer_.updated()); // Should have reduced the estimate. EXPECT_LT(bitrate_observer_.latest_bitrate(), 400000u); } void LegacyDelayBasedBweTest::TestWrappingHelper(int silence_time_s) { const int kFramerate = 100; const int kFrameIntervalMs = 1000 / kFramerate; int64_t send_time_ms = 0; uint16_t sequence_number = 0; for (size_t i = 0; i < 3000; ++i) { IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, sequence_number++, 1000); clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); send_time_ms += kFrameIntervalMs; } uint32_t bitrate_before = 0; std::vector ssrcs; bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_before); clock_.AdvanceTimeMilliseconds(silence_time_s * 1000); send_time_ms += silence_time_s * 1000; for (size_t i = 0; i < 24; ++i) { IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms, sequence_number++, 1000); clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs); send_time_ms += kFrameIntervalMs; } uint32_t bitrate_after = 0; bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_after); EXPECT_LT(bitrate_after, bitrate_before); } } // namespace webrtc