Index: media/audio/audio_power_monitor_unittest.cc |
diff --git a/media/audio/audio_power_monitor_unittest.cc b/media/audio/audio_power_monitor_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..17e8e766ffbdf36d55c51bedc94286b2c4a3eb8a |
--- /dev/null |
+++ b/media/audio/audio_power_monitor_unittest.cc |
@@ -0,0 +1,315 @@ |
+// Copyright (c) 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 "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/message_loop.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; |
+static const int kMeasurementPeriodMillis = 20; |
+ |
+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 for ::testing::Values(...). |
+ TestScenario(const TestScenario& other) |
+ : expected_power_(other.expected_power_), |
+ expected_clipped_(other.expected_clipped_) { |
+ bus_ = AudioBus::Create(other.bus_->channels(), other.bus_->frames()); |
+ other.bus_->CopyTo(bus_.get()); |
+ } |
+ |
+ 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); |
+ } |
+ } |
+ } |
+ |
+ const float expected_power_; |
+ const bool expected_clipped_; |
+ scoped_ptr<AudioBus> bus_; |
+ |
+ DISALLOW_ASSIGN(TestScenario); |
+}; |
+ |
+// An observer that receives power measurements. Each power measurement should |
+// should make progress towards the goal value. |
+class MeasurementObserver { |
+ public: |
+ MeasurementObserver(float goal_power_measurement, bool goal_clipped) |
+ : goal_power_measurement_(goal_power_measurement), |
+ goal_clipped_(goal_clipped), measurement_count_(0) {} |
+ |
+ 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_; |
+ const bool goal_clipped_; |
+ 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), |
+ base::TimeDelta::FromMilliseconds(kMeasurementPeriodMillis), |
+ &message_loop_, |
+ base::Bind(&AudioPowerMonitorTest::OnPowerMeasured, |
+ base::Unretained(this))) {} |
+ |
+ void FeedAndCheckExpectedPowerIsMeasured( |
+ const AudioBus& bus, float power, bool clipped) { |
+ // Feed the AudioPowerMonitor. It should post tasks to |message_loop_|. |
+ static const int kNumFeedIters = 100; |
+ for (int i = 0; i < kNumFeedIters; ++i) |
+ power_monitor_.Scan(bus, bus.frames()); |
+ |
+ // Set up an observer and run all the enqueued tasks. |
+ MeasurementObserver observer(power, clipped); |
+ current_observer_ = &observer; |
+ message_loop_.RunUntilIdle(); |
+ current_observer_ = NULL; |
+ |
+ // Check that the results recorded by the observer are the same whole-number |
+ // dBFS. |
+ if (observer.measurement_count() > 0) { |
+ EXPECT_EQ(static_cast<int>(power), |
+ static_cast<int>(observer.last_power_measurement())); |
+ EXPECT_EQ(clipped, observer.last_clipped()); |
+ } else { |
+ // Edge case: AudioPowerMonitor reported no measurements. This infers it |
+ // decided not to report any redundant measurements, which we assume is an |
+ // unclipped "zero power" result. |
+ EXPECT_EQ(static_cast<int>(-std::numeric_limits<float>::infinity()), |
+ static_cast<int>(power)); |
+ EXPECT_EQ(false, clipped); |
+ } |
+ } |
+ |
+ private: |
+ void OnPowerMeasured(float power, bool clipped) { |
+ CHECK(current_observer_); |
+ current_observer_->OnPowerMeasured(power, clipped); |
+ } |
+ |
+ base::MessageLoop message_loop_; |
+ AudioPowerMonitor power_monitor_; |
+ MeasurementObserver* current_observer_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitorTest); |
+}; |
+ |
+TEST_P(AudioPowerMonitorTest, MeasuresPowerOfSignal) { |
+ const TestScenario& scenario = GetParam(); |
+ |
+ scoped_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::kZeroPowerDBFS, false); |
+ FeedAndCheckExpectedPowerIsMeasured( |
+ scenario.data(), scenario.expected_power(), scenario.expected_clipped()); |
+ FeedAndCheckExpectedPowerIsMeasured( |
+ *zeroed_bus, AudioPowerMonitor::kZeroPowerDBFS, 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 kMonoContainsInfinity[] = { |
+ 0.0f, 0.0f, 0.0f, std::numeric_limits<float>::infinity() |
DaleCurtis
2013/07/02 22:26:34
Static initializer.
miu
2013/07/09 00:59:56
Done.
|
+}; |
+ |
+static const float kMonoContainsNaN[] = { |
+ 0.5f, -0.5f, 0.5f, std::numeric_limits<float>::quiet_NaN() |
DaleCurtis
2013/07/02 22:26:34
Ditto.
miu
2013/07/09 00:59:56
Done.
|
+}; |
+ |
+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, 1, -40, false), |
+ TestScenario(kMonoMaxAmplitude, 1, 1, |
+ AudioPowerMonitor::kMaxPowerDBFS, false), |
+ TestScenario(kMonoMaxAmplitude2, 1, 2, |
+ AudioPowerMonitor::kMaxPowerDBFS, false), |
+ TestScenario(kMonoHalfMaxAmplitude, 1, 4, -6, false), |
+ TestScenario(kMonoAmplitudeClipped, 1, 2, |
+ AudioPowerMonitor::kMaxPowerDBFS, true), |
+ TestScenario(kMonoMaxAmplitudeWithClip, 1, 4, |
+ AudioPowerMonitor::kMaxPowerDBFS, true), |
+ TestScenario(kMonoMaxAmplitudeWithClip2, 1, 4, |
+ AudioPowerMonitor::kMaxPowerDBFS, true), |
+ TestScenario(kMonoContainsInfinity, 1, 4, |
+ AudioPowerMonitor::kZeroPowerDBFS, true), |
+ TestScenario(kMonoContainsNaN, 1, 4, |
+ AudioPowerMonitor::kZeroPowerDBFS, false), |
+ TestScenario(kStereoSilentNoise, 2, 1, -46, false), |
+ TestScenario(kStereoMaxAmplitude, 2, 2, |
+ AudioPowerMonitor::kMaxPowerDBFS, 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 |