blob: 099cfa902078067e75a5ee95c58b1301d741df62 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/audio_power_monitor.h"
#include <limits>
#include <memory>
#include "base/macros.h"
#include "base/time/time.h"
#include "media/base/audio_bus.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
static const int kSampleRate = 48000;
static const int kFramesPerBuffer = 128;
static const int kTimeConstantMillis = 5;
namespace {
// Container for each parameterized test's data (input and expected results).
class TestScenario {
public:
TestScenario(const float* data, int num_channels, int num_frames,
float expected_power, bool expected_clipped)
: expected_power_(expected_power), expected_clipped_(expected_clipped) {
CreatePopulatedBuffer(data, num_channels, num_frames);
}
// Copy constructor and assignment operator for ::testing::Values(...).
TestScenario(const TestScenario& other) { *this = other; }
TestScenario& operator=(const TestScenario& other) {
this->expected_power_ = other.expected_power_;
this->expected_clipped_ = other.expected_clipped_;
this->bus_ = AudioBus::Create(other.bus_->channels(), other.bus_->frames());
other.bus_->CopyTo(this->bus_.get());
return *this;
}
// Returns this TestScenario, but with a bad sample value placed in the middle
// of channel 0.
TestScenario WithABadSample(float bad_value) const {
TestScenario result(*this);
result.bus_->channel(0)[result.bus_->frames() / 2] = bad_value;
return result;
}
const AudioBus& data() const {
return *bus_;
}
float expected_power() const {
return expected_power_;
}
bool expected_clipped() const {
return expected_clipped_;
}
private:
// Creates an AudioBus, sized and populated with kFramesPerBuffer frames of
// data. The given test |data| is repeated to fill the buffer.
void CreatePopulatedBuffer(
const float* data, int num_channels, int num_frames) {
bus_ = AudioBus::Create(num_channels, kFramesPerBuffer);
for (int ch = 0; ch < num_channels; ++ch) {
for (int frames = 0; frames < kFramesPerBuffer; frames += num_frames) {
const int num_to_copy = std::min(num_frames, kFramesPerBuffer - frames);
memcpy(bus_->channel(ch) + frames, data + num_frames * ch,
sizeof(float) * num_to_copy);
}
}
}
float expected_power_;
bool expected_clipped_;
std::unique_ptr<AudioBus> bus_;
};
// Value printer for TestScenario. Required to prevent Valgrind "access to
// uninitialized memory" errors (http://crbug.com/263315).
::std::ostream& operator<<(::std::ostream& os, const TestScenario& ts) {
return os << "{" << ts.data().channels() << "-channel signal} --> {"
<< ts.expected_power() << " dBFS, "
<< (ts.expected_clipped() ? "clipped" : "not clipped")
<< "}";
}
// An observer that receives power measurements. Each power measurement should
// should make progress towards the goal value.
class MeasurementObserver {
public:
explicit MeasurementObserver(float goal_power_measurement)
: goal_power_measurement_(goal_power_measurement),
measurement_count_(0),
last_power_measurement_(AudioPowerMonitor::zero_power()),
last_clipped_(false) {}
int measurement_count() const {
return measurement_count_;
}
float last_power_measurement() const {
return last_power_measurement_;
}
bool last_clipped() const {
return last_clipped_;
}
void OnPowerMeasured(float cur_power_measurement, bool clipped) {
if (measurement_count_ == 0) {
measurements_should_increase_ =
(cur_power_measurement < goal_power_measurement_);
} else {
SCOPED_TRACE(::testing::Message()
<< "Power: goal=" << goal_power_measurement_
<< "; last=" << last_power_measurement_
<< "; cur=" << cur_power_measurement);
if (last_power_measurement_ != goal_power_measurement_) {
if (measurements_should_increase_) {
EXPECT_LE(last_power_measurement_, cur_power_measurement)
<< "Measurements should be monotonically increasing.";
} else {
EXPECT_GE(last_power_measurement_, cur_power_measurement)
<< "Measurements should be monotonically decreasing.";
}
} else {
EXPECT_EQ(last_power_measurement_, cur_power_measurement)
<< "Measurements are numerically unstable at goal value.";
}
}
last_power_measurement_ = cur_power_measurement;
last_clipped_ = clipped;
++measurement_count_;
}
private:
const float goal_power_measurement_;
int measurement_count_;
bool measurements_should_increase_;
float last_power_measurement_;
bool last_clipped_;
DISALLOW_COPY_AND_ASSIGN(MeasurementObserver);
};
} // namespace
class AudioPowerMonitorTest : public ::testing::TestWithParam<TestScenario> {
public:
AudioPowerMonitorTest()
: power_monitor_(kSampleRate,
base::TimeDelta::FromMilliseconds(kTimeConstantMillis)) {
}
void FeedAndCheckExpectedPowerIsMeasured(
const AudioBus& bus, float power, bool clipped) {
// Feed the AudioPowerMonitor, read measurements from it, and record them in
// MeasurementObserver.
static const int kNumFeedIters = 100;
MeasurementObserver observer(power);
for (int i = 0; i < kNumFeedIters; ++i) {
power_monitor_.Scan(bus, bus.frames());
const std::pair<float, bool>& reading =
power_monitor_.ReadCurrentPowerAndClip();
observer.OnPowerMeasured(reading.first, reading.second);
}
// Check that the results recorded by the observer are the same whole-number
// dBFS.
EXPECT_EQ(static_cast<int>(power),
static_cast<int>(observer.last_power_measurement()));
EXPECT_EQ(clipped, observer.last_clipped());
}
private:
AudioPowerMonitor power_monitor_;
DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitorTest);
};
TEST_P(AudioPowerMonitorTest, MeasuresPowerOfSignal) {
const TestScenario& scenario = GetParam();
std::unique_ptr<AudioBus> zeroed_bus =
AudioBus::Create(scenario.data().channels(), scenario.data().frames());
zeroed_bus->Zero();
// Send a "zero power" audio signal, then this scenario's audio signal, then
// the "zero power" audio signal again; testing that the power monitor
// measurements match expected values.
FeedAndCheckExpectedPowerIsMeasured(
*zeroed_bus, AudioPowerMonitor::zero_power(), false);
FeedAndCheckExpectedPowerIsMeasured(
scenario.data(), scenario.expected_power(), scenario.expected_clipped());
FeedAndCheckExpectedPowerIsMeasured(
*zeroed_bus, AudioPowerMonitor::zero_power(), false);
}
static const float kMonoSilentNoise[] = {
0.01f, -0.01f
};
static const float kMonoMaxAmplitude[] = {
1.0f
};
static const float kMonoMaxAmplitude2[] = {
-1.0f, 1.0f
};
static const float kMonoHalfMaxAmplitude[] = {
0.5f, -0.5f, 0.5f, -0.5f
};
static const float kMonoAmplitudeClipped[] = {
2.0f, -2.0f
};
static const float kMonoMaxAmplitudeWithClip[] = {
2.0f, 0.0, 0.0f, 0.0f
};
static const float kMonoMaxAmplitudeWithClip2[] = {
4.0f, 0.0, 0.0f, 0.0f
};
static const float kStereoSilentNoise[] = {
// left channel
0.005f, -0.005f,
// right channel
0.005f, -0.005f
};
static const float kStereoMaxAmplitude[] = {
// left channel
1.0f, -1.0f,
// right channel
-1.0f, 1.0f
};
static const float kRightChannelMaxAmplitude[] = {
// left channel
0.0f, 0.0f, 0.0f, 0.0f,
// right channel
-1.0f, 1.0f, -1.0f, 1.0f
};
static const float kLeftChannelHalfMaxAmplitude[] = {
// left channel
0.5f, -0.5f, 0.5f, -0.5f,
// right channel
0.0f, 0.0f, 0.0f, 0.0f,
};
static const float kStereoMixed[] = {
// left channel
0.5f, -0.5f, 0.5f, -0.5f,
// right channel
-1.0f, 1.0f, -1.0f, 1.0f
};
static const float kStereoMixed2[] = {
// left channel
1.0f, -1.0f, 0.75f, -0.75f, 0.5f, -0.5f, 0.25f, -0.25f,
// right channel
0.25f, -0.25f, 0.5f, -0.5f, 0.75f, -0.75f, 1.0f, -1.0f
};
INSTANTIATE_TEST_CASE_P(
Scenarios, AudioPowerMonitorTest,
::testing::Values(
TestScenario(kMonoSilentNoise, 1, 2, -40, false),
TestScenario(kMonoMaxAmplitude, 1, 1,
AudioPowerMonitor::max_power(), false),
TestScenario(kMonoMaxAmplitude2, 1, 2,
AudioPowerMonitor::max_power(), false),
TestScenario(kMonoHalfMaxAmplitude, 1, 4, -6, false),
TestScenario(kMonoAmplitudeClipped, 1, 2,
AudioPowerMonitor::max_power(), true),
TestScenario(kMonoMaxAmplitudeWithClip, 1, 4,
AudioPowerMonitor::max_power(), true),
TestScenario(kMonoMaxAmplitudeWithClip2, 1, 4,
AudioPowerMonitor::max_power(), true),
TestScenario(kMonoSilentNoise, 1, 2,
AudioPowerMonitor::zero_power(), false).
WithABadSample(std::numeric_limits<float>::infinity()),
TestScenario(kMonoHalfMaxAmplitude, 1, 4,
AudioPowerMonitor::zero_power(), false).
WithABadSample(std::numeric_limits<float>::quiet_NaN()),
TestScenario(kStereoSilentNoise, 2, 2, -46, false),
TestScenario(kStereoMaxAmplitude, 2, 2,
AudioPowerMonitor::max_power(), false),
TestScenario(kRightChannelMaxAmplitude, 2, 4, -3, false),
TestScenario(kLeftChannelHalfMaxAmplitude, 2, 4, -9, false),
TestScenario(kStereoMixed, 2, 4, -2, false),
TestScenario(kStereoMixed2, 2, 8, -3, false)));
} // namespace media