Research Background
In 2012, the IETF established the RMCAT Working Group (RTP Media Congestion Avoidance Techniques) to develop standardized congestion control algorithms for real-time communication over the Internet. The emergence of WebRTC and peer-to-peer video conferencing created urgent demand for low-delay, responsive congestion control that could adapt to varying network conditions.
Building on this foundation, our research team received the Google Faculty Research Award in 2014 for advancing congestion control algorithms specifically tailored to WebRTC and low-latency scenarios.
Google Faculty Research Award 2014
Project Title: "Congestion Control for Web Real-Time Communication (WebRTC)"
Principal Investigator: S. Mascolo — Date: August 2014
Award granted for collaborating on the project "Congestion control for WebRTC" aiming at designing a real-time congestion control algorithm for video conferencing. Press coverage: La Repubblica Bari.
Publications
Our research produced peer-reviewed publications in top-tier venues:
- S. Holmer, H. Lundin, G. Carlucci, L. De Cicco, S. Mascolo — A Google Congestion Control Algorithm for Real-Time Communication — IETF draft, RMCAT WG, draft-ietf-rmcat-gcc-01, Oct 2015
- G. Carlucci, L. De Cicco, S. Holmer, S. Mascolo — Congestion Control for Web Real-Time Communication — IEEE/ACM Transactions on Networking, vol. 25, no. 5, pp. 2629–2642, Oct. 2017 (DOI: 10.1109/TNET.2017.2703615)
- L. De Cicco, G. Carlucci, S. Mascolo — Understanding the Dynamic Behaviour of the Google Congestion Control for RTCWeb — Packet Video Workshop, San Jose, CA, USA, 2013
- L. De Cicco, G. Carlucci, S. Mascolo — Experimental Investigation of the Google Congestion Control for Real-Time Flows — ACM SIGCOMM 2013 Workshop on Future Human-Centric Multimedia Networking, Hong Kong, China, August 2013
- G. Carlucci, L. De Cicco, S. Mascolo — Analysis and Design of the Google Congestion Control for Web Real-time Communication (WebRTC) — Proc. ACM MMSys, Klagenfurt, Austria, May 2016
- G. Carlucci, L. De Cicco, S. Mascolo — Modelling and Control for Web Real-Time Communication — Proc. of 53rd IEEE Conference on Decision and Control, Los Angeles, CA, USA, December 2014
Google Congestion Control Algorithm (GCC)
Overview
Nowadays, the Internet is rapidly evolving to become an equally efficient platform for multimedia content delivery. While YouTube streams video using TCP, time-sensitive applications such as Video Conferencing employ UDP because they can tolerate small loss percentages but not the delays introduced by TCP's retransmission-based loss recovery. Since UDP does not implement congestion control, these applications must implement it at the application layer. In our papers, we experimentally evaluate the Google Congestion Control (GCC) proposed in the RMCAT IETF WG. We found that the algorithm works as expected when a GCC flow accesses the bottleneck in isolation; however, GCC does not provide fair bandwidth utilization when a GCC flow shares the bottleneck with either another GCC or a TCP flow — our experimental investigation shows that the first version of GCC gets starved when a TCP flow joins the bottleneck, and starvation also occurs when two coexisting GCC flows share the bottleneck. To overcome these issues, we proposed an adaptive threshold mechanism which sets the threshold used by the over-use detector.
Overuse Estimator
The OveruseEstimator code is available on the Chromium/WebRTC codebase. The method Update uses a Kalman filter, described in the papers, that filters the one-way delay variation and link capacity values:
void OveruseEstimator::Update(int64_t t_delta,
double ts_delta,
int size_delta,
BandwidthUsage current_hypothesis,
int64_t now_ms) {
const double min_frame_period = UpdateMinFramePeriod(ts_delta);
const double t_ts_delta = t_delta - ts_delta;
double fs_delta = size_delta;
++num_of_deltas_;
if (num_of_deltas_ > kDeltaCounterMax) {
num_of_deltas_ = kDeltaCounterMax;
}
// Update the Kalman filter.
E_[0][0] += process_noise_[0];
E_[1][1] += process_noise_[1];
if ((current_hypothesis == BandwidthUsage::kBwOverusing &&
offset_ < prev_offset_) ||
(current_hypothesis == BandwidthUsage::kBwUnderusing &&
offset_ > prev_offset_)) {
E_[1][1] += 10 * process_noise_[1];
}
const double h[2] = {fs_delta, 1.0};
const double Eh[2] = {E_[0][0] * h[0] + E_[0][1] * h[1],
E_[1][0] * h[0] + E_[1][1] * h[1]};
const double residual = t_ts_delta - slope_ * h[0] - offset_;
const bool in_stable_state =
(current_hypothesis == BandwidthUsage::kBwNormal);
const double max_residual = 3.0 * sqrt(var_noise_);
// We try to filter out very late frames. For instance periodic key
// frames doesn't fit the Gaussian model well.
if (fabs(residual) < max_residual) {
UpdateNoiseEstimate(residual, min_frame_period, in_stable_state);
} else {
UpdateNoiseEstimate(residual < 0 ? -max_residual : max_residual,
min_frame_period, in_stable_state);
}
const double denom = var_noise_ + h[0] * Eh[0] + h[1] * Eh[1];
const double K[2] = {Eh[0] / denom, Eh[1] / denom};
const double IKh[2][2] = {{1.0 - K[0] * h[0], -K[0] * h[1]},
{-K[1] * h[0], 1.0 - K[1] * h[1]}};
const double e00 = E_[0][0];
const double e01 = E_[0][1];
// Update state.
E_[0][0] = e00 * IKh[0][0] + E_[1][0] * IKh[0][1];
E_[0][1] = e01 * IKh[0][0] + E_[1][1] * IKh[0][1];
E_[1][0] = e00 * IKh[1][0] + E_[1][0] * IKh[1][1];
E_[1][1] = e01 * IKh[1][0] + E_[1][1] * IKh[1][1];
// The covariance matrix must be positive semi-definite.
bool positive_semi_definite =
E_[0][0] + E_[1][1] >= 0 &&
E_[0][0] * E_[1][1] - E_[0][1] * E_[1][0] >= 0 && E_[0][0] >= 0;
RTC_DCHECK(positive_semi_definite);
if (!positive_semi_definite) {
RTC_LOG(LS_ERROR)
<< "The over-use estimator's covariance matrix is no longer "
"semi-definite.";
}
slope_ = slope_ + K[0] * residual;
prev_offset_ = offset_;
offset_ = offset_ + K[1] * residual;
}
Overuse Detector
The OveruseDetector code checks if the filtered one-way delay variation is above a threshold. These events drive a finite state machine that controls the encoding bitrate:
BandwidthUsage OveruseDetector::Detect(double offset,
double ts_delta,
int num_of_deltas,
int64_t now_ms) {
if (num_of_deltas < 2) {
return BandwidthUsage::kBwNormal;
}
const double T = std::min(num_of_deltas, kMaxNumDeltas) * offset;
if (T > threshold_) {
if (time_over_using_ == -1) {
// Initialize the timer. Assume that we've been
// over-using half of the time since the previous
// sample.
time_over_using_ = ts_delta / 2;
} else {
// Increment timer
time_over_using_ += ts_delta;
}
overuse_counter_++;
if (time_over_using_ > kOverUsingTimeThreshold && overuse_counter_ > 1) {
if (offset >= prev_offset_) {
time_over_using_ = 0;
overuse_counter_ = 0;
hypothesis_ = BandwidthUsage::kBwOverusing;
}
}
} else if (T < -threshold_) {
time_over_using_ = -1;
overuse_counter_ = 0;
hypothesis_ = BandwidthUsage::kBwUnderusing;
} else {
time_over_using_ = -1;
overuse_counter_ = 0;
hypothesis_ = BandwidthUsage::kBwNormal;
}
prev_offset_ = offset;
UpdateThreshold(T, now_ms);
return hypothesis_;
}
Adaptive Threshold
The method UpdateThreshold adapts the threshold according to the papers:
void OveruseDetector::UpdateThreshold(double modified_offset, int64_t now_ms) {
if (last_update_ms_ == -1)
last_update_ms_ = now_ms;
if (fabs(modified_offset) > threshold_ + kMaxAdaptOffsetMs) {
// Avoid adapting the threshold to big latency spikes, caused e.g.,
// by a sudden capacity drop.
last_update_ms_ = now_ms;
return;
}
const double k = fabs(modified_offset) < threshold_ ? kDown : kUp;
const int64_t kMaxTimeDeltaMs = 100;
int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs);
threshold_ += k * (fabs(modified_offset) - threshold_) * time_delta_ms;
threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f);
last_update_ms_ = now_ms;
}
Chromium Patches and Get Involved
Chromium patches implementing these changes:
Ways to get involved with Chromium: