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:
parent
bee671297a
commit
7667db4a74
@ -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}}}
|
||||
|
||||
@ -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_;
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user