Chromium Code Reviews| 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..1e5b19c2f64d113fa553bf5c7b748549399ad92e |
| --- /dev/null |
| +++ b/media/audio/audio_power_monitor_unittest.cc |
| @@ -0,0 +1,266 @@ |
| +// 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 <cmath> |
| + |
| +#include "base/bind.h" |
| +#include "base/bind_helpers.h" |
| +#include "base/message_loop.h" |
| +#include "base/synchronization/waitable_event.h" |
| +#include "base/threading/thread.h" |
| +#include "base/time.h" |
| +#include "media/base/audio_bus.h" |
| +#include "testing/gmock/include/gmock/gmock.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +using ::testing::_; |
| +using ::testing::FloatEq; |
| +using ::testing::InvokeWithoutArgs; |
| +using ::testing::TestWithParam; |
| +using ::testing::Values; |
| + |
| +namespace media { |
| +namespace { |
| + |
| +const int kSampleRate = 48000; |
| +const int kFramesPerBuffer = 128; |
| + |
| +class MockObserver { |
| + public: |
| + MOCK_METHOD1(OnPowerMeasured, void(float)); |
| +}; |
| + |
| +struct TestScenario { |
| + const float* data; |
| + int num_channels; |
| + int num_frames; |
| + float power_in_dbfs; |
| + |
| + TestScenario(const float* d, int c, int f, float p) |
| + : data(d), num_channels(c), num_frames(f), power_in_dbfs(p) {} |
| +}; |
| + |
| +class AudioPowerMonitorTest : public TestWithParam<TestScenario> { |
| + public: |
| + AudioPowerMonitorTest() |
| + : audio_manager_thread_(new base::Thread("AudioThread")), |
| + notification_received_(false, false) { |
| + audio_manager_thread_->Start(); |
| + audio_message_loop_ = audio_manager_thread_->message_loop_proxy(); |
| + } |
| + |
| + virtual ~AudioPowerMonitorTest() { |
| + SyncWithAudioThread(); |
| + } |
| + |
| + AudioPowerMonitor* power_monitor() const { |
| + return power_monitor_.get(); |
| + } |
| + |
| + void StartPowerMonitor(MockObserver* observer) { |
| + audio_message_loop_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&AudioPowerMonitorTest::StartMonitorOnAudioThread, |
| + base::Unretained(this), observer)); |
| + SyncWithAudioThread(); |
| + } |
| + |
| + void StopPowerMonitor() { |
| + audio_message_loop_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&AudioPowerMonitorTest::StopMonitorOnAudioThread, |
| + base::Unretained(this))); |
| + SyncWithAudioThread(); |
| + } |
| + |
| + // Creates an AudioBus, sized and populated with kFramesPerBuffer frames of |
| + // data. The given test |data| is repeated to fill the buffer. |
| + scoped_ptr<AudioBus> CreatePopulatedBuffer( |
| + const float* data, int num_channels, int num_frames) { |
| + scoped_ptr<AudioBus> bus = AudioBus::Create(num_channels, kFramesPerBuffer); |
|
DaleCurtis
2013/05/16 18:24:45
Instead of memcpy, etc, you could just create a wr
miu
2013/05/16 21:44:11
This method produces an AudioBus of kFramesPerBuff
|
| + 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); |
| + } |
| + } |
| + return bus.Pass(); |
| + } |
| + |
| + scoped_ptr<AudioBus> CreateZeroedBuffer(int num_channels) { |
| + scoped_ptr<AudioBus> bus = AudioBus::Create(num_channels, kFramesPerBuffer); |
| + bus->Zero(); |
| + return bus.Pass(); |
| + } |
| + |
| + |
| + void SignalNotificationReceived() { |
| + notification_received_.Signal(); |
| + } |
| + |
| + void WaitForNotificationReceived() { |
| + notification_received_.Wait(); |
| + } |
| + |
| + // Post a task on the audio thread and block until it is executed. This |
| + // provides a barrier: All previous tasks pending on the audio thread have |
| + // completed before this method returns. |
| + void SyncWithAudioThread() { |
| + base::WaitableEvent done(false, false); |
| + audio_message_loop_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done))); |
| + done.Wait(); |
| + } |
| + |
| + private: |
| + void StartMonitorOnAudioThread(MockObserver* observer) { |
| + const AudioPowerMonitor::PowerMeasurementCallback callback = |
| + base::Bind(&MockObserver::OnPowerMeasured, base::Unretained(observer)); |
| + power_monitor_.reset(new AudioPowerMonitor( |
| + kSampleRate, base::TimeDelta::FromMilliseconds(1), |
| + MessageLoop::current(), callback)); |
| + } |
| + |
| + void StopMonitorOnAudioThread() { |
| + power_monitor_.reset(); |
| + } |
| + |
| + scoped_ptr<base::Thread> audio_manager_thread_; |
| + scoped_refptr<base::MessageLoopProxy> audio_message_loop_; |
| + |
| + base::WaitableEvent notification_received_; |
| + |
| + scoped_ptr<AudioPowerMonitor> power_monitor_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(AudioPowerMonitorTest); |
| +}; |
| + |
| +TEST_P(AudioPowerMonitorTest, MeasuresPowerOfSignal) { |
| + const TestScenario& scenario = GetParam(); |
| + |
| + MockObserver observer; |
| + StartPowerMonitor(&observer); |
| + |
| + // Send silence and expect a callback with a "zero power" result. |
| + EXPECT_CALL(observer, |
| + OnPowerMeasured(FloatEq(AudioPowerMonitor::kZeroPowerDBFS))) |
| + .WillOnce(InvokeWithoutArgs( |
| + this, &AudioPowerMonitorTest::SignalNotificationReceived)) |
| + .RetiresOnSaturation(); |
| + scoped_ptr<AudioBus> bus = CreateZeroedBuffer(scenario.num_channels); |
| + power_monitor()->Scan(bus.get(), bus->frames()); |
| + WaitForNotificationReceived(); |
| + |
| + // Send audio signal data to the power monitor and expect a callback with the |
| + // correct result. |
| + EXPECT_CALL(observer, OnPowerMeasured(FloatEq(scenario.power_in_dbfs))) |
| + .WillOnce(InvokeWithoutArgs( |
| + this, &AudioPowerMonitorTest::SignalNotificationReceived)) |
| + .RetiresOnSaturation(); |
| + bus = CreatePopulatedBuffer( |
| + scenario.data, scenario.num_channels, scenario.num_frames); |
| + power_monitor()->Scan(bus.get(), bus->frames()); |
| + WaitForNotificationReceived(); |
| + |
| + // Send silence again and expect a callback with a "zero power" result again. |
| + EXPECT_CALL(observer, |
| + OnPowerMeasured(FloatEq(AudioPowerMonitor::kZeroPowerDBFS))) |
| + .WillOnce(InvokeWithoutArgs( |
| + this, &AudioPowerMonitorTest::SignalNotificationReceived)) |
| + .RetiresOnSaturation(); |
| + bus = CreateZeroedBuffer(scenario.num_channels); |
| + power_monitor()->Scan(bus.get(), bus->frames()); |
| + WaitForNotificationReceived(); |
| + |
| + StopPowerMonitor(); |
| +} |
| + |
| +const float kMonoSilence[] = { |
| + 0.0f |
| +}; |
| + |
| +const float kMonoMaxAmplitude[] = { |
| + 1.0f |
| +}; |
| + |
| +const float kMonoMaxAmplitude2[] = { |
| + -1.0f, 1.0f, |
| +}; |
| + |
| +const float kMonoHalfMaxAmplitude[] = { |
| + 0.5f, -0.5f, 0.5f, -0.5f |
| +}; |
| + |
| +const float kStereoSilence[] = { |
| + // left channel |
| + 0.0f, |
| + // right channel |
| + 0.0f |
| +}; |
| + |
| +const float kStereoMaxAmplitude[] = { |
| + // left channel |
| + 1.0f, -1.0f, |
| + // right channel |
| + -1.0f, 1.0f |
| +}; |
| + |
| +const float kRightChannelMaxAmplitude[] = { |
| + // left channel |
| + 0.0f, 0.0f, 0.0f, 0.0f, |
| + // right channel |
| + -1.0f, 1.0f, -1.0f, 1.0f |
| +}; |
| + |
| +const float kLeftChannelHalfMaxAmplitude[] = { |
| + // left channel |
| + 0.5f, -0.5f, 0.5f, -0.5f, |
| + // right channel |
| + 0.0f, 0.0f, 0.0f, 0.0f, |
| +}; |
| + |
| +const float kStereoMixed[] = { |
| + // left channel |
| + 0.5f, -0.5f, 0.5f, -0.5f, |
| + // right channel |
| + -1.0f, 1.0f, -1.0f, 1.0f |
| +}; |
| + |
| +const float kStereoMixed2[] = { |
| + // left channel |
| + 1.0f, -1.0f, 0.5f, -0.5f, 0.25f, -0.25f, 0.125f, -0.125f, |
| + // right channel |
| + 0.125f, -0.125f, 0.25f, -0.25f, 0.5f, -0.5f, 1.0f, -1.0f |
| +}; |
| + |
|
DaleCurtis
2013/05/16 18:24:45
A test case with values outside of [-1, 1] would b
miu
2013/05/16 21:44:11
Done. And also added ones containing infinity and
|
| +INSTANTIATE_TEST_CASE_P( |
| + Scenarios, AudioPowerMonitorTest, |
| + Values( |
| + TestScenario(kMonoSilence, 1, 1, |
| + AudioPowerMonitor::kZeroPowerDBFS), |
| + TestScenario(kMonoMaxAmplitude, 1, 1, |
| + AudioPowerMonitor::kMaxPowerDBFS), |
| + TestScenario(kMonoMaxAmplitude2, 1, 2, |
| + AudioPowerMonitor::kMaxPowerDBFS), |
| + TestScenario(kMonoHalfMaxAmplitude, 1, 4, |
| + -6.0206f), |
| + TestScenario(kStereoSilence, 2, 1, |
| + AudioPowerMonitor::kZeroPowerDBFS), |
| + TestScenario(kStereoMaxAmplitude, 2, 2, |
| + AudioPowerMonitor::kMaxPowerDBFS), |
| + TestScenario(kRightChannelMaxAmplitude, 2, 4, |
| + -3.0103f), |
| + TestScenario(kLeftChannelHalfMaxAmplitude, 2, 4, |
| + -9.0309f), |
| + TestScenario(kStereoMixed, 2, 4, |
| + -2.0412f), |
| + TestScenario(kStereoMixed2, 2, 8, |
| + -4.78821f))); |
| + |
| +} // namespace |
|
DaleCurtis
2013/05/16 18:24:45
This is a bit weird? For media/ we generally leav
miu
2013/05/16 21:44:11
Yes and no. I actually came up against the follow
|
| +} // namespace media |