blob: ba066a1983da7692c3df267e75d87e5ec314c0c5 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/amplitude_peak_detector.h"
#include "base/location.h"
#include "base/test/bind.h"
#include "media/base/audio_sample_types.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
// The loudness threshold in amplitude_peak_detector.cc corresponds to 0.5.
constexpr float kLoudSample = 0.6;
constexpr float kQuietSample = 0.4;
// Default values
constexpr int kChannels = 2;
constexpr int kFrames = 240;
struct SampleLocation {
int channel;
int index;
};
class AmplitudePeakDetectorTest : public testing::TestWithParam<int> {
public:
AmplitudePeakDetectorTest() = default;
AmplitudePeakDetectorTest(const AmplitudePeakDetectorTest&) = delete;
AmplitudePeakDetectorTest& operator=(const AmplitudePeakDetectorTest&) =
delete;
~AmplitudePeakDetectorTest() override = default;
void CreateDetector(AmplitudePeakDetector::PeakDetectedCB callback) {
peak_detector_ =
std::make_unique<AmplitudePeakDetector>(std::move(callback));
peak_detector_->SetIsTracingEnabledForTests(true);
}
void CreateDetectorWithPeakCounter(int* peak_count) {
CreateDetector(
base::BindLambdaForTesting([peak_count]() { ++(*peak_count); }));
}
std::unique_ptr<AudioBus> GetSilentAudioBus() {
auto bus = AudioBus::Create(kChannels, kFrames);
bus->Zero();
return bus;
}
// Creates an AudioBus with exactly one sample set.
std::unique_ptr<AudioBus> GetAudioBusWithSetValue(
float value,
const SampleLocation& location) {
auto bus = GetSilentAudioBus();
bus->channel(location.channel)[location.index] = value;
return bus;
}
std::unique_ptr<AudioBus> GetAudioBusWithLoudValue() {
return GetAudioBusWithSetValue(kLoudSample, {0, 0});
}
void RunSimpleDetectionTest(float value,
bool expect_peak,
base::StringPiece message) {
const SampleLocation kTestSampleLocations[] = {
{0, 0},
{0, kFrames / 2},
{0, kFrames - 1},
{kChannels - 1, 0},
{kChannels - 1, kFrames / 2},
{kChannels - 1, kFrames - 1},
};
for (const auto& location : kTestSampleLocations) {
CreateDetector(
expect_peak
? base::MakeExpectedRunAtLeastOnceClosure(FROM_HERE, message)
: base::MakeExpectedNotRunClosure(FROM_HERE, message));
auto bus = GetAudioBusWithSetValue(value, location);
peak_detector_->FindPeak(bus.get());
}
}
protected:
std::unique_ptr<AmplitudePeakDetector> peak_detector_;
};
TEST_F(AmplitudePeakDetectorTest, Constructor) {
peak_detector_ =
std::make_unique<AmplitudePeakDetector>(base::MakeExpectedNotRunClosure(
FROM_HERE, "Callback should not be run by default"));
}
TEST_F(AmplitudePeakDetectorTest, NoPeaks_Silent) {
CreateDetector(base::MakeExpectedNotRunClosure(
FROM_HERE, "No peaks should be detected for silent data"));
auto bus = GetSilentAudioBus();
peak_detector_->FindPeak(bus.get());
}
TEST_F(AmplitudePeakDetectorTest, NoPeaks_QuietValue) {
RunSimpleDetectionTest(kQuietSample, /*expect_peak=*/false,
"No peaks should be detected for positive quiet data");
RunSimpleDetectionTest(-kQuietSample, /*expect_peak=*/false,
"No peaks should be detected for negative quiet data");
}
TEST_F(AmplitudePeakDetectorTest, Peaks_LoudValue) {
RunSimpleDetectionTest(kLoudSample, /*expect_peak=*/true,
"Peaks should be detected for positive loud data");
RunSimpleDetectionTest(-kLoudSample, /*expect_peak=*/true,
"Peaks should be detected for negative loud data");
}
TEST_F(AmplitudePeakDetectorTest, Sequence_SilenceAndQuiet) {
auto silent_bus = GetSilentAudioBus();
auto quiet_bus = GetAudioBusWithSetValue(kQuietSample, {0, 0});
int peak_count = 0;
CreateDetectorWithPeakCounter(&peak_count);
// Make sure peaks are never detected in silent and quiet samples.
peak_detector_->FindPeak(silent_bus.get());
peak_detector_->FindPeak(quiet_bus.get());
peak_detector_->FindPeak(silent_bus.get());
peak_detector_->FindPeak(quiet_bus.get());
EXPECT_EQ(peak_count, 0);
}
TEST_F(AmplitudePeakDetectorTest, Sequence_ShortPeak) {
auto silent_bus = GetSilentAudioBus();
auto loud_bus = GetAudioBusWithLoudValue();
int peak_count = 0;
CreateDetectorWithPeakCounter(&peak_count);
peak_detector_->FindPeak(silent_bus.get());
EXPECT_EQ(peak_count, 0);
// We should immediately find a peak.
peak_detector_->FindPeak(loud_bus.get());
EXPECT_EQ(peak_count, 1);
// Exiting the peak should not run the callback.
peak_detector_->FindPeak(silent_bus.get());
EXPECT_EQ(peak_count, 1);
// The callback should be run again when another peak is found.
peak_detector_->FindPeak(loud_bus.get());
EXPECT_EQ(peak_count, 2);
}
TEST_F(AmplitudePeakDetectorTest, Sequence_LongPeak) {
auto silent_bus = GetSilentAudioBus();
auto loud_bus = GetAudioBusWithLoudValue();
int peak_count = 0;
CreateDetectorWithPeakCounter(&peak_count);
peak_detector_->FindPeak(silent_bus.get());
EXPECT_EQ(peak_count, 0);
// Long peaks should only run the callback once.
peak_detector_->FindPeak(loud_bus.get());
peak_detector_->FindPeak(loud_bus.get());
peak_detector_->FindPeak(loud_bus.get());
EXPECT_EQ(peak_count, 1);
// Exiting the peak should not run the callback.
peak_detector_->FindPeak(silent_bus.get());
EXPECT_EQ(peak_count, 1);
}
TEST_F(AmplitudePeakDetectorTest, NoTracing) {
auto loud_bus = GetAudioBusWithLoudValue();
int peak_count = 0;
CreateDetectorWithPeakCounter(&peak_count);
peak_detector_->SetIsTracingEnabledForTests(false);
// Callbacks should not be run when tracing is disabled.
peak_detector_->FindPeak(loud_bus.get());
EXPECT_EQ(peak_count, 0);
}
class FixedSampleAmplitudePeakDetector : public AmplitudePeakDetectorTest {
public:
FixedSampleAmplitudePeakDetector() = default;
FixedSampleAmplitudePeakDetector(const FixedSampleAmplitudePeakDetector&) =
delete;
FixedSampleAmplitudePeakDetector& operator=(
const FixedSampleAmplitudePeakDetector&) = delete;
~FixedSampleAmplitudePeakDetector() override = default;
template <typename SampleType>
void VerifyNoPeaksFoundInSilence() {
std::vector<SampleType> samples(
kFrames, FixedSampleTypeTraits<SampleType>::kZeroPointValue);
VerifyFindPeaks(samples.data(), /*expect_peak=*/false,
"No peak should be detected in zeroed values");
}
template <typename SampleType>
void RunSimpleDetectionTest_Fixed(float value,
bool expect_peak,
base::StringPiece message) {
std::vector<SampleType> samples(
kFrames, FixedSampleTypeTraits<SampleType>::kZeroPointValue);
// Verify finding peaks for values at the start of the data range.
samples[0] = FixedSampleTypeTraits<SampleType>::FromFloat(value);
VerifyFindPeaks(samples.data(), expect_peak, message);
// Reset the value.
samples[0] = FixedSampleTypeTraits<SampleType>::kZeroPointValue;
// Verify finding peaks for values at the end of the data range.
samples[kFrames - 1] = FixedSampleTypeTraits<SampleType>::FromFloat(value);
VerifyFindPeaks(samples.data(), expect_peak, message);
}
int bytes_per_samples() { return GetParam(); }
void VerifyFindPeaks(const void* data,
bool expect_peak,
base::StringPiece message) {
CreateDetector(
expect_peak
? base::MakeExpectedRunAtLeastOnceClosure(FROM_HERE, message)
: base::MakeExpectedNotRunClosure(FROM_HERE, message));
peak_detector_->FindPeak(data, kFrames, bytes_per_samples());
}
};
TEST_P(FixedSampleAmplitudePeakDetector, NoPeaks_Silent) {
switch (bytes_per_samples()) {
case 1:
VerifyNoPeaksFoundInSilence<uint8_t>();
break;
case 2:
VerifyNoPeaksFoundInSilence<int16_t>();
break;
case 4:
VerifyNoPeaksFoundInSilence<int32_t>();
break;
}
}
TEST_P(FixedSampleAmplitudePeakDetector, NoPeaks_Quiet) {
constexpr char message[] = "No peaks detected from quiet values";
switch (bytes_per_samples()) {
case 1:
RunSimpleDetectionTest_Fixed<uint8_t>(kQuietSample, false, message);
RunSimpleDetectionTest_Fixed<uint8_t>(-kQuietSample, false, message);
break;
case 2:
RunSimpleDetectionTest_Fixed<int16_t>(kQuietSample, false, message);
RunSimpleDetectionTest_Fixed<int16_t>(-kQuietSample, false, message);
break;
case 4:
RunSimpleDetectionTest_Fixed<int32_t>(kQuietSample, false, message);
RunSimpleDetectionTest_Fixed<int32_t>(-kQuietSample, false, message);
break;
}
}
TEST_P(FixedSampleAmplitudePeakDetector, Peaks_Loud) {
constexpr char message[] = "Peaks detected in loud values";
switch (bytes_per_samples()) {
case 1:
RunSimpleDetectionTest_Fixed<uint8_t>(kLoudSample, true, message);
RunSimpleDetectionTest_Fixed<uint8_t>(-kLoudSample, true, message);
break;
case 2:
RunSimpleDetectionTest_Fixed<int16_t>(kLoudSample, true, message);
RunSimpleDetectionTest_Fixed<int16_t>(-kLoudSample, true, message);
break;
case 4:
RunSimpleDetectionTest_Fixed<int32_t>(kLoudSample, true, message);
RunSimpleDetectionTest_Fixed<int32_t>(-kLoudSample, true, message);
break;
}
}
TEST_F(FixedSampleAmplitudePeakDetector, Sequence_SilenceAndQuiet) {
constexpr int kBytesPerSample = 4;
std::vector<uint32_t> silent_data(
kFrames, SignedInt32SampleTypeTraits::kZeroPointValue);
std::vector<uint32_t> quiet_data(
kFrames, SignedInt32SampleTypeTraits::FromFloat(kQuietSample));
int peak_count = 0;
CreateDetectorWithPeakCounter(&peak_count);
// Make sure peaks are never detected in silent and quiet samples.
peak_detector_->FindPeak(silent_data.data(), kFrames, kBytesPerSample);
peak_detector_->FindPeak(quiet_data.data(), kFrames, kBytesPerSample);
peak_detector_->FindPeak(silent_data.data(), kFrames, kBytesPerSample);
peak_detector_->FindPeak(quiet_data.data(), kFrames, kBytesPerSample);
EXPECT_EQ(peak_count, 0);
}
TEST_F(FixedSampleAmplitudePeakDetector, Sequence_ShortPeak) {
constexpr int kBytesPerSample = 4;
std::vector<uint32_t> silent_data(
kFrames, SignedInt32SampleTypeTraits::kZeroPointValue);
std::vector<uint32_t> loud_data(
kFrames, SignedInt32SampleTypeTraits::FromFloat(kLoudSample));
int peak_count = 0;
CreateDetectorWithPeakCounter(&peak_count);
peak_detector_->FindPeak(silent_data.data(), kFrames, kBytesPerSample);
EXPECT_EQ(peak_count, 0);
// We should immediately find a peak.
peak_detector_->FindPeak(loud_data.data(), kFrames, kBytesPerSample);
EXPECT_EQ(peak_count, 1);
// Exiting the peak should not run the callback.
peak_detector_->FindPeak(silent_data.data(), kFrames, kBytesPerSample);
EXPECT_EQ(peak_count, 1);
// The callback should be run again when another peak is found.
peak_detector_->FindPeak(loud_data.data(), kFrames, kBytesPerSample);
EXPECT_EQ(peak_count, 2);
}
TEST_P(FixedSampleAmplitudePeakDetector, Sequence_LongPeak) {
constexpr int kBytesPerSample = 4;
std::vector<uint32_t> silent_data(
kFrames, SignedInt32SampleTypeTraits::kZeroPointValue);
std::vector<uint32_t> loud_data(
kFrames, SignedInt32SampleTypeTraits::FromFloat(kLoudSample));
int peak_count = 0;
CreateDetectorWithPeakCounter(&peak_count);
peak_detector_->FindPeak(silent_data.data(), kFrames, kBytesPerSample);
EXPECT_EQ(peak_count, 0);
// Long peaks should only run the callback once.
peak_detector_->FindPeak(loud_data.data(), kFrames, kBytesPerSample);
peak_detector_->FindPeak(loud_data.data(), kFrames, kBytesPerSample);
peak_detector_->FindPeak(loud_data.data(), kFrames, kBytesPerSample);
EXPECT_EQ(peak_count, 1);
// Exiting the peak should not run the callback.
peak_detector_->FindPeak(silent_data.data(), kFrames, kBytesPerSample);
EXPECT_EQ(peak_count, 1);
}
TEST_P(FixedSampleAmplitudePeakDetector, NoTracing) {
constexpr int kBytesPerSample = 4;
std::vector<uint32_t> loud_data(
kFrames, SignedInt32SampleTypeTraits::FromFloat(kLoudSample));
int peak_count = 0;
CreateDetectorWithPeakCounter(&peak_count);
peak_detector_->SetIsTracingEnabledForTests(false);
// Callbacks should not be run when tracing is disabled.
peak_detector_->FindPeak(loud_data.data(), kFrames, kBytesPerSample);
EXPECT_EQ(peak_count, 0);
}
INSTANTIATE_TEST_SUITE_P(FixedSampleAmplitudePeakDetector,
FixedSampleAmplitudePeakDetector,
testing::Values(1, 2, 4));
} // namespace media