From b31fc75eb639f2937d741bb3e1b98a78408a262a Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Fri, 13 Apr 2018 11:48:11 -0700 Subject: [PATCH] Use max bitrate in Representation@bandwidth instead of average bitrate According to DASH spec (23009-1:2014): Consider a hypothetical constant bitrate channel of bandwidth with the value of this attribute in bits per second (bps). Then, if the Representation is continuously delivered at this bitrate, starting at any SAP that is indicated either by @startwithsap or by any Segment Index box, a client can be assured of having enough data for continuous playout providing playout begins after @minbuffertime * @bandwidth bits have been received (i.e. at time @minbuffertime after the first bit is received). For dependent Representations this value specifies the bandwidth according to the above definition for the aggregation of this Representation and all complementary Representations. This suggests that max bitrate should be used instead of average bitrate. Also cleaned up BandwidthEstimator code. Fixes #376. Change-Id: Ibf5896394c5c6bb820849771a2129c59202d2273 --- .../bear-640x360-av-live-cenc-golden.mpd | 4 +- ...ar-640x360-av-live-cenc-non-iop-golden.mpd | 4 +- ...r-640x360-av-live-cenc-rotation-golden.mpd | 4 +- ...0-av-live-cenc-rotation-no-pssh-golden.mpd | 4 +- ...0-av-live-cenc-rotation-non-iop-golden.mpd | 4 +- .../testdata/bear-640x360-av-live-golden.mpd | 4 +- ...-640x360-av-live-static-ad_cues-golden.mpd | 4 +- .../bear-640x360-av-live-static-golden.mpd | 4 +- packager/mpd/base/bandwidth_estimator.cc | 87 +++++++++++-------- packager/mpd/base/bandwidth_estimator.h | 74 ++++++++++++---- .../mpd/base/bandwidth_estimator_unittest.cc | 63 +++++++++----- packager/mpd/base/representation.cc | 2 +- packager/mpd/base/representation_unittest.cc | 4 +- 13 files changed, 169 insertions(+), 93 deletions(-) diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd index 9849d82ef7..cf91185d7a 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd @@ -7,7 +7,7 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + @@ -22,7 +22,7 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-non-iop-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-non-iop-golden.mpd index 41e66c930f..76eddfd8d1 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-non-iop-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-non-iop-golden.mpd @@ -3,7 +3,7 @@ - + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== @@ -18,7 +18,7 @@ - + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd index e9af6691d3..9d67db21b3 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd @@ -5,7 +5,7 @@ - + @@ -18,7 +18,7 @@ - + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-no-pssh-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-no-pssh-golden.mpd index 59ef0f3a3d..cb99e314b4 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-no-pssh-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-no-pssh-golden.mpd @@ -5,7 +5,7 @@ - + @@ -18,7 +18,7 @@ - + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-non-iop-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-non-iop-golden.mpd index 1244a592b8..682fa433ac 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-non-iop-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-non-iop-golden.mpd @@ -3,7 +3,7 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/packager/app/test/testdata/bear-640x360-av-live-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-golden.mpd index 5a0e4cdfca..99bca76bdb 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-golden.mpd @@ -3,7 +3,7 @@ - + @@ -14,7 +14,7 @@ - + diff --git a/packager/app/test/testdata/bear-640x360-av-live-static-ad_cues-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-static-ad_cues-golden.mpd index 72f56ed30c..15c92e2adf 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-static-ad_cues-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-static-ad_cues-golden.mpd @@ -3,7 +3,7 @@ - + @@ -13,7 +13,7 @@ - + diff --git a/packager/app/test/testdata/bear-640x360-av-live-static-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-static-golden.mpd index a4c435e0b0..7b5e2ead31 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-static-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-static-golden.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 49a63a7592..fbfc95c43a 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -240,7 +240,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 8210e39095..a318de76ae 100644 --- a/packager/mpd/base/representation_unittest.cc +++ b/packager/mpd/base/representation_unittest.cc @@ -461,7 +461,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()); } @@ -721,7 +721,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()); }