diff --git a/webrtc/modules/BUILD.gn b/webrtc/modules/BUILD.gn index 36ae692fbb..4a565c1a61 100644 --- a/webrtc/modules/BUILD.gn +++ b/webrtc/modules/BUILD.gn @@ -245,6 +245,7 @@ if (rtc_include_tests) { "audio_coding/audio_network_adaptor/dtx_controller_unittest.cc", "audio_coding/audio_network_adaptor/mock/mock_controller.h", "audio_coding/audio_network_adaptor/mock/mock_controller_manager.h", + "audio_coding/audio_network_adaptor/smoothing_filter_unittest.cc", ] deps = [ "audio_coding:audio_network_adaptor", diff --git a/webrtc/modules/audio_coding/BUILD.gn b/webrtc/modules/audio_coding/BUILD.gn index 4fe49e5b9d..2c90c7a758 100644 --- a/webrtc/modules/audio_coding/BUILD.gn +++ b/webrtc/modules/audio_coding/BUILD.gn @@ -710,6 +710,8 @@ source_set("audio_network_adaptor") { "audio_network_adaptor/dtx_controller.cc", "audio_network_adaptor/dtx_controller.h", "audio_network_adaptor/include/audio_network_adaptor.h", + "audio_network_adaptor/smoothing_filter.cc", + "audio_network_adaptor/smoothing_filter.h", ] configs += [ "../..:common_config" ] public_configs = [ "../..:common_inherited_config" ] diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/audio_network_adaptor.gypi b/webrtc/modules/audio_coding/audio_network_adaptor/audio_network_adaptor.gypi index 8e87815287..2a591c3586 100644 --- a/webrtc/modules/audio_coding/audio_network_adaptor/audio_network_adaptor.gypi +++ b/webrtc/modules/audio_coding/audio_network_adaptor/audio_network_adaptor.gypi @@ -24,7 +24,9 @@ 'controller_manager.h', 'dtx_controller.h', 'dtx_controller.cc', - 'include/audio_network_adaptor.h' + 'include/audio_network_adaptor.h', + 'smoothing_filter.h', + 'smoothing_filter.cc', ], # source }, ], # targets diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.cc b/webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.cc new file mode 100644 index 0000000000..8a8106918a --- /dev/null +++ b/webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.cc @@ -0,0 +1,62 @@ +/* + * 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 + +#include "webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.h" + +namespace webrtc { + +SmoothingFilterImpl::SmoothingFilterImpl(int time_constant_ms, + const Clock* clock) + : time_constant_ms_(time_constant_ms), + clock_(clock), + first_sample_received_(false), + initialized_(false), + first_sample_time_ms_(0), + last_sample_time_ms_(0), + filter_(0.0) {} + +void SmoothingFilterImpl::AddSample(float sample) { + if (!first_sample_received_) { + last_sample_time_ms_ = first_sample_time_ms_ = clock_->TimeInMilliseconds(); + first_sample_received_ = true; + RTC_DCHECK_EQ(rtc::ExpFilter::kValueUndefined, filter_.filtered()); + + // Since this is first sample, any value for argument 1 should work. + filter_.Apply(0.0f, sample); + return; + } + + int64_t now_ms = clock_->TimeInMilliseconds(); + if (!initialized_) { + float duration = now_ms - first_sample_time_ms_; + if (duration < static_cast(time_constant_ms_)) { + filter_.UpdateBase(exp(1.0f / duration)); + } else { + initialized_ = true; + filter_.UpdateBase(exp(1.0f / time_constant_ms_)); + } + } + + // The filter will do the following: + // float alpha = pow(base, last_update_time_ms_ - now_ms); + // filtered_ = alpha * filtered_ + (1 - alpha) * sample; + filter_.Apply(static_cast(last_sample_time_ms_ - now_ms), sample); + last_sample_time_ms_ = now_ms; +} + +rtc::Optional SmoothingFilterImpl::GetAverage() const { + float value = filter_.filtered(); + return value == rtc::ExpFilter::kValueUndefined ? rtc::Optional() + : rtc::Optional(value); +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.h b/webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.h new file mode 100644 index 0000000000..c4de7b5da5 --- /dev/null +++ b/webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_SMOOTHING_FILTER_H_ +#define WEBRTC_MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_SMOOTHING_FILTER_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/exp_filter.h" +#include "webrtc/base/optional.h" +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { + +class SmoothingFilter { + public: + virtual ~SmoothingFilter() = default; + virtual void AddSample(float sample) = 0; + virtual rtc::Optional GetAverage() const = 0; +}; + +// SmoothingFilterImpl applies an exponential filter +// alpha = exp(-sample_interval / time_constant); +// y[t] = alpha * y[t-1] + (1 - alpha) * sample; +class SmoothingFilterImpl final : public SmoothingFilter { + public: + // |time_constant_ms| is the time constant for the exponential filter. + SmoothingFilterImpl(int time_constant_ms, const Clock* clock); + + void AddSample(float sample) override; + rtc::Optional GetAverage() const override; + + private: + const int time_constant_ms_; + const Clock* const clock_; + + bool first_sample_received_; + bool initialized_; + int64_t first_sample_time_ms_; + int64_t last_sample_time_ms_; + rtc::ExpFilter filter_; + + RTC_DISALLOW_COPY_AND_ASSIGN(SmoothingFilterImpl); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_SMOOTHING_FILTER_H_ diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter_unittest.cc b/webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter_unittest.cc new file mode 100644 index 0000000000..388ae4f999 --- /dev/null +++ b/webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter_unittest.cc @@ -0,0 +1,108 @@ +/* + * 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 + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.h" + +namespace webrtc { + +namespace { + +constexpr int kTimeConstantMs = 1000; +constexpr float kMaxAbsError = 0.0001f; +constexpr int64_t kClockInitialTime = 123456; + +struct SmoothingFilterStates { + std::unique_ptr simulated_clock; + std::unique_ptr smoothing_filter; +}; + +SmoothingFilterStates CreateSmoothingFilter() { + SmoothingFilterStates states; + states.simulated_clock.reset(new SimulatedClock(kClockInitialTime)); + states.smoothing_filter.reset( + new SmoothingFilterImpl(kTimeConstantMs, states.simulated_clock.get())); + return states; +} + +void CheckOutput(SmoothingFilterStates* states, + int advance_time_ms, + float sample, + float expected_ouput) { + states->simulated_clock->AdvanceTimeMilliseconds(advance_time_ms); + states->smoothing_filter->AddSample(sample); + auto output = states->smoothing_filter->GetAverage(); + EXPECT_TRUE(output); + EXPECT_NEAR(expected_ouput, *output, kMaxAbsError); +} + +} // namespace + +TEST(SmoothingFilterTest, NoOutputWhenNoSampleAdded) { + auto states = CreateSmoothingFilter(); + EXPECT_FALSE(states.smoothing_filter->GetAverage()); +} + +// Python script to calculate the reference values used in this test. +// import math +// +// class ExpFilter: +// alpha = 0.0 +// old_value = 0.0 +// def calc(self, new_value): +// self.old_value = self.old_value * self.alpha +// + (1.0 - self.alpha) * new_value +// return self.old_value +// +// delta_t = 100.0 +// filter = ExpFilter() +// total_t = 100.0 +// filter.alpha = math.exp(-delta_t/ total_t) +// print filter.calc(1.0) +// total_t = 200.0 +// filter.alpha = math.exp(-delta_t/ total_t) +// print filter.calc(0.0) +// total_t = 300.0 +// filter.alpha = math.exp(-delta_t/ total_t) +// print filter.calc(1.0) +TEST(SmoothingFilterTest, CheckBehaviorBeforeInitialized) { + // Adding three samples, all added before |kTimeConstantMs| is reached. + constexpr int kTimeIntervalMs = 100; + auto states = CreateSmoothingFilter(); + states.smoothing_filter->AddSample(0.0); + CheckOutput(&states, kTimeIntervalMs, 1.0, 0.63212f); + CheckOutput(&states, kTimeIntervalMs, 0.0, 0.38340f); + CheckOutput(&states, kTimeIntervalMs, 1.0, 0.55818f); +} + +// Python script to calculate the reference value used in this test. +// (after defining ExpFilter as for CheckBehaviorBeforeInitialized) +// time_constant_ms = 1000.0 +// filter = ExpFilter() +// delta_t = 1100.0 +// filter.alpha = math.exp(-delta_t/ time_constant_ms) +// print filter.calc(1.0) +// delta_t = 100.0 +// filter.alpha = math.exp(-delta_t/ time_constant_ms) +// print filter.calc(0.0) +// print filter.calc(1.0) +TEST(SmoothingFilterTest, CheckBehaviorAfterInitialized) { + constexpr int kTimeIntervalMs = 100; + auto states = CreateSmoothingFilter(); + states.smoothing_filter->AddSample(0.0); + states.simulated_clock->AdvanceTimeMilliseconds(kTimeConstantMs); + CheckOutput(&states, kTimeIntervalMs, 1.0, 0.66713f); + CheckOutput(&states, kTimeIntervalMs, 0.0, 0.60364f); + CheckOutput(&states, kTimeIntervalMs, 1.0, 0.64136f); +} + +} // namespace webrtc