summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/audio/audio_manager.h7
-rw-r--r--media/audio/audio_manager_base.cc22
-rw-r--r--media/audio/audio_manager_base.h15
-rw-r--r--media/audio/audio_output_controller.cc2
-rw-r--r--media/audio/audio_output_dispatcher.cc115
-rw-r--r--media/audio/audio_output_dispatcher.h93
-rw-r--r--media/audio/audio_output_proxy.cc92
-rw-r--r--media/audio/audio_output_proxy.h64
-rw-r--r--media/audio/audio_output_proxy_unittest.cc353
-rw-r--r--media/audio/audio_parameters.cc22
-rw-r--r--media/audio/audio_parameters.h6
-rw-r--r--media/audio/audio_parameters_unittest.cc72
-rw-r--r--media/audio/linux/audio_manager_linux.cc5
-rw-r--r--media/media.gyp6
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',