Skip to content

Instantly share code, notes, and snippets.

@IMelker
Created July 21, 2023 07:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IMelker/16f26747fda4c21ef56eb5aad501d2ea to your computer and use it in GitHub Desktop.
Save IMelker/16f26747fda4c21ef56eb5aad501d2ea to your computer and use it in GitHub Desktop.
Bayesian bitrate estimator
#include <cmath>
#define INITIAL_WINDOW_MS 500
#define NONINITIAL_WINDOWS_MS 150
#define UNCERTAINTY_SCALE 10.0f
#define UNCERTAINTY_SYMMETRY_CAP 0.0f
#define ESTIMATE_FLOOR_KBPS 0.0f
class BitrateEstimator {
public:
BitrateEstimator();
~BitrateEstimator() = default;
void update(int64_t nowMs, int dataSize);
float bitrate() const;
private:
float updateWindow(int64_t nowMs, int bytes, int rateWindowMs);
int sum = 0;
int64_t currentWindowMs= 0;
int64_t prevTimeMs = -1;
float bitrateEstimateKbps = -1.0f;
float bitrateEstimateVar = 50.0f;
};
BitrateEstimator::BitrateEstimator() {
// A higher bitrate-estimate variance value allows the bitrate to change fast for the next few samples.
bitrateEstimateVar += 200;
}
void BitrateEstimator::update(int64_t nowMs, int dataSize) {
int rateWindowMs = NONINITIAL_WINDOWS_MS;
// We use a larger window at the beginning to get a more stable sample.
if (bitrateEstimateKbps < 0.f)
rateWindowMs = INITIAL_WINDOW_MS;
float bitrateSampleKbps = updateWindow(nowMs, dataSize, rateWindowMs);
if (bitrateSampleKbps < 0.0f)
return;
if (bitrateEstimateKbps < 0.0f) {
// First sample to initialize the estimate.
bitrateEstimateKbps = bitrateSampleKbps;
return;
}
// Define the sample uncertainty as a function of how far away it is from the current estimate.
// With low values of UNCERTAINTY_SYMMETRY_CAP we add more uncertainty to increases than to decreases.
// For higher values we approach symmetry.
float sampleUncertainty = UNCERTAINTY_SCALE * std::abs(bitrateEstimateKbps - bitrateSampleKbps) /
(bitrateEstimateKbps + std::min(bitrateSampleKbps, UNCERTAINTY_SYMMETRY_CAP));
float sampleVar = sampleUncertainty * sampleUncertainty;
// Update a bayesian estimate of the rate, weighting it lower if the sample uncertainty is large.
// The bitrate estimate uncertainty is increased with each update to model that the bitrate changes over time.
float predBitrateEstimateVar = bitrateEstimateVar + 5.f;
bitrateEstimateKbps = (sampleVar * bitrateEstimateKbps + predBitrateEstimateVar * bitrateSampleKbps) /
(sampleVar + predBitrateEstimateVar);
bitrateEstimateKbps = std::max(bitrateEstimateKbps, ESTIMATE_FLOOR_KBPS);
bitrateEstimateVar = sampleVar * predBitrateEstimateVar /
(sampleVar + predBitrateEstimateVar);
}
float BitrateEstimator::updateWindow(int64_t nowMs, int bytes, int rateWindowMs) {
// Reset if time moves backwards.
if (nowMs < prevTimeMs) {
prevTimeMs = -1;
sum = 0;
currentWindowMs = 0;
}
if (prevTimeMs >= 0) {
currentWindowMs += nowMs - prevTimeMs;
// Reset if nothing has been received for more than a full window.
if (nowMs - prevTimeMs > rateWindowMs) {
sum = 0;
currentWindowMs %= rateWindowMs;
}
}
prevTimeMs = nowMs;
float bitrateSample = -1.0f;
if (currentWindowMs >= rateWindowMs) {
bitrateSample = 8.0f * sum / static_cast<float>(rateWindowMs);
currentWindowMs -= rateWindowMs;
sum = 0;
}
sum += bytes;
return bitrateSample;
}
float BitrateEstimator::bitrate() const {
return std::max(bitrateEstimateKbps, ESTIMATE_FLOOR_KBPS);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment