diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-10-07 16:53:24 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-10-07 16:53:24 +0000 |
commit | 4f97d6740411fb53a4fb5f6e1af03e1f1b28c1ba (patch) | |
tree | 7e0e07b9d8d50fc5dd352d5b36d74fa3cb3427db /cras/src/server/rust/src/rate_estimator.rs | |
parent | 8d2c5b2edb9023b91d79709f2900ee1b643a3127 (diff) | |
parent | 8cfb92904b40ff98c82dd5309698999a28c91388 (diff) | |
download | adhd-4f97d6740411fb53a4fb5f6e1af03e1f1b28c1ba.tar.gz |
Snap for 7803083 from 8cfb92904b40ff98c82dd5309698999a28c91388 to mainline-tzdata2-release
Change-Id: Ice40f0028ec451c06ea7097e88c4152da1105d57
Diffstat (limited to 'cras/src/server/rust/src/rate_estimator.rs')
-rw-r--r-- | cras/src/server/rust/src/rate_estimator.rs | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/cras/src/server/rust/src/rate_estimator.rs b/cras/src/server/rust/src/rate_estimator.rs new file mode 100644 index 00000000..585f346b --- /dev/null +++ b/cras/src/server/rust/src/rate_estimator.rs @@ -0,0 +1,188 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +pub mod rate_estimator_bindings; + +use std::error; +use std::fmt; +use std::time::Duration; + +#[derive(Debug)] +pub enum Error { + InvalidSmoothFactor(f64), +} + +impl error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + match self { + InvalidSmoothFactor(sf) => write!(f, "Smooth factor {} is not between 0.0 and 1.0", sf), + } + } +} + +type Result<T> = std::result::Result<T, Error>; + +const MAX_RATE_SKEW: f64 = 100.0; + +/// Hold information to calculate linear least square from +/// several (x, y) samples. +#[derive(Debug, Default)] +struct LeastSquares { + sum_x: f64, + sum_y: f64, + sum_xy: f64, + sum_x2: f64, + num_samples: u32, +} + +impl LeastSquares { + fn new() -> Self { + Self::default() + } + + fn add_sample(&mut self, x: f64, y: f64) { + self.sum_x += x; + self.sum_y += y; + self.sum_xy += x * y; + self.sum_x2 += x * x; + self.num_samples += 1; + } + + fn best_fit_slope(&self) -> f64 { + let num = self.num_samples as f64 * self.sum_xy - self.sum_x * self.sum_y; + let den = self.num_samples as f64 * self.sum_x2 - self.sum_x * self.sum_x; + num / den + } +} + +/// An estimator holding the required information to determine the actual frame +/// rate of an audio device. +/// +/// # Members +/// * `last_level` - Buffer level of the audio device at last check time. +/// * `level_diff` - Number of frames written to or read from audio device +/// since the last check time. Rate estimator will use this +/// change plus the difference of buffer level to derive the +/// number of frames audio device has actually processed. +/// * `window_start` - The start time of the current window. +/// * `window_size` - The size of the window. +/// * `window_frames` - The number of frames accumulated in current window. +/// * `lsq` - The helper used to estimate sample rate. +/// * `smooth_factor` - A scaling factor used to average the previous and new +/// rate estimates to ensure that estimates do not change +/// too quickly. +/// * `estimated_rate` - The estimated rate at which samples are consumed. +pub struct RateEstimator { + last_level: i32, + level_diff: i32, + window_start: Option<Duration>, + window_size: Duration, + window_frames: u32, + lsq: LeastSquares, + smooth_factor: f64, + estimated_rate: f64, +} + +impl RateEstimator { + /// Creates a rate estimator. + /// + /// # Arguments + /// * `rate` - The initial value to estimate rate from. + /// * `window_size` - The window size of the rate estimator. + /// * `smooth_factor` - The coefficient used to calculate moving average + /// from old estimated rate values. Must be between + /// 0.0 and 1.0 + /// + /// # Errors + /// * If `smooth_factor` is not between 0.0 and 1.0 + pub fn try_new(rate: u32, window_size: Duration, smooth_factor: f64) -> Result<Self> { + if smooth_factor < 0.0 || smooth_factor > 1.0 { + return Err(Error::InvalidSmoothFactor(smooth_factor)); + } + + Ok(RateEstimator { + last_level: 0, + level_diff: 0, + window_start: None, + window_size, + window_frames: 0, + lsq: LeastSquares::new(), + smooth_factor, + estimated_rate: rate as f64, + }) + } + + /// Resets the estimated rate + /// + /// Reset the estimated rate to `rate`, and erase all collected data. + pub fn reset_rate(&mut self, rate: u32) { + self.last_level = 0; + self.level_diff = 0; + self.window_start = None; + self.window_frames = 0; + self.lsq = LeastSquares::new(); + self.estimated_rate = rate as f64; + } + + /// Adds additional frames transmitted to/from audio device. + /// + /// # Arguments + /// * `frames` - The number of frames written to the device. For input, + /// this should be negative to indicate how many samples + /// were read. + pub fn add_frames(&mut self, frames: i32) { + self.level_diff += frames; + } + + /// Gets the estimated rate. + pub fn get_estimated_rate(&self) -> f64 { + self.estimated_rate + } + + /// Check the timestamp and buffer level difference since last check time, + /// and use them as a new sample to update the estimated rate. + /// + /// # Arguments + /// * `level` - The current buffer level of audio device. + /// * `now` - The time at which this function is called. + /// + /// # Returns + /// True if the estimated rate is updated and window is reset, + /// otherwise false. + pub fn update_estimated_rate(&mut self, level: i32, now: Duration) -> bool { + let start = match self.window_start { + None => { + self.window_start = Some(now); + return false; + } + Some(t) => t, + }; + + let delta = match now.checked_sub(start) { + Some(d) => d, + None => return false, + }; + self.window_frames += (self.last_level - level + self.level_diff).abs() as u32; + self.level_diff = 0; + self.last_level = level; + + let secs = (delta.as_secs() as f64) + delta.subsec_nanos() as f64 / 1_000_000_000.0; + self.lsq.add_sample(secs, self.window_frames as f64); + if delta > self.window_size && self.lsq.num_samples > 1 { + let rate = self.lsq.best_fit_slope(); + if (self.estimated_rate - rate).abs() < MAX_RATE_SKEW { + self.estimated_rate = + rate * (1.0 - self.smooth_factor) + self.estimated_rate * self.smooth_factor; + } + self.lsq = LeastSquares::new(); + self.window_start = Some(now); + self.window_frames = 0; + return true; + } + false + } +} |