diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc')
-rw-r--r-- | third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc b/third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc new file mode 100644 index 0000000000..6c64d0852a --- /dev/null +++ b/third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/audio_mixer/frame_combiner.h" + +#include <cstdint> +#include <initializer_list> +#include <numeric> +#include <string> +#include <type_traits> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/rtp_packet_info.h" +#include "api/rtp_packet_infos.h" +#include "api/units/timestamp.h" +#include "audio/utility/audio_frame_operations.h" +#include "modules/audio_mixer/gain_change_calculator.h" +#include "modules/audio_mixer/sine_wave_generator.h" +#include "rtc_base/checks.h" +#include "rtc_base/strings/string_builder.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::UnorderedElementsAreArray; + +struct FrameCombinerConfig { + bool use_limiter; + int sample_rate_hz; + int number_of_channels; + float wave_frequency; +}; + +std::string ProduceDebugText(int sample_rate_hz, + int number_of_channels, + int number_of_sources) { + rtc::StringBuilder ss; + ss << "Sample rate: " << sample_rate_hz << " ,"; + ss << "number of channels: " << number_of_channels << " ,"; + ss << "number of sources: " << number_of_sources; + return ss.Release(); +} + +std::string ProduceDebugText(const FrameCombinerConfig& config) { + rtc::StringBuilder ss; + ss << "Sample rate: " << config.sample_rate_hz << " ,"; + ss << "number of channels: " << config.number_of_channels << " ,"; + ss << "limiter active: " << (config.use_limiter ? "on" : "off") << " ,"; + ss << "wave frequency: " << config.wave_frequency << " ,"; + return ss.Release(); +} + +AudioFrame frame1; +AudioFrame frame2; + +void SetUpFrames(int sample_rate_hz, int number_of_channels) { + RtpPacketInfo packet_info1(/*ssrc=*/1001, /*csrcs=*/{}, + /*rtp_timestamp=*/1000, + /*receive_time=*/Timestamp::Millis(1)); + RtpPacketInfo packet_info2(/*ssrc=*/4004, /*csrcs=*/{}, + /*rtp_timestamp=*/1234, + /*receive_time=*/Timestamp::Millis(2)); + RtpPacketInfo packet_info3(/*ssrc=*/7007, /*csrcs=*/{}, + /*rtp_timestamp=*/1333, + /*receive_time=*/Timestamp::Millis(2)); + + frame1.packet_infos_ = RtpPacketInfos({packet_info1}); + frame2.packet_infos_ = RtpPacketInfos({packet_info2, packet_info3}); + + for (auto* frame : {&frame1, &frame2}) { + frame->UpdateFrame(0, nullptr, rtc::CheckedDivExact(sample_rate_hz, 100), + sample_rate_hz, AudioFrame::kNormalSpeech, + AudioFrame::kVadActive, number_of_channels); + } +} +} // namespace + +// The limiter requires sample rate divisible by 2000. +TEST(FrameCombiner, BasicApiCallsLimiter) { + FrameCombiner combiner(true); + for (const int rate : {8000, 18000, 34000, 48000}) { + for (const int number_of_channels : {1, 2, 4, 8}) { + const std::vector<AudioFrame*> all_frames = {&frame1, &frame2}; + SetUpFrames(rate, number_of_channels); + + for (const int number_of_frames : {0, 1, 2}) { + SCOPED_TRACE( + ProduceDebugText(rate, number_of_channels, number_of_frames)); + const std::vector<AudioFrame*> frames_to_combine( + all_frames.begin(), all_frames.begin() + number_of_frames); + AudioFrame audio_frame_for_mixing; + combiner.Combine(frames_to_combine, number_of_channels, rate, + frames_to_combine.size(), &audio_frame_for_mixing); + } + } + } +} + +// The RtpPacketInfos field of the mixed packet should contain the union of the +// RtpPacketInfos from the frames that were actually mixed. +TEST(FrameCombiner, ContainsAllRtpPacketInfos) { + static constexpr int kSampleRateHz = 48000; + static constexpr int kNumChannels = 1; + FrameCombiner combiner(true); + const std::vector<AudioFrame*> all_frames = {&frame1, &frame2}; + SetUpFrames(kSampleRateHz, kNumChannels); + + for (const int number_of_frames : {0, 1, 2}) { + SCOPED_TRACE( + ProduceDebugText(kSampleRateHz, kNumChannels, number_of_frames)); + const std::vector<AudioFrame*> frames_to_combine( + all_frames.begin(), all_frames.begin() + number_of_frames); + + std::vector<RtpPacketInfo> packet_infos; + for (const auto& frame : frames_to_combine) { + packet_infos.insert(packet_infos.end(), frame->packet_infos_.begin(), + frame->packet_infos_.end()); + } + + AudioFrame audio_frame_for_mixing; + combiner.Combine(frames_to_combine, kNumChannels, kSampleRateHz, + frames_to_combine.size(), &audio_frame_for_mixing); + EXPECT_THAT(audio_frame_for_mixing.packet_infos_, + UnorderedElementsAreArray(packet_infos)); + } +} + +// There are DCHECKs in place to check for invalid parameters. +TEST(FrameCombinerDeathTest, DebugBuildCrashesWithManyChannels) { + FrameCombiner combiner(true); + for (const int rate : {8000, 18000, 34000, 48000}) { + for (const int number_of_channels : {10, 20, 21}) { + if (static_cast<size_t>(rate / 100 * number_of_channels) > + AudioFrame::kMaxDataSizeSamples) { + continue; + } + const std::vector<AudioFrame*> all_frames = {&frame1, &frame2}; + SetUpFrames(rate, number_of_channels); + + const int number_of_frames = 2; + SCOPED_TRACE( + ProduceDebugText(rate, number_of_channels, number_of_frames)); + const std::vector<AudioFrame*> frames_to_combine( + all_frames.begin(), all_frames.begin() + number_of_frames); + AudioFrame audio_frame_for_mixing; +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) + EXPECT_DEATH( + combiner.Combine(frames_to_combine, number_of_channels, rate, + frames_to_combine.size(), &audio_frame_for_mixing), + ""); +#elif !RTC_DCHECK_IS_ON + combiner.Combine(frames_to_combine, number_of_channels, rate, + frames_to_combine.size(), &audio_frame_for_mixing); +#endif + } + } +} + +TEST(FrameCombinerDeathTest, DebugBuildCrashesWithHighRate) { + FrameCombiner combiner(true); + for (const int rate : {50000, 96000, 128000, 196000}) { + for (const int number_of_channels : {1, 2, 3}) { + if (static_cast<size_t>(rate / 100 * number_of_channels) > + AudioFrame::kMaxDataSizeSamples) { + continue; + } + const std::vector<AudioFrame*> all_frames = {&frame1, &frame2}; + SetUpFrames(rate, number_of_channels); + + const int number_of_frames = 2; + SCOPED_TRACE( + ProduceDebugText(rate, number_of_channels, number_of_frames)); + const std::vector<AudioFrame*> frames_to_combine( + all_frames.begin(), all_frames.begin() + number_of_frames); + AudioFrame audio_frame_for_mixing; +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) + EXPECT_DEATH( + combiner.Combine(frames_to_combine, number_of_channels, rate, + frames_to_combine.size(), &audio_frame_for_mixing), + ""); +#elif !RTC_DCHECK_IS_ON + combiner.Combine(frames_to_combine, number_of_channels, rate, + frames_to_combine.size(), &audio_frame_for_mixing); +#endif + } + } +} + +// With no limiter, the rate has to be divisible by 100 since we use +// 10 ms frames. +TEST(FrameCombiner, BasicApiCallsNoLimiter) { + FrameCombiner combiner(false); + for (const int rate : {8000, 10000, 11000, 32000, 44100}) { + for (const int number_of_channels : {1, 2, 4, 8}) { + const std::vector<AudioFrame*> all_frames = {&frame1, &frame2}; + SetUpFrames(rate, number_of_channels); + + for (const int number_of_frames : {0, 1, 2}) { + SCOPED_TRACE( + ProduceDebugText(rate, number_of_channels, number_of_frames)); + const std::vector<AudioFrame*> frames_to_combine( + all_frames.begin(), all_frames.begin() + number_of_frames); + AudioFrame audio_frame_for_mixing; + combiner.Combine(frames_to_combine, number_of_channels, rate, + frames_to_combine.size(), &audio_frame_for_mixing); + } + } + } +} + +TEST(FrameCombiner, CombiningZeroFramesShouldProduceSilence) { + FrameCombiner combiner(false); + for (const int rate : {8000, 10000, 11000, 32000, 44100}) { + for (const int number_of_channels : {1, 2}) { + SCOPED_TRACE(ProduceDebugText(rate, number_of_channels, 0)); + + AudioFrame audio_frame_for_mixing; + + const std::vector<AudioFrame*> frames_to_combine; + combiner.Combine(frames_to_combine, number_of_channels, rate, + frames_to_combine.size(), &audio_frame_for_mixing); + const int16_t* audio_frame_for_mixing_data = + audio_frame_for_mixing.data(); + const std::vector<int16_t> mixed_data( + audio_frame_for_mixing_data, + audio_frame_for_mixing_data + number_of_channels * rate / 100); + + const std::vector<int16_t> expected(number_of_channels * rate / 100, 0); + EXPECT_EQ(mixed_data, expected); + EXPECT_THAT(audio_frame_for_mixing.packet_infos_, IsEmpty()); + } + } +} + +TEST(FrameCombiner, CombiningOneFrameShouldNotChangeFrame) { + FrameCombiner combiner(false); + for (const int rate : {8000, 10000, 11000, 32000, 44100}) { + for (const int number_of_channels : {1, 2, 4, 8, 10}) { + SCOPED_TRACE(ProduceDebugText(rate, number_of_channels, 1)); + + AudioFrame audio_frame_for_mixing; + + SetUpFrames(rate, number_of_channels); + int16_t* frame1_data = frame1.mutable_data(); + std::iota(frame1_data, frame1_data + number_of_channels * rate / 100, 0); + const std::vector<AudioFrame*> frames_to_combine = {&frame1}; + combiner.Combine(frames_to_combine, number_of_channels, rate, + frames_to_combine.size(), &audio_frame_for_mixing); + + const int16_t* audio_frame_for_mixing_data = + audio_frame_for_mixing.data(); + const std::vector<int16_t> mixed_data( + audio_frame_for_mixing_data, + audio_frame_for_mixing_data + number_of_channels * rate / 100); + + std::vector<int16_t> expected(number_of_channels * rate / 100); + std::iota(expected.begin(), expected.end(), 0); + EXPECT_EQ(mixed_data, expected); + EXPECT_THAT(audio_frame_for_mixing.packet_infos_, + ElementsAreArray(frame1.packet_infos_)); + } + } +} + +// Send a sine wave through the FrameCombiner, and check that the +// difference between input and output varies smoothly. Also check +// that it is inside reasonable bounds. This is to catch issues like +// chromium:695993 and chromium:816875. +TEST(FrameCombiner, GainCurveIsSmoothForAlternatingNumberOfStreams) { + // Rates are divisible by 2000 when limiter is active. + std::vector<FrameCombinerConfig> configs = { + {false, 30100, 2, 50.f}, {false, 16500, 1, 3200.f}, + {true, 8000, 1, 3200.f}, {true, 16000, 1, 50.f}, + {true, 18000, 8, 3200.f}, {true, 10000, 2, 50.f}, + }; + + for (const auto& config : configs) { + SCOPED_TRACE(ProduceDebugText(config)); + + FrameCombiner combiner(config.use_limiter); + + constexpr int16_t wave_amplitude = 30000; + SineWaveGenerator wave_generator(config.wave_frequency, wave_amplitude); + + GainChangeCalculator change_calculator; + float cumulative_change = 0.f; + + constexpr size_t iterations = 100; + + for (size_t i = 0; i < iterations; ++i) { + SetUpFrames(config.sample_rate_hz, config.number_of_channels); + wave_generator.GenerateNextFrame(&frame1); + AudioFrameOperations::Mute(&frame2); + + std::vector<AudioFrame*> frames_to_combine = {&frame1}; + if (i % 2 == 0) { + frames_to_combine.push_back(&frame2); + } + const size_t number_of_samples = + frame1.samples_per_channel_ * config.number_of_channels; + + // Ensures limiter is on if 'use_limiter'. + constexpr size_t number_of_streams = 2; + AudioFrame audio_frame_for_mixing; + combiner.Combine(frames_to_combine, config.number_of_channels, + config.sample_rate_hz, number_of_streams, + &audio_frame_for_mixing); + cumulative_change += change_calculator.CalculateGainChange( + rtc::ArrayView<const int16_t>(frame1.data(), number_of_samples), + rtc::ArrayView<const int16_t>(audio_frame_for_mixing.data(), + number_of_samples)); + } + + // Check that the gain doesn't vary too much. + EXPECT_LT(cumulative_change, 10); + + // Check that the latest gain is within reasonable bounds. It + // should be slightly less that 1. + EXPECT_LT(0.9f, change_calculator.LatestGain()); + EXPECT_LT(change_calculator.LatestGain(), 1.01f); + } +} +} // namespace webrtc |