| // Copyright (c) 2012 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 <list> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/rand_util.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/thread.h" |
| #include "media/audio/audio_io.h" |
| #include "media/audio/simple_sources.h" |
| #include "media/audio/virtual_audio_input_stream.h" |
| #include "media/audio/virtual_audio_output_stream.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::NotNull; |
| |
| namespace media { |
| |
| namespace { |
| |
| const AudioParameters kParams( |
| AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO, 8000, 8, 10); |
| |
| class MockInputCallback : public AudioInputStream::AudioInputCallback { |
| public: |
| MockInputCallback() |
| : data_pushed_(false, false) { |
| ON_CALL(*this, OnData(_, _, _, _, _)) |
| .WillByDefault(InvokeWithoutArgs(&data_pushed_, |
| &base::WaitableEvent::Signal)); |
| } |
| |
| virtual ~MockInputCallback() {} |
| |
| MOCK_METHOD5(OnData, void(AudioInputStream* stream, const uint8* data, |
| uint32 size, uint32 hardware_delay_bytes, |
| double volume)); |
| MOCK_METHOD1(OnClose, void(AudioInputStream* stream)); |
| MOCK_METHOD1(OnError, void(AudioInputStream* stream)); |
| |
| void WaitForDataPushes() { |
| for (int i = 0; i < 3; ++i) { |
| data_pushed_.Wait(); |
| } |
| } |
| |
| private: |
| base::WaitableEvent data_pushed_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockInputCallback); |
| }; |
| |
| class TestAudioSource : public SineWaveAudioSource { |
| public: |
| TestAudioSource() |
| : SineWaveAudioSource( |
| kParams.channel_layout(), 200.0, kParams.sample_rate()), |
| data_pulled_(false, false) {} |
| |
| virtual ~TestAudioSource() {} |
| |
| virtual int OnMoreData(AudioBus* audio_bus, |
| AudioBuffersState audio_buffers) OVERRIDE { |
| const int ret = SineWaveAudioSource::OnMoreData(audio_bus, audio_buffers); |
| data_pulled_.Signal(); |
| return ret; |
| } |
| |
| virtual int OnMoreIOData(AudioBus* source, |
| AudioBus* dest, |
| AudioBuffersState audio_buffers) OVERRIDE { |
| const int ret = |
| SineWaveAudioSource::OnMoreIOData(source, dest, audio_buffers); |
| data_pulled_.Signal(); |
| return ret; |
| } |
| |
| void WaitForDataPulls() { |
| for (int i = 0; i < 3; ++i) { |
| data_pulled_.Wait(); |
| } |
| } |
| |
| private: |
| base::WaitableEvent data_pulled_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestAudioSource); |
| }; |
| |
| } // namespace |
| |
| class VirtualAudioInputStreamTest : public testing::TestWithParam<bool> { |
| public: |
| VirtualAudioInputStreamTest() |
| : audio_thread_(new base::Thread("AudioThread")), |
| worker_thread_(new base::Thread("AudioWorkerThread")), |
| stream_(NULL), |
| closed_stream_(false, false) { |
| audio_thread_->Start(); |
| audio_message_loop_ = audio_thread_->message_loop_proxy(); |
| } |
| |
| virtual ~VirtualAudioInputStreamTest() { |
| SyncWithAudioThread(); |
| |
| DCHECK(output_streams_.empty()); |
| DCHECK(stopped_output_streams_.empty()); |
| } |
| |
| void Create() { |
| const bool worker_is_separate_thread = GetParam(); |
| stream_ = new VirtualAudioInputStream( |
| kParams, GetWorkerLoop(worker_is_separate_thread), |
| base::Bind(&base::DeletePointer<VirtualAudioInputStream>)); |
| stream_->Open(); |
| } |
| |
| void Start() { |
| EXPECT_CALL(input_callback_, OnClose(_)); |
| EXPECT_CALL(input_callback_, OnData(_, NotNull(), _, _, _)) |
| .Times(AtLeast(1)); |
| |
| ASSERT_TRUE(!!stream_); |
| stream_->Start(&input_callback_); |
| } |
| |
| void CreateAndStartOneOutputStream() { |
| ASSERT_TRUE(!!stream_); |
| AudioOutputStream* const output_stream = new VirtualAudioOutputStream( |
| kParams, |
| stream_, |
| base::Bind(&base::DeletePointer<VirtualAudioOutputStream>)); |
| output_streams_.push_back(output_stream); |
| |
| output_stream->Open(); |
| output_stream->Start(&source_); |
| } |
| |
| void Stop() { |
| ASSERT_TRUE(!!stream_); |
| stream_->Stop(); |
| } |
| |
| void Close() { |
| ASSERT_TRUE(!!stream_); |
| stream_->Close(); |
| stream_ = NULL; |
| closed_stream_.Signal(); |
| } |
| |
| void WaitForDataToFlow() { |
| // Wait until audio thread is idle before calling output_streams_.size(). |
| SyncWithAudioThread(); |
| |
| const int count = output_streams_.size(); |
| for (int i = 0; i < count; ++i) { |
| source_.WaitForDataPulls(); |
| } |
| |
| input_callback_.WaitForDataPushes(); |
| } |
| |
| void WaitUntilClosed() { |
| closed_stream_.Wait(); |
| } |
| |
| void StopAndCloseOneOutputStream() { |
| ASSERT_TRUE(!output_streams_.empty()); |
| AudioOutputStream* const output_stream = output_streams_.front(); |
| ASSERT_TRUE(!!output_stream); |
| output_streams_.pop_front(); |
| |
| output_stream->Stop(); |
| output_stream->Close(); |
| } |
| |
| void StopFirstOutputStream() { |
| ASSERT_TRUE(!output_streams_.empty()); |
| AudioOutputStream* const output_stream = output_streams_.front(); |
| ASSERT_TRUE(!!output_stream); |
| output_streams_.pop_front(); |
| output_stream->Stop(); |
| stopped_output_streams_.push_back(output_stream); |
| } |
| |
| void StopSomeOutputStreams() { |
| ASSERT_LE(2, static_cast<int>(output_streams_.size())); |
| for (int remaning = base::RandInt(1, output_streams_.size() - 1); |
| remaning > 0; --remaning) { |
| StopFirstOutputStream(); |
| } |
| } |
| |
| void RestartAllStoppedOutputStreams() { |
| typedef std::list<AudioOutputStream*>::const_iterator ConstIter; |
| for (ConstIter it = stopped_output_streams_.begin(); |
| it != stopped_output_streams_.end(); ++it) { |
| (*it)->Start(&source_); |
| output_streams_.push_back(*it); |
| } |
| stopped_output_streams_.clear(); |
| } |
| |
| const scoped_refptr<base::MessageLoopProxy>& audio_message_loop() const { |
| return audio_message_loop_; |
| } |
| |
| const scoped_refptr<base::MessageLoopProxy>& GetWorkerLoop( |
| bool worker_is_separate_thread) { |
| if (worker_is_separate_thread) { |
| if (!worker_thread_->IsRunning()) { |
| worker_thread_->Start(); |
| worker_message_loop_ = worker_thread_->message_loop_proxy(); |
| } |
| return worker_message_loop_; |
| } else { |
| return audio_message_loop_; |
| } |
| } |
| |
| private: |
| void SyncWithAudioThread() { |
| base::WaitableEvent done(false, false); |
| audio_message_loop_->PostTask( |
| FROM_HERE, |
| base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done))); |
| done.Wait(); |
| } |
| |
| scoped_ptr<base::Thread> audio_thread_; |
| scoped_refptr<base::MessageLoopProxy> audio_message_loop_; |
| scoped_ptr<base::Thread> worker_thread_; |
| scoped_refptr<base::MessageLoopProxy> worker_message_loop_; |
| |
| VirtualAudioInputStream* stream_; |
| MockInputCallback input_callback_; |
| base::WaitableEvent closed_stream_; |
| |
| std::list<AudioOutputStream*> output_streams_; |
| std::list<AudioOutputStream*> stopped_output_streams_; |
| TestAudioSource source_; |
| |
| DISALLOW_COPY_AND_ASSIGN(VirtualAudioInputStreamTest); |
| }; |
| |
| #define RUN_ON_AUDIO_THREAD(method) \ |
| audio_message_loop()->PostTask( \ |
| FROM_HERE, base::Bind(&VirtualAudioInputStreamTest::method, \ |
| base::Unretained(this))) |
| |
| TEST_P(VirtualAudioInputStreamTest, CreateAndClose) { |
| RUN_ON_AUDIO_THREAD(Create); |
| RUN_ON_AUDIO_THREAD(Close); |
| WaitUntilClosed(); |
| } |
| |
| TEST_P(VirtualAudioInputStreamTest, NoOutputs) { |
| RUN_ON_AUDIO_THREAD(Create); |
| RUN_ON_AUDIO_THREAD(Start); |
| WaitForDataToFlow(); |
| RUN_ON_AUDIO_THREAD(Stop); |
| RUN_ON_AUDIO_THREAD(Close); |
| WaitUntilClosed(); |
| } |
| |
| TEST_P(VirtualAudioInputStreamTest, SingleOutput) { |
| RUN_ON_AUDIO_THREAD(Create); |
| RUN_ON_AUDIO_THREAD(Start); |
| RUN_ON_AUDIO_THREAD(CreateAndStartOneOutputStream); |
| WaitForDataToFlow(); |
| RUN_ON_AUDIO_THREAD(StopAndCloseOneOutputStream); |
| RUN_ON_AUDIO_THREAD(Stop); |
| RUN_ON_AUDIO_THREAD(Close); |
| WaitUntilClosed(); |
| } |
| |
| TEST_P(VirtualAudioInputStreamTest, SingleOutputPausedAndRestarted) { |
| RUN_ON_AUDIO_THREAD(Create); |
| RUN_ON_AUDIO_THREAD(Start); |
| RUN_ON_AUDIO_THREAD(CreateAndStartOneOutputStream); |
| WaitForDataToFlow(); |
| RUN_ON_AUDIO_THREAD(StopFirstOutputStream); |
| RUN_ON_AUDIO_THREAD(RestartAllStoppedOutputStreams); |
| WaitForDataToFlow(); |
| RUN_ON_AUDIO_THREAD(StopAndCloseOneOutputStream); |
| RUN_ON_AUDIO_THREAD(Stop); |
| RUN_ON_AUDIO_THREAD(Close); |
| WaitUntilClosed(); |
| } |
| |
| TEST_P(VirtualAudioInputStreamTest, MultipleOutputs) { |
| RUN_ON_AUDIO_THREAD(Create); |
| RUN_ON_AUDIO_THREAD(Start); |
| RUN_ON_AUDIO_THREAD(CreateAndStartOneOutputStream); |
| WaitForDataToFlow(); |
| RUN_ON_AUDIO_THREAD(CreateAndStartOneOutputStream); |
| RUN_ON_AUDIO_THREAD(CreateAndStartOneOutputStream); |
| WaitForDataToFlow(); |
| RUN_ON_AUDIO_THREAD(StopFirstOutputStream); |
| RUN_ON_AUDIO_THREAD(StopFirstOutputStream); |
| WaitForDataToFlow(); |
| RUN_ON_AUDIO_THREAD(StopFirstOutputStream); |
| RUN_ON_AUDIO_THREAD(RestartAllStoppedOutputStreams); |
| WaitForDataToFlow(); |
| RUN_ON_AUDIO_THREAD(StopAndCloseOneOutputStream); |
| RUN_ON_AUDIO_THREAD(StopAndCloseOneOutputStream); |
| RUN_ON_AUDIO_THREAD(Stop); |
| RUN_ON_AUDIO_THREAD(StopAndCloseOneOutputStream); |
| RUN_ON_AUDIO_THREAD(Close); |
| WaitUntilClosed(); |
| } |
| |
| // A combination of all of the above tests with many output streams. |
| TEST_P(VirtualAudioInputStreamTest, ComprehensiveTest) { |
| static const int kNumOutputs = 8; |
| static const int kHalfNumOutputs = kNumOutputs / 2; |
| static const int kPauseIterations = 5; |
| |
| RUN_ON_AUDIO_THREAD(Create); |
| for (int i = 0; i < kHalfNumOutputs; ++i) { |
| RUN_ON_AUDIO_THREAD(CreateAndStartOneOutputStream); |
| } |
| RUN_ON_AUDIO_THREAD(Start); |
| WaitForDataToFlow(); |
| for (int i = 0; i < kHalfNumOutputs; ++i) { |
| RUN_ON_AUDIO_THREAD(CreateAndStartOneOutputStream); |
| } |
| WaitForDataToFlow(); |
| for (int i = 0; i < kPauseIterations; ++i) { |
| RUN_ON_AUDIO_THREAD(StopSomeOutputStreams); |
| WaitForDataToFlow(); |
| RUN_ON_AUDIO_THREAD(RestartAllStoppedOutputStreams); |
| WaitForDataToFlow(); |
| } |
| for (int i = 0; i < kHalfNumOutputs; ++i) { |
| RUN_ON_AUDIO_THREAD(StopAndCloseOneOutputStream); |
| } |
| RUN_ON_AUDIO_THREAD(Stop); |
| for (int i = 0; i < kHalfNumOutputs; ++i) { |
| RUN_ON_AUDIO_THREAD(StopAndCloseOneOutputStream); |
| } |
| RUN_ON_AUDIO_THREAD(Close); |
| WaitUntilClosed(); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(SingleVersusMultithreaded, |
| VirtualAudioInputStreamTest, |
| ::testing::Values(false, true)); |
| |
| } // namespace media |