Faster implementation of BitRateStats.
Landing cl for hguihot. At high bitrate, EraseOld() could account for a significant part of the total CPU usage on certain platforms. The new implementation eliminates per-packet memory allocations and records the number of bytes in buckets (one bucket per millisecond in window). R=stefan@webrtc.org Review URL: https://webrtc-codereview.appspot.com/4269004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@5175 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
326bcff879
commit
d89b52af80
@ -12,52 +12,73 @@
|
|||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
const float kBitrateAverageWindowMs = 500.0f;
|
const int kBitrateAverageWindowMs = 500;
|
||||||
|
|
||||||
BitRateStats::BitRateStats()
|
BitRateStats::BitRateStats()
|
||||||
: data_samples_(),
|
: num_buckets_(kBitrateAverageWindowMs + 1), // N ms in (N+1) buckets.
|
||||||
accumulated_bytes_(0) {
|
buckets_(new uint32_t[num_buckets_]()),
|
||||||
|
accumulated_bytes_(0),
|
||||||
|
oldest_time_(0),
|
||||||
|
oldest_index_(0),
|
||||||
|
bps_coefficient_(8.f * 1000.f / (num_buckets_ - 1)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
BitRateStats::~BitRateStats() {
|
BitRateStats::~BitRateStats() {
|
||||||
Init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BitRateStats::Init() {
|
void BitRateStats::Init() {
|
||||||
accumulated_bytes_ = 0;
|
accumulated_bytes_ = 0;
|
||||||
while (data_samples_.size() > 0) {
|
oldest_time_ = 0;
|
||||||
delete data_samples_.front();
|
oldest_index_ = 0;
|
||||||
data_samples_.pop_front();
|
for (int i = 0; i < num_buckets_; i++) {
|
||||||
|
buckets_[i] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BitRateStats::Update(uint32_t packet_size_bytes, int64_t now_ms) {
|
void BitRateStats::Update(uint32_t packet_size_bytes, int64_t now_ms) {
|
||||||
// Find an empty slot for storing the new sample and at the same time
|
if (now_ms < oldest_time_) {
|
||||||
// accumulate the history.
|
// Too old data is ignored.
|
||||||
data_samples_.push_back(new DataTimeSizeTuple(packet_size_bytes, now_ms));
|
return;
|
||||||
accumulated_bytes_ += packet_size_bytes;
|
|
||||||
EraseOld(now_ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BitRateStats::EraseOld(int64_t now_ms) {
|
|
||||||
while (data_samples_.size() > 0) {
|
|
||||||
if (now_ms - data_samples_.front()->time_complete_ms >
|
|
||||||
kBitrateAverageWindowMs) {
|
|
||||||
// Delete old sample
|
|
||||||
accumulated_bytes_ -= data_samples_.front()->size_bytes;
|
|
||||||
delete data_samples_.front();
|
|
||||||
data_samples_.pop_front();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EraseOld(now_ms);
|
||||||
|
|
||||||
|
int now_offset = static_cast<int>(now_ms - oldest_time_);
|
||||||
|
assert(now_offset < num_buckets_);
|
||||||
|
int index = oldest_index_ + now_offset;
|
||||||
|
if (index >= num_buckets_) {
|
||||||
|
index -= num_buckets_;
|
||||||
|
}
|
||||||
|
buckets_[index] += packet_size_bytes;
|
||||||
|
accumulated_bytes_ += packet_size_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t BitRateStats::BitRate(int64_t now_ms) {
|
uint32_t BitRateStats::BitRate(int64_t now_ms) {
|
||||||
// Calculate the average bit rate the past BITRATE_AVERAGE_WINDOW ms.
|
|
||||||
// Removes any old samples from the list.
|
|
||||||
EraseOld(now_ms);
|
EraseOld(now_ms);
|
||||||
return static_cast<uint32_t>(accumulated_bytes_ * 8.0f * 1000.0f /
|
return static_cast<uint32_t>(accumulated_bytes_ * bps_coefficient_ + 0.5f);
|
||||||
kBitrateAverageWindowMs + 0.5f);
|
}
|
||||||
|
|
||||||
|
void BitRateStats::EraseOld(int64_t now_ms) {
|
||||||
|
int64_t new_oldest_time = now_ms - num_buckets_ + 1;
|
||||||
|
if (new_oldest_time <= oldest_time_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (oldest_time_ < new_oldest_time) {
|
||||||
|
uint32_t num_bytes_in_oldest_bucket = buckets_[oldest_index_];
|
||||||
|
assert(accumulated_bytes_ >= num_bytes_in_oldest_bucket);
|
||||||
|
accumulated_bytes_ -= num_bytes_in_oldest_bucket;
|
||||||
|
buckets_[oldest_index_] = 0;
|
||||||
|
if (++oldest_index_ >= num_buckets_) {
|
||||||
|
oldest_index_ = 0;
|
||||||
|
}
|
||||||
|
++oldest_time_;
|
||||||
|
if (accumulated_bytes_ == 0) {
|
||||||
|
// This guarantees we go through all the buckets at most once, even if
|
||||||
|
// |new_oldest_time| is far greater than |oldest_time_|.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oldest_time_ = new_oldest_time;
|
||||||
}
|
}
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -11,8 +11,7 @@
|
|||||||
#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_BITRATE_ESTIMATOR_H_
|
#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_BITRATE_ESTIMATOR_H_
|
||||||
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_BITRATE_ESTIMATOR_H_
|
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_BITRATE_ESTIMATOR_H_
|
||||||
|
|
||||||
#include <list>
|
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
|
||||||
|
|
||||||
#include "webrtc/typedefs.h"
|
#include "webrtc/typedefs.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
@ -27,20 +26,24 @@ class BitRateStats {
|
|||||||
uint32_t BitRate(int64_t now_ms);
|
uint32_t BitRate(int64_t now_ms);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct DataTimeSizeTuple {
|
|
||||||
DataTimeSizeTuple(uint32_t size_bytes_in, int64_t time_complete_ms_in)
|
|
||||||
: size_bytes(size_bytes_in),
|
|
||||||
time_complete_ms(time_complete_ms_in) {
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t size_bytes;
|
|
||||||
int64_t time_complete_ms;
|
|
||||||
};
|
|
||||||
|
|
||||||
void EraseOld(int64_t now_ms);
|
void EraseOld(int64_t now_ms);
|
||||||
|
|
||||||
std::list<DataTimeSizeTuple*> data_samples_;
|
// Numbers of bytes are kept in buckets (circular buffer), with one bucket
|
||||||
|
// per millisecond.
|
||||||
|
const int num_buckets_;
|
||||||
|
scoped_array<uint32_t> buckets_;
|
||||||
|
|
||||||
|
// Total number of bytes recorded in buckets.
|
||||||
uint32_t accumulated_bytes_;
|
uint32_t accumulated_bytes_;
|
||||||
|
|
||||||
|
// Oldest time recorded in buckets.
|
||||||
|
int64_t oldest_time_;
|
||||||
|
|
||||||
|
// Bucket index of oldest bytes recorded in buckets.
|
||||||
|
int oldest_index_;
|
||||||
|
|
||||||
|
// To convert number of bytes in bits/second.
|
||||||
|
const float bps_coefficient_;
|
||||||
};
|
};
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ using webrtc::BitRateStats;
|
|||||||
|
|
||||||
class BitRateStatsTest : public ::testing::Test {
|
class BitRateStatsTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
BitRateStatsTest() {};
|
BitRateStatsTest() {}
|
||||||
BitRateStats stats_;
|
BitRateStats stats_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,4 +47,51 @@ TEST_F(BitRateStatsTest, TestStrictMode) {
|
|||||||
// the estimate should be 0.
|
// the estimate should be 0.
|
||||||
EXPECT_EQ(0u, stats_.BitRate(now_ms));
|
EXPECT_EQ(0u, stats_.BitRate(now_ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(BitRateStatsTest, IncreasingThenDecreasingBitrate) {
|
||||||
|
int64_t now_ms = 0;
|
||||||
|
stats_.Init();
|
||||||
|
// Expecting 0 after init.
|
||||||
|
uint32_t bitrate = stats_.BitRate(now_ms);
|
||||||
|
EXPECT_EQ(0u, bitrate);
|
||||||
|
// 1000 bytes per millisecond until plateau is reached.
|
||||||
|
while (++now_ms < 10000) {
|
||||||
|
stats_.Update(1000, now_ms);
|
||||||
|
uint32_t new_bitrate = stats_.BitRate(now_ms);
|
||||||
|
if (new_bitrate != bitrate) {
|
||||||
|
// New bitrate must be higher than previous one.
|
||||||
|
EXPECT_GT(new_bitrate, bitrate);
|
||||||
|
} else {
|
||||||
|
// Plateau reached, 8000 kbps expected.
|
||||||
|
EXPECT_NEAR(8000000u, bitrate, 80000u);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bitrate = new_bitrate;
|
||||||
|
}
|
||||||
|
// 1000 bytes per millisecond until 10-second mark, 8000 kbps expected.
|
||||||
|
while (++now_ms < 10000) {
|
||||||
|
stats_.Update(1000, now_ms);
|
||||||
|
bitrate = stats_.BitRate(now_ms);
|
||||||
|
EXPECT_NEAR(8000000u, bitrate, 80000u);
|
||||||
|
}
|
||||||
|
// Zero bytes per millisecond until 0 is reached.
|
||||||
|
while (++now_ms < 20000) {
|
||||||
|
stats_.Update(0, now_ms);
|
||||||
|
uint32_t new_bitrate = stats_.BitRate(now_ms);
|
||||||
|
if (new_bitrate != bitrate) {
|
||||||
|
// New bitrate must be lower than previous one.
|
||||||
|
EXPECT_LT(new_bitrate, bitrate);
|
||||||
|
} else {
|
||||||
|
// 0 kbps expected.
|
||||||
|
EXPECT_EQ(0u, bitrate);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bitrate = new_bitrate;
|
||||||
|
}
|
||||||
|
// Zero bytes per millisecond until 20-second mark, 0 kbps expected.
|
||||||
|
while (++now_ms < 20000) {
|
||||||
|
stats_.Update(0, now_ms);
|
||||||
|
EXPECT_EQ(0u, stats_.BitRate(now_ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
Loading…
x
Reference in New Issue
Block a user