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}
This commit is contained in:
minyue 2016-12-28 02:57:50 -08:00 committed by Commit bot
parent bee671297a
commit 7667db4a74
3 changed files with 70 additions and 36 deletions

View File

@ -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<int64_t>(now_ms);
init_end_time_ms_ = rtc::Optional<int64_t>(now_ms + init_time_ms_);
last_state_time_ms_ = now_ms;
return;
}
@ -48,15 +51,16 @@ void SmoothingFilterImpl::AddSample(float sample) {
}
rtc::Optional<float> 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<float>();
}
ExtrapolateLastSample(clock_->TimeInMilliseconds());
return rtc::Optional<float>(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}}}

View File

@ -59,7 +59,7 @@ class SmoothingFilterImpl final : public SmoothingFilter {
const float init_const_;
const Clock* const clock_;
rtc::Optional<int64_t> first_sample_time_ms_;
rtc::Optional<int64_t> init_end_time_ms_;
float last_sample_;
float alpha_;
float state_;

View File

@ -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<SmoothingFilterImpl> 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.