Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "media/audio/audio_power_monitor.h" | |
| 6 | |
| 7 #include <limits> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/bind_helpers.h" | |
| 11 #include "base/message_loop.h" | |
| 12 #include "base/time/time.h" | |
| 13 #include "media/base/audio_bus.h" | |
| 14 #include "testing/gtest/include/gtest/gtest.h" | |
| 15 | |
| 16 namespace media { | |
| 17 | |
| 18 static const int kSampleRate = 48000; | |
| 19 static const int kFramesPerBuffer = 128; | |
| 20 | |
| 21 static const int kTimeConstantMillis = 5; | |
| 22 static const int kMeasurementPeriodMillis = 20; | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 // Container for each parameterized test's data (input and expected results). | |
| 27 class TestScenario { | |
| 28 public: | |
| 29 TestScenario(const float* data, int num_channels, int num_frames, | |
| 30 float expected_power, bool expected_clipped) | |
| 31 : expected_power_(expected_power), expected_clipped_(expected_clipped) { | |
| 32 CreatePopulatedBuffer(data, num_channels, num_frames); | |
| 33 } | |
| 34 | |
| 35 // Copy constructor for ::testing::Values(...). | |
| 36 TestScenario(const TestScenario& other) | |
| 37 : expected_power_(other.expected_power_), | |
| 38 expected_clipped_(other.expected_clipped_) { | |
| 39 bus_ = AudioBus::Create(other.bus_->channels(), other.bus_->frames()); | |
| 40 other.bus_->CopyTo(bus_.get()); | |
| 41 } | |
| 42 | |
| 43 const AudioBus& data() const { | |
| 44 return *bus_; | |
| 45 } | |
| 46 | |
| 47 float expected_power() const { | |
| 48 return expected_power_; | |
| 49 } | |
| 50 | |
| 51 bool expected_clipped() const { | |
| 52 return expected_clipped_; | |
| 53 } | |
| 54 | |
| 55 private: | |
| 56 // Creates an AudioBus, sized and populated with kFramesPerBuffer frames of | |
| 57 // data. The given test |data| is repeated to fill the buffer. | |
| 58 void CreatePopulatedBuffer( | |
| 59 const float* data, int num_channels, int num_frames) { | |
| 60 bus_ = AudioBus::Create(num_channels, kFramesPerBuffer); | |
| 61 for (int ch = 0; ch < num_channels; ++ch) { | |
| 62 for (int frames = 0; frames < kFramesPerBuffer; frames += num_frames) { | |
| 63 const int num_to_copy = std::min(num_frames, kFramesPerBuffer - frames); | |
| 64 memcpy(bus_->channel(ch) + frames, data + num_frames * ch, | |
| 65 sizeof(float) * num_to_copy); | |
| 66 } | |
| 67 } | |
| 68 } | |
| 69 | |
| 70 const float expected_power_; | |
| 71 const bool expected_clipped_; | |
| 72 scoped_ptr<AudioBus> bus_; | |
| 73 | |
| 74 DISALLOW_ASSIGN(TestScenario); | |
| 75 }; | |
| 76 | |
| 77 // An observer that receives power measurements. Each power measurement should | |
| 78 // should make progress towards the goal value. | |
| 79 class MeasurementObserver { | |
| 80 public: | |
| 81 MeasurementObserver(float goal_power_measurement, bool goal_clipped) | |
| 82 : goal_power_measurement_(goal_power_measurement), | |
| 83 goal_clipped_(goal_clipped), measurement_count_(0) {} | |
| 84 | |
| 85 int measurement_count() const { | |
| 86 return measurement_count_; | |
| 87 } | |
| 88 | |
| 89 float last_power_measurement() const { | |
| 90 return last_power_measurement_; | |
| 91 } | |
| 92 | |
| 93 bool last_clipped() const { | |
| 94 return last_clipped_; | |
| 95 } | |
| 96 | |
| 97 void OnPowerMeasured(float cur_power_measurement, bool clipped) { | |
| 98 if (measurement_count_ == 0) { | |
| 99 measurements_should_increase_ = | |
| 100 (cur_power_measurement < goal_power_measurement_); | |
| 101 } else { | |
| 102 SCOPED_TRACE(::testing::Message() | |
| 103 << "Power: goal=" << goal_power_measurement_ | |
| 104 << "; last=" << last_power_measurement_ | |
| 105 << "; cur=" << cur_power_measurement); | |
| 106 | |
| 107 if (last_power_measurement_ != goal_power_measurement_) { | |
| 108 if (measurements_should_increase_) { | |
| 109 EXPECT_LE(last_power_measurement_, cur_power_measurement) | |
| 110 << "Measurements should be monotonically increasing."; | |
| 111 } else { | |
| 112 EXPECT_GE(last_power_measurement_, cur_power_measurement) | |
| 113 << "Measurements should be monotonically decreasing."; | |
| 114 } | |
| 115 } else { | |
| 116 EXPECT_EQ(last_power_measurement_, cur_power_measurement) | |
| 117 << "Measurements are numerically unstable at goal value."; | |
| 118 } | |
| 119 } | |
| 120 | |
| 121 last_power_measurement_ = cur_power_measurement; | |
| 122 last_clipped_ = clipped; | |
| 123 ++measurement_count_; | |
| 124 } | |
| 125 | |
| 126 private: | |
| 127 const float goal_power_measurement_; | |
| 128 const bool goal_clipped_; | |
| 129 int measurement_count_; | |
| 130 bool measurements_should_increase_; | |
| 131 float last_power_measurement_; | |
| 132 bool last_clipped_; | |
| 133 | |
| 134 DISALLOW_COPY_AND_ASSIGN(MeasurementObserver); | |
| 135 }; | |
| 136 | |
| 137 } // namespace | |
| 138 | |
| 139 class AudioPowerMonitorTest : public ::testing::TestWithParam<TestScenario> { | |
| 140 public: | |
| 141 AudioPowerMonitorTest() | |
| 142 : power_monitor_( | |
| 143 kSampleRate, | |
| 144 base::TimeDelta::FromMilliseconds(kTimeConstantMillis), | |
| 145 base::TimeDelta::FromMilliseconds(kMeasurementPeriodMillis), | |
| 146 &message_loop_, | |
| 147 base::Bind(&AudioPowerMonitorTest::OnPowerMeasured, | |
| 148 base::Unretained(this))) {} | |
| 149 | |
| 150 void FeedAndCheckExpectedPowerIsMeasured( | |
| 151 const AudioBus& bus, float power, bool clipped) { | |
| 152 // Feed the AudioPowerMonitor. It should post tasks to |message_loop_|. | |
| 153 static const int kNumFeedIters = 100; | |
| 154 for (int i = 0; i < kNumFeedIters; ++i) | |
| 155 power_monitor_.Scan(bus, bus.frames()); | |
| 156 | |
| 157 // Set up an observer and run all the enqueued tasks. | |
| 158 MeasurementObserver observer(power, clipped); | |
| 159 current_observer_ = &observer; | |
| 160 message_loop_.RunUntilIdle(); | |
| 161 current_observer_ = NULL; | |
| 162 | |
| 163 // Check that the results recorded by the observer are the same whole-number | |
| 164 // dBFS. | |
| 165 if (observer.measurement_count() > 0) { | |
| 166 EXPECT_EQ(static_cast<int>(power), | |
| 167 static_cast<int>(observer.last_power_measurement())); | |
| 168 EXPECT_EQ(clipped, observer.last_clipped()); | |
| 169 } else { | |
| 170 // Edge case: AudioPowerMonitor reported no measurements. This infers it | |
| 171 // decided not to report any redundant measurements, which we assume is an | |
| 172 // unclipped "zero power" result. | |
| 173 EXPECT_EQ(static_cast<int>(-std::numeric_limits<float>::infinity()), | |
| 174 static_cast<int>(power)); | |
| 175 EXPECT_EQ(false, clipped); | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 private: | |
| 180 void OnPowerMeasured(float power, bool clipped) { | |
| 181 CHECK(current_observer_); | |
| 182 current_observer_->OnPowerMeasured(power, clipped); | |
| 183 } | |
| 184 | |
| 185 base::MessageLoop message_loop_; | |
| 186 AudioPowerMonitor power_monitor_; | |
| 187 MeasurementObserver* current_observer_; | |
| 188 | |
| 189 DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitorTest); | |
| 190 }; | |
| 191 | |
| 192 TEST_P(AudioPowerMonitorTest, MeasuresPowerOfSignal) { | |
| 193 const TestScenario& scenario = GetParam(); | |
| 194 | |
| 195 scoped_ptr<AudioBus> zeroed_bus = | |
| 196 AudioBus::Create(scenario.data().channels(), scenario.data().frames()); | |
| 197 zeroed_bus->Zero(); | |
| 198 | |
| 199 // Send a "zero power" audio signal, then this scenario's audio signal, then | |
| 200 // the "zero power" audio signal again; testing that the power monitor | |
| 201 // measurements match expected values. | |
| 202 FeedAndCheckExpectedPowerIsMeasured( | |
| 203 *zeroed_bus, AudioPowerMonitor::kZeroPowerDBFS, false); | |
| 204 FeedAndCheckExpectedPowerIsMeasured( | |
| 205 scenario.data(), scenario.expected_power(), scenario.expected_clipped()); | |
| 206 FeedAndCheckExpectedPowerIsMeasured( | |
| 207 *zeroed_bus, AudioPowerMonitor::kZeroPowerDBFS, false); | |
| 208 } | |
| 209 | |
| 210 static const float kMonoSilentNoise[] = { | |
| 211 0.01f, -0.01f | |
| 212 }; | |
| 213 | |
| 214 static const float kMonoMaxAmplitude[] = { | |
| 215 1.0f | |
| 216 }; | |
| 217 | |
| 218 static const float kMonoMaxAmplitude2[] = { | |
| 219 -1.0f, 1.0f | |
| 220 }; | |
| 221 | |
| 222 static const float kMonoHalfMaxAmplitude[] = { | |
| 223 0.5f, -0.5f, 0.5f, -0.5f | |
| 224 }; | |
| 225 | |
| 226 static const float kMonoAmplitudeClipped[] = { | |
| 227 2.0f, -2.0f | |
| 228 }; | |
| 229 | |
| 230 static const float kMonoMaxAmplitudeWithClip[] = { | |
| 231 2.0f, 0.0, 0.0f, 0.0f | |
| 232 }; | |
| 233 | |
| 234 static const float kMonoMaxAmplitudeWithClip2[] = { | |
| 235 4.0f, 0.0, 0.0f, 0.0f | |
| 236 }; | |
| 237 | |
| 238 static const float kMonoContainsInfinity[] = { | |
| 239 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.
| |
| 240 }; | |
| 241 | |
| 242 static const float kMonoContainsNaN[] = { | |
| 243 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.
| |
| 244 }; | |
| 245 | |
| 246 static const float kStereoSilentNoise[] = { | |
| 247 // left channel | |
| 248 0.005f, -0.005f, | |
| 249 // right channel | |
| 250 0.005f, -0.005f | |
| 251 }; | |
| 252 | |
| 253 static const float kStereoMaxAmplitude[] = { | |
| 254 // left channel | |
| 255 1.0f, -1.0f, | |
| 256 // right channel | |
| 257 -1.0f, 1.0f | |
| 258 }; | |
| 259 | |
| 260 static const float kRightChannelMaxAmplitude[] = { | |
| 261 // left channel | |
| 262 0.0f, 0.0f, 0.0f, 0.0f, | |
| 263 // right channel | |
| 264 -1.0f, 1.0f, -1.0f, 1.0f | |
| 265 }; | |
| 266 | |
| 267 static const float kLeftChannelHalfMaxAmplitude[] = { | |
| 268 // left channel | |
| 269 0.5f, -0.5f, 0.5f, -0.5f, | |
| 270 // right channel | |
| 271 0.0f, 0.0f, 0.0f, 0.0f, | |
| 272 }; | |
| 273 | |
| 274 static const float kStereoMixed[] = { | |
| 275 // left channel | |
| 276 0.5f, -0.5f, 0.5f, -0.5f, | |
| 277 // right channel | |
| 278 -1.0f, 1.0f, -1.0f, 1.0f | |
| 279 }; | |
| 280 | |
| 281 static const float kStereoMixed2[] = { | |
| 282 // left channel | |
| 283 1.0f, -1.0f, 0.75f, -0.75f, 0.5f, -0.5f, 0.25f, -0.25f, | |
| 284 // right channel | |
| 285 0.25f, -0.25f, 0.5f, -0.5f, 0.75f, -0.75f, 1.0f, -1.0f | |
| 286 }; | |
| 287 | |
| 288 INSTANTIATE_TEST_CASE_P( | |
| 289 Scenarios, AudioPowerMonitorTest, | |
| 290 ::testing::Values( | |
| 291 TestScenario(kMonoSilentNoise, 1, 1, -40, false), | |
| 292 TestScenario(kMonoMaxAmplitude, 1, 1, | |
| 293 AudioPowerMonitor::kMaxPowerDBFS, false), | |
| 294 TestScenario(kMonoMaxAmplitude2, 1, 2, | |
| 295 AudioPowerMonitor::kMaxPowerDBFS, false), | |
| 296 TestScenario(kMonoHalfMaxAmplitude, 1, 4, -6, false), | |
| 297 TestScenario(kMonoAmplitudeClipped, 1, 2, | |
| 298 AudioPowerMonitor::kMaxPowerDBFS, true), | |
| 299 TestScenario(kMonoMaxAmplitudeWithClip, 1, 4, | |
| 300 AudioPowerMonitor::kMaxPowerDBFS, true), | |
| 301 TestScenario(kMonoMaxAmplitudeWithClip2, 1, 4, | |
| 302 AudioPowerMonitor::kMaxPowerDBFS, true), | |
| 303 TestScenario(kMonoContainsInfinity, 1, 4, | |
| 304 AudioPowerMonitor::kZeroPowerDBFS, true), | |
| 305 TestScenario(kMonoContainsNaN, 1, 4, | |
| 306 AudioPowerMonitor::kZeroPowerDBFS, false), | |
| 307 TestScenario(kStereoSilentNoise, 2, 1, -46, false), | |
| 308 TestScenario(kStereoMaxAmplitude, 2, 2, | |
| 309 AudioPowerMonitor::kMaxPowerDBFS, false), | |
| 310 TestScenario(kRightChannelMaxAmplitude, 2, 4, -3, false), | |
| 311 TestScenario(kLeftChannelHalfMaxAmplitude, 2, 4, -9, false), | |
| 312 TestScenario(kStereoMixed, 2, 4, -2, false), | |
| 313 TestScenario(kStereoMixed2, 2, 8, -3, false))); | |
| 314 | |
| 315 } // namespace media | |
| OLD | NEW |