From 609b047b0758f8f1fba6d1d9ba4bb9094ae79489 Mon Sep 17 00:00:00 2001 From: Jakob Ivarsson Date: Mon, 19 Oct 2020 09:19:34 +0200 Subject: [PATCH] Add NetEq decision logic unit tests. - Add buffer level filter and delay manager mocks and make them injectable for easier testing. - Add a basic set of tests for simple cases and recently added features. Bug: webrtc:10333 Change-Id: I8b6f73b8ad99ad6859ed1279086c0bd68b7687be Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/188623 Commit-Queue: Jakob Ivarsson Reviewed-by: Ivo Creusen Cr-Commit-Position: refs/heads/master@{#32433} --- modules/audio_coding/BUILD.gn | 2 + modules/audio_coding/neteq/decision_logic.cc | 25 ++- modules/audio_coding/neteq/decision_logic.h | 9 +- .../neteq/decision_logic_unittest.cc | 207 ++++++++++++++++-- .../neteq/mock/mock_buffer_level_filter.h | 28 +++ .../neteq/mock/mock_delay_manager.h | 39 ++++ 6 files changed, 278 insertions(+), 32 deletions(-) create mode 100644 modules/audio_coding/neteq/mock/mock_buffer_level_filter.h create mode 100644 modules/audio_coding/neteq/mock/mock_delay_manager.h diff --git a/modules/audio_coding/BUILD.gn b/modules/audio_coding/BUILD.gn index cdf7821d6f..9d332496c2 100644 --- a/modules/audio_coding/BUILD.gn +++ b/modules/audio_coding/BUILD.gn @@ -1976,7 +1976,9 @@ if (rtc_include_tests) { "neteq/expand_unittest.cc", "neteq/histogram_unittest.cc", "neteq/merge_unittest.cc", + "neteq/mock/mock_buffer_level_filter.h", "neteq/mock/mock_decoder_database.h", + "neteq/mock/mock_delay_manager.h", "neteq/mock/mock_dtmf_buffer.h", "neteq/mock/mock_dtmf_tone_generator.h", "neteq/mock/mock_expand.h", diff --git a/modules/audio_coding/neteq/decision_logic.cc b/modules/audio_coding/neteq/decision_logic.cc index fb26b9995f..9c0ee96824 100644 --- a/modules/audio_coding/neteq/decision_logic.cc +++ b/modules/audio_coding/neteq/decision_logic.cc @@ -34,9 +34,18 @@ constexpr int kDecelerationTargetLevelOffsetMs = 85; namespace webrtc { DecisionLogic::DecisionLogic(NetEqController::Config config) - : delay_manager_(DelayManager::Create(config.max_packets_in_buffer, - config.base_min_delay_ms, - config.tick_timer)), + : DecisionLogic(config, + DelayManager::Create(config.max_packets_in_buffer, + config.base_min_delay_ms, + config.tick_timer), + std::make_unique()) {} + +DecisionLogic::DecisionLogic( + NetEqController::Config config, + std::unique_ptr delay_manager, + std::unique_ptr buffer_level_filter) + : delay_manager_(std::move(delay_manager)), + buffer_level_filter_(std::move(buffer_level_filter)), tick_timer_(config.tick_timer), disallow_time_stretching_(!config.allow_time_stretching), timescale_countdown_( @@ -82,7 +91,7 @@ void DecisionLogic::SoftReset() { tick_timer_->GetNewCountdown(kMinTimescaleInterval + 1); time_stretched_cn_samples_ = 0; delay_manager_->Reset(); - buffer_level_filter_.Reset(); + buffer_level_filter_->Reset(); } void DecisionLogic::SetSampleRate(int fs_hz, size_t output_size_samples) { @@ -221,7 +230,7 @@ absl::optional DecisionLogic::PacketArrived( } void DecisionLogic::FilterBufferLevel(size_t buffer_size_samples) { - buffer_level_filter_.SetTargetBufferLevel(delay_manager_->TargetDelayMs()); + buffer_level_filter_->SetTargetBufferLevel(delay_manager_->TargetDelayMs()); int time_stretched_samples = time_stretched_cn_samples_; if (prev_time_scale_) { @@ -229,7 +238,7 @@ void DecisionLogic::FilterBufferLevel(size_t buffer_size_samples) { timescale_countdown_ = tick_timer_->GetNewCountdown(kMinTimescaleInterval); } - buffer_level_filter_.Update(buffer_size_samples, time_stretched_samples); + buffer_level_filter_->Update(buffer_size_samples, time_stretched_samples); prev_time_scale_ = false; time_stretched_cn_samples_ = 0; } @@ -300,7 +309,7 @@ NetEq::Operation DecisionLogic::ExpectedPacketAvailable(NetEq::Mode prev_mode, std::max(target_level_samples, low_limit + 20 * samples_per_ms); const int buffer_level_samples = - buffer_level_filter_.filtered_current_level(); + buffer_level_filter_->filtered_current_level(); if (buffer_level_samples >= high_limit << 2) return NetEq::Operation::kFastAccelerate; if (TimescaleAllowed()) { @@ -402,7 +411,7 @@ NetEq::Operation DecisionLogic::FuturePacketAvailable( } bool DecisionLogic::UnderTargetLevel() const { - return buffer_level_filter_.filtered_current_level() < + return buffer_level_filter_->filtered_current_level() < delay_manager_->TargetDelayMs() * sample_rate_ / 1000; } diff --git a/modules/audio_coding/neteq/decision_logic.h b/modules/audio_coding/neteq/decision_logic.h index d69c249fd3..08feba64db 100644 --- a/modules/audio_coding/neteq/decision_logic.h +++ b/modules/audio_coding/neteq/decision_logic.h @@ -11,6 +11,8 @@ #ifndef MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_H_ #define MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_H_ +#include + #include "api/neteq/neteq.h" #include "api/neteq/neteq_controller.h" #include "api/neteq/tick_timer.h" @@ -29,6 +31,9 @@ class DecisionLogic : public NetEqController { // Constructor. DecisionLogic(NetEqController::Config config); + DecisionLogic(NetEqController::Config config, + std::unique_ptr delay_manager, + std::unique_ptr buffer_level_filter); ~DecisionLogic() override; @@ -95,7 +100,7 @@ class DecisionLogic : public NetEqController { bool PeakFound() const override { return false; } int GetFilteredBufferLevel() const override { - return buffer_level_filter_.filtered_current_level(); + return buffer_level_filter_->filtered_current_level(); } // Accessors and mutators. @@ -168,7 +173,7 @@ class DecisionLogic : public NetEqController { bool MaxWaitForPacket() const; std::unique_ptr delay_manager_; - BufferLevelFilter buffer_level_filter_; + std::unique_ptr buffer_level_filter_; const TickTimer* tick_timer_; int sample_rate_; size_t output_size_samples_; diff --git a/modules/audio_coding/neteq/decision_logic_unittest.cc b/modules/audio_coding/neteq/decision_logic_unittest.cc index 56e8b84722..c29350e80b 100644 --- a/modules/audio_coding/neteq/decision_logic_unittest.cc +++ b/modules/audio_coding/neteq/decision_logic_unittest.cc @@ -15,34 +15,197 @@ #include "api/neteq/neteq_controller.h" #include "api/neteq/tick_timer.h" #include "modules/audio_coding/neteq/buffer_level_filter.h" -#include "modules/audio_coding/neteq/decoder_database.h" #include "modules/audio_coding/neteq/delay_manager.h" -#include "modules/audio_coding/neteq/packet_buffer.h" -#include "modules/audio_coding/neteq/statistics_calculator.h" +#include "modules/audio_coding/neteq/mock/mock_buffer_level_filter.h" +#include "modules/audio_coding/neteq/mock/mock_delay_manager.h" +#include "test/field_trial.h" #include "test/gtest.h" -#include "test/mock_audio_decoder_factory.h" namespace webrtc { -TEST(DecisionLogic, CreateAndDestroy) { - int fs_hz = 8000; - int output_size_samples = fs_hz / 100; // Samples per 10 ms. - DecoderDatabase decoder_database( - new rtc::RefCountedObject, absl::nullopt); - TickTimer tick_timer; - StatisticsCalculator stats; - PacketBuffer packet_buffer(10, &tick_timer); - BufferLevelFilter buffer_level_filter; - NetEqController::Config config; - config.tick_timer = &tick_timer; - config.base_min_delay_ms = 0; - config.max_packets_in_buffer = 240; - config.enable_rtx_handling = false; - config.allow_time_stretching = true; - auto logic = std::make_unique(std::move(config)); - logic->SetSampleRate(fs_hz, output_size_samples); +namespace { + +constexpr int kSampleRate = 8000; +constexpr int kSamplesPerMs = kSampleRate / 1000; +constexpr int kOutputSizeSamples = kSamplesPerMs * 10; +constexpr int kMinTimescaleInterval = 5; + +NetEqController::NetEqStatus CreateNetEqStatus(NetEq::Mode last_mode, + int current_delay_ms) { + NetEqController::NetEqStatus status; + status.play_dtmf = false; + status.last_mode = last_mode; + status.target_timestamp = 1234; + status.generated_noise_samples = 0; + status.expand_mutefactor = 0; + status.packet_buffer_info.num_samples = current_delay_ms * kSamplesPerMs; + status.packet_buffer_info.span_samples = current_delay_ms * kSamplesPerMs; + status.packet_buffer_info.span_samples_no_dtx = + current_delay_ms * kSamplesPerMs; + status.packet_buffer_info.dtx_or_cng = false; + status.next_packet = {status.target_timestamp, false, false}; + return status; } -// TODO(jakobi): Write more tests. +using ::testing::Return; + +} // namespace + +class DecisionLogicTest : public ::testing::Test { + protected: + DecisionLogicTest() { + test::ScopedFieldTrials field_trial( + "WebRTC-Audio-NetEqDecisionLogicSettings/" + "estimate_dtx_delay:true,time_stretch_cn:true/"); + + NetEqController::Config config; + config.tick_timer = &tick_timer_; + config.allow_time_stretching = true; + std::unique_ptr histogram = + std::make_unique(200, 12345, 2); + auto delay_manager = std::make_unique( + 200, 0, 12300, config.tick_timer, std::move(histogram)); + mock_delay_manager_ = delay_manager.get(); + auto buffer_level_filter = std::make_unique(); + mock_buffer_level_filter_ = buffer_level_filter.get(); + decision_logic_ = std::make_unique( + config, std::move(delay_manager), std::move(buffer_level_filter)); + decision_logic_->SetSampleRate(kSampleRate, kOutputSizeSamples); + } + + TickTimer tick_timer_; + std::unique_ptr decision_logic_; + MockDelayManager* mock_delay_manager_; + MockBufferLevelFilter* mock_buffer_level_filter_; +}; + +TEST_F(DecisionLogicTest, NormalOperation) { + EXPECT_CALL(*mock_delay_manager_, TargetDelayMs()) + .WillRepeatedly(Return(100)); + EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level()) + .WillRepeatedly(Return(90 * kSamplesPerMs)); + + bool reset_decoder = false; + tick_timer_.Increment(kMinTimescaleInterval + 1); + EXPECT_EQ(decision_logic_->GetDecision( + CreateNetEqStatus(NetEq::Mode::kNormal, 100), &reset_decoder), + NetEq::Operation::kNormal); + EXPECT_FALSE(reset_decoder); +} + +TEST_F(DecisionLogicTest, Accelerate) { + EXPECT_CALL(*mock_delay_manager_, TargetDelayMs()) + .WillRepeatedly(Return(100)); + EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level()) + .WillRepeatedly(Return(110 * kSamplesPerMs)); + + bool reset_decoder = false; + tick_timer_.Increment(kMinTimescaleInterval + 1); + EXPECT_EQ(decision_logic_->GetDecision( + CreateNetEqStatus(NetEq::Mode::kNormal, 100), &reset_decoder), + NetEq::Operation::kAccelerate); + EXPECT_FALSE(reset_decoder); +} + +TEST_F(DecisionLogicTest, FastAccelerate) { + EXPECT_CALL(*mock_delay_manager_, TargetDelayMs()) + .WillRepeatedly(Return(100)); + EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level()) + .WillRepeatedly(Return(400 * kSamplesPerMs)); + + bool reset_decoder = false; + tick_timer_.Increment(kMinTimescaleInterval + 1); + EXPECT_EQ(decision_logic_->GetDecision( + CreateNetEqStatus(NetEq::Mode::kNormal, 100), &reset_decoder), + NetEq::Operation::kFastAccelerate); + EXPECT_FALSE(reset_decoder); +} + +TEST_F(DecisionLogicTest, PreemptiveExpand) { + EXPECT_CALL(*mock_delay_manager_, TargetDelayMs()) + .WillRepeatedly(Return(100)); + EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level()) + .WillRepeatedly(Return(50 * kSamplesPerMs)); + + bool reset_decoder = false; + tick_timer_.Increment(kMinTimescaleInterval + 1); + EXPECT_EQ(decision_logic_->GetDecision( + CreateNetEqStatus(NetEq::Mode::kNormal, 100), &reset_decoder), + NetEq::Operation::kPreemptiveExpand); + EXPECT_FALSE(reset_decoder); +} + +TEST_F(DecisionLogicTest, DecelerationTargetLevelOffset) { + EXPECT_CALL(*mock_delay_manager_, TargetDelayMs()) + .WillRepeatedly(Return(500)); + EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level()) + .WillRepeatedly(Return(400 * kSamplesPerMs)); + + bool reset_decoder = false; + tick_timer_.Increment(kMinTimescaleInterval + 1); + EXPECT_EQ(decision_logic_->GetDecision( + CreateNetEqStatus(NetEq::Mode::kNormal, 400), &reset_decoder), + NetEq::Operation::kPreemptiveExpand); + EXPECT_FALSE(reset_decoder); +} + +TEST_F(DecisionLogicTest, PostponeDecodeAfterExpand) { + EXPECT_CALL(*mock_delay_manager_, TargetDelayMs()) + .WillRepeatedly(Return(500)); + + // Below 50% target delay threshold. + bool reset_decoder = false; + EXPECT_EQ(decision_logic_->GetDecision( + CreateNetEqStatus(NetEq::Mode::kExpand, 200), &reset_decoder), + NetEq::Operation::kExpand); + EXPECT_FALSE(reset_decoder); + + // Above 50% target delay threshold. + EXPECT_EQ(decision_logic_->GetDecision( + CreateNetEqStatus(NetEq::Mode::kExpand, 250), &reset_decoder), + NetEq::Operation::kNormal); + EXPECT_FALSE(reset_decoder); +} + +TEST_F(DecisionLogicTest, TimeStrechComfortNoise) { + EXPECT_CALL(*mock_delay_manager_, TargetDelayMs()) + .WillRepeatedly(Return(500)); + + { + bool reset_decoder = false; + // Below target window. + auto status = CreateNetEqStatus(NetEq::Mode::kCodecInternalCng, 400); + status.generated_noise_samples = 400 * kSamplesPerMs; + status.next_packet->timestamp = + status.target_timestamp + 400 * kSamplesPerMs; + EXPECT_EQ(decision_logic_->GetDecision(status, &reset_decoder), + NetEq::Operation::kCodecInternalCng); + EXPECT_FALSE(reset_decoder); + } + + { + bool reset_decoder = false; + // Above target window. + auto status = CreateNetEqStatus(NetEq::Mode::kCodecInternalCng, 600); + status.generated_noise_samples = 200 * kSamplesPerMs; + status.next_packet->timestamp = + status.target_timestamp + 400 * kSamplesPerMs; + EXPECT_EQ(decision_logic_->GetDecision(status, &reset_decoder), + NetEq::Operation::kNormal); + EXPECT_FALSE(reset_decoder); + + // The buffer level filter should be adjusted with the number of samples + // that was skipped. + int timestamp_leap = status.next_packet->timestamp - + status.target_timestamp - + status.generated_noise_samples; + EXPECT_CALL(*mock_buffer_level_filter_, + Update(400 * kSamplesPerMs, timestamp_leap)); + EXPECT_EQ(decision_logic_->GetDecision( + CreateNetEqStatus(NetEq::Mode::kNormal, 400), &reset_decoder), + NetEq::Operation::kNormal); + EXPECT_FALSE(reset_decoder); + } +} } // namespace webrtc diff --git a/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h b/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h new file mode 100644 index 0000000000..503f6ac6bd --- /dev/null +++ b/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 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_CODING_NETEQ_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_ +#define MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_ + +#include "modules/audio_coding/neteq/buffer_level_filter.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockBufferLevelFilter : public BufferLevelFilter { + public: + MOCK_METHOD(void, + Update, + (size_t buffer_size_samples, int time_stretched_samples)); + MOCK_METHOD(int, filtered_current_level, (), (const)); +}; + +} // namespace webrtc +#endif // MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_ diff --git a/modules/audio_coding/neteq/mock/mock_delay_manager.h b/modules/audio_coding/neteq/mock/mock_delay_manager.h new file mode 100644 index 0000000000..0631f6fcc9 --- /dev/null +++ b/modules/audio_coding/neteq/mock/mock_delay_manager.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 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_CODING_NETEQ_MOCK_MOCK_DELAY_MANAGER_H_ +#define MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_MANAGER_H_ + +#include +#include + +#include "api/neteq/tick_timer.h" +#include "modules/audio_coding/neteq/delay_manager.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockDelayManager : public DelayManager { + public: + MockDelayManager(size_t max_packets_in_buffer, + int base_minimum_delay_ms, + int histogram_quantile, + const TickTimer* tick_timer, + std::unique_ptr histogram) + : DelayManager(max_packets_in_buffer, + base_minimum_delay_ms, + histogram_quantile, + tick_timer, + std::move(histogram)) {} + MOCK_METHOD(int, TargetDelayMs, (), (const)); +}; + +} // namespace webrtc +#endif // MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_MANAGER_H_