File
Blob: firmware/vendor/str0m/src/bwe/smoother.rs
| 1 | use std::collections::VecDeque; |
| 2 | use std::time::{Duration, Instant}; |
| 3 | |
| 4 | use crate::rtp_::Bitrate; |
| 5 | |
| 6 | /// Time window for averaging estimate changes. |
| 7 | const ESTIMATE_WINDOW: Duration = Duration::from_secs(3); |
| 8 | |
| 9 | type TimeBitrate = (Instant, Bitrate); |
| 10 | |
| 11 | const TOLERANCE: f64 = 0.05; |
| 12 | |
| 13 | /// Smooths BWE estimates by averaging over a time window. |
| 14 | pub struct EstimateSmoother { |
| 15 | estimates: VecDeque<TimeBitrate>, |
| 16 | maybe_emit: bool, |
| 17 | emitted: Option<Bitrate>, |
| 18 | } |
| 19 | |
| 20 | impl EstimateSmoother { |
| 21 | pub fn new() -> Self { |
| 22 | Self { |
| 23 | estimates: VecDeque::new(), |
| 24 | maybe_emit: false, |
| 25 | emitted: None, |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | /// Record a new estimate and update the smoothed average. |
| 30 | pub fn record(&mut self, now: Instant, estimate: Bitrate) { |
| 31 | // Did value change from previous? |
| 32 | let do_update = self.estimates.back().map(|b| b.1) != Some(estimate); |
| 33 | |
| 34 | if do_update { |
| 35 | self.maybe_emit = true; |
| 36 | self.estimates.push_back((now, estimate)); |
| 37 | } |
| 38 | |
| 39 | // Remove entries older than the window. |
| 40 | while let Some((time, _)) = self.estimates.front() { |
| 41 | if now.duration_since(*time) > ESTIMATE_WINDOW { |
| 42 | // Keep last entry |
| 43 | if self.estimates.len() == 1 { |
| 44 | break; |
| 45 | } |
| 46 | |
| 47 | self.maybe_emit = true; |
| 48 | self.estimates.pop_front(); |
| 49 | } else { |
| 50 | break; |
| 51 | } |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | /// Poll for an estimate to emit. Returns Some only when there's a new value to emit. |
| 56 | pub fn poll(&mut self) -> Option<Bitrate> { |
| 57 | if !self.maybe_emit { |
| 58 | return None; |
| 59 | } |
| 60 | |
| 61 | if self.estimates.is_empty() { |
| 62 | return None; |
| 63 | } |
| 64 | |
| 65 | let total: f64 = self.estimates.iter().map(|b| b.1.as_f64()).sum(); |
| 66 | let avg = total / self.estimates.len() as f64; |
| 67 | let rate: Bitrate = avg.into(); |
| 68 | |
| 69 | // This forces emitting if we have the first ever value, or a last |
| 70 | // where the rest of the window is gone (estimates stop coming). |
| 71 | let force = self.estimates.len() == 1 && self.emitted != Some(rate); |
| 72 | |
| 73 | // Emit if we deviate enough from previously emitted. |
| 74 | let deviate = if let Some(emitted) = self.emitted { |
| 75 | !in_tolerance(emitted, rate) |
| 76 | } else { |
| 77 | true |
| 78 | }; |
| 79 | |
| 80 | self.maybe_emit = false; |
| 81 | |
| 82 | // Are we not to emit? |
| 83 | if !force && !deviate { |
| 84 | return None; |
| 85 | } |
| 86 | |
| 87 | self.emitted = Some(rate); |
| 88 | Some(rate) |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | fn in_tolerance(b1: Bitrate, b2: Bitrate) -> bool { |
| 93 | let min = b1 * (1.0 - TOLERANCE); |
| 94 | let max = b1 * (1.0 + TOLERANCE); |
| 95 | b2 > min && b2 < max |
| 96 | } |