Corrections of the render buffering scheme in AEC3 to ensure causality

This CL modifies the refactored render buffering scheme in AEC3
so that:
-A non-causal state can never occur which means that situations with
 nonrecoverable echo should not occur.
-For a stable audio pipeline with a predefined API call jitter,
 render overruns and underruns can never occur.

Bug: webrtc:8629,chromium:793305
Change-Id: I06ba1c368f92db95274090b08475dd02dbb85145
Reviewed-on: https://webrtc-review.googlesource.com/29861
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21215}
This commit is contained in:
Per Åhgren 2017-12-11 21:34:19 +01:00 committed by Commit Bot
parent efc5fbd8e0
commit c59a576c86
34 changed files with 535 additions and 408 deletions

View File

@ -71,7 +71,8 @@ TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) {
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
}
const auto& render_buffer = render_delay_buffer->GetRenderBuffer();
@ -84,10 +85,10 @@ TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) {
G.im[0] = 0.f;
G.im[G.im.size() - 1] = 0.f;
AdaptPartitions_NEON(render_buffer, G, H_NEON);
AdaptPartitions(render_buffer, G, H_C);
AdaptPartitions_NEON(render_buffer, G, H_NEON);
AdaptPartitions(render_buffer, G, H_C);
AdaptPartitions_NEON(*render_buffer, G, H_NEON);
AdaptPartitions(*render_buffer, G, H_C);
AdaptPartitions_NEON(*render_buffer, G, H_NEON);
AdaptPartitions(*render_buffer, G, H_C);
for (size_t l = 0; l < H_C.size(); ++l) {
for (size_t j = 0; j < H_C[l].im.size(); ++j) {
@ -96,8 +97,8 @@ TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) {
}
}
ApplyFilter_NEON(render_buffer, H_NEON, &S_NEON);
ApplyFilter(render_buffer, H_C, &S_C);
ApplyFilter_NEON(*render_buffer, H_NEON, &S_NEON);
ApplyFilter(*render_buffer, H_C, &S_C);
for (size_t j = 0; j < S_C.re.size(); ++j) {
EXPECT_NEAR(S_C.re[j], S_NEON.re[j], fabs(S_C.re[j] * 0.00001f));
EXPECT_NEAR(S_C.im[j], S_NEON.im[j], fabs(S_C.re[j] * 0.00001f));
@ -182,11 +183,12 @@ TEST(AdaptiveFirFilter, FilterAdaptationSse2Optimizations) {
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
const auto& render_buffer = render_delay_buffer->GetRenderBuffer();
ApplyFilter_SSE2(render_buffer, H_SSE2, &S_SSE2);
ApplyFilter(render_buffer, H_C, &S_C);
ApplyFilter_SSE2(*render_buffer, H_SSE2, &S_SSE2);
ApplyFilter(*render_buffer, H_C, &S_C);
for (size_t j = 0; j < S_C.re.size(); ++j) {
EXPECT_FLOAT_EQ(S_C.re[j], S_SSE2.re[j]);
EXPECT_FLOAT_EQ(S_C.im[j], S_SSE2.im[j]);
@ -197,8 +199,8 @@ TEST(AdaptiveFirFilter, FilterAdaptationSse2Optimizations) {
std::for_each(G.im.begin(), G.im.end(),
[&](float& a) { a = random_generator.Rand<float>(); });
AdaptPartitions_SSE2(render_buffer, G, H_SSE2);
AdaptPartitions(render_buffer, G, H_C);
AdaptPartitions_SSE2(*render_buffer, G, H_SSE2);
AdaptPartitions(*render_buffer, G, H_C);
for (size_t k = 0; k < H_C.size(); ++k) {
for (size_t j = 0; j < H_C[k].re.size(); ++j) {
@ -277,7 +279,7 @@ TEST(AdaptiveFirFilter, NullFilterOutput) {
AdaptiveFirFilter filter(9, DetectOptimization(), &data_dumper);
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
EXPECT_DEATH(filter.Filter(render_delay_buffer->GetRenderBuffer(), nullptr),
EXPECT_DEATH(filter.Filter(*render_delay_buffer->GetRenderBuffer(), nullptr),
"");
}
@ -311,6 +313,7 @@ TEST(AdaptiveFirFilter, FilterAndAdapt) {
Aec3Fft fft;
EchoCanceller3Config config;
config.delay.min_echo_path_delay_blocks = 0;
config.delay.default_delay = 1;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, 3));
ShadowFilterUpdateGain gain;
@ -362,12 +365,13 @@ TEST(AdaptiveFirFilter, FilterAndAdapt) {
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
const auto& render_buffer = render_delay_buffer->GetRenderBuffer();
render_signal_analyzer.Update(render_buffer, aec_state.FilterDelay());
render_signal_analyzer.Update(*render_buffer, aec_state.FilterDelay());
filter.Filter(render_buffer, &S);
filter.Filter(*render_buffer, &S);
fft.Ifft(S, &s_scratch);
std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2,
e.begin(),
@ -379,14 +383,14 @@ TEST(AdaptiveFirFilter, FilterAndAdapt) {
s[k] = kScale * s_scratch[k + kFftLengthBy2];
}
gain.Compute(render_buffer, render_signal_analyzer, E,
gain.Compute(*render_buffer, render_signal_analyzer, E,
filter.SizePartitions(), false, &G);
filter.Adapt(render_buffer, G);
filter.Adapt(*render_buffer, G);
aec_state.HandleEchoPathChange(EchoPathVariability(
false, EchoPathVariability::DelayAdjustment::kNone, false));
aec_state.Update(filter.FilterFrequencyResponse(),
filter.FilterImpulseResponse(), true, rtc::nullopt,
render_buffer, E2_main, Y2, x[0], s, false);
*render_buffer, E2_main, Y2, x[0], s, false);
}
// Verify that the filter is able to perform well.
EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),

View File

@ -48,6 +48,8 @@ constexpr size_t kMaxNumBands = 3;
constexpr size_t kSubFrameLength = 80;
constexpr size_t kBlockSize = kFftLengthBy2;
constexpr size_t kBlockSizeLog2 = 6;
constexpr size_t kExtendedBlockSize = 2 * kFftLengthBy2;
constexpr size_t kMatchedFilterWindowSizeSubBlocks = 32;
constexpr size_t kMatchedFilterAlignmentShiftSizeSubBlocks =
@ -80,12 +82,16 @@ constexpr size_t GetDownSampledBufferSize(size_t down_sampling_factor,
constexpr size_t GetRenderDelayBufferSize(size_t down_sampling_factor,
size_t num_matched_filters) {
return GetDownSampledBufferSize(down_sampling_factor, num_matched_filters) /
(kBlockSize / down_sampling_factor);
(kBlockSize / down_sampling_factor) +
kAdaptiveFilterLength + 1;
}
// Detects what kind of optimizations to use for the code.
Aec3Optimization DetectOptimization();
static_assert(1 << kBlockSizeLog2 == kBlockSize,
"Proper number of shifts for blocksize");
static_assert(1 == NumBandsForRate(8000), "Number of bands for 8 kHz");
static_assert(1 == NumBandsForRate(16000), "Number of bands for 16 kHz");
static_assert(2 == NumBandsForRate(32000), "Number of bands for 32 kHz");

View File

@ -49,7 +49,7 @@ TEST(AecState, NormalUsage) {
// Verify that linear AEC usability is false when the filter is diverged and
// there is no external delay reported.
state.Update(diverged_filter_frequency_response, impulse_response, true,
rtc::nullopt, render_delay_buffer->GetRenderBuffer(), E2_main,
rtc::nullopt, *render_delay_buffer->GetRenderBuffer(), E2_main,
Y2, x[0], s, false);
EXPECT_FALSE(state.UsableLinearEstimate());
@ -57,7 +57,7 @@ TEST(AecState, NormalUsage) {
std::fill(x[0].begin(), x[0].end(), 101.f);
for (int k = 0; k < 3000; ++k) {
state.Update(converged_filter_frequency_response, impulse_response, true, 2,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
false);
}
EXPECT_TRUE(state.UsableLinearEstimate());
@ -67,7 +67,7 @@ TEST(AecState, NormalUsage) {
state.HandleEchoPathChange(EchoPathVariability(
true, EchoPathVariability::DelayAdjustment::kNone, false));
state.Update(converged_filter_frequency_response, impulse_response, true, 2,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
false);
EXPECT_FALSE(state.UsableLinearEstimate());
@ -76,25 +76,25 @@ TEST(AecState, NormalUsage) {
state.HandleEchoPathChange(EchoPathVariability(
true, EchoPathVariability::DelayAdjustment::kNewDetectedDelay, false));
state.Update(converged_filter_frequency_response, impulse_response, true, 2,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
false);
EXPECT_FALSE(state.ActiveRender());
for (int k = 0; k < 1000; ++k) {
state.Update(converged_filter_frequency_response, impulse_response, true, 2,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
false);
}
EXPECT_TRUE(state.ActiveRender());
// Verify that echo leakage is properly reported.
state.Update(converged_filter_frequency_response, impulse_response, true, 2,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
false);
EXPECT_FALSE(state.EchoLeakageDetected());
state.Update(converged_filter_frequency_response, impulse_response, true, 2,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
true);
EXPECT_TRUE(state.EchoLeakageDetected());
@ -104,19 +104,20 @@ TEST(AecState, NormalUsage) {
}
x[0][0] = 5000.f;
for (size_t k = 0; k < render_delay_buffer->GetRenderBuffer().Buffer().size();
++k) {
for (size_t k = 0;
k < render_delay_buffer->GetRenderBuffer()->Buffer().size(); ++k) {
render_delay_buffer->Insert(x);
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
}
Y2.fill(10.f * 10000.f * 10000.f);
for (size_t k = 0; k < 1000; ++k) {
state.Update(converged_filter_frequency_response, impulse_response, true, 2,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
false);
}
@ -133,7 +134,7 @@ TEST(AecState, NormalUsage) {
Y2.fill(10.f * E2_main[0]);
for (size_t k = 0; k < 1000; ++k) {
state.Update(converged_filter_frequency_response, impulse_response, true, 2,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
false);
}
ASSERT_TRUE(state.UsableLinearEstimate());
@ -154,7 +155,7 @@ TEST(AecState, NormalUsage) {
Y2.fill(5.f * E2_main[0]);
for (size_t k = 0; k < 1000; ++k) {
state.Update(converged_filter_frequency_response, impulse_response, true, 2,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
false);
}
@ -204,7 +205,7 @@ TEST(AecState, ConvergedFilterDelay) {
frequency_response[k][0] = 0.f;
state.HandleEchoPathChange(echo_path_variability);
state.Update(frequency_response, impulse_response, true, rtc::nullopt,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x, s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x, s,
false);
EXPECT_TRUE(k == (kFilterLength - 1) || state.FilterDelay());
if (k != (kFilterLength - 1)) {
@ -243,7 +244,7 @@ TEST(AecState, ExternalDelay) {
state.HandleEchoPathChange(EchoPathVariability(
false, EchoPathVariability::DelayAdjustment::kNone, false));
state.Update(frequency_response, impulse_response, true, k * kBlockSize + 5,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x, s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x, s,
false);
EXPECT_TRUE(state.ExternalDelay());
EXPECT_EQ(k, state.ExternalDelay());
@ -254,7 +255,7 @@ TEST(AecState, ExternalDelay) {
state.HandleEchoPathChange(EchoPathVariability(
false, EchoPathVariability::DelayAdjustment::kNone, false));
state.Update(frequency_response, impulse_response, true, rtc::nullopt,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x, s,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x, s,
false);
EXPECT_FALSE(state.ExternalDelay());
}

View File

@ -55,6 +55,7 @@ class BlockProcessorImpl final : public BlockProcessor {
std::unique_ptr<EchoRemover> echo_remover_;
BlockProcessorMetrics metrics_;
RenderDelayBuffer::BufferingEvent render_event_;
size_t capture_call_counter_ = 0;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockProcessorImpl);
};
@ -86,6 +87,9 @@ void BlockProcessorImpl::ProcessCapture(
RTC_DCHECK(capture_block);
RTC_DCHECK_EQ(NumBandsForRate(sample_rate_hz_), capture_block->size());
RTC_DCHECK_EQ(kBlockSize, (*capture_block)[0].size());
capture_call_counter_++;
data_dumper_->DumpRaw("aec3_processblock_call_order",
static_cast<int>(BlockProcessorApiCall::kCapture));
data_dumper_->DumpWav("aec3_processblock_capture_input", kBlockSize,
@ -111,30 +115,35 @@ void BlockProcessorImpl::ProcessCapture(
echo_path_variability.delay_change =
EchoPathVariability::DelayAdjustment::kBufferFlush;
delay_controller_->Reset();
render_buffer_->Reset();
RTC_LOG(LS_WARNING) << "Reset due to render buffer overrun.";
RTC_LOG(LS_WARNING) << "Reset due to render buffer overrun at block "
<< capture_call_counter_;
}
// Update the render buffers with any newly arrived render blocks and prepare
// the render buffers for reading the render data corresponding to the current
// capture block.
render_event_ = render_buffer_->PrepareCaptureCall();
render_event_ = render_buffer_->PrepareCaptureProcessing();
RTC_DCHECK(RenderDelayBuffer::BufferingEvent::kRenderOverrun !=
render_event_);
if (render_event_ == RenderDelayBuffer::BufferingEvent::kRenderUnderrun) {
echo_path_variability.delay_change =
EchoPathVariability::DelayAdjustment::kBufferReadjustment;
EchoPathVariability::DelayAdjustment::kDelayReset;
delay_controller_->Reset();
render_buffer_->Reset();
capture_properly_started_ = false;
render_properly_started_ = false;
RTC_LOG(LS_WARNING) << "Reset due to render buffer underrrun at block "
<< capture_call_counter_;
} else if (render_event_ == RenderDelayBuffer::BufferingEvent::kApiCallSkew) {
// There have been too many render calls in a row. Reset to avoid noncausal
// echo.
echo_path_variability.delay_change =
EchoPathVariability::DelayAdjustment::kDelayReset;
delay_controller_->Reset();
render_buffer_->Reset();
capture_properly_started_ = false;
render_properly_started_ = false;
RTC_LOG(LS_WARNING) << "Reset due to render buffer api skew at block "
<< capture_call_counter_;
}
data_dumper_->DumpWav("aec3_processblock_capture_input2", kBlockSize,
@ -143,30 +152,32 @@ void BlockProcessorImpl::ProcessCapture(
// Compute and and apply the render delay required to achieve proper signal
// alignment.
const size_t estimated_delay = delay_controller_->GetDelay(
rtc::Optional<size_t> estimated_delay = delay_controller_->GetDelay(
render_buffer_->GetDownsampledRenderBuffer(), (*capture_block)[0]);
const size_t new_delay =
std::min(render_buffer_->MaxDelay(), estimated_delay);
bool delay_change = render_buffer_->Delay() != new_delay;
if (delay_change && new_delay >= config_.delay.min_echo_path_delay_blocks) {
echo_path_variability.delay_change =
EchoPathVariability::DelayAdjustment::kNewDetectedDelay;
render_buffer_->SetDelay(new_delay);
RTC_DCHECK_EQ(render_buffer_->Delay(), new_delay);
delay_controller_->SetDelay(new_delay);
} else if (delay_change &&
new_delay < config_.delay.min_echo_path_delay_blocks) {
// A noncausal delay has been detected. This can only happen if there is
// clockdrift, an audio pipeline issue has occurred or the specified minimum
// delay is too short. Perform a full reset.
echo_path_variability.delay_change =
EchoPathVariability::DelayAdjustment::kDelayReset;
delay_controller_->Reset();
render_buffer_->Reset();
capture_properly_started_ = false;
render_properly_started_ = false;
RTC_LOG(LS_WARNING) << "Reset due to noncausal delay.";
if (estimated_delay) {
bool delay_change = render_buffer_->SetDelay(*estimated_delay);
if (delay_change) {
RTC_LOG(LS_WARNING) << "Delay changed to " << *estimated_delay
<< " at block " << capture_call_counter_;
if (render_buffer_->CausalDelay()) {
echo_path_variability.delay_change =
EchoPathVariability::DelayAdjustment::kNewDetectedDelay;
} else {
// A noncausal delay has been detected. This can only happen if there is
// clockdrift, an audio pipeline issue has occurred or the specified
// minimum delay is too short. Perform a full reset.
echo_path_variability.delay_change =
EchoPathVariability::DelayAdjustment::kDelayReset;
delay_controller_->Reset();
render_buffer_->Reset();
capture_properly_started_ = false;
render_properly_started_ = false;
RTC_LOG(LS_WARNING) << "Reset due to noncausal delay at block "
<< capture_call_counter_;
}
}
}
// Remove the echo from the capture signal.
@ -207,7 +218,8 @@ void BlockProcessorImpl::UpdateEchoLeakageStatus(bool leakage_detected) {
void BlockProcessorImpl::GetMetrics(EchoControl::Metrics* metrics) const {
echo_remover_->GetMetrics(metrics);
const int block_size_ms = sample_rate_hz_ == 8000 ? 8 : 4;
metrics->delay_ms = static_cast<int>(render_buffer_->Delay()) * block_size_ms;
rtc::Optional<size_t> delay = render_buffer_->Delay();
metrics->delay_ms = delay ? static_cast<int>(*delay) * block_size_ms : 0;
}
} // namespace
@ -217,7 +229,9 @@ BlockProcessor* BlockProcessor::Create(const EchoCanceller3Config& config,
std::unique_ptr<RenderDelayBuffer> render_buffer(
RenderDelayBuffer::Create(config, NumBandsForRate(sample_rate_hz)));
std::unique_ptr<RenderDelayController> delay_controller(
RenderDelayController::Create(config, sample_rate_hz));
RenderDelayController::Create(
config, RenderDelayBuffer::DelayEstimatorOffset(config),
sample_rate_hz));
std::unique_ptr<EchoRemover> echo_remover(
EchoRemover::Create(config, sample_rate_hz));
return Create(config, sample_rate_hz, std::move(render_buffer),
@ -229,7 +243,9 @@ BlockProcessor* BlockProcessor::Create(
int sample_rate_hz,
std::unique_ptr<RenderDelayBuffer> render_buffer) {
std::unique_ptr<RenderDelayController> delay_controller(
RenderDelayController::Create(config, sample_rate_hz));
RenderDelayController::Create(
config, RenderDelayBuffer::DelayEstimatorOffset(config),
sample_rate_hz));
std::unique_ptr<EchoRemover> echo_remover(
EchoRemover::Create(config, sample_rate_hz));
return Create(config, sample_rate_hz, std::move(render_buffer),

View File

@ -116,9 +116,6 @@ TEST(BlockProcessor, DISABLED_DelayControllerIntegration) {
EXPECT_CALL(*render_delay_buffer_mock, Insert(_))
.Times(kNumBlocks)
.WillRepeatedly(Return(RenderDelayBuffer::BufferingEvent::kNone));
EXPECT_CALL(*render_delay_buffer_mock, IsBlockAvailable())
.Times(kNumBlocks)
.WillRepeatedly(Return(true));
EXPECT_CALL(*render_delay_buffer_mock, SetDelay(kDelayInBlocks))
.Times(AtLeast(1));
EXPECT_CALL(*render_delay_buffer_mock, MaxDelay()).WillOnce(Return(30));
@ -161,10 +158,7 @@ TEST(BlockProcessor, DISABLED_SubmoduleIntegration) {
EXPECT_CALL(*render_delay_buffer_mock, Insert(_))
.Times(kNumBlocks - 1)
.WillRepeatedly(Return(RenderDelayBuffer::BufferingEvent::kNone));
EXPECT_CALL(*render_delay_buffer_mock, IsBlockAvailable())
.Times(kNumBlocks)
.WillRepeatedly(Return(true));
EXPECT_CALL(*render_delay_buffer_mock, PrepareCaptureCall())
EXPECT_CALL(*render_delay_buffer_mock, PrepareCaptureProcessing())
.Times(kNumBlocks);
EXPECT_CALL(*render_delay_buffer_mock, SetDelay(9)).Times(AtLeast(1));
EXPECT_CALL(*render_delay_buffer_mock, Delay())

View File

@ -13,7 +13,8 @@
namespace webrtc {
DownsampledRenderBuffer::DownsampledRenderBuffer(size_t downsampled_buffer_size)
: size(downsampled_buffer_size), buffer(downsampled_buffer_size, 0.f) {
: size(static_cast<int>(downsampled_buffer_size)),
buffer(downsampled_buffer_size, 0.f) {
std::fill(buffer.begin(), buffer.end(), 0.f);
}

View File

@ -23,17 +23,20 @@ struct DownsampledRenderBuffer {
explicit DownsampledRenderBuffer(size_t downsampled_buffer_size);
~DownsampledRenderBuffer();
size_t IncIndex(size_t index) {
return index < (buffer.size() - 1) ? index + 1 : 0;
int IncIndex(int index) const {
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return index < size - 1 ? index + 1 : 0;
}
size_t DecIndex(size_t index) {
return index > 0 ? index - 1 : buffer.size() - 1;
int DecIndex(int index) const {
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return index > 0 ? index - 1 : size - 1;
}
size_t OffsetIndex(size_t index, int offset) {
int OffsetIndex(int index, int offset) const {
RTC_DCHECK_GE(buffer.size(), offset);
return (buffer.size() + index + offset) % buffer.size();
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return (size + index + offset) % size;
}
void UpdateWriteIndex(int offset) { write = OffsetIndex(write, offset); }
@ -43,7 +46,7 @@ struct DownsampledRenderBuffer {
void IncReadIndex() { read = IncIndex(read); }
void DecReadIndex() { read = DecIndex(read); }
size_t size;
const int size;
std::vector<float> buffer;
int write = 0;
int read = 0;

View File

@ -65,25 +65,15 @@ TEST(EchoPathDelayEstimator, DelayEstimation) {
for (size_t delay_samples : {30, 64, 150, 200, 800, 4000}) {
SCOPED_TRACE(ProduceDebugText(delay_samples, down_sampling_factor));
config.delay.min_echo_path_delay_blocks = 0;
while ((config.delay.min_echo_path_delay_blocks + 1) * kBlockSize <
delay_samples &&
config.delay.min_echo_path_delay_blocks + 1 <= 5) {
++config.delay.min_echo_path_delay_blocks;
}
const int delay_estimate_offset =
std::max<int>(std::min(config.delay.api_call_jitter_blocks,
config.delay.min_echo_path_delay_blocks) -
1,
0);
config.delay.api_call_jitter_blocks = 5;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, 3));
DelayBuffer<float> signal_delay_buffer(delay_samples);
DelayBuffer<float> signal_delay_buffer(
delay_samples + 2 * config.delay.api_call_jitter_blocks * 64);
EchoPathDelayEstimator estimator(&data_dumper, config);
rtc::Optional<size_t> estimated_delay_samples;
for (size_t k = 0; k < (300 + delay_samples / kBlockSize); ++k) {
for (size_t k = 0; k < (500 + (delay_samples) / kBlockSize); ++k) {
RandomizeSampleVector(&random_generator, render[0]);
signal_delay_buffer.Delay(render[0], capture);
render_delay_buffer->Insert(render);
@ -92,7 +82,9 @@ TEST(EchoPathDelayEstimator, DelayEstimation) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
estimated_delay_samples = estimator.EstimateDelay(
render_delay_buffer->GetDownsampledRenderBuffer(), capture);
}
@ -100,10 +92,10 @@ TEST(EchoPathDelayEstimator, DelayEstimation) {
if (estimated_delay_samples) {
// Due to the internal down-sampling done inside the delay estimator
// the estimated delay cannot be expected to be exact to the true delay.
EXPECT_NEAR(
delay_samples,
*estimated_delay_samples + delay_estimate_offset * kBlockSize,
config.delay.down_sampling_factor);
EXPECT_NEAR(delay_samples,
*estimated_delay_samples -
(config.delay.api_call_jitter_blocks + 1) * 64,
config.delay.down_sampling_factor);
} else {
ADD_FAILURE();
}
@ -127,7 +119,8 @@ TEST(EchoPathDelayEstimator, NoInitialDelayestimates) {
RandomizeSampleVector(&random_generator, render[0]);
std::copy(render[0].begin(), render[0].end(), capture.begin());
render_delay_buffer->Insert(render);
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
EXPECT_FALSE(estimator.EstimateDelay(
render_delay_buffer->GetDownsampledRenderBuffer(), capture));
}
@ -151,7 +144,8 @@ TEST(EchoPathDelayEstimator, NoDelayEstimatesForLowLevelRenderSignals) {
}
std::copy(render[0].begin(), render[0].end(), capture.begin());
render_delay_buffer->Insert(render);
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
EXPECT_FALSE(estimator.EstimateDelay(
render_delay_buffer->GetDownsampledRenderBuffer(), capture));
}
@ -172,7 +166,7 @@ TEST(EchoPathDelayEstimator, NoDelayEstimatesForUncorrelatedSignals) {
RandomizeSampleVector(&random_generator, render[0]);
RandomizeSampleVector(&random_generator, capture);
render_delay_buffer->Insert(render);
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
EXPECT_FALSE(estimator.EstimateDelay(
render_delay_buffer->GetDownsampledRenderBuffer(), capture));
}

View File

@ -61,7 +61,7 @@ class EchoRemoverImpl final : public EchoRemover {
void ProcessCapture(const rtc::Optional<size_t>& echo_path_delay_samples,
const EchoPathVariability& echo_path_variability,
bool capture_signal_saturation,
const RenderBuffer& render_buffer,
RenderBuffer* render_buffer,
std::vector<std::vector<float>>* capture) override;
// Updates the status on whether echo leakage is detected in the output of the
@ -123,11 +123,11 @@ void EchoRemoverImpl::ProcessCapture(
const rtc::Optional<size_t>& echo_path_delay_samples,
const EchoPathVariability& echo_path_variability,
bool capture_signal_saturation,
const RenderBuffer& render_buffer,
RenderBuffer* render_buffer,
std::vector<std::vector<float>>* capture) {
const std::vector<std::vector<float>>& x = render_buffer.MostRecentBlock();
const std::vector<std::vector<float>>& x = render_buffer->MostRecentBlock();
std::vector<std::vector<float>>* y = capture;
RTC_DCHECK(render_buffer);
RTC_DCHECK(y);
RTC_DCHECK_EQ(x.size(), NumBandsForRate(sample_rate_hz_));
RTC_DCHECK_EQ(y->size(), NumBandsForRate(sample_rate_hz_));
@ -143,6 +143,8 @@ void EchoRemoverImpl::ProcessCapture(
data_dumper_->DumpRaw("aec3_echo_remover_capture_input", y0);
data_dumper_->DumpRaw("aec3_echo_remover_render_input", x0);
render_buffer->UpdateSpectralSum();
aec_state_.UpdateCaptureSaturation(capture_signal_saturation);
if (echo_path_variability.AudioPathChanged()) {
@ -165,10 +167,10 @@ void EchoRemoverImpl::ProcessCapture(
auto& e_main = subtractor_output.e_main;
// Analyze the render signal.
render_signal_analyzer_.Update(render_buffer, aec_state_.FilterDelay());
render_signal_analyzer_.Update(*render_buffer, aec_state_.FilterDelay());
// Perform linear echo cancellation.
subtractor_.Process(render_buffer, y0, render_signal_analyzer_, aec_state_,
subtractor_.Process(*render_buffer, y0, render_signal_analyzer_, aec_state_,
&subtractor_output);
// Compute spectra.
@ -180,7 +182,7 @@ void EchoRemoverImpl::ProcessCapture(
aec_state_.Update(subtractor_.FilterFrequencyResponse(),
subtractor_.FilterImpulseResponse(),
subtractor_.ConvergedFilter(), echo_path_delay_samples,
render_buffer, E2_main, Y2, x0, subtractor_output.s_main,
*render_buffer, E2_main, Y2, x0, subtractor_output.s_main,
echo_leakage_detected_);
// Choose the linear output.
@ -191,7 +193,7 @@ void EchoRemoverImpl::ProcessCapture(
const auto& E2 = output_selector_.UseSubtractorOutput() ? E2_main : Y2;
// Estimate the residual echo power.
residual_echo_estimator_.Estimate(aec_state_, render_buffer, S2_linear, Y2,
residual_echo_estimator_.Estimate(aec_state_, *render_buffer, S2_linear, Y2,
&R2);
// Estimate the comfort noise.
@ -229,7 +231,7 @@ void EchoRemoverImpl::ProcessCapture(
data_dumper_->DumpRaw("aec3_E2_shadow", E2_shadow);
data_dumper_->DumpRaw("aec3_S2_linear", S2_linear);
data_dumper_->DumpRaw("aec3_Y2", Y2);
data_dumper_->DumpRaw("aec3_X2", render_buffer.Spectrum(0));
data_dumper_->DumpRaw("aec3_X2", render_buffer->Spectrum(0));
data_dumper_->DumpRaw("aec3_R2", R2);
data_dumper_->DumpRaw("aec3_erle", aec_state_.Erle());
data_dumper_->DumpRaw("aec3_erl", aec_state_.Erl());

View File

@ -37,7 +37,7 @@ class EchoRemover {
const rtc::Optional<size_t>& echo_path_delay_samples,
const EchoPathVariability& echo_path_variability,
bool capture_signal_saturation,
const RenderBuffer& render_buffer,
RenderBuffer* render_buffer,
std::vector<std::vector<float>>* capture) = 0;
// Updates the status on whether echo leakage is detected in the output of the

View File

@ -64,7 +64,8 @@ TEST(EchoRemover, BasicApiCalls) {
(k % 6 == 0 ? rtc::Optional<size_t>(k * 10)
: rtc::nullopt);
render_buffer->Insert(render);
render_buffer->PrepareCaptureCall();
render_buffer->PrepareCaptureProcessing();
remover->ProcessCapture(echo_path_delay_samples, echo_path_variability,
k % 2 == 0 ? true : false,
render_buffer->GetRenderBuffer(), &capture);
@ -162,9 +163,8 @@ TEST(EchoRemover, BasicEchoRemoval) {
std::unique_ptr<EchoRemover> remover(EchoRemover::Create(config, rate));
std::unique_ptr<RenderDelayBuffer> render_buffer(
RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
if (delay_samples != render_buffer->Delay() * kBlockSize) {
render_buffer->SetDelay(delay_samples / kBlockSize);
}
render_buffer->SetDelay(delay_samples / kBlockSize);
std::vector<std::unique_ptr<DelayBuffer<float>>> delay_buffers(x.size());
for (size_t j = 0; j < x.size(); ++j) {
delay_buffers[j].reset(new DelayBuffer<float>(delay_samples));
@ -192,7 +192,7 @@ TEST(EchoRemover, BasicEchoRemoval) {
}
render_buffer->Insert(x);
render_buffer->PrepareCaptureCall();
render_buffer->PrepareCaptureProcessing();
remover->ProcessCapture(delay_samples, echo_path_variability, false,
render_buffer->GetRenderBuffer(), &y);

View File

@ -12,7 +12,7 @@
namespace webrtc {
FftBuffer::FftBuffer(size_t size) : buffer(size) {
FftBuffer::FftBuffer(size_t size) : size(static_cast<int>(size)), buffer(size) {
for (auto& b : buffer) {
b.Clear();
}

View File

@ -24,17 +24,20 @@ struct FftBuffer {
explicit FftBuffer(size_t size);
~FftBuffer();
size_t IncIndex(size_t index) {
return index < buffer.size() - 1 ? index + 1 : 0;
int IncIndex(int index) const {
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return index < size - 1 ? index + 1 : 0;
}
size_t DecIndex(size_t index) {
return index > 0 ? index - 1 : buffer.size() - 1;
int DecIndex(int index) const {
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return index > 0 ? index - 1 : size - 1;
}
size_t OffsetIndex(size_t index, int offset) {
int OffsetIndex(int index, int offset) const {
RTC_DCHECK_GE(buffer.size(), offset);
return (buffer.size() + index + offset) % buffer.size();
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return (size + index + offset) % size;
}
void UpdateWriteIndex(int offset) { write = OffsetIndex(write, offset); }
@ -44,9 +47,10 @@ struct FftBuffer {
void IncReadIndex() { read = IncIndex(read); }
void DecReadIndex() { read = DecIndex(read); }
const int size;
std::vector<FftData> buffer;
size_t write = 0;
size_t read = 0;
int write = 0;
int read = 0;
};
} // namespace webrtc

View File

@ -52,6 +52,7 @@ void RunFilterUpdateTest(int num_blocks_to_process,
std::vector<float> y(kBlockSize, 0.f);
EchoCanceller3Config config;
config.delay.min_echo_path_delay_blocks = 0;
config.delay.default_delay = 1;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, 3));
AecState aec_state(config);
@ -98,13 +99,14 @@ void RunFilterUpdateTest(int num_blocks_to_process,
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
render_signal_analyzer.Update(render_delay_buffer->GetRenderBuffer(),
render_signal_analyzer.Update(*render_delay_buffer->GetRenderBuffer(),
aec_state.FilterDelay());
// Apply the main filter.
main_filter.Filter(render_delay_buffer->GetRenderBuffer(), &S);
main_filter.Filter(*render_delay_buffer->GetRenderBuffer(), &S);
fft.Ifft(S, &s_scratch);
std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2,
e_main.begin(),
@ -117,7 +119,7 @@ void RunFilterUpdateTest(int num_blocks_to_process,
}
// Apply the shadow filter.
shadow_filter.Filter(render_delay_buffer->GetRenderBuffer(), &S);
shadow_filter.Filter(*render_delay_buffer->GetRenderBuffer(), &S);
fft.Ifft(S, &s_scratch);
std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2,
e_shadow.begin(),
@ -131,23 +133,23 @@ void RunFilterUpdateTest(int num_blocks_to_process,
E_shadow.Spectrum(Aec3Optimization::kNone, output.E2_shadow);
// Adapt the shadow filter.
shadow_gain.Compute(render_delay_buffer->GetRenderBuffer(),
shadow_gain.Compute(*render_delay_buffer->GetRenderBuffer(),
render_signal_analyzer, E_shadow,
shadow_filter.SizePartitions(), saturation, &G);
shadow_filter.Adapt(render_delay_buffer->GetRenderBuffer(), G);
shadow_filter.Adapt(*render_delay_buffer->GetRenderBuffer(), G);
// Adapt the main filter
main_gain.Compute(render_delay_buffer->GetRenderBuffer(),
main_gain.Compute(*render_delay_buffer->GetRenderBuffer(),
render_signal_analyzer, output, main_filter, saturation,
&G);
main_filter.Adapt(render_delay_buffer->GetRenderBuffer(), G);
main_filter.Adapt(*render_delay_buffer->GetRenderBuffer(), G);
// Update the delay.
aec_state.HandleEchoPathChange(EchoPathVariability(
false, EchoPathVariability::DelayAdjustment::kNone, false));
aec_state.Update(main_filter.FilterFrequencyResponse(),
main_filter.FilterImpulseResponse(), true, rtc::nullopt,
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0],
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0],
s, false);
}
@ -177,7 +179,7 @@ TEST(MainFilterUpdateGain, NullDataOutputGain) {
RenderSignalAnalyzer analyzer;
SubtractorOutput output;
MainFilterUpdateGain gain;
EXPECT_DEATH(gain.Compute(render_delay_buffer->GetRenderBuffer(), analyzer,
EXPECT_DEATH(gain.Compute(*render_delay_buffer->GetRenderBuffer(), analyzer,
output, filter, false, nullptr),
"");
}

View File

@ -145,16 +145,17 @@ TEST(MatchedFilter, LagEstimation) {
ApmDataDumper data_dumper(0);
for (size_t delay_samples : {5, 64, 150, 200, 800, 1000}) {
SCOPED_TRACE(ProduceDebugText(delay_samples, down_sampling_factor));
EchoCanceller3Config config;
config.delay.down_sampling_factor = down_sampling_factor;
config.delay.num_filters = kNumMatchedFilters;
config.delay.min_echo_path_delay_blocks = 0;
config.delay.api_call_jitter_blocks = 0;
Decimator capture_decimator(down_sampling_factor);
DelayBuffer<float> signal_delay_buffer(down_sampling_factor *
delay_samples);
MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size,
kWindowSizeSubBlocks, kNumMatchedFilters,
kAlignmentShiftSubBlocks, 150);
EchoCanceller3Config config;
config.delay.down_sampling_factor = down_sampling_factor;
config.delay.num_filters = kNumMatchedFilters;
config.delay.min_echo_path_delay_blocks = 0;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, 3));
@ -164,11 +165,13 @@ TEST(MatchedFilter, LagEstimation) {
RandomizeSampleVector(&random_generator, render[0]);
signal_delay_buffer.Delay(render[0], capture);
render_delay_buffer->Insert(render);
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
std::array<float, kBlockSize> downsampled_capture_data;
rtc::ArrayView<float> downsampled_capture(
downsampled_capture_data.data(), sub_block_size);

View File

@ -15,7 +15,7 @@
namespace webrtc {
MatrixBuffer::MatrixBuffer(size_t size, size_t height, size_t width)
: size(size),
: size(static_cast<int>(size)),
buffer(size,
std::vector<std::vector<float>>(height,
std::vector<float>(width, 0.f))) {

View File

@ -23,17 +23,20 @@ struct MatrixBuffer {
MatrixBuffer(size_t size, size_t height, size_t width);
~MatrixBuffer();
size_t IncIndex(size_t index) {
return index < buffer.size() - 1 ? index + 1 : 0;
int IncIndex(int index) const {
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return index < size - 1 ? index + 1 : 0;
}
size_t DecIndex(size_t index) {
return index > 0 ? index - 1 : buffer.size() - 1;
int DecIndex(int index) const {
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return index > 0 ? index - 1 : size - 1;
}
size_t OffsetIndex(size_t index, int offset) {
RTC_DCHECK_GE(buffer.size(), offset);
return (buffer.size() + index + offset) % buffer.size();
int OffsetIndex(int index, int offset) const {
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
RTC_DCHECK_GE(size, offset);
return (size + index + offset) % size;
}
void UpdateWriteIndex(int offset) { write = OffsetIndex(write, offset); }
@ -43,10 +46,10 @@ struct MatrixBuffer {
void IncReadIndex() { read = IncIndex(read); }
void DecReadIndex() { read = DecIndex(read); }
size_t size;
const int size;
std::vector<std::vector<std::vector<float>>> buffer;
size_t write = 0;
size_t read = 0;
int write = 0;
int read = 0;
};
} // namespace webrtc

View File

@ -30,7 +30,7 @@ class MockEchoRemover : public EchoRemover {
void(const rtc::Optional<size_t>& echo_path_delay_samples,
const EchoPathVariability& echo_path_variability,
bool capture_signal_saturation,
const RenderBuffer& render_buffer,
RenderBuffer* render_buffer,
std::vector<std::vector<float>>* capture));
MOCK_METHOD1(UpdateEchoLeakageStatus, void(bool leakage_detected));

View File

@ -48,18 +48,17 @@ class MockRenderDelayBuffer : public RenderDelayBuffer {
MOCK_METHOD1(Insert,
RenderDelayBuffer::BufferingEvent(
const std::vector<std::vector<float>>& block));
MOCK_METHOD0(PrepareCaptureCall, RenderDelayBuffer::BufferingEvent());
MOCK_METHOD1(SetDelay, void(size_t delay));
MOCK_CONST_METHOD0(Delay, size_t());
MOCK_METHOD0(PrepareCaptureProcessing, RenderDelayBuffer::BufferingEvent());
MOCK_METHOD1(SetDelay, bool(size_t delay));
MOCK_CONST_METHOD0(Delay, rtc::Optional<size_t>());
MOCK_CONST_METHOD0(MaxDelay, size_t());
MOCK_CONST_METHOD0(MaxApiJitter, size_t());
MOCK_CONST_METHOD0(IsBlockAvailable, bool());
MOCK_CONST_METHOD0(GetRenderBuffer, const RenderBuffer&());
MOCK_METHOD0(GetRenderBuffer, RenderBuffer*());
MOCK_CONST_METHOD0(GetDownsampledRenderBuffer,
const DownsampledRenderBuffer&());
MOCK_CONST_METHOD0(CausalDelay, bool());
private:
const RenderBuffer& FakeGetRenderBuffer() const { return render_buffer_; }
RenderBuffer* FakeGetRenderBuffer() { return &render_buffer_; }
const DownsampledRenderBuffer& FakeGetDownsampledRenderBuffer() const {
return downsampled_render_buffer_;
}

View File

@ -26,9 +26,10 @@ class MockRenderDelayController : public RenderDelayController {
MOCK_METHOD0(Reset, void());
MOCK_METHOD1(SetDelay, void(size_t render_delay));
MOCK_METHOD2(GetDelay,
size_t(const DownsampledRenderBuffer& render_buffer,
rtc::ArrayView<const float> capture));
MOCK_METHOD2(
GetDelay,
rtc::Optional<size_t>(const DownsampledRenderBuffer& render_buffer,
rtc::ArrayView<const float> capture));
MOCK_CONST_METHOD0(AlignmentHeadroomSamples, rtc::Optional<size_t>());
};

View File

@ -64,7 +64,7 @@ class RenderBuffer {
private:
const MatrixBuffer* const block_buffer_;
VectorBuffer* spectrum_buffer_;
const VectorBuffer* const spectrum_buffer_;
const FftBuffer* const fft_buffer_;
const size_t spectral_sums_length_;
std::array<float, kFftLengthBy2Plus1> spectral_sums_;

View File

@ -37,51 +37,109 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer {
void Reset() override;
BufferingEvent Insert(const std::vector<std::vector<float>>& block) override;
BufferingEvent PrepareCaptureCall() override;
void SetDelay(size_t delay) override;
size_t Delay() const override { return delay_; }
BufferingEvent PrepareCaptureProcessing() override;
bool SetDelay(size_t delay) override;
rtc::Optional<size_t> Delay() const override { return delay_; }
size_t MaxDelay() const override {
return blocks_.buffer.size() - 1 - kBufferHeadroom;
}
size_t MaxApiJitter() const override { return max_api_jitter_; }
const RenderBuffer& GetRenderBuffer() const override {
return echo_remover_buffer_;
}
RenderBuffer* GetRenderBuffer() override { return &echo_remover_buffer_; }
const DownsampledRenderBuffer& GetDownsampledRenderBuffer() const override {
return low_rate_;
}
bool CausalDelay() const override;
private:
static int instance_count_;
std::unique_ptr<ApmDataDumper> data_dumper_;
const Aec3Optimization optimization_;
const size_t api_call_jitter_blocks_;
const size_t min_echo_path_delay_blocks_;
const EchoCanceller3Config config_;
const int sub_block_size_;
MatrixBuffer blocks_;
VectorBuffer spectra_;
FftBuffer ffts_;
size_t delay_;
int max_api_jitter_ = 0;
int render_surplus_ = 0;
bool first_reset_occurred_ = false;
rtc::Optional<size_t> delay_;
rtc::Optional<int> internal_delay_;
RenderBuffer echo_remover_buffer_;
DownsampledRenderBuffer low_rate_;
Decimator render_decimator_;
const std::vector<std::vector<float>> zero_block_;
const Aec3Fft fft_;
size_t capture_call_counter_ = 0;
std::vector<float> render_ds_;
int render_calls_in_a_row_ = 0;
void UpdateBuffersWithLatestBlock(size_t previous_write);
void IncreaseRead();
void IncreaseInsert();
int LowRateBufferOffset() const { return DelayEstimatorOffset(config_) >> 1; }
int MaxExternalDelayToInternalDelay(size_t delay) const;
void ApplyDelay(int delay);
void InsertBlock(const std::vector<std::vector<float>>& block,
int previous_write);
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayBufferImpl);
};
// Increases the write indices for the render buffers.
void IncreaseWriteIndices(int sub_block_size,
MatrixBuffer* blocks,
VectorBuffer* spectra,
FftBuffer* ffts,
DownsampledRenderBuffer* low_rate) {
low_rate->UpdateWriteIndex(-sub_block_size);
blocks->IncWriteIndex();
spectra->DecWriteIndex();
ffts->DecWriteIndex();
}
// Increases the read indices for the render buffers.
void IncreaseReadIndices(const rtc::Optional<int>& delay,
int sub_block_size,
MatrixBuffer* blocks,
VectorBuffer* spectra,
FftBuffer* ffts,
DownsampledRenderBuffer* low_rate) {
RTC_DCHECK_NE(low_rate->read, low_rate->write);
low_rate->UpdateReadIndex(-sub_block_size);
if (blocks->read != blocks->write) {
blocks->IncReadIndex();
spectra->DecReadIndex();
ffts->DecReadIndex();
} else {
// Only allow underrun for blocks_ when the delay is not set.
RTC_DCHECK(!delay);
}
}
// Checks for a render buffer overrun.
bool RenderOverrun(const MatrixBuffer& b, const DownsampledRenderBuffer& l) {
return l.read == l.write || b.read == b.write;
}
// Checks for a render buffer underrun. If the delay is not specified, only the
// low rate buffer underrun is counted as the delay offset for the other buffers
// is unknown.
bool RenderUnderrun(const rtc::Optional<int>& delay,
const MatrixBuffer& b,
const DownsampledRenderBuffer& l) {
return l.read == l.write || (delay && b.read == b.write);
}
// Computes the latency in the buffer (the number of unread elements).
int BufferLatency(const DownsampledRenderBuffer& l) {
return (l.buffer.size() + l.read - l.write) % l.buffer.size();
}
// Computes the mismatch between the number of render and capture calls based on
// the known offset (achieved during reset) of the low rate buffer.
bool ApiCallSkew(const DownsampledRenderBuffer& low_rate_buffer,
int sub_block_size,
int low_rate_buffer_offset_sub_blocks) {
int latency = BufferLatency(low_rate_buffer);
int skew = abs(low_rate_buffer_offset_sub_blocks * sub_block_size - latency);
int skew_limit = low_rate_buffer_offset_sub_blocks * sub_block_size;
return skew >= skew_limit;
}
int RenderDelayBufferImpl::instance_count_ = 0;
RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config,
@ -89,8 +147,7 @@ RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config,
: data_dumper_(
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
optimization_(DetectOptimization()),
api_call_jitter_blocks_(config.delay.api_call_jitter_blocks),
min_echo_path_delay_blocks_(config.delay.min_echo_path_delay_blocks),
config_(config),
sub_block_size_(
static_cast<int>(config.delay.down_sampling_factor > 0
? kBlockSize / config.delay.down_sampling_factor
@ -101,7 +158,6 @@ RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config,
kBlockSize),
spectra_(blocks_.buffer.size(), kFftLengthBy2Plus1),
ffts_(blocks_.buffer.size()),
delay_(min_echo_path_delay_blocks_),
echo_remover_buffer_(kAdaptiveFilterLength, &blocks_, &spectra_, &ffts_),
low_rate_(GetDownSampledBufferSize(config.delay.down_sampling_factor,
config.delay.num_filters)),
@ -111,127 +167,153 @@ RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config,
render_ds_(sub_block_size_, 0.f) {
RTC_DCHECK_EQ(blocks_.buffer.size(), ffts_.buffer.size());
RTC_DCHECK_EQ(spectra_.buffer.size(), ffts_.buffer.size());
// Necessary condition to avoid unrecoverable echp due to noncausal alignment.
RTC_DCHECK_EQ(DelayEstimatorOffset(config_), LowRateBufferOffset() * 2);
Reset();
first_reset_occurred_ = false;
}
RenderDelayBufferImpl::~RenderDelayBufferImpl() = default;
// Resets the buffer delays and clears the reported delays.
void RenderDelayBufferImpl::Reset() {
delay_ = min_echo_path_delay_blocks_;
const int offset1 = std::max<int>(
std::min(api_call_jitter_blocks_, min_echo_path_delay_blocks_), 1);
const int offset2 = static_cast<int>(delay_ + offset1);
const int offset3 = offset1 * sub_block_size_;
low_rate_.read = low_rate_.OffsetIndex(low_rate_.write, offset3);
blocks_.read = blocks_.OffsetIndex(blocks_.write, -offset2);
spectra_.read = spectra_.OffsetIndex(spectra_.write, offset2);
ffts_.read = ffts_.OffsetIndex(ffts_.write, offset2);
render_surplus_ = 0;
first_reset_occurred_ = true;
// Pre-fill the low rate buffer (which is used for delay estimation) to add
// headroom for the allowed api call jitter.
low_rate_.read = low_rate_.OffsetIndex(
low_rate_.write, LowRateBufferOffset() * sub_block_size_);
// Set the render buffer delays to the default delay.
ApplyDelay(config_.delay.default_delay);
// Unset the delays which are set by ApplyConfig.
delay_ = rtc::nullopt;
internal_delay_ = rtc::nullopt;
}
// Inserts a new block into the render buffers.
RenderDelayBuffer::BufferingEvent RenderDelayBufferImpl::Insert(
const std::vector<std::vector<float>>& block) {
RTC_DCHECK_EQ(block.size(), blocks_.buffer[0].size());
RTC_DCHECK_EQ(block[0].size(), blocks_.buffer[0][0].size());
BufferingEvent event = BufferingEvent::kNone;
// Increase the write indices to where the new blocks should be written.
const int previous_write = blocks_.write;
IncreaseWriteIndices(sub_block_size_, &blocks_, &spectra_, &ffts_,
&low_rate_);
++render_surplus_;
if (first_reset_occurred_) {
++render_calls_in_a_row_;
max_api_jitter_ = std::max(max_api_jitter_, render_calls_in_a_row_);
// Allow overrun and do a reset when render overrun occurrs due to more render
// data being inserted than capture data is received.
BufferingEvent event = RenderOverrun(blocks_, low_rate_)
? event = BufferingEvent::kRenderOverrun
: BufferingEvent::kNone;
// Insert the new render block into the specified position.
InsertBlock(block, previous_write);
if (event != BufferingEvent::kNone) {
Reset();
}
const size_t previous_write = blocks_.write;
IncreaseInsert();
if (low_rate_.read == low_rate_.write || blocks_.read == blocks_.write) {
// Render overrun due to more render data being inserted than read. Discard
// the oldest render data.
event = BufferingEvent::kRenderOverrun;
IncreaseRead();
}
for (size_t k = 0; k < block.size(); ++k) {
std::copy(block[k].begin(), block[k].end(),
blocks_.buffer[blocks_.write][k].begin());
}
UpdateBuffersWithLatestBlock(previous_write);
return event;
}
RenderDelayBuffer::BufferingEvent RenderDelayBufferImpl::PrepareCaptureCall() {
// Prepares the render buffers for processing another capture block.
RenderDelayBuffer::BufferingEvent
RenderDelayBufferImpl::PrepareCaptureProcessing() {
BufferingEvent event = BufferingEvent::kNone;
render_calls_in_a_row_ = 0;
if (low_rate_.read == low_rate_.write || blocks_.read == blocks_.write) {
if (RenderUnderrun(internal_delay_, blocks_, low_rate_)) {
// Don't increase the read indices if there is a render underrun.
event = BufferingEvent::kRenderUnderrun;
} else {
IncreaseRead();
}
--render_surplus_;
// Increase the read indices in the render buffers to point to the most
// recent block to use in the capture processing.
IncreaseReadIndices(internal_delay_, sub_block_size_, &blocks_, &spectra_,
&ffts_, &low_rate_);
echo_remover_buffer_.UpdateSpectralSum();
if (render_surplus_ >= static_cast<int>(api_call_jitter_blocks_)) {
event = BufferingEvent::kApiCallSkew;
RTC_LOG(LS_WARNING) << "Api call skew detected at " << capture_call_counter_
<< ".";
// Check for skew in the API calls which, if too large, causes the delay
// estimation to be noncausal. Doing this check after the render indice
// increase saves one unit of allowed skew. Note that the skew check only
// should need to be one-sided as one of the skew directions results in an
// underrun.
bool skew = ApiCallSkew(low_rate_, sub_block_size_, LowRateBufferOffset());
event = skew ? BufferingEvent::kApiCallSkew : BufferingEvent::kNone;
}
if (event != BufferingEvent::kNone) {
Reset();
}
++capture_call_counter_;
return event;
}
void RenderDelayBufferImpl::SetDelay(size_t delay) {
if (delay_ == delay) {
return;
// Sets the delay and returns a bool indicating whether the delay was changed.
bool RenderDelayBufferImpl::SetDelay(size_t delay) {
if (delay_ && *delay_ == delay) {
return false;
}
const int delta_delay = static_cast<int>(delay_) - static_cast<int>(delay);
delay_ = delay;
if (delay_ > MaxDelay()) {
delay_ = std::min(MaxDelay(), delay);
RTC_NOTREACHED();
}
// Recompute the read indices according to the set delay.
blocks_.UpdateReadIndex(delta_delay);
spectra_.UpdateReadIndex(-delta_delay);
ffts_.UpdateReadIndex(-delta_delay);
// Compute the internal delay and limit the delay to the allowed range.
int internal_delay = MaxExternalDelayToInternalDelay(*delay_);
internal_delay_ =
std::min(MaxDelay(), static_cast<size_t>(std::max(internal_delay, 0)));
// Apply the delay to the buffers.
ApplyDelay(*internal_delay_);
return true;
}
void RenderDelayBufferImpl::UpdateBuffersWithLatestBlock(
size_t previous_write) {
render_decimator_.Decimate(blocks_.buffer[blocks_.write][0], render_ds_);
std::copy(render_ds_.rbegin(), render_ds_.rend(),
low_rate_.buffer.begin() + low_rate_.write);
// Returns whether the specified delay is causal.
bool RenderDelayBufferImpl::CausalDelay() const {
return !internal_delay_ ||
*internal_delay_ >=
static_cast<int>(config_.delay.min_echo_path_delay_blocks);
}
fft_.PaddedFft(blocks_.buffer[blocks_.write][0],
blocks_.buffer[previous_write][0], &ffts_.buffer[ffts_.write]);
// Maps the externally computed delay to the delay used internally.
int RenderDelayBufferImpl::MaxExternalDelayToInternalDelay(
size_t external_delay_blocks) const {
const int latency = BufferLatency(low_rate_);
RTC_DCHECK_LT(0, sub_block_size_);
RTC_DCHECK_EQ(0, latency % sub_block_size_);
int latency_blocks = latency / sub_block_size_;
return latency_blocks + static_cast<int>(external_delay_blocks) -
DelayEstimatorOffset(config_);
}
ffts_.buffer[ffts_.write].Spectrum(optimization_,
spectra_.buffer[spectra_.write]);
};
// Set the read indices according to the delay.
void RenderDelayBufferImpl::ApplyDelay(int delay) {
blocks_.read = blocks_.OffsetIndex(blocks_.write, -delay);
spectra_.read = spectra_.OffsetIndex(spectra_.write, delay);
ffts_.read = ffts_.OffsetIndex(ffts_.write, delay);
}
void RenderDelayBufferImpl::IncreaseRead() {
low_rate_.UpdateReadIndex(-sub_block_size_);
blocks_.IncReadIndex();
spectra_.DecReadIndex();
ffts_.DecReadIndex();
};
// Inserts a block into the render buffers.
void RenderDelayBufferImpl::InsertBlock(
const std::vector<std::vector<float>>& block,
int previous_write) {
auto& b = blocks_;
auto& lr = low_rate_;
auto& ds = render_ds_;
auto& f = ffts_;
auto& s = spectra_;
RTC_DCHECK_EQ(block.size(), b.buffer[b.write].size());
for (size_t k = 0; k < block.size(); ++k) {
RTC_DCHECK_EQ(block[k].size(), b.buffer[b.write][k].size());
std::copy(block[k].begin(), block[k].end(), b.buffer[b.write][k].begin());
}
void RenderDelayBufferImpl::IncreaseInsert() {
low_rate_.UpdateWriteIndex(-sub_block_size_);
blocks_.IncWriteIndex();
spectra_.DecWriteIndex();
ffts_.DecWriteIndex();
};
render_decimator_.Decimate(block[0], ds);
std::copy(ds.rbegin(), ds.rend(), lr.buffer.begin() + lr.write);
fft_.PaddedFft(block[0], b.buffer[previous_write][0], &f.buffer[f.write]);
f.buffer[f.write].Spectrum(optimization_, s.buffer[s.write]);
}
} // namespace
int RenderDelayBuffer::RenderDelayBuffer::DelayEstimatorOffset(
const EchoCanceller3Config& config) {
return config.delay.api_call_jitter_blocks * 2;
}
RenderDelayBuffer* RenderDelayBuffer::Create(const EchoCanceller3Config& config,
size_t num_bands) {
return new RenderDelayBufferImpl(config, num_bands);

View File

@ -49,25 +49,29 @@ class RenderDelayBuffer {
// Updates the buffers one step based on the specified buffer delay. Returns
// an enum indicating whether there was a special event that occurred.
virtual BufferingEvent PrepareCaptureCall() = 0;
virtual BufferingEvent PrepareCaptureProcessing() = 0;
// Sets the buffer delay.
virtual void SetDelay(size_t delay) = 0;
// Sets the buffer delay and returns a bool indicating whether the delay
// changed.
virtual bool SetDelay(size_t delay) = 0;
// Gets the buffer delay.
virtual size_t Delay() const = 0;
virtual rtc::Optional<size_t> Delay() const = 0;
// Gets the buffer delay.
virtual size_t MaxDelay() const = 0;
// Gets the observed jitter in the render and capture call sequence.
virtual size_t MaxApiJitter() const = 0;
// Returns the render buffer for the echo remover.
virtual const RenderBuffer& GetRenderBuffer() const = 0;
virtual RenderBuffer* GetRenderBuffer() = 0;
// Returns the downsampled render buffer.
virtual const DownsampledRenderBuffer& GetDownsampledRenderBuffer() const = 0;
// Returns whether the current delay is noncausal.
virtual bool CausalDelay() const = 0;
// Returns the maximum non calusal offset that can occur in the delay buffer.
static int DelayEstimatorOffset(const EchoCanceller3Config& config);
};
} // namespace webrtc

View File

@ -45,12 +45,16 @@ TEST(RenderDelayBuffer, BufferOverflow) {
EXPECT_EQ(RenderDelayBuffer::BufferingEvent::kNone,
delay_buffer->Insert(block_to_insert));
}
bool overrun_occurred = false;
for (size_t k = 0; k < 1000; ++k) {
delay_buffer->Insert(block_to_insert);
RenderDelayBuffer::BufferingEvent event =
delay_buffer->Insert(block_to_insert);
overrun_occurred =
overrun_occurred ||
RenderDelayBuffer::BufferingEvent::kRenderOverrun == event;
}
EXPECT_EQ(RenderDelayBuffer::BufferingEvent::kRenderOverrun,
delay_buffer->Insert(block_to_insert));
EXPECT_TRUE(overrun_occurred);
}
}
@ -63,7 +67,7 @@ TEST(RenderDelayBuffer, AvailableBlock) {
kNumBands, std::vector<float>(kBlockSize, 1.f));
EXPECT_EQ(RenderDelayBuffer::BufferingEvent::kNone,
delay_buffer->Insert(input_block));
delay_buffer->PrepareCaptureCall();
delay_buffer->PrepareCaptureProcessing();
}
// Verifies the SetDelay method.
@ -71,11 +75,12 @@ TEST(RenderDelayBuffer, SetDelay) {
EchoCanceller3Config config;
std::unique_ptr<RenderDelayBuffer> delay_buffer(
RenderDelayBuffer::Create(config, 1));
EXPECT_EQ(config.delay.min_echo_path_delay_blocks, delay_buffer->Delay());
ASSERT_FALSE(delay_buffer->Delay());
for (size_t delay = config.delay.min_echo_path_delay_blocks + 1; delay < 20;
++delay) {
delay_buffer->SetDelay(delay);
EXPECT_EQ(delay, delay_buffer->Delay());
ASSERT_TRUE(delay_buffer->Delay());
EXPECT_EQ(delay, *delay_buffer->Delay());
}
}

View File

@ -28,12 +28,13 @@ namespace {
class RenderDelayControllerImpl final : public RenderDelayController {
public:
RenderDelayControllerImpl(const EchoCanceller3Config& config,
int non_causal_offset,
int sample_rate_hz);
~RenderDelayControllerImpl() override;
void Reset() override;
void SetDelay(size_t render_delay) override;
size_t GetDelay(const DownsampledRenderBuffer& render_buffer,
rtc::ArrayView<const float> capture) override;
rtc::Optional<size_t> GetDelay(const DownsampledRenderBuffer& render_buffer,
rtc::ArrayView<const float> capture) override;
rtc::Optional<size_t> AlignmentHeadroomSamples() const override {
return headroom_samples_;
}
@ -41,32 +42,30 @@ class RenderDelayControllerImpl final : public RenderDelayController {
private:
static int instance_count_;
std::unique_ptr<ApmDataDumper> data_dumper_;
const size_t min_echo_path_delay_;
const size_t default_delay_;
size_t delay_;
rtc::Optional<size_t> delay_;
EchoPathDelayEstimator delay_estimator_;
size_t blocks_since_last_delay_estimate_ = 300000;
int echo_path_delay_samples_;
size_t align_call_counter_ = 0;
rtc::Optional<size_t> headroom_samples_;
std::vector<float> capture_delay_buffer_;
int capture_delay_buffer_index_ = 0;
std::vector<float> delay_buf_;
int delay_buf_index_ = 0;
RenderDelayControllerMetrics metrics_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayControllerImpl);
};
size_t ComputeNewBufferDelay(size_t current_delay,
size_t echo_path_delay_samples) {
size_t ComputeNewBufferDelay(rtc::Optional<size_t> current_delay,
size_t delay_samples) {
// The below division is not exact and the truncation is intended.
const int echo_path_delay_blocks = echo_path_delay_samples / kBlockSize;
const int echo_path_delay_blocks = delay_samples >> kBlockSizeLog2;
constexpr int kDelayHeadroomBlocks = 1;
// Compute the buffer delay increase required to achieve the desired latency.
size_t new_delay = std::max(echo_path_delay_blocks - kDelayHeadroomBlocks, 0);
// Add hysteresis.
if (new_delay == current_delay + 1) {
new_delay = current_delay;
if (current_delay) {
if (new_delay == *current_delay + 1) {
new_delay = *current_delay;
}
}
return new_delay;
@ -76,32 +75,24 @@ int RenderDelayControllerImpl::instance_count_ = 0;
RenderDelayControllerImpl::RenderDelayControllerImpl(
const EchoCanceller3Config& config,
int non_causal_offset,
int sample_rate_hz)
: data_dumper_(
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
min_echo_path_delay_(config.delay.min_echo_path_delay_blocks),
default_delay_(
std::max(config.delay.default_delay, min_echo_path_delay_)),
delay_(default_delay_),
delay_estimator_(data_dumper_.get(), config),
echo_path_delay_samples_(default_delay_ * kBlockSize),
capture_delay_buffer_(
kBlockSize * (config.delay.api_call_jitter_blocks + 2),
0.f) {
delay_buf_(kBlockSize * non_causal_offset, 0.f) {
RTC_DCHECK(ValidFullBandRate(sample_rate_hz));
delay_estimator_.LogDelayEstimationProperties(sample_rate_hz,
capture_delay_buffer_.size());
delay_buf_.size());
}
RenderDelayControllerImpl::~RenderDelayControllerImpl() = default;
void RenderDelayControllerImpl::Reset() {
delay_ = default_delay_;
blocks_since_last_delay_estimate_ = 300000;
echo_path_delay_samples_ = delay_ * kBlockSize;
delay_ = rtc::nullopt;
align_call_counter_ = 0;
headroom_samples_ = rtc::nullopt;
std::fill(capture_delay_buffer_.begin(), capture_delay_buffer_.end(), 0.f);
std::fill(delay_buf_.begin(), delay_buf_.end(), 0.f);
delay_estimator_.Reset();
}
@ -114,60 +105,43 @@ void RenderDelayControllerImpl::SetDelay(size_t render_delay) {
}
}
size_t RenderDelayControllerImpl::GetDelay(
rtc::Optional<size_t> RenderDelayControllerImpl::GetDelay(
const DownsampledRenderBuffer& render_buffer,
rtc::ArrayView<const float> capture) {
RTC_DCHECK_EQ(kBlockSize, capture.size());
++align_call_counter_;
// Estimate the delay with a delayed capture signal in order to catch
// noncausal delays.
RTC_DCHECK_LT(capture_delay_buffer_index_ + kBlockSize - 1,
capture_delay_buffer_.size());
const rtc::Optional<size_t> echo_path_delay_samples_shifted =
delay_estimator_.EstimateDelay(
render_buffer,
rtc::ArrayView<const float>(
&capture_delay_buffer_[capture_delay_buffer_index_], kBlockSize));
// Estimate the delay with a delayed capture.
RTC_DCHECK_LT(delay_buf_index_ + kBlockSize - 1, delay_buf_.size());
rtc::ArrayView<const float> capture_delayed(&delay_buf_[delay_buf_index_],
kBlockSize);
auto delay_samples =
delay_estimator_.EstimateDelay(render_buffer, capture_delayed);
std::copy(capture.begin(), capture.end(),
capture_delay_buffer_.begin() + capture_delay_buffer_index_);
capture_delay_buffer_index_ =
(capture_delay_buffer_index_ + kBlockSize) % capture_delay_buffer_.size();
if (echo_path_delay_samples_shifted) {
blocks_since_last_delay_estimate_ = 0;
// Correct for the capture signal delay.
const int echo_path_delay_samples_corrected =
static_cast<int>(*echo_path_delay_samples_shifted) -
static_cast<int>(capture_delay_buffer_.size());
echo_path_delay_samples_ = std::max(0, echo_path_delay_samples_corrected);
delay_buf_.begin() + delay_buf_index_);
delay_buf_index_ = (delay_buf_index_ + kBlockSize) % delay_buf_.size();
if (delay_samples) {
// Compute and set new render delay buffer delay.
const size_t new_delay =
ComputeNewBufferDelay(delay_, echo_path_delay_samples_);
if (align_call_counter_ > kNumBlocksPerSecond) {
delay_ = new_delay;
delay_ = ComputeNewBufferDelay(delay_, static_cast<int>(*delay_samples));
// Update render delay buffer headroom.
if (echo_path_delay_samples_corrected >= 0) {
const int headroom = echo_path_delay_samples_ - delay_ * kBlockSize;
RTC_DCHECK_LE(0, headroom);
headroom_samples_ = headroom;
} else {
headroom_samples_ = rtc::nullopt;
}
const int headroom =
static_cast<int>(*delay_samples) - *delay_ * kBlockSize;
RTC_DCHECK_LE(0, headroom);
headroom_samples_ = headroom;
}
metrics_.Update(echo_path_delay_samples_, delay_);
metrics_.Update(static_cast<int>(*delay_samples), delay_ ? *delay_ : 0);
} else {
metrics_.Update(rtc::nullopt, delay_);
metrics_.Update(rtc::nullopt, delay_ ? *delay_ : 0);
}
data_dumper_->DumpRaw("aec3_render_delay_controller_delay", 1,
&echo_path_delay_samples_);
data_dumper_->DumpRaw("aec3_render_delay_controller_buffer_delay", delay_);
data_dumper_->DumpRaw("aec3_render_delay_controller_delay",
delay_samples ? *delay_samples : 0);
data_dumper_->DumpRaw("aec3_render_delay_controller_buffer_delay",
delay_ ? *delay_ : 0);
return delay_;
}
@ -176,8 +150,10 @@ size_t RenderDelayControllerImpl::GetDelay(
RenderDelayController* RenderDelayController::Create(
const EchoCanceller3Config& config,
int non_causal_offset,
int sample_rate_hz) {
return new RenderDelayControllerImpl(config, sample_rate_hz);
return new RenderDelayControllerImpl(config, non_causal_offset,
sample_rate_hz);
}
} // namespace webrtc

View File

@ -24,6 +24,7 @@ namespace webrtc {
class RenderDelayController {
public:
static RenderDelayController* Create(const EchoCanceller3Config& config,
int non_causal_offset,
int sample_rate_hz);
virtual ~RenderDelayController() = default;
@ -34,8 +35,9 @@ class RenderDelayController {
virtual void SetDelay(size_t render_delay) = 0;
// Aligns the render buffer content with the capture signal.
virtual size_t GetDelay(const DownsampledRenderBuffer& render_buffer,
rtc::ArrayView<const float> capture) = 0;
virtual rtc::Optional<size_t> GetDelay(
const DownsampledRenderBuffer& render_buffer,
rtc::ArrayView<const float> capture) = 0;
// Returns an approximate value for the headroom in the buffer alignment.
virtual rtc::Optional<size_t> AlignmentHeadroomSamples() const = 0;

View File

@ -58,7 +58,8 @@ TEST(RenderDelayController, NoRenderSignal) {
std::unique_ptr<RenderDelayBuffer> delay_buffer(
RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
std::unique_ptr<RenderDelayController> delay_controller(
RenderDelayController::Create(config, rate));
RenderDelayController::Create(
config, RenderDelayBuffer::DelayEstimatorOffset(config), rate));
for (size_t k = 0; k < 100; ++k) {
EXPECT_EQ(config.delay.min_echo_path_delay_blocks,
delay_controller->GetDelay(
@ -72,7 +73,7 @@ TEST(RenderDelayController, NoRenderSignal) {
// Verifies the basic API call sequence.
TEST(RenderDelayController, BasicApiCalls) {
std::vector<float> capture_block(kBlockSize, 0.f);
size_t delay_blocks = 0;
rtc::Optional<size_t> delay_blocks = 0;
for (size_t num_matched_filters = 4; num_matched_filters == 10;
num_matched_filters++) {
for (auto down_sampling_factor : kDownSamplingFactors) {
@ -85,13 +86,17 @@ TEST(RenderDelayController, BasicApiCalls) {
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
std::unique_ptr<RenderDelayController> delay_controller(
RenderDelayController::Create(EchoCanceller3Config(), rate));
RenderDelayController::Create(
EchoCanceller3Config(),
RenderDelayBuffer::DelayEstimatorOffset(config), rate));
for (size_t k = 0; k < 10; ++k) {
render_delay_buffer->Insert(render_block);
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
delay_blocks = delay_controller->GetDelay(
render_delay_buffer->GetDownsampledRenderBuffer(), capture_block);
}
EXPECT_TRUE(delay_blocks);
EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples());
EXPECT_EQ(config.delay.min_echo_path_delay_blocks, delay_blocks);
}
@ -104,7 +109,6 @@ TEST(RenderDelayController, BasicApiCalls) {
TEST(RenderDelayController, Alignment) {
Random random_generator(42U);
std::vector<float> capture_block(kBlockSize, 0.f);
size_t delay_blocks = 0;
for (size_t num_matched_filters = 4; num_matched_filters == 10;
num_matched_filters++) {
for (auto down_sampling_factor : kDownSamplingFactors) {
@ -117,21 +121,25 @@ TEST(RenderDelayController, Alignment) {
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
for (size_t delay_samples : {15, 50, 150, 200, 800, 4000}) {
rtc::Optional<size_t> delay_blocks;
SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
std::unique_ptr<RenderDelayController> delay_controller(
RenderDelayController::Create(config, rate));
RenderDelayController::Create(
config, RenderDelayBuffer::DelayEstimatorOffset(config),
rate));
DelayBuffer<float> signal_delay_buffer(delay_samples);
for (size_t k = 0; k < (400 + delay_samples / kBlockSize); ++k) {
RandomizeSampleVector(&random_generator, render_block[0]);
signal_delay_buffer.Delay(render_block[0], capture_block);
render_delay_buffer->Insert(render_block);
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
delay_blocks = delay_controller->GetDelay(
render_delay_buffer->GetDownsampledRenderBuffer(),
capture_block);
}
ASSERT_TRUE(!!delay_blocks);
constexpr int kDelayHeadroomBlocks = 1;
size_t expected_delay_blocks =
@ -143,7 +151,7 @@ TEST(RenderDelayController, Alignment) {
const rtc::Optional<size_t> headroom_samples =
delay_controller->AlignmentHeadroomSamples();
ASSERT_TRUE(headroom_samples);
EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize,
EXPECT_NEAR(delay_samples - *delay_blocks * kBlockSize,
*headroom_samples, 4);
}
}
@ -155,7 +163,6 @@ TEST(RenderDelayController, Alignment) {
// delays.
TEST(RenderDelayController, NonCausalAlignment) {
Random random_generator(42U);
size_t delay_blocks = 0;
for (size_t num_matched_filters = 4; num_matched_filters == 10;
num_matched_filters++) {
for (auto down_sampling_factor : kDownSamplingFactors) {
@ -169,24 +176,27 @@ TEST(RenderDelayController, NonCausalAlignment) {
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
for (int delay_samples : {-15, -50, -150, -200}) {
rtc::Optional<size_t> delay_blocks;
SCOPED_TRACE(ProduceDebugText(rate, -delay_samples));
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
std::unique_ptr<RenderDelayController> delay_controller(
RenderDelayController::Create(EchoCanceller3Config(), rate));
RenderDelayController::Create(
EchoCanceller3Config(),
RenderDelayBuffer::DelayEstimatorOffset(config), rate));
DelayBuffer<float> signal_delay_buffer(-delay_samples);
for (int k = 0;
k < (400 - delay_samples / static_cast<int>(kBlockSize)); ++k) {
RandomizeSampleVector(&random_generator, capture_block[0]);
signal_delay_buffer.Delay(capture_block[0], render_block[0]);
render_delay_buffer->Insert(render_block);
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
delay_blocks = delay_controller->GetDelay(
render_delay_buffer->GetDownsampledRenderBuffer(),
capture_block[0]);
}
EXPECT_EQ(0u, delay_blocks);
ASSERT_FALSE(delay_blocks);
const rtc::Optional<size_t> headroom_samples =
delay_controller->AlignmentHeadroomSamples();
@ -212,12 +222,14 @@ TEST(RenderDelayController, AlignmentWithJitter) {
std::vector<std::vector<float>> render_block(
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
for (size_t delay_samples : {15, 50, 300, 800}) {
size_t delay_blocks = 0;
rtc::Optional<size_t> delay_blocks;
SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
std::unique_ptr<RenderDelayController> delay_controller(
RenderDelayController::Create(config, rate));
RenderDelayController::Create(
config, RenderDelayBuffer::DelayEstimatorOffset(config),
rate));
DelayBuffer<float> signal_delay_buffer(delay_samples);
for (size_t j = 0; j < (1000 + delay_samples / kBlockSize) /
config.delay.api_call_jitter_blocks +
@ -233,7 +245,7 @@ TEST(RenderDelayController, AlignmentWithJitter) {
}
for (size_t k = 0; k < (config.delay.api_call_jitter_blocks - 1);
++k) {
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
delay_blocks = delay_controller->GetDelay(
render_delay_buffer->GetDownsampledRenderBuffer(),
capture_block_buffer[k]);
@ -248,12 +260,13 @@ TEST(RenderDelayController, AlignmentWithJitter) {
expected_delay_blocks = 0;
}
EXPECT_EQ(expected_delay_blocks, delay_blocks);
ASSERT_TRUE(delay_blocks);
EXPECT_EQ(expected_delay_blocks, *delay_blocks);
const rtc::Optional<size_t> headroom_samples =
delay_controller->AlignmentHeadroomSamples();
ASSERT_TRUE(headroom_samples);
EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize,
EXPECT_NEAR(delay_samples - *delay_blocks * kBlockSize,
*headroom_samples, 4);
}
}
@ -277,7 +290,8 @@ TEST(RenderDelayController, InitialHeadroom) {
RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
std::unique_ptr<RenderDelayController> delay_controller(
RenderDelayController::Create(config, rate));
RenderDelayController::Create(
config, RenderDelayBuffer::DelayEstimatorOffset(config), rate));
EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples());
}
}
@ -296,7 +310,9 @@ TEST(RenderDelayController, WrongCaptureSize) {
RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
EXPECT_DEATH(
std::unique_ptr<RenderDelayController>(
RenderDelayController::Create(EchoCanceller3Config(), rate))
RenderDelayController::Create(
EchoCanceller3Config(),
RenderDelayBuffer::DelayEstimatorOffset(config), rate))
->GetDelay(render_delay_buffer->GetDownsampledRenderBuffer(),
block),
"");
@ -313,8 +329,9 @@ TEST(RenderDelayController, DISABLED_WrongSampleRate) {
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
EXPECT_DEATH(
std::unique_ptr<RenderDelayController>(
RenderDelayController::Create(EchoCanceller3Config(), rate)),
std::unique_ptr<RenderDelayController>(RenderDelayController::Create(
EchoCanceller3Config(),
RenderDelayBuffer::DelayEstimatorOffset(config), rate)),
"");
}
}

View File

@ -70,9 +70,9 @@ TEST(RenderSignalAnalyzer, NoFalseDetectionOfNarrowBands) {
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
analyzer.Update(render_delay_buffer->GetRenderBuffer(),
analyzer.Update(*render_delay_buffer->GetRenderBuffer(),
rtc::Optional<size_t>(0));
}
@ -109,9 +109,9 @@ TEST(RenderSignalAnalyzer, NarrowBandDetection) {
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
analyzer.Update(render_delay_buffer->GetRenderBuffer(),
analyzer.Update(*render_delay_buffer->GetRenderBuffer(),
known_delay ? rtc::Optional<size_t>(0) : rtc::nullopt);
}
};

View File

@ -32,7 +32,7 @@ TEST(ResidualEchoEstimator, NullResidualEchoPowerOutput) {
std::array<float, kFftLengthBy2Plus1> S2_linear;
std::array<float, kFftLengthBy2Plus1> Y2;
EXPECT_DEATH(ResidualEchoEstimator(EchoCanceller3Config{})
.Estimate(aec_state, render_delay_buffer->GetRenderBuffer(),
.Estimate(aec_state, *render_delay_buffer->GetRenderBuffer(),
S2_linear, Y2, nullptr),
"");
}
@ -89,13 +89,13 @@ TEST(ResidualEchoEstimator, DISABLED_BasicTest) {
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
aec_state.HandleEchoPathChange(echo_path_variability);
aec_state.Update(H2, h, true, 2, render_delay_buffer->GetRenderBuffer(),
aec_state.Update(H2, h, true, 2, *render_delay_buffer->GetRenderBuffer(),
E2_main, Y2, x[0], s, false);
estimator.Estimate(aec_state, render_delay_buffer->GetRenderBuffer(),
estimator.Estimate(aec_state, *render_delay_buffer->GetRenderBuffer(),
S2_linear, Y2, &R2);
}
std::for_each(R2.begin(), R2.end(),

View File

@ -42,6 +42,7 @@ void RunFilterUpdateTest(int num_blocks_to_process,
EchoCanceller3Config config;
config.delay.min_echo_path_delay_blocks = 0;
config.delay.default_delay = 1;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, 3));
@ -76,12 +77,13 @@ void RunFilterUpdateTest(int num_blocks_to_process,
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
render_signal_analyzer.Update(render_delay_buffer->GetRenderBuffer(),
render_signal_analyzer.Update(*render_delay_buffer->GetRenderBuffer(),
delay_samples / kBlockSize);
shadow_filter.Filter(render_delay_buffer->GetRenderBuffer(), &S);
shadow_filter.Filter(*render_delay_buffer->GetRenderBuffer(), &S);
fft.Ifft(S, &s);
std::transform(y.begin(), y.end(), s.begin() + kFftLengthBy2,
e_shadow.begin(),
@ -90,10 +92,10 @@ void RunFilterUpdateTest(int num_blocks_to_process,
[](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); });
fft.ZeroPaddedFft(e_shadow, &E_shadow);
shadow_gain.Compute(render_delay_buffer->GetRenderBuffer(),
shadow_gain.Compute(*render_delay_buffer->GetRenderBuffer(),
render_signal_analyzer, E_shadow,
shadow_filter.SizePartitions(), saturation, &G);
shadow_filter.Adapt(render_delay_buffer->GetRenderBuffer(), G);
shadow_filter.Adapt(*render_delay_buffer->GetRenderBuffer(), G);
}
std::copy(e_shadow.begin(), e_shadow.end(), e_last_block->begin());

View File

@ -35,6 +35,7 @@ float RunSubtractorTest(int num_blocks_to_process,
SubtractorOutput output;
EchoCanceller3Config config;
config.delay.min_echo_path_delay_blocks = 0;
config.delay.default_delay = 1;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, 3));
RenderSignalAnalyzer render_signal_analyzer;
@ -61,8 +62,9 @@ float RunSubtractorTest(int num_blocks_to_process,
if (k == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureCall();
render_signal_analyzer.Update(render_delay_buffer->GetRenderBuffer(),
render_delay_buffer->PrepareCaptureProcessing();
render_delay_buffer->GetRenderBuffer()->UpdateSpectralSum();
render_signal_analyzer.Update(*render_delay_buffer->GetRenderBuffer(),
aec_state.FilterDelay());
// Handle echo path changes.
@ -73,7 +75,7 @@ float RunSubtractorTest(int num_blocks_to_process,
true, EchoPathVariability::DelayAdjustment::kNewDetectedDelay,
false));
}
subtractor.Process(render_delay_buffer->GetRenderBuffer(), y,
subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
render_signal_analyzer, aec_state, &output);
aec_state.HandleEchoPathChange(EchoPathVariability(
@ -82,7 +84,7 @@ float RunSubtractorTest(int num_blocks_to_process,
subtractor.FilterImpulseResponse(),
subtractor.ConvergedFilter(),
rtc::Optional<size_t>(delay_samples / kBlockSize),
render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0],
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0],
output.s_main, false);
}
@ -122,7 +124,7 @@ TEST(Subtractor, DISABLED_NullOutput) {
RenderSignalAnalyzer render_signal_analyzer;
std::vector<float> y(kBlockSize, 0.f);
EXPECT_DEATH(subtractor.Process(render_delay_buffer->GetRenderBuffer(), y,
EXPECT_DEATH(subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
render_signal_analyzer,
AecState(EchoCanceller3Config{}), nullptr),
"");
@ -138,7 +140,7 @@ TEST(Subtractor, WrongCaptureSize) {
std::vector<float> y(kBlockSize - 1, 0.f);
SubtractorOutput output;
EXPECT_DEATH(subtractor.Process(render_delay_buffer->GetRenderBuffer(), y,
EXPECT_DEATH(subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
render_signal_analyzer,
AecState(EchoCanceller3Config{}), &output),
"");

View File

@ -71,7 +71,7 @@ TEST(SuppressionGain, BasicGainComputation) {
s.fill(10.f);
aec_state.Update(
subtractor.FilterFrequencyResponse(), subtractor.FilterImpulseResponse(),
subtractor.ConvergedFilter(), 10, render_delay_buffer->GetRenderBuffer(),
subtractor.ConvergedFilter(), 10, *render_delay_buffer->GetRenderBuffer(),
E2, Y2, x[0], s, false);
suppression_gain.GetGain(E2, R2, N2, analyzer, aec_state, x, &high_bands_gain,
&g);
@ -88,14 +88,14 @@ TEST(SuppressionGain, BasicGainComputation) {
aec_state.Update(
subtractor.FilterFrequencyResponse(),
subtractor.FilterImpulseResponse(), subtractor.ConvergedFilter(), 10,
render_delay_buffer->GetRenderBuffer(), E2, Y2, x[0], s, false);
*render_delay_buffer->GetRenderBuffer(), E2, Y2, x[0], s, false);
}
for (int k = 0; k < 100; ++k) {
aec_state.Update(
subtractor.FilterFrequencyResponse(),
subtractor.FilterImpulseResponse(), subtractor.ConvergedFilter(), 10,
render_delay_buffer->GetRenderBuffer(), E2, Y2, x[0], s, false);
*render_delay_buffer->GetRenderBuffer(), E2, Y2, x[0], s, false);
suppression_gain.GetGain(E2, R2, N2, analyzer, aec_state, x,
&high_bands_gain, &g);
}
@ -111,7 +111,7 @@ TEST(SuppressionGain, BasicGainComputation) {
aec_state.Update(
subtractor.FilterFrequencyResponse(),
subtractor.FilterImpulseResponse(), subtractor.ConvergedFilter(), 10,
render_delay_buffer->GetRenderBuffer(), E2, Y2, x[0], s, false);
*render_delay_buffer->GetRenderBuffer(), E2, Y2, x[0], s, false);
suppression_gain.GetGain(E2, R2, N2, analyzer, aec_state, x,
&high_bands_gain, &g);
}

View File

@ -15,7 +15,8 @@
namespace webrtc {
VectorBuffer::VectorBuffer(size_t size, size_t height)
: size(size), buffer(size, std::vector<float>(height, 0.f)) {
: size(static_cast<int>(size)),
buffer(size, std::vector<float>(height, 0.f)) {
for (auto& c : buffer) {
std::fill(c.begin(), c.end(), 0.f);
}

View File

@ -23,17 +23,20 @@ struct VectorBuffer {
VectorBuffer(size_t size, size_t height);
~VectorBuffer();
size_t IncIndex(size_t index) {
return index < buffer.size() - 1 ? index + 1 : 0;
int IncIndex(int index) const {
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return index < size - 1 ? index + 1 : 0;
}
size_t DecIndex(size_t index) {
return index > 0 ? index - 1 : buffer.size() - 1;
int DecIndex(int index) const {
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return index > 0 ? index - 1 : size - 1;
}
size_t OffsetIndex(size_t index, int offset) {
RTC_DCHECK_GE(buffer.size(), offset);
return (buffer.size() + index + offset) % buffer.size();
int OffsetIndex(int index, int offset) const {
RTC_DCHECK_GE(size, offset);
RTC_DCHECK_EQ(buffer.size(), static_cast<size_t>(size));
return (size + index + offset) % size;
}
void UpdateWriteIndex(int offset) { write = OffsetIndex(write, offset); }
@ -43,10 +46,10 @@ struct VectorBuffer {
void IncReadIndex() { read = IncIndex(read); }
void DecReadIndex() { read = DecIndex(read); }
size_t size;
const int size;
std::vector<std::vector<float>> buffer;
size_t write = 0;
size_t read = 0;
int write = 0;
int read = 0;
};
} // namespace webrtc