From 7667db4a74ce630c19fff9e0ff953d1990b6301d Mon Sep 17 00:00:00 2001 From: minyue Date: Wed, 28 Dec 2016 02:57:50 -0800 Subject: [PATCH] Fixing init time error in smoothing filter. BUG=webrtc:6909, webrtc:6303 TBR=tina.legrand@webrtc.org Review-Url: https://codereview.webrtc.org/2582043002 Cr-Commit-Position: refs/heads/master@{#15817} --- webrtc/common_audio/smoothing_filter.cc | 73 ++++++++++++------- webrtc/common_audio/smoothing_filter.h | 2 +- .../common_audio/smoothing_filter_unittest.cc | 31 ++++++-- 3 files changed, 70 insertions(+), 36 deletions(-) diff --git a/webrtc/common_audio/smoothing_filter.cc b/webrtc/common_audio/smoothing_filter.cc index d2dde9d854..2ab25981f1 100644 --- a/webrtc/common_audio/smoothing_filter.cc +++ b/webrtc/common_audio/smoothing_filter.cc @@ -14,17 +14,20 @@ namespace webrtc { -SmoothingFilterImpl::SmoothingFilterImpl(int init_time_ms_, const Clock* clock) - : init_time_ms_(init_time_ms_), +SmoothingFilterImpl::SmoothingFilterImpl(int init_time_ms, const Clock* clock) + : init_time_ms_(init_time_ms), // Duing the initalization time, we use an increasing alpha. Specifically, - // alpha(n) = exp(pow(init_factor_, n)), + // alpha(n) = exp(-powf(init_factor_, n)), // where |init_factor_| is chosen such that // alpha(init_time_ms_) = exp(-1.0f / init_time_ms_), - init_factor_(pow(init_time_ms_, 1.0f / init_time_ms_)), + init_factor_(init_time_ms_ == 0 ? 0.0f : powf(init_time_ms_, + -1.0f / init_time_ms_)), // |init_const_| is to a factor to help the calculation during // initialization phase. - init_const_(1.0f / (init_time_ms_ - - pow(init_time_ms_, 1.0f - 1.0f / init_time_ms_))), + init_const_(init_time_ms_ == 0 + ? 0.0f + : init_time_ms_ - + powf(init_time_ms_, 1.0f - 1.0f / init_time_ms_)), clock_(clock) { UpdateAlpha(init_time_ms_); } @@ -34,11 +37,11 @@ SmoothingFilterImpl::~SmoothingFilterImpl() = default; void SmoothingFilterImpl::AddSample(float sample) { const int64_t now_ms = clock_->TimeInMilliseconds(); - if (!first_sample_time_ms_) { + if (!init_end_time_ms_) { // This is equivalent to assuming the filter has been receiving the same // value as the first sample since time -infinity. state_ = last_sample_ = sample; - first_sample_time_ms_ = rtc::Optional(now_ms); + init_end_time_ms_ = rtc::Optional(now_ms + init_time_ms_); last_state_time_ms_ = now_ms; return; } @@ -48,15 +51,16 @@ void SmoothingFilterImpl::AddSample(float sample) { } rtc::Optional SmoothingFilterImpl::GetAverage() { - if (!first_sample_time_ms_) + if (!init_end_time_ms_) { + // |init_end_time_ms_| undefined since we have not received any sample. return rtc::Optional(); + } ExtrapolateLastSample(clock_->TimeInMilliseconds()); return rtc::Optional(state_); } bool SmoothingFilterImpl::SetTimeConstantMs(int time_constant_ms) { - if (!first_sample_time_ms_ || - last_state_time_ms_ < *first_sample_time_ms_ + init_time_ms_) { + if (!init_end_time_ms_ || last_state_time_ms_ < *init_end_time_ms_) { return false; } UpdateAlpha(time_constant_ms); @@ -64,34 +68,43 @@ bool SmoothingFilterImpl::SetTimeConstantMs(int time_constant_ms) { } void SmoothingFilterImpl::UpdateAlpha(int time_constant_ms) { - alpha_ = exp(-1.0f / time_constant_ms); + alpha_ = time_constant_ms == 0 ? 0.0f : exp(-1.0f / time_constant_ms); } void SmoothingFilterImpl::ExtrapolateLastSample(int64_t time_ms) { RTC_DCHECK_GE(time_ms, last_state_time_ms_); - RTC_DCHECK(first_sample_time_ms_); + RTC_DCHECK(init_end_time_ms_); float multiplier = 0.0f; - if (time_ms <= *first_sample_time_ms_ + init_time_ms_) { + + if (time_ms <= *init_end_time_ms_) { // Current update is to be made during initialization phase. // We update the state as if the |alpha| has been increased according - // alpha(n) = exp(pow(init_factor_, n)), + // alpha(n) = exp(-powf(init_factor_, n)), // where n is the time (in millisecond) since the first sample received. // With algebraic derivation as shown in the Appendix, we can find that the // state can be updated in a similar manner as if alpha is a constant, // except for a different multiplier. - multiplier = exp(-init_const_ * - (pow(init_factor_, - *first_sample_time_ms_ + init_time_ms_ - last_state_time_ms_) - - pow(init_factor_, *first_sample_time_ms_ + init_time_ms_ - time_ms))); + if (init_time_ms_ == 0) { + // This means |init_factor_| = 0. + multiplier = 0.0f; + } else if (init_time_ms_ == 1) { + // This means |init_factor_| = 1. + multiplier = exp(last_state_time_ms_ - time_ms); + } else { + multiplier = + exp(-(powf(init_factor_, last_state_time_ms_ - *init_end_time_ms_) - + powf(init_factor_, time_ms - *init_end_time_ms_)) / + init_const_); + } } else { - if (last_state_time_ms_ < *first_sample_time_ms_ + init_time_ms_) { + if (last_state_time_ms_ < *init_end_time_ms_) { // The latest state update was made during initialization phase. // We first extrapolate to the initialization time. - ExtrapolateLastSample(*first_sample_time_ms_ + init_time_ms_); + ExtrapolateLastSample(*init_end_time_ms_); // Then extrapolate the rest by the following. } - multiplier = pow(alpha_, time_ms - last_state_time_ms_); + multiplier = powf(alpha_, time_ms - last_state_time_ms_); } state_ = multiplier * state_ + (1.0f - multiplier) * last_sample_; @@ -108,17 +121,21 @@ void SmoothingFilterImpl::ExtrapolateLastSample(int64_t time_ms) { // &= \left(\prod_{i=m}^{n-1} \alpha_i\right) y(m) + // \left(1 - \prod_{i=m}^{n-1} \alpha_i \right) x(m) // \end{align} -// Taking $\alpha_{n} = \exp{\gamma^n}$, $\gamma$ denotes init\_factor\_, the +// Taking $\alpha_{n} = \exp(-\gamma^n)$, $\gamma$ denotes init\_factor\_, the // multiplier becomes // \begin{align} // \prod_{i=m}^{n-1} \alpha_i -// &= \exp\left(\prod_{i=m}^{n-1} \gamma^i \right) \\* -// &= \exp\left(\frac{\gamma^m - \gamma^n}{1 - \gamma} \right) +// &= \exp\left(-\sum_{i=m}^{n-1} \gamma^i \right) \\* +// &= \begin{cases} +// \exp\left(-\frac{\gamma^m - \gamma^n}{1 - \gamma} \right) +// & \gamma \neq 1 \\* +// m-n & \gamma = 1 +// \end{cases} // \end{align} -// We know $\gamma = T^\frac{1}{T}$, where $T$ denotes init\_time\_ms\_. Then +// We know $\gamma = T^{-\frac{1}{T}}$, where $T$ denotes init\_time\_ms\_. Then // $1 - \gamma$ approaches zero when $T$ increases. This can cause numerical -// difficulties. We multiply $T$ to both numerator and denominator in the -// fraction. See. +// difficulties. We multiply $T$ (if $T > 0$) to both numerator and denominator +// in the fraction. See. // \begin{align} // \frac{\gamma^m - \gamma^n}{1 - \gamma} // &= \frac{T^\frac{T-m}{T} - T^\frac{T-n}{T}}{T - T^{1-\frac{1}{T}}} diff --git a/webrtc/common_audio/smoothing_filter.h b/webrtc/common_audio/smoothing_filter.h index 4967a34442..4a0efd99b6 100644 --- a/webrtc/common_audio/smoothing_filter.h +++ b/webrtc/common_audio/smoothing_filter.h @@ -59,7 +59,7 @@ class SmoothingFilterImpl final : public SmoothingFilter { const float init_const_; const Clock* const clock_; - rtc::Optional first_sample_time_ms_; + rtc::Optional init_end_time_ms_; float last_sample_; float alpha_; float state_; diff --git a/webrtc/common_audio/smoothing_filter_unittest.cc b/webrtc/common_audio/smoothing_filter_unittest.cc index ff06d50d8c..9ffbf3c5f5 100644 --- a/webrtc/common_audio/smoothing_filter_unittest.cc +++ b/webrtc/common_audio/smoothing_filter_unittest.cc @@ -18,7 +18,6 @@ namespace webrtc { namespace { -constexpr int kInitTimeMs = 795; constexpr float kMaxAbsError = 1e-5f; constexpr int64_t kClockInitialTime = 123456; @@ -27,11 +26,11 @@ struct SmoothingFilterStates { std::unique_ptr smoothing_filter; }; -SmoothingFilterStates CreateSmoothingFilter() { +SmoothingFilterStates CreateSmoothingFilter(int init_time_ms) { SmoothingFilterStates states; states.simulated_clock.reset(new SimulatedClock(kClockInitialTime)); states.smoothing_filter.reset( - new SmoothingFilterImpl(kInitTimeMs, states.simulated_clock.get())); + new SmoothingFilterImpl(init_time_ms, states.simulated_clock.get())); return states; } @@ -54,7 +53,8 @@ void CheckOutput(SmoothingFilterStates* states, } // namespace TEST(SmoothingFilterTest, NoOutputWhenNoSampleAdded) { - auto states = CreateSmoothingFilter(); + constexpr int kInitTimeMs = 100; + auto states = CreateSmoothingFilter(kInitTimeMs); EXPECT_FALSE(states.smoothing_filter->GetAverage()); } @@ -103,7 +103,8 @@ TEST(SmoothingFilterTest, NoOutputWhenNoSampleAdded) { // filter.add_sample(1.0) // print filter.state TEST(SmoothingFilterTest, CheckBehaviorAroundInitTime) { - auto states = CreateSmoothingFilter(); + constexpr int kInitTimeMs = 795; + auto states = CreateSmoothingFilter(kInitTimeMs); CheckOutput(&states, 1.0f, 500, 1.0f); CheckOutput(&states, 0.5f, 100, 0.680562264029f); CheckOutput(&states, 1.0f, 100, 0.794207139813f); @@ -113,8 +114,23 @@ TEST(SmoothingFilterTest, CheckBehaviorAroundInitTime) { CheckOutput(&states, 1.0f, 100, 0.815545922911f); } +TEST(SmoothingFilterTest, InitTimeEqualsZero) { + constexpr int kInitTimeMs = 0; + auto states = CreateSmoothingFilter(kInitTimeMs); + CheckOutput(&states, 1.0f, 1, 1.0f); + CheckOutput(&states, 0.5f, 1, 0.5f); +} + +TEST(SmoothingFilterTest, InitTimeEqualsOne) { + constexpr int kInitTimeMs = 1; + auto states = CreateSmoothingFilter(kInitTimeMs); + CheckOutput(&states, 1.0f, 1, 1.0f); + CheckOutput(&states, 0.5f, 1, 1.0f * exp(-1.0f) + (1.0f - exp(-1.0f)) * 0.5f); +} + TEST(SmoothingFilterTest, GetAverageOutputsEmptyBeforeFirstSample) { - auto states = CreateSmoothingFilter(); + constexpr int kInitTimeMs = 100; + auto states = CreateSmoothingFilter(kInitTimeMs); EXPECT_FALSE(states.smoothing_filter->GetAverage()); constexpr float kFirstSample = 1.2345f; states.smoothing_filter->AddSample(kFirstSample); @@ -123,7 +139,8 @@ TEST(SmoothingFilterTest, GetAverageOutputsEmptyBeforeFirstSample) { } TEST(SmoothingFilterTest, CannotChangeTimeConstantDuringInitialization) { - auto states = CreateSmoothingFilter(); + constexpr int kInitTimeMs = 100; + auto states = CreateSmoothingFilter(kInitTimeMs); states.smoothing_filter->AddSample(0.0); // During initialization, |SetTimeConstantMs| does not take effect.