diff --git a/packager/app/test/testdata/live-profile-and-encryption-and-non-dash-if-iop/output.mpd b/packager/app/test/testdata/live-profile-and-encryption-and-non-dash-if-iop/output.mpd index 2f573473c8..bea52dd9fd 100644 --- a/packager/app/test/testdata/live-profile-and-encryption-and-non-dash-if-iop/output.mpd +++ b/packager/app/test/testdata/live-profile-and-encryption-and-non-dash-if-iop/output.mpd @@ -3,7 +3,7 @@ - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== @@ -18,7 +18,7 @@ - + diff --git a/packager/app/test/testdata/live-profile-and-encryption/output.mpd b/packager/app/test/testdata/live-profile-and-encryption/output.mpd index 416e419b6f..af3716c67c 100644 --- a/packager/app/test/testdata/live-profile-and-encryption/output.mpd +++ b/packager/app/test/testdata/live-profile-and-encryption/output.mpd @@ -7,7 +7,7 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + @@ -22,7 +22,7 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + diff --git a/packager/app/test/testdata/live-profile-and-key-rotation-and-no-pssh-in-stream/output.mpd b/packager/app/test/testdata/live-profile-and-key-rotation-and-no-pssh-in-stream/output.mpd index d1faca5262..363a07f620 100644 --- a/packager/app/test/testdata/live-profile-and-key-rotation-and-no-pssh-in-stream/output.mpd +++ b/packager/app/test/testdata/live-profile-and-key-rotation-and-no-pssh-in-stream/output.mpd @@ -5,7 +5,7 @@ - + @@ -18,7 +18,7 @@ - + diff --git a/packager/app/test/testdata/live-profile-and-key-rotation-and-non-dash-if-iop/output.mpd b/packager/app/test/testdata/live-profile-and-key-rotation-and-non-dash-if-iop/output.mpd index 51a6f3df78..ce87c5b9e2 100644 --- a/packager/app/test/testdata/live-profile-and-key-rotation-and-non-dash-if-iop/output.mpd +++ b/packager/app/test/testdata/live-profile-and-key-rotation-and-non-dash-if-iop/output.mpd @@ -3,7 +3,7 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/packager/app/test/testdata/live-profile-and-key-rotation/output.mpd b/packager/app/test/testdata/live-profile-and-key-rotation/output.mpd index 4c9eb7365c..73d35287c2 100644 --- a/packager/app/test/testdata/live-profile-and-key-rotation/output.mpd +++ b/packager/app/test/testdata/live-profile-and-key-rotation/output.mpd @@ -5,7 +5,7 @@ - + @@ -18,7 +18,7 @@ - + diff --git a/packager/app/test/testdata/live-profile/output.mpd b/packager/app/test/testdata/live-profile/output.mpd index b77da2c78f..a0f2f0ccf1 100644 --- a/packager/app/test/testdata/live-profile/output.mpd +++ b/packager/app/test/testdata/live-profile/output.mpd @@ -3,7 +3,7 @@ - + @@ -14,7 +14,7 @@ - + diff --git a/packager/app/test/testdata/live-static-profile-and-ad-cues/output.mpd b/packager/app/test/testdata/live-static-profile-and-ad-cues/output.mpd index e024ad5333..b65833f3ac 100644 --- a/packager/app/test/testdata/live-static-profile-and-ad-cues/output.mpd +++ b/packager/app/test/testdata/live-static-profile-and-ad-cues/output.mpd @@ -3,7 +3,7 @@ - + @@ -13,7 +13,7 @@ - + diff --git a/packager/app/test/testdata/live-static-profile/output.mpd b/packager/app/test/testdata/live-static-profile/output.mpd index 5c2a2b0a90..72137159c4 100644 --- a/packager/app/test/testdata/live-static-profile/output.mpd +++ b/packager/app/test/testdata/live-static-profile/output.mpd @@ -3,7 +3,7 @@ - + @@ -14,7 +14,7 @@ - + diff --git a/packager/mpd/base/bandwidth_estimator.cc b/packager/mpd/base/bandwidth_estimator.cc index 1f8d6fd37d..38cf42edb0 100644 --- a/packager/mpd/base/bandwidth_estimator.cc +++ b/packager/mpd/base/bandwidth_estimator.cc @@ -7,57 +7,68 @@ #include "packager/mpd/base/bandwidth_estimator.h" #include -#include #include "packager/base/logging.h" -const int BandwidthEstimator::kUseAllBlocks = 0; +namespace shaka { -BandwidthEstimator::BandwidthEstimator(int num_blocks) - : num_blocks_for_estimation_(num_blocks), - harmonic_mean_denominator_(0.0), - num_blocks_added_(0) {} +BandwidthEstimator::BandwidthEstimator(size_t num_blocks) + : sliding_queue_(num_blocks) {} BandwidthEstimator::~BandwidthEstimator() {} void BandwidthEstimator::AddBlock(uint64_t size, double duration) { DCHECK_GT(duration, 0.0); DCHECK_GT(size, 0u); - if (num_blocks_for_estimation_ < 0 && - static_cast(history_.size()) >= -1 * num_blocks_for_estimation_) { - // Short circuiting the case where |num_blocks_for_estimation_| number of - // blocks have been added already. - return; - } - const int kBitsInByte = 8; const double bits_per_second_reciprocal = duration / (kBitsInByte * size); - harmonic_mean_denominator_ += bits_per_second_reciprocal; - if (num_blocks_for_estimation_ == kUseAllBlocks) { - DCHECK_EQ(history_.size(), 0u); - ++num_blocks_added_; - return; - } - - history_.push_back(bits_per_second_reciprocal); - if (num_blocks_for_estimation_ > 0 && - static_cast(history_.size()) > num_blocks_for_estimation_) { - harmonic_mean_denominator_ -= history_.front(); - history_.pop_front(); - } - - DCHECK_NE(num_blocks_for_estimation_, kUseAllBlocks); - DCHECK_LE(static_cast(history_.size()), abs(num_blocks_for_estimation_)); - DCHECK_EQ(num_blocks_added_, 0u); - return; + sliding_queue_.Add(bits_per_second_reciprocal); } uint64_t BandwidthEstimator::Estimate() const { - if (harmonic_mean_denominator_ == 0.0) - return 0; - - const uint64_t num_blocks = num_blocks_for_estimation_ == kUseAllBlocks - ? num_blocks_added_ - : history_.size(); - return static_cast(ceil(num_blocks / harmonic_mean_denominator_)); + return sliding_queue_.size() == 0 + ? 0 + : static_cast( + ceil(sliding_queue_.size() / sliding_queue_.sum())); } + +uint64_t BandwidthEstimator::Max() const { + // The first element has minimum "bits per second reciprocal", thus the + // reverse is maximum "bits per second". + return sliding_queue_.size() == 0 + ? 0 + : static_cast(ceil(1 / sliding_queue_.min())); +} + +BandwidthEstimator::SlidingQueue::SlidingQueue(size_t window_size) + : window_size_(window_size) {} + +void BandwidthEstimator::SlidingQueue::Add(double value) { + // Remove elements if needed to form a monotonic non-decreasing sequence. + while (!min_.empty() && min_.back() > value) + min_.pop_back(); + min_.push_back(value); + + if (window_size_ == kUseAllBlocks) { + size_++; + sum_ += value; + min_.resize(1); // Keep only the minimum one. + return; + } + + window_.push_back(value); + sum_ += value; + + if (window_.size() <= window_size_) { + size_++; + return; + } + + if (min_.front() == window_.front()) + min_.pop_front(); + + sum_ -= window_.front(); + window_.pop_front(); +} + +} // namespace shaka diff --git a/packager/mpd/base/bandwidth_estimator.h b/packager/mpd/base/bandwidth_estimator.h index c82b59f47d..e7f59e2c55 100644 --- a/packager/mpd/base/bandwidth_estimator.h +++ b/packager/mpd/base/bandwidth_estimator.h @@ -10,34 +10,76 @@ #include #include -#include +#include + +namespace shaka { class BandwidthEstimator { public: - /// @param num_blocks is the number of latest blocks to use. Negative values - /// use first N blocks. 0 uses all. - explicit BandwidthEstimator(int num_blocks); + /// @param num_blocks is the number of latest blocks to use. 0 uses all. + static constexpr size_t kUseAllBlocks = 0; + explicit BandwidthEstimator(size_t num_blocks); ~BandwidthEstimator(); - // @param size is the size of the block in bytes. Should be positive. - // @param duration is the length in seconds. Should be positive. + /// @param size is the size of the block in bytes. Should be positive. + /// @param duration is the length in seconds. Should be positive. void AddBlock(uint64_t size, double duration); - // @return The estimate bandwidth, in bits per second, from the harmonic mean - // of the number of blocks specified in the constructor. The value is - // rounded up to the nearest integer. + /// @return The estimate bandwidth, in bits per second, from the harmonic mean + /// of the number of blocks specified in the constructor. The value is + /// rounded up to the nearest integer. uint64_t Estimate() const; - static const int kUseAllBlocks; + /// @return The max bandwidth, in bits per second, of the number of blocks + /// specified in the constructor. The value is rounded up to the + /// nearest integer. + uint64_t Max() const; private: - const int num_blocks_for_estimation_; - double harmonic_mean_denominator_; + BandwidthEstimator(const BandwidthEstimator&) = delete; + BandwidthEstimator& operator=(const BandwidthEstimator&) = delete; - // This is not used when num_blocks_for_estimation_ != 0. Therefore it should - // always be 0 if num_blocks_for_estimation_ != 0. - size_t num_blocks_added_; - std::list history_; + // A sliding queue that provide convenient functions to get the minimum value + // and the sum when window slides. + class SlidingQueue { + public: + // |window_size| defines the size of the sliding window. 0 uses all. + explicit SlidingQueue(size_t window_size); + + // Add a new value. Old values may be moved out. + void Add(double value); + + // Return the sum of the values in the sliding window. + double sum() const { return sum_; } + // Return the number of values in the sliding window. + double size() const { return size_; } + // Return the minimum value of the values in the sliding window. + double min() const { return min_.front(); } + + private: + SlidingQueue(const SlidingQueue&) = delete; + SlidingQueue& operator=(const SlidingQueue&) = delete; + + const size_t window_size_; + size_t size_ = 0; + double sum_ = 0; + // Keeps track of the values in the sliding window. Not needed if + // |window_size| is kUseAllBlocks. + std::deque window_; + // Keeps track of a monotonic non-decreasing sequence of values, i.e. + // local minimum values in the sliding window. The front() is always the + // global minimum, i.e. the minimum value in the sliding window. + // This is achieved through: + // (1) New value is added to the back with the original values before it + // that are larger removed as they are no longer useful. + // (2) When a value is removed from |window_|, if it is the minimum value, + // it is also removed from |min_|; if it is not, it means the value is not + // present in |min_|. + std::deque min_; + }; + SlidingQueue sliding_queue_; }; +} // namespace shaka + #endif // MPD_BASE_BANDWIDTH_ESTIMATOR_H_ diff --git a/packager/mpd/base/bandwidth_estimator_unittest.cc b/packager/mpd/base/bandwidth_estimator_unittest.cc index 947756c21c..ad24cc85e2 100644 --- a/packager/mpd/base/bandwidth_estimator_unittest.cc +++ b/packager/mpd/base/bandwidth_estimator_unittest.cc @@ -14,10 +14,15 @@ namespace shaka { namespace { -const int kNumBlocksForEstimate = 5; -const int kFirstOneBlockForEstimate = -1; +const size_t kNumBlocksForEstimate = 5; const uint64_t kBitsInByte = 8; const int kEstimateRoundError = 1; + +struct Bandwidth { + uint64_t average; + uint64_t max; +}; + } // namespace // Make sure that averaging of 5 blocks works, and also when there aren't all 5 @@ -25,23 +30,20 @@ const int kEstimateRoundError = 1; TEST(BandwidthEstimatorTest, FiveBlocksFiveBlocksAdded) { BandwidthEstimator be(kNumBlocksForEstimate); const double kDuration = 1.0; - const uint64_t kExpectedResults[] = { + const Bandwidth kExpectedResults[] = { // Harmonic mean of [1 * 8], [1 * 8, 2 * 8], ... // 8 is the number of bits in a byte and 1, 2, ... is from the loop // counter below. // Note that these are rounded up. - 8, - 11, - 14, - 16, - 18 + {8, 8}, {11, 2 * 8}, {14, 3 * 8}, {16, 4 * 8}, {18, 5 * 8}, }; static_assert(kNumBlocksForEstimate == arraysize(kExpectedResults), "incorrect_number_of_expectations"); for (uint64_t i = 1; i <= arraysize(kExpectedResults); ++i) { be.AddBlock(i, kDuration); - EXPECT_EQ(kExpectedResults[i - 1], be.Estimate()); + EXPECT_EQ(kExpectedResults[i - 1].average, be.Estimate()); + EXPECT_EQ(kExpectedResults[i - 1].max, be.Max()); } } @@ -65,9 +67,10 @@ TEST(BandwidthEstimatorTest, FiveBlocksNormal) { } EXPECT_NEAR(kExptectedEstimate, be.Estimate(), kEstimateRoundError); + // All blocks are of the same bitrate, so Max is the same as average. + EXPECT_NEAR(kExptectedEstimate, be.Max(), kEstimateRoundError); } -// Average all the blocks! TEST(BandwidthEstimatorTest, AllBlocks) { BandwidthEstimator be(BandwidthEstimator::kUseAllBlocks); const uint64_t kNumBlocksToAdd = 100; @@ -78,19 +81,39 @@ TEST(BandwidthEstimatorTest, AllBlocks) { // The harmonic mean of 8, 16, ... , 800; rounded up. const uint64_t kExptectedEstimate = 155; EXPECT_EQ(kExptectedEstimate, be.Estimate()); + const uint64_t kMax = 100 * 8; + EXPECT_EQ(kMax, be.Max()); } -// Use only the first one. -TEST(BandwidthEstimatorTest, FirstOneBlock) { - BandwidthEstimator be(kFirstOneBlockForEstimate); - const double kDuration = 11.0; - const uint64_t kExptectedEstimate = 123456; - be.AddBlock(kExptectedEstimate * kDuration / kBitsInByte, kDuration); +TEST(BandwidthEstimatorTest, MaxWithSlidingWindow) { + BandwidthEstimator be(kNumBlocksForEstimate); + const double kDuration = 1.0 * kBitsInByte; - // Anything. Should be ignored. - for (int i = 0; i < 1000; ++i) - be.AddBlock(100000, 10); - EXPECT_EQ(kExptectedEstimate, be.Estimate()); + // clang-format off + const uint64_t kSizes[] = { + // Sequence 1: Monotonic decreasing. + 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, + // Sequence 2: Monotonic increasing. + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + // Sequence 3: Random sequence. + 10, 1, 9, 6, 9, 5, 4, 9, 7, 8, + }; + const uint64_t kExpectedMaxes[] = { + // Sequence 1. + 10, 10, 10, 10, 10, 9, 8, 7, 6, 5, + // Sequence 2. + 4, 3, 3, 4, 5, 6, 7, 8, 9, 10, + // Sequence 3. + 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, + }; + // clang-format on + + static_assert(arraysize(kSizes) == arraysize(kExpectedMaxes), + "incorrect_number_of_expectations"); + for (size_t i = 0; i < arraysize(kSizes); ++i) { + be.AddBlock(kSizes[i], kDuration); + EXPECT_EQ(kExpectedMaxes[i], be.Max()); + } } } // namespace shaka diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc index ab44094d29..4680d063f9 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -242,7 +242,7 @@ xml::scoped_xml_ptr Representation::GetXml() { const uint64_t bandwidth = media_info_.has_bandwidth() ? media_info_.bandwidth() - : bandwidth_estimator_.Estimate(); + : bandwidth_estimator_.Max(); DCHECK(!(HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_))); diff --git a/packager/mpd/base/representation_unittest.cc b/packager/mpd/base/representation_unittest.cc index 241b1fb26b..6d9941543a 100644 --- a/packager/mpd/base/representation_unittest.cc +++ b/packager/mpd/base/representation_unittest.cc @@ -470,7 +470,7 @@ class SegmentTemplateTest : public RepresentationTest { " \n" " \n" "\n"; - return base::StringPrintf(kOutputTemplate, bandwidth_estimator_.Estimate(), + return base::StringPrintf(kOutputTemplate, bandwidth_estimator_.Max(), expected_s_elements_.c_str()); } @@ -730,7 +730,7 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest { " \n" "\n"; - return base::StringPrintf(kOutputTemplate, bandwidth_estimator_.Estimate(), + return base::StringPrintf(kOutputTemplate, bandwidth_estimator_.Max(), expected_start_number, expected_s_element.c_str()); }