Add ClippingPredictorLevelBuffer circular buffer.

Bug: webrtc:12774
Change-Id: I2b26660e3fe051ab358dd5298ba5098f275943da
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/219631
Reviewed-by: Minyue Li <minyue@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Commit-Queue: Hanna Silen <silen@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34167}
This commit is contained in:
Hanna Silen 2021-05-31 14:08:04 +02:00 committed by WebRTC LUCI CQ
parent 4f26a3c7e8
commit ea72ee6350
4 changed files with 293 additions and 0 deletions

View File

@ -38,6 +38,18 @@ rtc_library("agc") {
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
rtc_library("clipping_predictor_level_buffer") {
sources = [
"clipping_predictor_level_buffer.cc",
"clipping_predictor_level_buffer.h",
]
deps = [
"../../../rtc_base:checks",
"../../../rtc_base:logging",
]
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
rtc_library("level_estimation") {
sources = [
"agc.cc",
@ -96,6 +108,7 @@ if (rtc_include_tests) {
testonly = true
sources = [
"agc_manager_direct_unittest.cc",
"clipping_predictor_level_buffer_unittest.cc",
"loudness_histogram_unittest.cc",
"mock_agc.h",
]
@ -103,6 +116,7 @@ if (rtc_include_tests) {
deps = [
":agc",
":clipping_predictor_level_buffer",
":gain_control_interface",
":level_estimation",
"..:mocks",

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2021 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 "modules/audio_processing/agc/clipping_predictor_level_buffer.h"
#include <algorithm>
#include <cmath>
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
bool ClippingPredictorLevelBuffer::Level::operator==(const Level& level) const {
constexpr float kEpsilon = 1e-6f;
return std::fabs(average - level.average) < kEpsilon &&
std::fabs(max - level.max) < kEpsilon;
}
ClippingPredictorLevelBuffer::ClippingPredictorLevelBuffer(int capacity)
: tail_(-1), size_(0), data_(std::max(1, capacity)) {
if (capacity > kMaxCapacity) {
RTC_LOG(LS_WARNING) << "[agc]: ClippingPredictorLevelBuffer exceeds the "
<< "maximum allowed capacity. Capacity: " << capacity;
}
RTC_DCHECK(!data_.empty());
}
void ClippingPredictorLevelBuffer::Reset() {
tail_ = -1;
size_ = 0;
}
void ClippingPredictorLevelBuffer::Push(Level level) {
++tail_;
if (tail_ == Capacity()) {
tail_ = 0;
}
if (size_ < Capacity()) {
size_++;
}
data_[tail_] = level;
}
// TODO(bugs.webrtc.org/12774): Optimize partial computation for long buffers.
absl::optional<ClippingPredictorLevelBuffer::Level>
ClippingPredictorLevelBuffer::ComputePartialMetrics(int delay,
int num_items) const {
RTC_DCHECK_GE(delay, 0);
RTC_DCHECK_LT(delay, Capacity());
RTC_DCHECK_GT(num_items, 0);
RTC_DCHECK_LE(num_items, Capacity());
RTC_DCHECK_LE(delay + num_items, Capacity());
if (delay + num_items > Size()) {
return absl::nullopt;
}
float sum = 0.0f;
float max = 0.0f;
for (int i = 0; i < num_items && i < Size(); ++i) {
int idx = tail_ - delay - i;
if (idx < 0) {
idx += Capacity();
}
sum += data_[idx].average;
max = std::fmax(data_[idx].max, max);
}
return absl::optional<Level>({sum / static_cast<float>(num_items), max});
}
} // namespace webrtc

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2021 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 MODULES_AUDIO_PROCESSING_AGC_CLIPPING_PREDICTOR_LEVEL_BUFFER_H_
#define MODULES_AUDIO_PROCESSING_AGC_CLIPPING_PREDICTOR_LEVEL_BUFFER_H_
#include <memory>
#include <vector>
#include "absl/types/optional.h"
namespace webrtc {
// A circular buffer to store frame-wise `Level` items for clipping prediction.
// The current implementation is not optimized for large buffer lengths.
class ClippingPredictorLevelBuffer {
public:
struct Level {
float average;
float max;
bool operator==(const Level& level) const;
};
// Recommended maximum capacity. It is possible to create a buffer with a
// larger capacity, but the implementation is not optimized for large values.
static constexpr int kMaxCapacity = 100;
// Ctor. Sets the buffer capacity to max(1, `capacity`) and logs a warning
// message if the capacity is greater than `kMaxCapacity`.
explicit ClippingPredictorLevelBuffer(int capacity);
~ClippingPredictorLevelBuffer() {}
ClippingPredictorLevelBuffer(const ClippingPredictorLevelBuffer&) = delete;
ClippingPredictorLevelBuffer& operator=(const ClippingPredictorLevelBuffer&) =
delete;
void Reset();
// Returns the current number of items stored in the buffer.
int Size() const { return size_; }
// Returns the capacity of the buffer.
int Capacity() const { return data_.size(); }
// Adds a `level` item into the circular buffer `data_`. Stores at most
// `Capacity()` items. If more items are pushed, the new item replaces the
// least recently pushed item.
void Push(Level level);
// If at least `num_items` + `delay` items have been pushed, returns the
// average and maximum value for the `num_items` most recently pushed items
// from `delay` to `delay` - `num_items` (a delay equal to zero corresponds
// to the most recently pushed item). The value of `delay` is limited to
// [0, N] and `num_items` to [1, M] where N + M is the capacity of the buffer.
absl::optional<Level> ComputePartialMetrics(int delay, int num_items) const;
private:
int tail_;
int size_;
std::vector<Level> data_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_PROCESSING_AGC_CLIPPING_PREDICTOR_LEVEL_BUFFER_H_

View File

@ -0,0 +1,131 @@
/*
* Copyright (c) 2021 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 "modules/audio_processing/agc/clipping_predictor_level_buffer.h"
#include <algorithm>
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::Eq;
using ::testing::Optional;
class ClippingPredictorLevelBufferParametrization
: public ::testing::TestWithParam<int> {
protected:
int capacity() const { return GetParam(); }
};
TEST_P(ClippingPredictorLevelBufferParametrization, CheckEmptyBufferSize) {
ClippingPredictorLevelBuffer buffer(capacity());
EXPECT_EQ(buffer.Capacity(), std::max(capacity(), 1));
EXPECT_EQ(buffer.Size(), 0);
}
TEST_P(ClippingPredictorLevelBufferParametrization, CheckHalfEmptyBufferSize) {
ClippingPredictorLevelBuffer buffer(capacity());
for (int i = 0; i < buffer.Capacity() / 2; ++i) {
buffer.Push({2, 4});
}
EXPECT_EQ(buffer.Capacity(), std::max(capacity(), 1));
EXPECT_EQ(buffer.Size(), std::max(capacity(), 1) / 2);
}
TEST_P(ClippingPredictorLevelBufferParametrization, CheckFullBufferSize) {
ClippingPredictorLevelBuffer buffer(capacity());
for (int i = 0; i < buffer.Capacity(); ++i) {
buffer.Push({2, 4});
}
EXPECT_EQ(buffer.Capacity(), std::max(capacity(), 1));
EXPECT_EQ(buffer.Size(), std::max(capacity(), 1));
}
TEST_P(ClippingPredictorLevelBufferParametrization, CheckLargeBufferSize) {
ClippingPredictorLevelBuffer buffer(capacity());
for (int i = 0; i < 2 * buffer.Capacity(); ++i) {
buffer.Push({2, 4});
}
EXPECT_EQ(buffer.Capacity(), std::max(capacity(), 1));
EXPECT_EQ(buffer.Size(), std::max(capacity(), 1));
}
TEST_P(ClippingPredictorLevelBufferParametrization, CheckSizeAfterReset) {
ClippingPredictorLevelBuffer buffer(capacity());
buffer.Push({1, 1});
buffer.Push({1, 1});
buffer.Reset();
EXPECT_EQ(buffer.Capacity(), std::max(capacity(), 1));
EXPECT_EQ(buffer.Size(), 0);
buffer.Push({1, 1});
EXPECT_EQ(buffer.Capacity(), std::max(capacity(), 1));
EXPECT_EQ(buffer.Size(), 1);
}
INSTANTIATE_TEST_SUITE_P(ClippingPredictorLevelBufferTest,
ClippingPredictorLevelBufferParametrization,
::testing::Values(-1, 0, 1, 123));
TEST(ClippingPredictorLevelBufferTest, CheckMetricsAfterFullBuffer) {
ClippingPredictorLevelBuffer buffer(/*capacity=*/2);
buffer.Push({1, 2});
buffer.Push({3, 6});
EXPECT_THAT(buffer.ComputePartialMetrics(/*delay=*/0, /*num_items=*/1),
Optional(Eq(ClippingPredictorLevelBuffer::Level{3, 6})));
EXPECT_THAT(buffer.ComputePartialMetrics(/*delay=*/1, /*num_items=*/1),
Optional(Eq(ClippingPredictorLevelBuffer::Level{1, 2})));
EXPECT_THAT(buffer.ComputePartialMetrics(/*delay=*/0, /*num_items=*/2),
Optional(Eq(ClippingPredictorLevelBuffer::Level{2, 6})));
}
TEST(ClippingPredictorLevelBufferTest, CheckMetricsAfterPushBeyondCapacity) {
ClippingPredictorLevelBuffer buffer(/*capacity=*/2);
buffer.Push({1, 1});
buffer.Push({3, 6});
buffer.Push({5, 10});
buffer.Push({7, 14});
buffer.Push({6, 12});
EXPECT_THAT(buffer.ComputePartialMetrics(/*delay=*/0, /*num_items=*/1),
Optional(Eq(ClippingPredictorLevelBuffer::Level{6, 12})));
EXPECT_THAT(buffer.ComputePartialMetrics(/*delay=*/1, /*num_items=*/1),
Optional(Eq(ClippingPredictorLevelBuffer::Level{7, 14})));
EXPECT_THAT(buffer.ComputePartialMetrics(/*delay=*/0, /*num_items=*/2),
Optional(Eq(ClippingPredictorLevelBuffer::Level{6.5f, 14})));
}
TEST(ClippingPredictorLevelBufferTest, CheckMetricsAfterTooFewItems) {
ClippingPredictorLevelBuffer buffer(/*capacity=*/4);
buffer.Push({1, 2});
buffer.Push({3, 6});
EXPECT_EQ(buffer.ComputePartialMetrics(/*delay=*/0, /*num_items=*/3),
absl::nullopt);
EXPECT_EQ(buffer.ComputePartialMetrics(/*delay=*/2, /*num_items=*/1),
absl::nullopt);
}
TEST(ClippingPredictorLevelBufferTest, CheckMetricsAfterReset) {
ClippingPredictorLevelBuffer buffer(/*capacity=*/2);
buffer.Push({1, 2});
buffer.Reset();
buffer.Push({5, 10});
buffer.Push({7, 14});
EXPECT_THAT(buffer.ComputePartialMetrics(/*delay=*/0, /*num_items=*/1),
Optional(Eq(ClippingPredictorLevelBuffer::Level{7, 14})));
EXPECT_THAT(buffer.ComputePartialMetrics(/*delay=*/0, /*num_items=*/2),
Optional(Eq(ClippingPredictorLevelBuffer::Level{6, 14})));
EXPECT_THAT(buffer.ComputePartialMetrics(/*delay=*/1, /*num_items=*/1),
Optional(Eq(ClippingPredictorLevelBuffer::Level{5, 10})));
}
} // namespace
} // namespace webrtc