diff options
-rw-r--r-- | media/audio/audio_manager.h | 7 | ||||
-rw-r--r-- | media/audio/audio_manager_base.cc | 22 | ||||
-rw-r--r-- | media/audio/audio_manager_base.h | 15 | ||||
-rw-r--r-- | media/audio/audio_output_controller.cc | 2 | ||||
-rw-r--r-- | media/audio/audio_output_dispatcher.cc | 115 | ||||
-rw-r--r-- | media/audio/audio_output_dispatcher.h | 93 | ||||
-rw-r--r-- | media/audio/audio_output_proxy.cc | 92 | ||||
-rw-r--r-- | media/audio/audio_output_proxy.h | 64 | ||||
-rw-r--r-- | media/audio/audio_output_proxy_unittest.cc | 353 | ||||
-rw-r--r-- | media/audio/audio_parameters.cc | 22 | ||||
-rw-r--r-- | media/audio/audio_parameters.h | 6 | ||||
-rw-r--r-- | media/audio/audio_parameters_unittest.cc | 72 | ||||
-rw-r--r-- | media/audio/linux/audio_manager_linux.cc | 5 | ||||
-rw-r--r-- | media/media.gyp | 6 |
14 files changed, 872 insertions, 2 deletions
diff --git a/media/audio/audio_manager.h b/media/audio/audio_manager.h index cba88a9..aa7f018 100644 --- a/media/audio/audio_manager.h +++ b/media/audio/audio_manager.h @@ -54,6 +54,13 @@ class AudioManager { // Do not free the returned AudioOutputStream. It is owned by AudioManager. virtual AudioOutputStream* MakeAudioOutputStream(AudioParameters params) = 0; + // Creates new audio output proxy. A proxy implements + // AudioOutputStream interface, but unlike regular output stream + // created with MakeAudioOutputStream() it opens device only when a + // sound is actually playing. + virtual AudioOutputStream* MakeAudioOutputStreamProxy( + const AudioParameters& params) = 0; + // Factory to create audio recording streams. // |channels| can be 1 or 2. // |sample_rate| is in hertz and can be any value supported by the platform. diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc index 95a47f1..e522240 100644 --- a/media/audio/audio_manager_base.cc +++ b/media/audio/audio_manager_base.cc @@ -3,12 +3,21 @@ // found in the LICENSE file. #include "media/audio/audio_manager_base.h" +#include "media/audio/audio_output_dispatcher.h" +#include "media/audio/audio_output_proxy.h" + +namespace { +const int kStreamCloseDelayMs = 5000; +} // namespace AudioManagerBase::AudioManagerBase() : audio_thread_("AudioThread"), initialized_(false) { } +AudioManagerBase::~AudioManagerBase() { +} + void AudioManagerBase::Init() { initialized_ = audio_thread_.Start(); } @@ -21,3 +30,16 @@ MessageLoop* AudioManagerBase::GetMessageLoop() { DCHECK(initialized_); return audio_thread_.message_loop(); } + +AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy( + const AudioParameters& params) { + if (!initialized_) + return NULL; + + scoped_refptr<AudioOutputDispatcher>& dispatcher = + output_dispatchers_[params]; + if (!dispatcher) + dispatcher = new AudioOutputDispatcher(this, params, kStreamCloseDelayMs); + + return new AudioOutputProxy(dispatcher); +} diff --git a/media/audio/audio_manager_base.h b/media/audio/audio_manager_base.h index 9665982..aa664f4 100644 --- a/media/audio/audio_manager_base.h +++ b/media/audio/audio_manager_base.h @@ -5,9 +5,13 @@ #ifndef MEDIA_AUDIO_AUDIO_MANAGER_BASE_H_ #define MEDIA_AUDIO_AUDIO_MANAGER_BASE_H_ +#include <map> + #include "base/thread.h" #include "media/audio/audio_manager.h" +class AudioOutputDispatcher; + // AudioManagerBase provides AudioManager functions common for all platforms. class AudioManagerBase : public AudioManager { public: @@ -19,8 +23,15 @@ class AudioManagerBase : public AudioManager { virtual string16 GetAudioInputDeviceModel(); + virtual AudioOutputStream* MakeAudioOutputStreamProxy( + const AudioParameters& params); + protected: - virtual ~AudioManagerBase() {} + virtual ~AudioManagerBase(); + + typedef std::map<AudioParameters, scoped_refptr<AudioOutputDispatcher>, + AudioParameters::Compare> + AudioOutputDispatchersMap; bool initialized() { return initialized_; } @@ -30,6 +41,8 @@ class AudioManagerBase : public AudioManager { bool initialized_; + AudioOutputDispatchersMap output_dispatchers_; + DISALLOW_COPY_AND_ASSIGN(AudioManagerBase); }; diff --git a/media/audio/audio_output_controller.cc b/media/audio/audio_output_controller.cc index 033d4c5..ee2614e 100644 --- a/media/audio/audio_output_controller.cc +++ b/media/audio/audio_output_controller.cc @@ -142,7 +142,7 @@ void AudioOutputController::DoCreate(AudioParameters params) { return; DCHECK(state_ == kEmpty); - stream_ = AudioManager::GetAudioManager()->MakeAudioOutputStream(params); + stream_ = AudioManager::GetAudioManager()->MakeAudioOutputStreamProxy(params); if (!stream_) { // TODO(hclam): Define error types. handler_->OnError(this, 0); diff --git a/media/audio/audio_output_dispatcher.cc b/media/audio/audio_output_dispatcher.cc new file mode 100644 index 0000000..3f9d848 --- /dev/null +++ b/media/audio/audio_output_dispatcher.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2010 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_output_dispatcher.h" + +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "media/audio/audio_io.h" + +AudioOutputDispatcher::AudioOutputDispatcher( + AudioManager* audio_manager, const AudioParameters& params, + int close_delay_ms) + : audio_manager_(audio_manager), + message_loop_(audio_manager->GetMessageLoop()), + params_(params), + paused_proxies_(0), + ALLOW_THIS_IN_INITIALIZER_LIST(close_timer_( + base::TimeDelta::FromMilliseconds(close_delay_ms), + this, &AudioOutputDispatcher::ClosePendingStreams)) { +} + +AudioOutputDispatcher::~AudioOutputDispatcher() { +} + +bool AudioOutputDispatcher::StreamOpened() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + paused_proxies_++; + + // Ensure that there is at least one open stream. + if (streams_.empty() && !CreateAndOpenStream()) { + return false; + } + + close_timer_.Reset(); + + return true; +} + +AudioOutputStream* AudioOutputDispatcher::StreamStarted() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + + if (streams_.empty() && !CreateAndOpenStream()) { + return NULL; + } + + AudioOutputStream* stream = streams_.back(); + streams_.pop_back(); + + DCHECK_GT(paused_proxies_, 0u); + paused_proxies_--; + + close_timer_.Reset(); + + // Schedule task to allocate streams for other proxies if we need to. + message_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &AudioOutputDispatcher::OpenTask)); + + return stream; +} + +void AudioOutputDispatcher::StreamStopped(AudioOutputStream* stream) { + DCHECK_EQ(MessageLoop::current(), message_loop_); + paused_proxies_++; + streams_.push_back(stream); + close_timer_.Reset(); +} + +void AudioOutputDispatcher::StreamClosed() { + DCHECK_EQ(MessageLoop::current(), message_loop_); + + DCHECK_GT(paused_proxies_, 0u); + paused_proxies_--; + + while (streams_.size() > paused_proxies_) { + streams_.back()->Close(); + streams_.pop_back(); + } +} + +MessageLoop* AudioOutputDispatcher::message_loop() { + return message_loop_; +} + +bool AudioOutputDispatcher::CreateAndOpenStream() { + AudioOutputStream* stream = + audio_manager_->MakeAudioOutputStream(params_); + if (!stream) { + return false; + } + if (!stream->Open()) { + stream->Close(); + return false; + } + streams_.push_back(stream); + return true; +} + +void AudioOutputDispatcher::OpenTask() { + // Make sure that we have at least one stream allocated if there + // are paused streams. + if (paused_proxies_ > 0 && streams_.empty()) { + CreateAndOpenStream(); + } + + close_timer_.Reset(); +} + +// This method is called by |close_timer_|. +void AudioOutputDispatcher::ClosePendingStreams() { + while (!streams_.empty()) { + streams_.back()->Close(); + streams_.pop_back(); + } +} diff --git a/media/audio/audio_output_dispatcher.h b/media/audio/audio_output_dispatcher.h new file mode 100644 index 0000000..0fa7010 --- /dev/null +++ b/media/audio/audio_output_dispatcher.h @@ -0,0 +1,93 @@ +// Copyright (c) 2010 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. + +// AudioOutputDispatcher dispatches creation and deletion of audio +// output streams. AudioOutputProxy objects use this class to allocate +// and recycle actual audio output streams. When playback is started, +// the proxy calls StreamStarted() to get an output stream that it +// uses to play the sound. When playback is stopped, the proxy returns +// the stream back to the dispatcher by calling StreamStopped(). +// +// To avoid opening and closing audio devices more frequently than it +// is neccessary, each dispatcher has a pool of inactive physical +// streams. A stream is closed only if it hasn't been used for a +// certain period of time (specified in the constructor). +// +// AudioManagerBase creates one AudioOutputDispatcher per each +// possible set of audio parameters, i.e. streams with different +// parameters are managed independently. + +#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_ +#define MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/timer.h" +#include "media/audio/audio_manager.h" +#include "media/audio/audio_parameters.h" + +class AudioOutputStream; +class MessageLoop; + +class AudioOutputDispatcher + : public base::RefCountedThreadSafe<AudioOutputDispatcher> { + public: + // |close_delay_ms| specifies delay after the stream is paused until + // the audio device is closed. + AudioOutputDispatcher(AudioManager* audio_manager, + const AudioParameters& params, + int close_delay_ms); + ~AudioOutputDispatcher(); + + // Called by AudioOutputProxy when the stream is closed. Opens a new + // physical stream if there are no pending streams in |streams_|. + // Returns false, if it fails to open it. + bool StreamOpened(); + + // Called by AudioOutputProxy when the stream is started. If there + // are pending streams in |streams_| then it returns one of them, + // otherwise creates a new one. Returns a physical stream that must + // be used, or NULL if it fails to open audio device. Ownership of + // the result is passed to the caller. + AudioOutputStream* StreamStarted(); + + // Called by AudioOutputProxy when the stream is stopped. Returns + // |stream| to the pool of pending streams (i.e. |streams_|). + // Ownership of the |stream| is passed to the dispatcher. + void StreamStopped(AudioOutputStream* stream); + + // Called by AudioOutputProxy when the stream is closed. + void StreamClosed(); + + MessageLoop* message_loop(); + + private: + friend class AudioOutputProxyTest; + + // Creates a new physical output stream, opens it and pushes to + // |streams_|. Returns false if the stream couldn't be created or + // opened. + bool CreateAndOpenStream(); + + // A task scheduled by StreamStarted(). Opens a new stream and puts + // it in |streams_|. + void OpenTask(); + + // Called by |close_timer_|. Closes all pending stream. + void ClosePendingStreams(); + + AudioManager* audio_manager_; + MessageLoop* message_loop_; + AudioParameters params_; + + size_t paused_proxies_; + std::vector<AudioOutputStream*> streams_; + base::DelayTimer<AudioOutputDispatcher> close_timer_; + + DISALLOW_COPY_AND_ASSIGN(AudioOutputDispatcher); +}; + +#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_ diff --git a/media/audio/audio_output_proxy.cc b/media/audio/audio_output_proxy.cc new file mode 100644 index 0000000..c162867 --- /dev/null +++ b/media/audio/audio_output_proxy.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2010 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_output_proxy.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "media/audio/audio_manager.h" +#include "media/audio/audio_output_dispatcher.h" + +AudioOutputProxy::AudioOutputProxy(AudioOutputDispatcher* dispatcher) + : dispatcher_(dispatcher), + state_(kCreated), + physical_stream_(NULL), + volume_(1.0) { + DCHECK_EQ(MessageLoop::current(), dispatcher_->message_loop()); +} + +AudioOutputProxy::~AudioOutputProxy() { + DCHECK_EQ(MessageLoop::current(), dispatcher_->message_loop()); + DCHECK(state_ == kCreated || state_ == kClosed); + DCHECK(!physical_stream_); +} + +bool AudioOutputProxy::Open() { + DCHECK_EQ(MessageLoop::current(), dispatcher_->message_loop()); + DCHECK_EQ(state_, kCreated); + + if (!dispatcher_->StreamOpened()) { + state_ = kError; + return false; + } + + state_ = kOpened; + return true; +} + +void AudioOutputProxy::Start(AudioSourceCallback* callback) { + DCHECK_EQ(MessageLoop::current(), dispatcher_->message_loop()); + DCHECK(physical_stream_ == NULL); + DCHECK_EQ(state_, kOpened); + + physical_stream_= dispatcher_->StreamStarted(); + if (!physical_stream_) { + state_ = kError; + callback->OnError(this, 0); + return; + } + + physical_stream_->SetVolume(volume_); + physical_stream_->Start(callback); + state_ = kPlaying; +} + +void AudioOutputProxy::Stop() { + DCHECK_EQ(MessageLoop::current(), dispatcher_->message_loop()); + if (state_ != kPlaying) + return; + + DCHECK(physical_stream_); + physical_stream_->Stop(); + dispatcher_->StreamStopped(physical_stream_); + physical_stream_ = NULL; + state_ = kOpened; +} + +void AudioOutputProxy::SetVolume(double volume) { + DCHECK_EQ(MessageLoop::current(), dispatcher_->message_loop()); + volume_ = volume; + if (physical_stream_) { + physical_stream_->SetVolume(volume); + } +} + +void AudioOutputProxy::GetVolume(double* volume) { + DCHECK_EQ(MessageLoop::current(), dispatcher_->message_loop()); + *volume = volume_; +} + +void AudioOutputProxy::Close() { + DCHECK_EQ(MessageLoop::current(), dispatcher_->message_loop()); + DCHECK(state_ == kCreated || state_ == kError || state_ == kOpened); + DCHECK(!physical_stream_); + + if (state_ != kCreated) { + dispatcher_->StreamClosed(); + } + dispatcher_->message_loop()->DeleteSoon(FROM_HERE, this); + state_ = kClosed; +} diff --git a/media/audio/audio_output_proxy.h b/media/audio/audio_output_proxy.h new file mode 100644 index 0000000..4835cc8 --- /dev/null +++ b/media/audio/audio_output_proxy.h @@ -0,0 +1,64 @@ +// Copyright (c) 2010 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. + +#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_STREAM_PROXY_H_ +#define MEDIA_AUDIO_AUDIO_OUTPUT_STREAM_PROXY_H_ + +#include "base/basictypes.h" +#include "base/task.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_parameters.h" + +class AudioOutputDispatcher; + +// AudioOutputProxy is an audio otput stream that uses resources more +// efficiently than a regular audio output stream: it opens audio +// device only when sound is playing, i.e. between Start() and Stop() +// (there is still one physical stream per each audio output proxy in +// playing state). +// +// AudioOutputProxy uses AudioOutputDispatcher to open and close +// physical output streams. +class AudioOutputProxy : public AudioOutputStream { + public: + // Caller keeps ownership of |dispatcher|. + AudioOutputProxy(AudioOutputDispatcher* dispatcher); + + // AudioOutputStream interface. + virtual bool Open(); + virtual void Start(AudioSourceCallback* callback); + virtual void Stop(); + virtual void SetVolume(double volume); + virtual void GetVolume(double* volume); + virtual void Close(); + + private: + // Needs to access destructor. + friend class DeleteTask<AudioOutputProxy>; + + enum State { + kCreated, + kOpened, + kPlaying, + kClosed, + kError, + }; + + virtual ~AudioOutputProxy(); + + scoped_refptr<AudioOutputDispatcher> dispatcher_; + State state_; + + // The actual audio stream. Must be set to NULL in any state other + // than kPlaying. + AudioOutputStream* physical_stream_; + + // Need to save volume here, so that we can restore it in case the stream + // is stopped, and then started again. + double volume_; + + DISALLOW_COPY_AND_ASSIGN(AudioOutputProxy); +}; + +#endif // MEDIA_AUDIO_AUDIO_OUTPUT_STREAM_PROXY_H_ diff --git a/media/audio/audio_output_proxy_unittest.cc b/media/audio/audio_output_proxy_unittest.cc new file mode 100644 index 0000000..efc164c --- /dev/null +++ b/media/audio/audio_output_proxy_unittest.cc @@ -0,0 +1,353 @@ +// Copyright (c) 2010 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 "base/message_loop.h" +#include "base/platform_thread.h" +#include "media/audio/audio_output_dispatcher.h" +#include "media/audio/audio_output_proxy.h" +#include "media/audio/audio_manager.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Mock; +using ::testing::Return; + +namespace { +const int kTestCloseDelayMs = 100; + +// Used in the test where we don't want a stream to be closed unexpectedly. +const int kTestBigCloseDelayMs = 1000 * 1000; +} // namespace + +class MockAudioOutputStream : public AudioOutputStream { + public: + MockAudioOutputStream() {} + + MOCK_METHOD0(Open, bool()); + MOCK_METHOD1(Start, void(AudioSourceCallback* callback)); + MOCK_METHOD0(Stop, void()); + MOCK_METHOD1(SetVolume, void(double volume)); + MOCK_METHOD1(GetVolume, void(double* volume)); + MOCK_METHOD0(Close, void()); +}; + +class MockAudioManager : public AudioManager { + public: + MockAudioManager() { }; + + MOCK_METHOD0(Init, void()); + MOCK_METHOD0(HasAudioOutputDevices, bool()); + MOCK_METHOD0(HasAudioInputDevices, bool()); + MOCK_METHOD0(GetAudioInputDeviceModel, string16()); + MOCK_METHOD1(MakeAudioOutputStream, AudioOutputStream*( + AudioParameters params)); + MOCK_METHOD1(MakeAudioOutputStreamProxy, AudioOutputStream*( + const AudioParameters& params)); + MOCK_METHOD1(MakeAudioInputStream, AudioInputStream*( + AudioParameters params)); + MOCK_METHOD0(MuteAll, void()); + MOCK_METHOD0(UnMuteAll, void()); + MOCK_METHOD0(GetMessageLoop, MessageLoop*()); +}; + +class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback { + public: + MOCK_METHOD4(OnMoreData, uint32(AudioOutputStream* stream, + uint8* dest, uint32 max_size, + AudioBuffersState buffers_state)); + MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code)); +}; + +class AudioOutputProxyTest : public testing::Test { + protected: + virtual void SetUp() { + EXPECT_CALL(manager_, GetMessageLoop()) + .WillRepeatedly(Return(&message_loop_)); + InitDispatcher(kTestCloseDelayMs); + } + + virtual void TearDown() { + // All paused proxies should have been closed at this point. + EXPECT_EQ(0u, dispatcher_->paused_proxies_); + + // This is necessary to free all proxy objects that have been + // closed by the test. + message_loop_.RunAllPending(); + } + + void InitDispatcher(int close_delay_ms) { + AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, 2, 44100, + 16, 1024); + dispatcher_ = new AudioOutputDispatcher(&manager_, params, close_delay_ms); + } + + MessageLoop message_loop_; + scoped_refptr<AudioOutputDispatcher> dispatcher_; + MockAudioManager manager_; + MockAudioSourceCallback callback_; +}; + +TEST_F(AudioOutputProxyTest, CreateAndClose) { + AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_); + proxy->Close(); +} + +TEST_F(AudioOutputProxyTest, OpenAndClose) { + MockAudioOutputStream stream; + + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(&stream)); + EXPECT_CALL(stream, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream, Close()) + .Times(1); + + AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_); + EXPECT_TRUE(proxy->Open()); + proxy->Close(); +} + +// Create a stream, and verify that it is closed after kTestCloseDelayMs. +// if it doesn't start playing. +TEST_F(AudioOutputProxyTest, CreateAndWait) { + MockAudioOutputStream stream; + + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(&stream)); + EXPECT_CALL(stream, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream, Close()) + .Times(1); + + AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_); + EXPECT_TRUE(proxy->Open()); + + // Simulate a delay. + PlatformThread::Sleep(kTestCloseDelayMs * 2); + message_loop_.RunAllPending(); + + // Verify expectation before calling Close(). + Mock::VerifyAndClear(&stream); + + proxy->Close(); +} + +// Create a stream, and then calls Start() and Stop(). +TEST_F(AudioOutputProxyTest, StartAndStop) { + MockAudioOutputStream stream; + + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(&stream)); + EXPECT_CALL(stream, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream, Start(_)) + .Times(1); + EXPECT_CALL(stream, SetVolume(_)) + .Times(1); + EXPECT_CALL(stream, Stop()) + .Times(1); + EXPECT_CALL(stream, Close()) + .Times(1); + + AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_); + EXPECT_TRUE(proxy->Open()); + + proxy->Start(&callback_); + proxy->Stop(); + + proxy->Close(); +} + +// Verify that the stream is closed after Stop is called. +TEST_F(AudioOutputProxyTest, CloseAfterStop) { + MockAudioOutputStream stream; + + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(&stream)); + EXPECT_CALL(stream, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream, Start(_)) + .Times(1); + EXPECT_CALL(stream, SetVolume(_)) + .Times(1); + EXPECT_CALL(stream, Stop()) + .Times(1); + EXPECT_CALL(stream, Close()) + .Times(1); + + AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_); + EXPECT_TRUE(proxy->Open()); + + proxy->Start(&callback_); + proxy->Stop(); + + // Simulate a delay. + message_loop_.RunAllPending(); + PlatformThread::Sleep(kTestCloseDelayMs * 10); + message_loop_.RunAllPending(); + + // Verify expectation before calling Close(). + Mock::VerifyAndClear(&stream); + + proxy->Close(); +} + +// Create two streams, but don't start them. Only one device must be open. +TEST_F(AudioOutputProxyTest, TwoStreams) { + MockAudioOutputStream stream; + + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(&stream)); + EXPECT_CALL(stream, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream, Close()) + .Times(1); + + AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher_); + AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher_); + EXPECT_TRUE(proxy1->Open()); + EXPECT_TRUE(proxy2->Open()); + proxy1->Close(); + proxy2->Close(); +} + +// Two streams: verify that second stream is allocated when the first +// starts playing. +TEST_F(AudioOutputProxyTest, TwoStreams_OnePlaying) { + MockAudioOutputStream stream1; + MockAudioOutputStream stream2; + + InitDispatcher(kTestBigCloseDelayMs); + + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(&stream1)) + .WillOnce(Return(&stream2)); + + EXPECT_CALL(stream1, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream1, Start(_)) + .Times(1); + EXPECT_CALL(stream1, SetVolume(_)) + .Times(1); + EXPECT_CALL(stream1, Stop()) + .Times(1); + EXPECT_CALL(stream1, Close()) + .Times(1); + + EXPECT_CALL(stream2, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream2, Close()) + .Times(1); + + AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher_); + AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher_); + EXPECT_TRUE(proxy1->Open()); + EXPECT_TRUE(proxy2->Open()); + + proxy1->Start(&callback_); + message_loop_.RunAllPending(); + proxy1->Stop(); + + proxy1->Close(); + proxy2->Close(); +} + +// Two streams, both are playing. Dispatcher should not open a third stream. +TEST_F(AudioOutputProxyTest, TwoStreams_BothPlaying) { + MockAudioOutputStream stream1; + MockAudioOutputStream stream2; + + InitDispatcher(kTestBigCloseDelayMs); + + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(&stream1)) + .WillOnce(Return(&stream2)); + + EXPECT_CALL(stream1, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream1, Start(_)) + .Times(1); + EXPECT_CALL(stream1, SetVolume(_)) + .Times(1); + EXPECT_CALL(stream1, Stop()) + .Times(1); + EXPECT_CALL(stream1, Close()) + .Times(1); + + EXPECT_CALL(stream2, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream2, Start(_)) + .Times(1); + EXPECT_CALL(stream2, SetVolume(_)) + .Times(1); + EXPECT_CALL(stream2, Stop()) + .Times(1); + EXPECT_CALL(stream2, Close()) + .Times(1); + + AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher_); + AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher_); + EXPECT_TRUE(proxy1->Open()); + EXPECT_TRUE(proxy2->Open()); + + proxy1->Start(&callback_); + proxy2->Start(&callback_); + proxy1->Stop(); + proxy2->Stop(); + + proxy1->Close(); + proxy2->Close(); +} + +// Open() method failed. +TEST_F(AudioOutputProxyTest, OpenFailed) { + MockAudioOutputStream stream; + + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(&stream)); + EXPECT_CALL(stream, Open()) + .WillOnce(Return(false)); + EXPECT_CALL(stream, Close()) + .Times(1); + + AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_); + EXPECT_FALSE(proxy->Open()); + proxy->Close(); +} + +// Start() method failed. +TEST_F(AudioOutputProxyTest, StartFailed) { + MockAudioOutputStream stream; + + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(&stream)); + EXPECT_CALL(stream, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(stream, Close()) + .Times(1); + + AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_); + EXPECT_TRUE(proxy->Open()); + + // Simulate a delay. + PlatformThread::Sleep(kTestCloseDelayMs); + message_loop_.RunAllPending(); + + // Verify expectation before calling Close(). + Mock::VerifyAndClear(&stream); + + // |stream| is closed at this point. Start() should reopen it again. + EXPECT_CALL(manager_, MakeAudioOutputStream(_)) + .WillOnce(Return(reinterpret_cast<AudioOutputStream*>(NULL))); + + EXPECT_CALL(callback_, OnError(_, _)) + .Times(1); + + proxy->Start(&callback_); + + Mock::VerifyAndClear(&callback_); + + proxy->Close(); +} diff --git a/media/audio/audio_parameters.cc b/media/audio/audio_parameters.cc index b0a691b..d1d44a7 100644 --- a/media/audio/audio_parameters.cc +++ b/media/audio/audio_parameters.cc @@ -37,3 +37,25 @@ bool AudioParameters::IsValid() const { int AudioParameters::GetPacketSize() const { return samples_per_packet * channels * bits_per_sample / 8; } + +bool AudioParameters::Compare::operator()( + const AudioParameters& a, + const AudioParameters& b) const { + if (a.format < b.format) + return true; + if (a.format > b.format) + return false; + if (a.channels < b.channels) + return true; + if (a.channels > b.channels) + return false; + if (a.sample_rate < b.sample_rate) + return true; + if (a.sample_rate > b.sample_rate) + return false; + if (a.bits_per_sample < b.bits_per_sample) + return true; + if (a.bits_per_sample > b.bits_per_sample) + return false; + return a.samples_per_packet < b.samples_per_packet; +} diff --git a/media/audio/audio_parameters.h b/media/audio/audio_parameters.h index 9172d74..2842bc1 100644 --- a/media/audio/audio_parameters.h +++ b/media/audio/audio_parameters.h @@ -8,6 +8,12 @@ #include "base/basictypes.h" struct AudioParameters { + // Compare is useful when AudioParameters is used as a key in std::map. + class Compare { + public: + bool operator()(const AudioParameters& a, const AudioParameters& b) const; + }; + enum Format { AUDIO_PCM_LINEAR = 0, // PCM is 'raw' amplitude samples. AUDIO_PCM_LOW_LATENCY, // Linear PCM, low latency requested. diff --git a/media/audio/audio_parameters_unittest.cc b/media/audio/audio_parameters_unittest.cc new file mode 100644 index 0000000..61b8ad0 --- /dev/null +++ b/media/audio/audio_parameters_unittest.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2010 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 "base/basictypes.h" +#include "base/string_number_conversions.h" +#include "media/audio/audio_parameters.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(AudioParameters, GetPacketSize) { + EXPECT_EQ(100, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, + 1, 1000, 8, 100).GetPacketSize()); + EXPECT_EQ(200, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, + 1, 1000, 16, 100).GetPacketSize()); + EXPECT_EQ(200, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, + 2, 1000, 8, 100).GetPacketSize()); + EXPECT_EQ(200, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, + 1, 1000, 8, 200).GetPacketSize()); + EXPECT_EQ(800, AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, + 2, 1000, 16, 200).GetPacketSize()); +} + +TEST(AudioParameters, Compare) { + AudioParameters values[] = { + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 1, 1000, 8, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 1, 1000, 8, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 1, 1000, 16, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 1, 1000, 16, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 1, 2000, 8, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 1, 2000, 8, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 1, 2000, 16, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 1, 2000, 16, 200), + + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 2, 1000, 8, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 2, 1000, 8, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 2, 1000, 16, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 2, 1000, 16, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 2, 2000, 8, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 2, 2000, 8, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 2, 2000, 16, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, 2, 2000, 16, 200), + + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 1, 1000, 8, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 1, 1000, 8, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 1, 1000, 16, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 1, 1000, 16, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 1, 2000, 8, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 1, 2000, 8, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 1, 2000, 16, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 1, 2000, 16, 200), + + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 2, 1000, 8, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 2, 1000, 8, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 2, 1000, 16, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 2, 1000, 16, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 2, 2000, 8, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 2, 2000, 8, 200), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 2, 2000, 16, 100), + AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 2, 2000, 16, 200), + }; + + AudioParameters::Compare target; + for (size_t i = 0; i < arraysize(values); ++i) { + for (size_t j = 0; j < arraysize(values); ++j) { + SCOPED_TRACE("i=" + base::IntToString(i) + " j=" + base::IntToString(j)); + EXPECT_EQ(i < j, target(values[i], values[j])); + } + + // Verify that a value is never less than itself. + EXPECT_FALSE(target(values[i], values[i])); + } +} diff --git a/media/audio/linux/audio_manager_linux.cc b/media/audio/linux/audio_manager_linux.cc index fdf8044..73f4ace 100644 --- a/media/audio/linux/audio_manager_linux.cc +++ b/media/audio/linux/audio_manager_linux.cc @@ -6,6 +6,7 @@ #include "base/command_line.h" #include "base/logging.h" +#include "media/audio/audio_output_dispatcher.h" #include "media/audio/fake_audio_input_stream.h" #include "media/audio/fake_audio_output_stream.h" #include "media/audio/linux/alsa_input.h" @@ -94,6 +95,10 @@ AudioManagerLinux::~AudioManagerLinux() { // This way we make sure activities of the audio streams are all stopped // before we destroy them. audio_thread_.Stop(); + + // Free output dispatchers, closing all remaining open streams. + output_dispatchers_.clear(); + active_streams_.clear(); } diff --git a/media/media.gyp b/media/media.gyp index e129623..110bf92 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -31,6 +31,10 @@ 'audio/audio_manager_base.h', 'audio/audio_output_controller.cc', 'audio/audio_output_controller.h', + 'audio/audio_output_dispatcher.cc', + 'audio/audio_output_dispatcher.h', + 'audio/audio_output_proxy.cc', + 'audio/audio_output_proxy.h', 'audio/audio_parameters.cc', 'audio/audio_parameters.h', 'audio/audio_util.cc', @@ -255,6 +259,8 @@ 'audio/audio_input_controller_unittest.cc', 'audio/audio_input_unittest.cc', 'audio/audio_output_controller_unittest.cc', + 'audio/audio_output_proxy_unittest.cc', + 'audio/audio_parameters_unittest.cc', 'audio/audio_util_unittest.cc', 'audio/fake_audio_input_stream_unittest.cc', 'audio/linux/alsa_output_unittest.cc', |