Source code
Revision control
Copy as Markdown
Other Tools
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "common_audio/smoothing_filter.h"
#include <cmath>
#include <optional>
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::FloatNear;
using ::testing::Optional;
constexpr float kMaxAbsError = 1e-5f;
struct SmoothingFilterStates {
explicit SmoothingFilterStates(int init_time_ms)
: now(Timestamp::Millis(123'456)), smoothing_filter(init_time_ms) {}
Timestamp now;
SmoothingFilterImpl smoothing_filter;
};
// This function does the following:
// 1. Add a sample to filter at current clock,
// 2. Advance the clock by `advance_time_ms`,
// 3. Get the output of both SmoothingFilter and verify that it equals to an
// expected value.
void CheckOutput(SmoothingFilterStates* states,
float sample,
int advance_time_ms,
float expected_ouput) {
states->smoothing_filter.AddSample(sample, states->now);
states->now += TimeDelta::Millis(advance_time_ms);
EXPECT_THAT(states->smoothing_filter.GetAverage(states->now),
Optional(FloatNear(expected_ouput, kMaxAbsError)));
}
} // namespace
TEST(SmoothingFilterTest, NoOutputWhenNoSampleAdded) {
constexpr int kInitTimeMs = 100;
SmoothingFilterStates states(kInitTimeMs);
EXPECT_EQ(states.smoothing_filter.GetAverage(states.now), std::nullopt);
}
// Python script to calculate the reference values used in this test.
// import math
//
// class ExpFilter:
// def add_sample(self, new_value):
// self.state = self.state * self.alpha + (1.0 - self.alpha) * new_value
//
// filter = ExpFilter()
// init_time = 795
// init_factor = (1.0 / init_time) ** (1.0 / init_time)
//
// filter.state = 1.0
//
// for time_now in range(1, 500):
// filter.alpha = math.exp(-init_factor ** time_now)
// filter.add_sample(1.0)
// print filter.state
//
// for time_now in range(500, 600):
// filter.alpha = math.exp(-init_factor ** time_now)
// filter.add_sample(0.5)
// print filter.state
//
// for time_now in range(600, 700):
// filter.alpha = math.exp(-init_factor ** time_now)
// filter.add_sample(1.0)
// print filter.state
//
// for time_now in range(700, init_time):
// filter.alpha = math.exp(-init_factor ** time_now)
// filter.add_sample(1.0)
//
// filter.alpha = math.exp(-1.0 / init_time)
// for time_now in range(init_time, 800):
// filter.add_sample(1.0)
// print filter.state
//
// for i in range(800, 900):
// filter.add_sample(0.5)
// print filter.state
//
// for i in range(900, 1000):
// filter.add_sample(1.0)
// print filter.state
TEST(SmoothingFilterTest, CheckBehaviorAroundInitTime) {
constexpr int kInitTimeMs = 795;
SmoothingFilterStates states(kInitTimeMs);
CheckOutput(&states, 1.0f, 500, 1.0f);
CheckOutput(&states, 0.5f, 100, 0.680562264029f);
CheckOutput(&states, 1.0f, 100, 0.794207139813f);
// Next step will go across initialization time.
CheckOutput(&states, 1.0f, 100, 0.829803409752f);
CheckOutput(&states, 0.5f, 100, 0.790821764210f);
CheckOutput(&states, 1.0f, 100, 0.815545922911f);
}
TEST(SmoothingFilterTest, InitTimeEqualsZero) {
constexpr int kInitTimeMs = 0;
SmoothingFilterStates states(kInitTimeMs);
CheckOutput(&states, 1.0f, 1, 1.0f);
CheckOutput(&states, 0.5f, 1, 0.5f);
}
TEST(SmoothingFilterTest, InitTimeEqualsOne) {
constexpr int kInitTimeMs = 1;
SmoothingFilterStates states(kInitTimeMs);
CheckOutput(&states, 1.0f, 1, 1.0f);
CheckOutput(&states, 0.5f, 1,
1.0f * std::exp(-1.0f) + (1.0f - std::exp(-1.0f)) * 0.5f);
}
TEST(SmoothingFilterTest, GetAverageOutputsEmptyBeforeFirstSample) {
constexpr int kInitTimeMs = 100;
SmoothingFilterStates states(kInitTimeMs);
EXPECT_FALSE(states.smoothing_filter.GetAverage(states.now));
constexpr float kFirstSample = 1.2345f;
states.smoothing_filter.AddSample(kFirstSample, states.now);
EXPECT_EQ(kFirstSample, states.smoothing_filter.GetAverage(states.now));
}
TEST(SmoothingFilterTest, CannotChangeTimeConstantDuringInitialization) {
constexpr int kInitTimeMs = 100;
SmoothingFilterStates states(kInitTimeMs);
states.smoothing_filter.AddSample(0.0, states.now);
// During initialization, `SetTimeConstantMs` does not take effect.
states.now += TimeDelta::Millis(kInitTimeMs - 1);
states.smoothing_filter.AddSample(0.0, states.now);
EXPECT_FALSE(states.smoothing_filter.SetTimeConstantMs(kInitTimeMs * 2));
EXPECT_NE(std::exp(-1.0f / (kInitTimeMs * 2)),
states.smoothing_filter.alpha());
states.now += TimeDelta::Millis(1);
states.smoothing_filter.AddSample(0.0, states.now);
// When initialization finishes, the time constant should be come
// `kInitTimeConstantMs`.
EXPECT_FLOAT_EQ(std::exp(-1.0f / kInitTimeMs),
states.smoothing_filter.alpha());
// After initialization, `SetTimeConstantMs` takes effect.
EXPECT_TRUE(states.smoothing_filter.SetTimeConstantMs(kInitTimeMs * 2));
EXPECT_FLOAT_EQ(std::exp(-1.0f / (kInitTimeMs * 2)),
states.smoothing_filter.alpha());
}
} // namespace webrtc