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:
parent
4f26a3c7e8
commit
ea72ee6350
@ -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",
|
||||
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user