pub trait BandwidthEstimator: Send + Sync {
// Required methods
fn on_reports(&mut self, now: Instant, reports: &[PacketReport]);
fn target_bitrate(&self) -> f64;
// Provided methods
fn handle_timeout(&mut self, _now: Instant) { ... }
fn poll_timeout(&self) -> Option<Instant> { ... }
fn stats(&self) -> EstimatorStats { ... }
}Expand description
A congestion control algorithm: acknowledged packets in, a target bitrate out.
§Why this is not an Interceptor
An estimator does not transform packets, it observes them. Making it an interceptor would give
it a position in the chain ordering it does not need, four bind methods it has no use for, and —
fatally — bury it inside a Box<dyn Interceptor> the application cannot reach.
CongestionControlInterceptor is the interceptor; this is the algorithm it drives.
§Why the seam is here rather than at the RTCP packet
Upstream’s equivalent interface has six methods, of which one returns a writer, one takes raw RTCP, and one takes a callback. A custom estimator there must parse feedback, own a history of sent packets, own a pacer, and correctly wrap a writer — four responsibilities that have nothing to do with the algorithm, reimplemented per algorithm.
Here PacketReport is already resolved by History: departure joined with
arrival, per packet, in send order. What is left is a function from acknowledgements to a number,
which is what a congestion control algorithm actually is.
§Clocks
There are none. Every instant arrives as a parameter, so a test can pin an exact bitrate trajectory for a given feedback sequence rather than asserting that something eventually happens.
Required Methods§
Sourcefn on_reports(&mut self, now: Instant, reports: &[PacketReport])
fn on_reports(&mut self, now: Instant, reports: &[PacketReport])
Packets whose fate the remote has now reported, in send order.
Each report carries the instant it left this endpoint — the pacer’s release instant, not the instant the application enqueued it — the instant it arrived on the receiver’s clock, its size, and whether it arrived at all. That is everything a delay-based or loss-based estimator needs.
May be called with an empty slice; an implementation should treat that as “no news”.
Sourcefn target_bitrate(&self) -> f64
fn target_bitrate(&self) -> f64
The current estimate, in bits per second.
Read after every on_reports and after every
handle_timeout; a change is what reaches the pacer.
Provided Methods§
Sourcefn handle_timeout(&mut self, _now: Instant)
fn handle_timeout(&mut self, _now: Instant)
Periodic work, for an estimator that has any. Most do not.
Sourcefn poll_timeout(&self) -> Option<Instant>
fn poll_timeout(&self) -> Option<Instant>
When this estimator next wants waking, or None if it does not.
None when idle, and the instant must advance — a deadline at or before the now just
handed to handle_timeout is a busy-loop that wakes the whole chain.
Sourcefn stats(&self) -> EstimatorStats
fn stats(&self) -> EstimatorStats
Whatever the algorithm wants to expose. Never load-bearing.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".