diff options
Diffstat (limited to 'chromium/content/browser/media/capture/web_contents_audio_input_stream_unittest.cc')
-rw-r--r-- | chromium/content/browser/media/capture/web_contents_audio_input_stream_unittest.cc | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/chromium/content/browser/media/capture/web_contents_audio_input_stream_unittest.cc b/chromium/content/browser/media/capture/web_contents_audio_input_stream_unittest.cc new file mode 100644 index 00000000000..72f6109029d --- /dev/null +++ b/chromium/content/browser/media/capture/web_contents_audio_input_stream_unittest.cc @@ -0,0 +1,513 @@ +// 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 "content/browser/media/capture/web_contents_audio_input_stream.h" + +#include <list> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "content/browser/browser_thread_impl.h" +#include "content/browser/media/capture/audio_mirroring_manager.h" +#include "content/browser/media/capture/web_contents_tracker.h" +#include "media/audio/simple_sources.h" +#include "media/audio/virtual_audio_input_stream.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Assign; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::NotNull; +using ::testing::SaveArg; +using ::testing::WithArgs; + +using media::AudioInputStream; +using media::AudioOutputStream; +using media::AudioParameters; +using media::SineWaveAudioSource; +using media::VirtualAudioInputStream; +using media::VirtualAudioOutputStream; + +namespace content { + +namespace { + +const int kRenderProcessId = 123; +const int kRenderViewId = 456; +const int kAnotherRenderProcessId = 789; +const int kAnotherRenderViewId = 1; + +const AudioParameters& TestAudioParameters() { + static const AudioParameters params( + AudioParameters::AUDIO_FAKE, + media::CHANNEL_LAYOUT_STEREO, + AudioParameters::kAudioCDSampleRate, 16, + AudioParameters::kAudioCDSampleRate / 100); + return params; +} + +class MockAudioMirroringManager : public AudioMirroringManager { + public: + MockAudioMirroringManager() : AudioMirroringManager() {} + virtual ~MockAudioMirroringManager() {} + + MOCK_METHOD3(StartMirroring, + void(int render_process_id, int render_view_id, + MirroringDestination* destination)); + MOCK_METHOD3(StopMirroring, + void(int render_process_id, int render_view_id, + MirroringDestination* destination)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAudioMirroringManager); +}; + +class MockWebContentsTracker : public WebContentsTracker { + public: + MockWebContentsTracker() : WebContentsTracker() {} + + MOCK_METHOD3(Start, + void(int render_process_id, int render_view_id, + const ChangeCallback& callback)); + MOCK_METHOD0(Stop, void()); + + private: + virtual ~MockWebContentsTracker() {} + + DISALLOW_COPY_AND_ASSIGN(MockWebContentsTracker); +}; + +// A fully-functional VirtualAudioInputStream, but methods are mocked to allow +// tests to check how/when they are invoked. +class MockVirtualAudioInputStream : public VirtualAudioInputStream { + public: + explicit MockVirtualAudioInputStream( + const scoped_refptr<base::MessageLoopProxy>& worker_loop) + : VirtualAudioInputStream(TestAudioParameters(), worker_loop, + VirtualAudioInputStream::AfterCloseCallback()), + real_(TestAudioParameters(), worker_loop, + base::Bind(&MockVirtualAudioInputStream::OnRealStreamHasClosed, + base::Unretained(this))), + real_stream_is_closed_(false) { + // Set default actions of mocked methods to delegate to the concrete + // implementation. + ON_CALL(*this, Open()) + .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Open)); + ON_CALL(*this, Start(_)) + .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Start)); + ON_CALL(*this, Stop()) + .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Stop)); + ON_CALL(*this, Close()) + .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Close)); + ON_CALL(*this, GetMaxVolume()) + .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::GetMaxVolume)); + ON_CALL(*this, SetVolume(_)) + .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::SetVolume)); + ON_CALL(*this, GetVolume()) + .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::GetVolume)); + ON_CALL(*this, SetAutomaticGainControl(_)) + .WillByDefault( + Invoke(&real_, &VirtualAudioInputStream::SetAutomaticGainControl)); + ON_CALL(*this, GetAutomaticGainControl()) + .WillByDefault( + Invoke(&real_, &VirtualAudioInputStream::GetAutomaticGainControl)); + ON_CALL(*this, AddOutputStream(NotNull(), _)) + .WillByDefault( + Invoke(&real_, &VirtualAudioInputStream::AddOutputStream)); + ON_CALL(*this, RemoveOutputStream(NotNull(), _)) + .WillByDefault( + Invoke(&real_, &VirtualAudioInputStream::RemoveOutputStream)); + } + + ~MockVirtualAudioInputStream() { + DCHECK(real_stream_is_closed_); + } + + MOCK_METHOD0(Open, bool()); + MOCK_METHOD1(Start, void(AudioInputStream::AudioInputCallback*)); + MOCK_METHOD0(Stop, void()); + MOCK_METHOD0(Close, void()); + MOCK_METHOD0(GetMaxVolume, double()); + MOCK_METHOD1(SetVolume, void(double)); + MOCK_METHOD0(GetVolume, double()); + MOCK_METHOD1(SetAutomaticGainControl, void(bool)); + MOCK_METHOD0(GetAutomaticGainControl, bool()); + MOCK_METHOD2(AddOutputStream, void(VirtualAudioOutputStream*, + const AudioParameters&)); + MOCK_METHOD2(RemoveOutputStream, void(VirtualAudioOutputStream*, + const AudioParameters&)); + + private: + void OnRealStreamHasClosed(VirtualAudioInputStream* stream) { + DCHECK_EQ(&real_, stream); + DCHECK(!real_stream_is_closed_); + real_stream_is_closed_ = true; + } + + VirtualAudioInputStream real_; + bool real_stream_is_closed_; + + DISALLOW_COPY_AND_ASSIGN(MockVirtualAudioInputStream); +}; + +class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { + public: + MockAudioInputCallback() {} + + MOCK_METHOD4(OnData, + void(AudioInputStream* stream, + const media::AudioBus* src, + uint32 hardware_delay_bytes, + double volume)); + MOCK_METHOD1(OnError, void(AudioInputStream* stream)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAudioInputCallback); +}; + +} // namespace + +class WebContentsAudioInputStreamTest : public testing::Test { + public: + WebContentsAudioInputStreamTest() + : audio_thread_("Audio thread"), + io_thread_(BrowserThread::IO), + mock_mirroring_manager_(new MockAudioMirroringManager()), + mock_tracker_(new MockWebContentsTracker()), + mock_vais_(NULL), + wcais_(NULL), + destination_(NULL), + current_render_process_id_(kRenderProcessId), + current_render_view_id_(kRenderViewId), + on_data_event_(false, false) { + audio_thread_.Start(); + io_thread_.Start(); + } + + virtual ~WebContentsAudioInputStreamTest() { + audio_thread_.Stop(); + io_thread_.Stop(); + + DCHECK(!mock_vais_); + DCHECK(!wcais_); + EXPECT_FALSE(destination_); + DCHECK(streams_.empty()); + DCHECK(sources_.empty()); + } + + void Open() { + mock_vais_ = + new MockVirtualAudioInputStream(audio_thread_.message_loop_proxy()); + EXPECT_CALL(*mock_vais_, Open()); + EXPECT_CALL(*mock_vais_, Close()); // At Close() time. + + ASSERT_EQ(kRenderProcessId, current_render_process_id_); + ASSERT_EQ(kRenderViewId, current_render_view_id_); + EXPECT_CALL(*mock_tracker_.get(), Start(kRenderProcessId, kRenderViewId, _)) + .WillOnce(DoAll( + SaveArg<2>(&change_callback_), + WithArgs<0, 1>(Invoke(&change_callback_, + &WebContentsTracker::ChangeCallback::Run)))); + EXPECT_CALL(*mock_tracker_.get(), Stop()); // At Close() time. + + wcais_ = new WebContentsAudioInputStream( + current_render_process_id_, current_render_view_id_, + mock_mirroring_manager_.get(), + mock_tracker_, mock_vais_); + wcais_->Open(); + } + + void Start() { + EXPECT_CALL(*mock_vais_, Start(&mock_input_callback_)); + EXPECT_CALL(*mock_vais_, Stop()); // At Stop() time. + + EXPECT_CALL(*mock_mirroring_manager_, + StartMirroring(kRenderProcessId, kRenderViewId, NotNull())) + .WillOnce(SaveArg<2>(&destination_)) + .RetiresOnSaturation(); + // At Stop() time, or when the mirroring target changes: + EXPECT_CALL(*mock_mirroring_manager_, + StopMirroring(kRenderProcessId, kRenderViewId, NotNull())) + .WillOnce(Assign( + &destination_, + static_cast<AudioMirroringManager::MirroringDestination*>(NULL))) + .RetiresOnSaturation(); + + EXPECT_CALL(mock_input_callback_, OnData(NotNull(), NotNull(), _, _)) + .WillRepeatedly( + InvokeWithoutArgs(&on_data_event_, &base::WaitableEvent::Signal)); + + wcais_->Start(&mock_input_callback_); + + // Test plumbing of volume controls and automatic gain controls. Calls to + // wcais_ methods should delegate directly to mock_vais_. + EXPECT_CALL(*mock_vais_, GetVolume()); + double volume = wcais_->GetVolume(); + EXPECT_CALL(*mock_vais_, GetMaxVolume()); + const double max_volume = wcais_->GetMaxVolume(); + volume *= 2.0; + if (volume < max_volume) { + volume = max_volume; + } + EXPECT_CALL(*mock_vais_, SetVolume(volume)); + wcais_->SetVolume(volume); + EXPECT_CALL(*mock_vais_, GetAutomaticGainControl()); + bool auto_gain = wcais_->GetAutomaticGainControl(); + auto_gain = !auto_gain; + EXPECT_CALL(*mock_vais_, SetAutomaticGainControl(auto_gain)); + wcais_->SetAutomaticGainControl(auto_gain); + } + + void AddAnotherInput() { + // Note: WCAIS posts a task to invoke + // MockAudioMirroringManager::StartMirroring() on the IO thread, which + // causes our mock to set |destination_|. Block until that has happened. + base::WaitableEvent done(false, false); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, base::Bind( + &base::WaitableEvent::Signal, base::Unretained(&done))); + done.Wait(); + ASSERT_TRUE(destination_); + + EXPECT_CALL(*mock_vais_, AddOutputStream(NotNull(), _)) + .RetiresOnSaturation(); + // Later, when stream is closed: + EXPECT_CALL(*mock_vais_, RemoveOutputStream(NotNull(), _)) + .RetiresOnSaturation(); + + const AudioParameters& params = TestAudioParameters(); + AudioOutputStream* const out = destination_->AddInput(params); + ASSERT_TRUE(out); + streams_.push_back(out); + EXPECT_TRUE(out->Open()); + SineWaveAudioSource* const source = new SineWaveAudioSource( + params.channel_layout(), 200.0, params.sample_rate()); + sources_.push_back(source); + out->Start(source); + } + + void RemoveOneInputInFIFOOrder() { + ASSERT_FALSE(streams_.empty()); + AudioOutputStream* const out = streams_.front(); + streams_.pop_front(); + out->Stop(); + out->Close(); // Self-deletes. + ASSERT_TRUE(!sources_.empty()); + delete sources_.front(); + sources_.pop_front(); + } + + void ChangeMirroringTarget() { + const int next_render_process_id = + current_render_process_id_ == kRenderProcessId ? + kAnotherRenderProcessId : kRenderProcessId; + const int next_render_view_id = + current_render_view_id_ == kRenderViewId ? + kAnotherRenderViewId : kRenderViewId; + + EXPECT_CALL(*mock_mirroring_manager_, + StartMirroring(next_render_process_id, next_render_view_id, + NotNull())) + .WillOnce(SaveArg<2>(&destination_)) + .RetiresOnSaturation(); + // At Stop() time, or when the mirroring target changes: + EXPECT_CALL(*mock_mirroring_manager_, + StopMirroring(next_render_process_id, next_render_view_id, + NotNull())) + .WillOnce(Assign( + &destination_, + static_cast<AudioMirroringManager::MirroringDestination*>(NULL))) + .RetiresOnSaturation(); + + // Simulate OnTargetChange() callback from WebContentsTracker. + EXPECT_FALSE(change_callback_.is_null()); + change_callback_.Run(next_render_process_id, next_render_view_id); + + current_render_process_id_ = next_render_process_id; + current_render_view_id_ = next_render_view_id; + } + + void LoseMirroringTarget() { + EXPECT_CALL(mock_input_callback_, OnError(_)); + + // Simulate OnTargetChange() callback from WebContentsTracker. + EXPECT_FALSE(change_callback_.is_null()); + change_callback_.Run(-1, -1); + } + + void Stop() { + wcais_->Stop(); + } + + void Close() { + // WebContentsAudioInputStream self-destructs on Close(). Its internal + // objects hang around until they are no longer referred to (e.g., as tasks + // on other threads shut things down). + wcais_->Close(); + wcais_ = NULL; + mock_vais_ = NULL; + } + + void RunOnAudioThread(const base::Closure& closure) { + audio_thread_.message_loop()->PostTask(FROM_HERE, closure); + } + + // Block the calling thread until OnData() callbacks are being made. + void WaitForData() { + // Note: Arbitrarily chosen, but more iterations causes tests to take + // significantly more time. + static const int kNumIterations = 3; + for (int i = 0; i < kNumIterations; ++i) + on_data_event_.Wait(); + } + + private: + base::Thread audio_thread_; + BrowserThreadImpl io_thread_; + + scoped_ptr<MockAudioMirroringManager> mock_mirroring_manager_; + scoped_refptr<MockWebContentsTracker> mock_tracker_; + + MockVirtualAudioInputStream* mock_vais_; // Owned by wcais_. + WebContentsAudioInputStream* wcais_; // Self-destructs on Close(). + + // Mock consumer of audio data. + MockAudioInputCallback mock_input_callback_; + + // Provided by WebContentsAudioInputStream to the mock WebContentsTracker. + // This callback is saved here, and test code will invoke it to simulate + // target change events. + WebContentsTracker::ChangeCallback change_callback_; + + // Provided by WebContentsAudioInputStream to the mock AudioMirroringManager. + // A pointer to the implementation is saved here, and test code will invoke it + // to simulate: 1) calls to AddInput(); and 2) diverting audio data. + AudioMirroringManager::MirroringDestination* destination_; + + // Current target RenderView. These get flipped in ChangedMirroringTarget(). + int current_render_process_id_; + int current_render_view_id_; + + // Streams provided by calls to WebContentsAudioInputStream::AddInput(). Each + // is started with a simulated source of audio data. + std::list<AudioOutputStream*> streams_; + std::list<SineWaveAudioSource*> sources_; // 1:1 with elements in streams_. + + base::WaitableEvent on_data_event_; + + DISALLOW_COPY_AND_ASSIGN(WebContentsAudioInputStreamTest); +}; + +#define RUN_ON_AUDIO_THREAD(method) \ + RunOnAudioThread(base::Bind(&WebContentsAudioInputStreamTest::method, \ + base::Unretained(this))) + +TEST_F(WebContentsAudioInputStreamTest, OpenedButNeverStarted) { + RUN_ON_AUDIO_THREAD(Open); + RUN_ON_AUDIO_THREAD(Close); +} + +TEST_F(WebContentsAudioInputStreamTest, MirroringNothing) { + RUN_ON_AUDIO_THREAD(Open); + RUN_ON_AUDIO_THREAD(Start); + WaitForData(); + RUN_ON_AUDIO_THREAD(Stop); + RUN_ON_AUDIO_THREAD(Close); +} + +TEST_F(WebContentsAudioInputStreamTest, MirroringOutputOutlivesSession) { + RUN_ON_AUDIO_THREAD(Open); + RUN_ON_AUDIO_THREAD(Start); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + WaitForData(); + RUN_ON_AUDIO_THREAD(Stop); + RUN_ON_AUDIO_THREAD(Close); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); +} + +TEST_F(WebContentsAudioInputStreamTest, MirroringOutputWithinSession) { + RUN_ON_AUDIO_THREAD(Open); + RUN_ON_AUDIO_THREAD(Start); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + WaitForData(); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); + RUN_ON_AUDIO_THREAD(Stop); + RUN_ON_AUDIO_THREAD(Close); +} + +TEST_F(WebContentsAudioInputStreamTest, MirroringNothingWithTargetChange) { + RUN_ON_AUDIO_THREAD(Open); + RUN_ON_AUDIO_THREAD(Start); + RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); + RUN_ON_AUDIO_THREAD(Stop); + RUN_ON_AUDIO_THREAD(Close); +} + +TEST_F(WebContentsAudioInputStreamTest, MirroringOneStreamAfterTargetChange) { + RUN_ON_AUDIO_THREAD(Open); + RUN_ON_AUDIO_THREAD(Start); + RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + WaitForData(); + RUN_ON_AUDIO_THREAD(Stop); + RUN_ON_AUDIO_THREAD(Close); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); +} + +TEST_F(WebContentsAudioInputStreamTest, MirroringOneStreamWithTargetChange) { + RUN_ON_AUDIO_THREAD(Open); + RUN_ON_AUDIO_THREAD(Start); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + WaitForData(); + RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + WaitForData(); + RUN_ON_AUDIO_THREAD(Stop); + RUN_ON_AUDIO_THREAD(Close); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); +} + +TEST_F(WebContentsAudioInputStreamTest, MirroringLostTarget) { + RUN_ON_AUDIO_THREAD(Open); + RUN_ON_AUDIO_THREAD(Start); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + WaitForData(); + RUN_ON_AUDIO_THREAD(LoseMirroringTarget); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); + RUN_ON_AUDIO_THREAD(Stop); + RUN_ON_AUDIO_THREAD(Close); +} + +TEST_F(WebContentsAudioInputStreamTest, MirroringMultipleStreamsAndTargets) { + RUN_ON_AUDIO_THREAD(Open); + RUN_ON_AUDIO_THREAD(Start); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + WaitForData(); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + WaitForData(); + RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); + WaitForData(); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); + RUN_ON_AUDIO_THREAD(AddAnotherInput); + WaitForData(); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); + WaitForData(); + RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); + RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); + RUN_ON_AUDIO_THREAD(Stop); + RUN_ON_AUDIO_THREAD(Close); +} + +} // namespace content |