summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorenal@chromium.org <enal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-19 17:44:10 +0000
committerenal@chromium.org <enal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-19 17:44:10 +0000
commit08c1c1399c69de70485abcbc80ba9884f8d41224 (patch)
treec27b9f6d0608d3750fbfbab7d2b1c7cdf62e7f28
parent3ae7a56eddef4427b218c2151bb331a02a5cc394 (diff)
downloadchromium_src-08c1c1399c69de70485abcbc80ba9884f8d41224.zip
chromium_src-08c1c1399c69de70485abcbc80ba9884f8d41224.tar.gz
chromium_src-08c1c1399c69de70485abcbc80ba9884f8d41224.tar.bz2
Re-land software audio mixer.
Code goes through old or new paths depending on the AudioParameters or command-line flag. Added unit tests. Changed suppression for http://code.google.com/p/chromium/issues/detail?id=123112 because call stack changed (class was split into 2). Review URL: http://codereview.chromium.org/9691001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@133010 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--media/audio/audio_manager_base.cc18
-rw-r--r--media/audio/audio_manager_base.h1
-rw-r--r--media/audio/audio_output_dispatcher.cc154
-rw-r--r--media/audio/audio_output_dispatcher.h105
-rw-r--r--media/audio/audio_output_dispatcher_impl.cc200
-rw-r--r--media/audio/audio_output_dispatcher_impl.h102
-rw-r--r--media/audio/audio_output_mixer.cc251
-rw-r--r--media/audio/audio_output_mixer.h92
-rw-r--r--media/audio/audio_output_proxy.cc23
-rw-r--r--media/audio/audio_output_proxy.h4
-rw-r--r--media/audio/audio_output_proxy_unittest.cc372
-rw-r--r--media/base/media_switches.cc3
-rw-r--r--media/base/media_switches.h2
-rw-r--r--media/media.gyp4
-rw-r--r--tools/valgrind/tsan/suppressions_mac.txt2
15 files changed, 987 insertions, 346 deletions
diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc
index 45bf0fc..a0c1c4c 100644
--- a/media/audio/audio_manager_base.cc
+++ b/media/audio/audio_manager_base.cc
@@ -5,12 +5,15 @@
#include "media/audio/audio_manager_base.h"
#include "base/bind.h"
+#include "base/command_line.h"
#include "base/message_loop_proxy.h"
#include "base/threading/thread.h"
-#include "media/audio/audio_output_dispatcher.h"
+#include "media/audio/audio_output_dispatcher_impl.h"
+#include "media/audio/audio_output_mixer.h"
#include "media/audio/audio_output_proxy.h"
#include "media/audio/fake_audio_input_stream.h"
#include "media/audio/fake_audio_output_stream.h"
+#include "media/base/media_switches.h"
namespace media {
@@ -136,9 +139,16 @@ AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
scoped_refptr<AudioOutputDispatcher>& dispatcher =
output_dispatchers_[params];
- if (!dispatcher)
- dispatcher = new AudioOutputDispatcher(
- this, params, base::TimeDelta::FromSeconds(kStreamCloseDelaySeconds));
+ if (!dispatcher) {
+ base::TimeDelta close_delay =
+ base::TimeDelta::FromSeconds(kStreamCloseDelaySeconds);
+ const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+ if (cmd_line->HasSwitch(switches::kEnableAudioMixer)) {
+ dispatcher = new AudioOutputMixer(this, params, close_delay);
+ } else {
+ dispatcher = new AudioOutputDispatcherImpl(this, params, close_delay);
+ }
+ }
return new AudioOutputProxy(dispatcher);
}
diff --git a/media/audio/audio_manager_base.h b/media/audio/audio_manager_base.h
index acb85b6..498b44c 100644
--- a/media/audio/audio_manager_base.h
+++ b/media/audio/audio_manager_base.h
@@ -6,6 +6,7 @@
#define MEDIA_AUDIO_AUDIO_MANAGER_BASE_H_
#include <map>
+#include <string>
#include "base/atomic_ref_count.h"
#include "base/compiler_specific.h"
diff --git a/media/audio/audio_output_dispatcher.cc b/media/audio/audio_output_dispatcher.cc
index 998fc1b..bfd3fb8 100644
--- a/media/audio/audio_output_dispatcher.cc
+++ b/media/audio/audio_output_dispatcher.cc
@@ -4,29 +4,16 @@
#include "media/audio/audio_output_dispatcher.h"
-#include "base/bind.h"
-#include "base/compiler_specific.h"
#include "base/message_loop.h"
-#include "base/time.h"
-#include "media/audio/audio_io.h"
namespace media {
AudioOutputDispatcher::AudioOutputDispatcher(
- AudioManager* audio_manager, const AudioParameters& params,
- base::TimeDelta close_delay)
+ AudioManager* audio_manager,
+ const AudioParameters& params)
: audio_manager_(audio_manager),
message_loop_(MessageLoop::current()),
- params_(params),
- pause_delay_(base::TimeDelta::FromMilliseconds(
- 2 * params.frames_per_buffer() *
- base::Time::kMillisecondsPerSecond / params.sample_rate())),
- paused_proxies_(0),
- ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)),
- close_timer_(FROM_HERE,
- close_delay,
- weak_this_.GetWeakPtr(),
- &AudioOutputDispatcher::ClosePendingStreams) {
+ params_(params) {
// We expect to be instantiated on the audio thread. Otherwise the
// message_loop_ member will point to the wrong message loop!
DCHECK(audio_manager->GetMessageLoop()->BelongsToCurrentThread());
@@ -36,139 +23,4 @@ AudioOutputDispatcher::~AudioOutputDispatcher() {
DCHECK_EQ(MessageLoop::current(), message_loop_);
}
-bool AudioOutputDispatcher::StreamOpened() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
- paused_proxies_++;
-
- // Ensure that there is at least one open stream.
- if (idle_streams_.empty() && !CreateAndOpenStream()) {
- return false;
- }
-
- close_timer_.Reset();
-
- return true;
-}
-
-AudioOutputStream* AudioOutputDispatcher::StreamStarted() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- if (idle_streams_.empty() && !CreateAndOpenStream()) {
- return NULL;
- }
-
- AudioOutputStream* stream = idle_streams_.back();
- idle_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, base::Bind(
- &AudioOutputDispatcher::OpenTask, weak_this_.GetWeakPtr()));
-
- return stream;
-}
-
-void AudioOutputDispatcher::StreamStopped(AudioOutputStream* stream) {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- paused_proxies_++;
-
- pausing_streams_.push_front(stream);
-
- // Don't recycle stream until two buffers worth of time has elapsed.
- message_loop_->PostDelayedTask(
- FROM_HERE,
- base::Bind(&AudioOutputDispatcher::StopStreamTask,
- weak_this_.GetWeakPtr()),
- pause_delay_);
-}
-
-void AudioOutputDispatcher::StopStreamTask() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- if (pausing_streams_.empty())
- return;
-
- AudioOutputStream* stream = pausing_streams_.back();
- pausing_streams_.pop_back();
- idle_streams_.push_back(stream);
- close_timer_.Reset();
-}
-
-void AudioOutputDispatcher::StreamClosed() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- while (!pausing_streams_.empty()) {
- idle_streams_.push_back(pausing_streams_.back());
- pausing_streams_.pop_back();
- }
-
- DCHECK_GT(paused_proxies_, 0u);
- paused_proxies_--;
-
- while (idle_streams_.size() > paused_proxies_) {
- idle_streams_.back()->Close();
- idle_streams_.pop_back();
- }
-}
-
-void AudioOutputDispatcher::Shutdown() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- // Cancel any pending tasks to close paused streams or create new ones.
- weak_this_.InvalidateWeakPtrs();
-
- // No AudioOutputProxy objects should hold a reference to us when we get
- // to this stage.
- DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference";
-
- AudioOutputStreamList::iterator it = idle_streams_.begin();
- for (; it != idle_streams_.end(); ++it)
- (*it)->Close();
- idle_streams_.clear();
-
- it = pausing_streams_.begin();
- for (; it != pausing_streams_.end(); ++it)
- (*it)->Close();
- pausing_streams_.clear();
-}
-
-bool AudioOutputDispatcher::CreateAndOpenStream() {
- AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(params_);
- if (!stream)
- return false;
-
- if (!stream->Open()) {
- stream->Close();
- return false;
- }
- idle_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 && idle_streams_.empty() &&
- pausing_streams_.empty()) {
- CreateAndOpenStream();
- }
-
- close_timer_.Reset();
-}
-
-// This method is called by |close_timer_|.
-void AudioOutputDispatcher::ClosePendingStreams() {
- DCHECK_EQ(MessageLoop::current(), message_loop_);
-
- while (!idle_streams_.empty()) {
- idle_streams_.back()->Close();
- idle_streams_.pop_back();
- }
-}
-
} // namespace media
diff --git a/media/audio/audio_output_dispatcher.h b/media/audio/audio_output_dispatcher.h
index 79474a4..5c96873 100644
--- a/media/audio/audio_output_dispatcher.h
+++ b/media/audio/audio_output_dispatcher.h
@@ -2,33 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// AudioOutputDispatcher is a single-threaded class that 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
-// audio. When playback is stopped, the proxy returns the stream back to the
-// dispatcher by calling StreamStopped().
+// AudioOutputDispatcher is a single-threaded base class that 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 StartStream() to get an output stream that it
+// uses to play audio. When playback is stopped, the proxy returns the stream
+// back to the dispatcher by calling StopStream().
//
-// To avoid opening and closing audio devices more frequently than necessary,
-// 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 via the
-// constructor).
-//
-// AudioManagerBase creates one AudioOutputDispatcher on the audio thread for
-// each possible set of audio parameters. I.e streams with different parameters
-// are managed independently. The AudioOutputDispatcher instance is then
-// deleted on the audio thread when the AudioManager shuts down.
+// AudioManagerBase creates one specialization of AudioOutputDispatcher on the
+// audio thread for each possible set of audio parameters. I.e streams with
+// different parameters are managed independently. The AudioOutputDispatcher
+// instance is then deleted on the audio thread when the AudioManager shuts
+// down.
#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
#define MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
-#include <vector>
-#include <list>
-
#include "base/basictypes.h"
#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
#include "base/timer.h"
+#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_parameters.h"
@@ -36,60 +29,43 @@ class MessageLoop;
namespace media {
-class AudioOutputStream;
+class AudioOutputProxy;
class MEDIA_EXPORT 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,
- base::TimeDelta close_delay);
- ~AudioOutputDispatcher();
+ const AudioParameters& params);
- // Called by AudioOutputProxy when the stream is closed. Opens a new
- // physical stream if there are no pending streams in |idle_streams_|.
+ // Called by AudioOutputProxy to open the stream.
// Returns false, if it fails to open it.
- bool StreamOpened();
-
- // Called by AudioOutputProxy when the stream is started. If there
- // are pending streams in |idle_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();
+ virtual bool OpenStream() = 0;
- // Called by AudioOutputProxy when the stream is stopped. Holds the
- // stream temporarily in |pausing_streams_| and then |stream| is
- // added to the pool of pending streams (i.e. |idle_streams_|).
- // Ownership of the |stream| is passed to the dispatcher.
- void StreamStopped(AudioOutputStream* stream);
+ // Called by AudioOutputProxy when the stream is started.
+ // Uses |callback| to get source data and report errors, if any.
+ // Does *not* take ownership of this callback.
+ // Returns true if started successfully, false otherwise.
+ virtual bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) = 0;
- // Called by AudioOutputProxy when the stream is closed.
- void StreamClosed();
-
- // Called on the audio thread when the AudioManager is shutting down.
- void Shutdown();
+ // Called by AudioOutputProxy when the stream is stopped.
+ // Ownership of the |stream_proxy| is passed to the dispatcher.
+ virtual void StopStream(AudioOutputProxy* stream_proxy) = 0;
- private:
- friend class AudioOutputProxyTest;
- // Creates a new physical output stream, opens it and pushes to
- // |idle_streams_|. Returns false if the stream couldn't be created or
- // opened.
- bool CreateAndOpenStream();
+ // Called by AudioOutputProxy when the volume is set.
+ virtual void StreamVolumeSet(AudioOutputProxy* stream_proxy,
+ double volume) = 0;
- // A task scheduled by StreamStarted(). Opens a new stream and puts
- // it in |idle_streams_|.
- void OpenTask();
+ // Called by AudioOutputProxy when the stream is closed.
+ virtual void CloseStream(AudioOutputProxy* stream_proxy) = 0;
- // Before a stream is reused, it should sit idle for a bit. This task is
- // called once that time has elapsed.
- void StopStreamTask();
+ // Called on the audio thread when the AudioManager is shutting down.
+ virtual void Shutdown() = 0;
- // Called by |close_timer_|. Closes all pending stream.
- void ClosePendingStreams();
+ protected:
+ friend class base::RefCountedThreadSafe<AudioOutputDispatcher>;
+ virtual ~AudioOutputDispatcher();
// A no-reference-held pointer (we don't want circular references) back to the
// AudioManager that owns this object.
@@ -97,16 +73,7 @@ class MEDIA_EXPORT AudioOutputDispatcher
MessageLoop* message_loop_;
AudioParameters params_;
- base::TimeDelta pause_delay_;
- size_t paused_proxies_;
- typedef std::list<AudioOutputStream*> AudioOutputStreamList;
- AudioOutputStreamList idle_streams_;
- AudioOutputStreamList pausing_streams_;
-
- // Used to post delayed tasks to ourselves that we cancel inside Shutdown().
- base::WeakPtrFactory<AudioOutputDispatcher> weak_this_;
- base::DelayTimer<AudioOutputDispatcher> close_timer_;
-
+ private:
DISALLOW_COPY_AND_ASSIGN(AudioOutputDispatcher);
};
diff --git a/media/audio/audio_output_dispatcher_impl.cc b/media/audio/audio_output_dispatcher_impl.cc
new file mode 100644
index 0000000..8a060b7
--- /dev/null
+++ b/media/audio/audio_output_dispatcher_impl.cc
@@ -0,0 +1,200 @@
+// 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 "media/audio/audio_output_dispatcher_impl.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/time.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_output_proxy.h"
+#include "media/audio/audio_util.h"
+
+namespace media {
+
+AudioOutputDispatcherImpl::AudioOutputDispatcherImpl(
+ AudioManager* audio_manager,
+ const AudioParameters& params,
+ const base::TimeDelta& close_delay)
+ : AudioOutputDispatcher(audio_manager, params),
+ pause_delay_(base::TimeDelta::FromMilliseconds(
+ 2 * params.frames_per_buffer() *
+ base::Time::kMillisecondsPerSecond / params.sample_rate())),
+ paused_proxies_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)),
+ close_timer_(FROM_HERE,
+ close_delay,
+ weak_this_.GetWeakPtr(),
+ &AudioOutputDispatcherImpl::ClosePendingStreams) {
+}
+
+AudioOutputDispatcherImpl::~AudioOutputDispatcherImpl() {
+ DCHECK(proxy_to_physical_map_.empty());
+ DCHECK(idle_streams_.empty());
+ DCHECK(pausing_streams_.empty());
+}
+
+bool AudioOutputDispatcherImpl::OpenStream() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ paused_proxies_++;
+
+ // Ensure that there is at least one open stream.
+ if (idle_streams_.empty() && !CreateAndOpenStream())
+ return false;
+
+ close_timer_.Reset();
+ return true;
+}
+
+bool AudioOutputDispatcherImpl::StartStream(
+ AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ if (idle_streams_.empty() && !CreateAndOpenStream())
+ return false;
+
+ AudioOutputStream* physical_stream = idle_streams_.back();
+ DCHECK(physical_stream);
+ idle_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, base::Bind(
+ &AudioOutputDispatcherImpl::OpenTask, weak_this_.GetWeakPtr()));
+
+ double volume = 0;
+ stream_proxy->GetVolume(&volume);
+ physical_stream->SetVolume(volume);
+ physical_stream->Start(callback);
+ proxy_to_physical_map_[stream_proxy] = physical_stream;
+ return true;
+}
+
+void AudioOutputDispatcherImpl::StopStream(AudioOutputProxy* stream_proxy) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
+ DCHECK(it != proxy_to_physical_map_.end());
+ AudioOutputStream* physical_stream = it->second;
+ proxy_to_physical_map_.erase(it);
+
+ physical_stream->Stop();
+
+ ++paused_proxies_;
+
+ pausing_streams_.push_front(physical_stream);
+
+ // Don't recycle stream until two buffers worth of time has elapsed.
+ message_loop_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&AudioOutputDispatcherImpl::StopStreamTask,
+ weak_this_.GetWeakPtr()),
+ pause_delay_);
+}
+
+void AudioOutputDispatcherImpl::StreamVolumeSet(AudioOutputProxy* stream_proxy,
+ double volume) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+ AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
+ if (it != proxy_to_physical_map_.end()) {
+ AudioOutputStream* physical_stream = it->second;
+ physical_stream->SetVolume(volume);
+ }
+}
+
+void AudioOutputDispatcherImpl::StopStreamTask() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ if (pausing_streams_.empty())
+ return;
+
+ AudioOutputStream* stream = pausing_streams_.back();
+ pausing_streams_.pop_back();
+ idle_streams_.push_back(stream);
+ close_timer_.Reset();
+}
+
+void AudioOutputDispatcherImpl::CloseStream(AudioOutputProxy* stream_proxy) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ while (!pausing_streams_.empty()) {
+ idle_streams_.push_back(pausing_streams_.back());
+ pausing_streams_.pop_back();
+ }
+
+ DCHECK_GT(paused_proxies_, 0u);
+ paused_proxies_--;
+
+ while (idle_streams_.size() > paused_proxies_) {
+ idle_streams_.back()->Close();
+ idle_streams_.pop_back();
+ }
+}
+
+void AudioOutputDispatcherImpl::Shutdown() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ // Cancel any pending tasks to close paused streams or create new ones.
+ weak_this_.InvalidateWeakPtrs();
+
+ // No AudioOutputProxy objects should hold a reference to us when we get
+ // to this stage.
+ DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference";
+
+ AudioOutputStreamList::iterator it = idle_streams_.begin();
+ for (; it != idle_streams_.end(); ++it)
+ (*it)->Close();
+ idle_streams_.clear();
+
+ it = pausing_streams_.begin();
+ for (; it != pausing_streams_.end(); ++it)
+ (*it)->Close();
+ pausing_streams_.clear();
+}
+
+bool AudioOutputDispatcherImpl::CreateAndOpenStream() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+ AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(params_);
+ if (!stream)
+ return false;
+
+ if (!stream->Open()) {
+ stream->Close();
+ return false;
+ }
+ idle_streams_.push_back(stream);
+ return true;
+}
+
+void AudioOutputDispatcherImpl::OpenTask() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+ // Make sure that we have at least one stream allocated if there
+ // are paused streams.
+ if (paused_proxies_ > 0 && idle_streams_.empty() &&
+ pausing_streams_.empty()) {
+ CreateAndOpenStream();
+ }
+
+ close_timer_.Reset();
+}
+
+// This method is called by |close_timer_|.
+void AudioOutputDispatcherImpl::ClosePendingStreams() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+ while (!idle_streams_.empty()) {
+ idle_streams_.back()->Close();
+ idle_streams_.pop_back();
+ }
+}
+
+} // namespace media
diff --git a/media/audio/audio_output_dispatcher_impl.h b/media/audio/audio_output_dispatcher_impl.h
new file mode 100644
index 0000000..8ad7dda
--- /dev/null
+++ b/media/audio/audio_output_dispatcher_impl.h
@@ -0,0 +1,102 @@
+// 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.
+
+// AudioOutputDispatcherImpl is an implementation of AudioOutputDispatcher.
+//
+// To avoid opening and closing audio devices more frequently than necessary,
+// 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 via the
+// constructor).
+//
+
+#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_IMPL_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_IMPL_H_
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_output_dispatcher.h"
+#include "media/audio/audio_parameters.h"
+
+class MessageLoop;
+
+namespace media {
+
+class AudioOutputProxy;
+
+class MEDIA_EXPORT AudioOutputDispatcherImpl : public AudioOutputDispatcher {
+ public:
+ // |close_delay_ms| specifies delay after the stream is paused until
+ // the audio device is closed.
+ AudioOutputDispatcherImpl(AudioManager* audio_manager,
+ const AudioParameters& params,
+ const base::TimeDelta& close_delay);
+
+ // Opens a new physical stream if there are no pending streams in
+ // |idle_streams_|.
+ virtual bool OpenStream() OVERRIDE;
+
+ // If there are pending streams in |idle_streams_| then it reuses one of
+ // them, otherwise creates a new one.
+ virtual bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) OVERRIDE;
+
+ // Holds the physical stream temporarily in |pausing_streams_| and then
+ // |stream| is added to the pool of pending streams (i.e. |idle_streams_|).
+ virtual void StopStream(AudioOutputProxy* stream_proxy) OVERRIDE;
+
+ virtual void StreamVolumeSet(AudioOutputProxy* stream_proxy,
+ double volume) OVERRIDE;
+
+ virtual void CloseStream(AudioOutputProxy* stream_proxy) OVERRIDE;
+
+ virtual void Shutdown() OVERRIDE;
+
+ private:
+ typedef std::map<AudioOutputProxy*, AudioOutputStream*> AudioStreamMap;
+ friend class base::RefCountedThreadSafe<AudioOutputDispatcherImpl>;
+ virtual ~AudioOutputDispatcherImpl();
+
+ friend class AudioOutputProxyTest;
+
+ // Creates a new physical output stream, opens it and pushes to
+ // |idle_streams_|. Returns false if the stream couldn't be created or
+ // opened.
+ bool CreateAndOpenStream();
+
+ // A task scheduled by StartStream(). Opens a new stream and puts
+ // it in |idle_streams_|.
+ void OpenTask();
+
+ // Before a stream is reused, it should sit idle for a bit. This task is
+ // called once that time has elapsed.
+ void StopStreamTask();
+
+ // Called by |close_timer_|. Closes all pending streams.
+ void ClosePendingStreams();
+
+ base::TimeDelta pause_delay_;
+ size_t paused_proxies_;
+ typedef std::list<AudioOutputStream*> AudioOutputStreamList;
+ AudioOutputStreamList idle_streams_;
+ AudioOutputStreamList pausing_streams_;
+
+ // Used to post delayed tasks to ourselves that we cancel inside Shutdown().
+ base::WeakPtrFactory<AudioOutputDispatcherImpl> weak_this_;
+ base::DelayTimer<AudioOutputDispatcherImpl> close_timer_;
+
+ AudioStreamMap proxy_to_physical_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioOutputDispatcherImpl);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_IMPL_H_
diff --git a/media/audio/audio_output_mixer.cc b/media/audio/audio_output_mixer.cc
new file mode 100644
index 0000000..d091672
--- /dev/null
+++ b/media/audio/audio_output_mixer.cc
@@ -0,0 +1,251 @@
+// 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 "media/audio/audio_output_mixer.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/time.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_output_proxy.h"
+#include "media/audio/audio_util.h"
+
+namespace media {
+
+AudioOutputMixer::AudioOutputMixer(AudioManager* audio_manager,
+ const AudioParameters& params,
+ const base::TimeDelta& close_delay)
+ : AudioOutputDispatcher(audio_manager, params),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)),
+ close_timer_(FROM_HERE,
+ close_delay,
+ weak_this_.GetWeakPtr(),
+ &AudioOutputMixer::ClosePhysicalStream) {
+ // TODO(enal): align data.
+ mixer_data_.reset(new uint8[params_.GetBytesPerBuffer()]);
+}
+
+AudioOutputMixer::~AudioOutputMixer() {
+}
+
+bool AudioOutputMixer::OpenStream() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ if (physical_stream_.get())
+ return true;
+ AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(params_);
+ if (!stream)
+ return false;
+ if (!stream->Open()) {
+ stream->Close();
+ return false;
+ }
+ physical_stream_.reset(stream);
+ close_timer_.Reset();
+ return true;
+}
+
+bool AudioOutputMixer::StartStream(
+ AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ // May need to re-open the physical stream if no active proxies and
+ // enough time had pass.
+ OpenStream();
+ if (!physical_stream_.get())
+ return false;
+
+ double volume = 0.0;
+ stream_proxy->GetVolume(&volume);
+ bool should_start = proxies_.empty();
+ {
+ base::AutoLock lock(lock_);
+ ProxyData* proxy_data = &proxies_[stream_proxy];
+ proxy_data->audio_source_callback = callback;
+ proxy_data->volume = volume;
+ proxy_data->pending_bytes = 0;
+ }
+ // We cannot start physical stream under the lock,
+ // OnMoreData() would try acquiring it...
+ if (should_start) {
+ physical_stream_->SetVolume(1.0);
+ physical_stream_->Start(this);
+ }
+ return true;
+}
+
+void AudioOutputMixer::StopStream(AudioOutputProxy* stream_proxy) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ // Because of possible deadlock we cannot stop physical stream under the lock
+ // (physical_stream_->Stop() can call OnError(), and it acquires the lock to
+ // iterate through proxies), so acquire the lock, update proxy list, release
+ // the lock, and only then stop physical stream if necessary.
+ bool stop_physical_stream = false;
+ {
+ base::AutoLock lock(lock_);
+ ProxyMap::iterator it = proxies_.find(stream_proxy);
+ if (it != proxies_.end()) {
+ proxies_.erase(it);
+ stop_physical_stream = proxies_.empty();
+ }
+ }
+ if (physical_stream_.get()) {
+ if (stop_physical_stream)
+ physical_stream_->Stop();
+ close_timer_.Reset();
+ }
+}
+
+void AudioOutputMixer::StreamVolumeSet(AudioOutputProxy* stream_proxy,
+ double volume) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ ProxyMap::iterator it = proxies_.find(stream_proxy);
+
+ // Do nothing if stream is not currently playing.
+ if (it != proxies_.end()) {
+ base::AutoLock lock(lock_);
+ it->second.volume = volume;
+ }
+}
+
+void AudioOutputMixer::CloseStream(AudioOutputProxy* stream_proxy) {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ StopStream(stream_proxy);
+}
+
+void AudioOutputMixer::Shutdown() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ // Cancel any pending tasks to close physical stream.
+ weak_this_.InvalidateWeakPtrs();
+
+ while (!proxies_.empty()) {
+ CloseStream(proxies_.begin()->first);
+ }
+ ClosePhysicalStream();
+
+ // No AudioOutputProxy objects should hold a reference to us when we get
+ // to this stage.
+ DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference";
+}
+
+void AudioOutputMixer::ClosePhysicalStream() {
+ DCHECK_EQ(MessageLoop::current(), message_loop_);
+
+ if (proxies_.empty() && physical_stream_.get() != NULL)
+ physical_stream_.release()->Close();
+}
+
+// AudioSourceCallback implementation.
+uint32 AudioOutputMixer::OnMoreData(AudioOutputStream* stream,
+ uint8* dest,
+ uint32 max_size,
+ AudioBuffersState buffers_state) {
+ max_size = std::min(max_size,
+ static_cast<uint32>(params_.GetBytesPerBuffer()));
+ // TODO(enal): consider getting rid of lock as it is in time-critical code.
+ // E.g. swap |proxies_| with local variable, and merge 2 lists
+ // at the end. That would speed things up but complicate stopping
+ // the stream.
+ base::AutoLock lock(lock_);
+ if (proxies_.empty())
+ return 0;
+ uint32 actual_total_size = 0;
+ uint32 bytes_per_sample = params_.bits_per_sample() >> 3;
+
+ // Go through all the streams, getting data for every one of them
+ // and mixing it into destination.
+ // Minor optimization: for the first stream we are writing data directly into
+ // destination. This way we don't have to mix the data when there is only one
+ // active stream, and net win in other cases, too.
+ bool first_stream = true;
+ uint8* actual_dest = dest;
+ for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) {
+ AudioOutputProxy* stream_proxy = it->first;
+ ProxyData* proxy_data = &it->second;
+ // TODO(enal): We don't know |pending _bytes| for individual stream, and we
+ // should give that value to individual stream's OnMoreData(). I believe it
+ // can be used there to evaluate exact position of data it should return.
+ // Current code "sorta works" if everything works perfectly, but would have
+ // problems if some of the buffers are only partially filled -- we don't
+ // know how how much data was in the buffer OS returned to us, so we cannot
+ // correctly calculate new value. If we know number of buffers we can solve
+ // the problem by storing not one value but vector of them.
+ int pending_bytes = std::min(proxy_data->pending_bytes,
+ buffers_state.pending_bytes);
+ // Note: there is no way we can deduce hardware_delay_bytes for the
+ // particular proxy stream. Use zero instead.
+ uint32 actual_size = proxy_data->audio_source_callback->OnMoreData(
+ stream_proxy,
+ actual_dest,
+ max_size,
+ AudioBuffersState(pending_bytes, 0));
+
+ // Should update pending_bytes for each proxy.
+ // If stream ended, pending_bytes goes down by max_size.
+ if (actual_size == 0) {
+ pending_bytes -= max_size;
+ proxy_data->pending_bytes = std::max(pending_bytes, 0);
+ continue;
+ }
+
+ // Otherwise, it goes up by amount of data. It cannot exceed max amount of
+ // data we can buffer, but we don't know that value. So we increment
+ // pending_bytes unconditionally but adjust it before actual use (which
+ // would be on a next OnMoreData() call).
+ proxy_data->pending_bytes = pending_bytes + actual_size;
+
+ // No need to mix muted stream.
+ double volume = proxy_data->volume;
+ if (volume == 0.0)
+ continue;
+
+ // Different handling for first and all subsequent streams.
+ if (first_stream) {
+ if (volume != 1.0) {
+ media::AdjustVolume(actual_dest,
+ actual_size,
+ params_.channels(),
+ bytes_per_sample,
+ volume);
+ }
+ if (actual_size < max_size)
+ memset(dest + actual_size, 0, max_size - actual_size);
+ first_stream = false;
+ actual_dest = mixer_data_.get();
+ actual_total_size = actual_size;
+ } else {
+ media::MixStreams(dest,
+ actual_dest,
+ actual_size,
+ bytes_per_sample,
+ volume);
+ actual_total_size = std::max(actual_size, actual_total_size);
+ }
+ }
+ return actual_total_size;
+}
+
+void AudioOutputMixer::OnError(AudioOutputStream* stream, int code) {
+ base::AutoLock lock(lock_);
+ for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) {
+ it->second.audio_source_callback->OnError(it->first, code);
+ }
+}
+
+void AudioOutputMixer::WaitTillDataReady() {
+ base::AutoLock lock(lock_);
+ for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) {
+ it->second.audio_source_callback->WaitTillDataReady();
+ }
+}
+
+} // namespace media
diff --git a/media/audio/audio_output_mixer.h b/media/audio/audio_output_mixer.h
new file mode 100644
index 0000000..ae94b5f
--- /dev/null
+++ b/media/audio/audio_output_mixer.h
@@ -0,0 +1,92 @@
+// 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.
+
+// AudioOutputMixer is a class that implements browser-side audio mixer.
+// AudioOutputMixer implements both AudioOutputDispatcher and
+// AudioSourceCallback interfaces.
+
+#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_MIXER_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_MIXER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/timer.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_output_dispatcher.h"
+#include "media/audio/audio_parameters.h"
+
+namespace media {
+
+class MEDIA_EXPORT AudioOutputMixer
+ : public AudioOutputDispatcher,
+ public AudioOutputStream::AudioSourceCallback {
+ public:
+ AudioOutputMixer(AudioManager* audio_manager,
+ const AudioParameters& params,
+ const base::TimeDelta& close_delay);
+
+ // AudioOutputDispatcher interface.
+ virtual bool OpenStream() OVERRIDE;
+ virtual bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) OVERRIDE;
+ virtual void StopStream(AudioOutputProxy* stream_proxy) OVERRIDE;
+ virtual void StreamVolumeSet(AudioOutputProxy* stream_proxy,
+ double volume) OVERRIDE;
+ virtual void CloseStream(AudioOutputProxy* stream_proxy) OVERRIDE;
+ virtual void Shutdown() OVERRIDE;
+
+ // AudioSourceCallback interface.
+ virtual uint32 OnMoreData(AudioOutputStream* stream,
+ uint8* dest,
+ uint32 max_size,
+ AudioBuffersState buffers_state) OVERRIDE;
+ virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE;
+ virtual void WaitTillDataReady() OVERRIDE;
+
+ private:
+ friend class base::RefCountedThreadSafe<AudioOutputMixer>;
+ virtual ~AudioOutputMixer();
+
+ // Called by |close_timer_|. Closes physical stream.
+ void ClosePhysicalStream();
+
+ // The |lock_| must be acquired whenever we modify |proxies_| in the audio
+ // manager thread or accessing it in the hardware audio thread. Read in the
+ // audio manager thread is safe.
+ base::Lock lock_;
+
+ // List of audio output proxies currently being played.
+ // For every proxy we store aux structure containing data necessary for
+ // mixing.
+ struct ProxyData {
+ AudioOutputStream::AudioSourceCallback* audio_source_callback;
+ double volume;
+ int pending_bytes;
+ };
+ typedef std::map<AudioOutputProxy*, ProxyData> ProxyMap;
+ ProxyMap proxies_;
+
+ // Physical stream for this mixer.
+ scoped_ptr<AudioOutputStream> physical_stream_;
+
+ // Temporary buffer used when mixing. Allocated in the constructor
+ // to avoid constant allocation/deallocation in the callback.
+ scoped_array<uint8> mixer_data_;
+
+ // Used to post delayed tasks to ourselves that we cancel inside Shutdown().
+ base::WeakPtrFactory<AudioOutputMixer> weak_this_;
+ base::DelayTimer<AudioOutputMixer> close_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioOutputMixer);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_MIXER_H_
diff --git a/media/audio/audio_output_proxy.cc b/media/audio/audio_output_proxy.cc
index b1d65ba..a9cfe68 100644
--- a/media/audio/audio_output_proxy.cc
+++ b/media/audio/audio_output_proxy.cc
@@ -14,21 +14,19 @@ namespace media {
AudioOutputProxy::AudioOutputProxy(AudioOutputDispatcher* dispatcher)
: dispatcher_(dispatcher),
state_(kCreated),
- physical_stream_(NULL),
volume_(1.0) {
}
AudioOutputProxy::~AudioOutputProxy() {
DCHECK(CalledOnValidThread());
DCHECK(state_ == kCreated || state_ == kClosed);
- DCHECK(!physical_stream_);
}
bool AudioOutputProxy::Open() {
DCHECK(CalledOnValidThread());
DCHECK_EQ(state_, kCreated);
- if (!dispatcher_->StreamOpened()) {
+ if (!dispatcher_->OpenStream()) {
state_ = kError;
return false;
}
@@ -39,18 +37,13 @@ bool AudioOutputProxy::Open() {
void AudioOutputProxy::Start(AudioSourceCallback* callback) {
DCHECK(CalledOnValidThread());
- DCHECK(physical_stream_ == NULL);
DCHECK_EQ(state_, kOpened);
- physical_stream_= dispatcher_->StreamStarted();
- if (!physical_stream_) {
+ if (!dispatcher_->StartStream(callback, this)) {
state_ = kError;
callback->OnError(this, 0);
return;
}
-
- physical_stream_->SetVolume(volume_);
- physical_stream_->Start(callback);
state_ = kPlaying;
}
@@ -59,19 +52,14 @@ void AudioOutputProxy::Stop() {
if (state_ != kPlaying)
return;
- DCHECK(physical_stream_);
- physical_stream_->Stop();
- dispatcher_->StreamStopped(physical_stream_);
- physical_stream_ = NULL;
+ dispatcher_->StopStream(this);
state_ = kOpened;
}
void AudioOutputProxy::SetVolume(double volume) {
DCHECK(CalledOnValidThread());
volume_ = volume;
- if (physical_stream_) {
- physical_stream_->SetVolume(volume);
- }
+ dispatcher_->StreamVolumeSet(this, volume);
}
void AudioOutputProxy::GetVolume(double* volume) {
@@ -82,10 +70,9 @@ void AudioOutputProxy::GetVolume(double* volume) {
void AudioOutputProxy::Close() {
DCHECK(CalledOnValidThread());
DCHECK(state_ == kCreated || state_ == kError || state_ == kOpened);
- DCHECK(!physical_stream_);
if (state_ != kCreated)
- dispatcher_->StreamClosed();
+ dispatcher_->CloseStream(this);
state_ = kClosed;
diff --git a/media/audio/audio_output_proxy.h b/media/audio/audio_output_proxy.h
index d34f81f..1fb35a3 100644
--- a/media/audio/audio_output_proxy.h
+++ b/media/audio/audio_output_proxy.h
@@ -53,10 +53,6 @@ class MEDIA_EXPORT 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_;
diff --git a/media/audio/audio_output_proxy_unittest.cc b/media/audio/audio_output_proxy_unittest.cc
index 3dbfd2f2..a3adbac 100644
--- a/media/audio/audio_output_proxy_unittest.cc
+++ b/media/audio/audio_output_proxy_unittest.cc
@@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <string>
+
#include "base/message_loop.h"
#include "base/message_loop_proxy.h"
#include "base/threading/platform_thread.h"
-#include "media/audio/audio_output_dispatcher.h"
+#include "media/audio/audio_output_dispatcher_impl.h"
+#include "media/audio/audio_output_mixer.h"
#include "media/audio/audio_output_proxy.h"
#include "media/audio/audio_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -88,7 +91,7 @@ class AudioOutputProxyTest : public testing::Test {
virtual void TearDown() {
// All paused proxies should have been closed at this point.
- EXPECT_EQ(0u, dispatcher_->paused_proxies_);
+ EXPECT_EQ(0u, dispatcher_impl_->paused_proxies_);
// This is necessary to free all proxy objects that have been
// closed by the test.
@@ -98,42 +101,166 @@ class AudioOutputProxyTest : public testing::Test {
void InitDispatcher(base::TimeDelta close_delay) {
AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
CHANNEL_LAYOUT_STEREO, 44100, 16, 1024);
- dispatcher_ = new AudioOutputDispatcher(&manager(), params, close_delay);
+ dispatcher_impl_ = new AudioOutputDispatcherImpl(&manager(),
+ params,
+ close_delay);
+ mixer_ = new AudioOutputMixer(&manager(), params, close_delay);
// Necessary to know how long the dispatcher will wait before posting
// StopStreamTask.
- pause_delay_ = dispatcher_->pause_delay_;
+ pause_delay_ = dispatcher_impl_->pause_delay_;
}
MockAudioManager& manager() {
return manager_;
}
+ // Wait for the close timer to fire.
+ void WaitForCloseTimer(const int timer_delay_ms) {
+ message_loop_.RunAllPending(); // OpenTask() may reset the timer.
+ base::PlatformThread::Sleep(
+ base::TimeDelta::FromMilliseconds(timer_delay_ms) * 2);
+ message_loop_.RunAllPending();
+ }
+
+ // Methods that do actual tests.
+ void OpenAndClose(AudioOutputDispatcher* dispatcher) {
+ 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();
+ WaitForCloseTimer(kTestCloseDelayMs);
+ }
+
+ // Create a stream, and then calls Start() and Stop().
+ void StartAndStop(AudioOutputDispatcher* dispatcher) {
+ 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();
+ WaitForCloseTimer(kTestCloseDelayMs);
+ }
+
+ // Verify that the stream is closed after Stop is called.
+ void CloseAfterStop(AudioOutputDispatcher* dispatcher) {
+ 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();
+
+ // Wait for StopStream() to post StopStreamTask().
+ base::PlatformThread::Sleep(pause_delay_ * 2);
+ WaitForCloseTimer(kTestCloseDelayMs);
+
+ // Verify expectation before calling Close().
+ Mock::VerifyAndClear(&stream);
+
+ proxy->Close();
+ }
+
+ // Create two streams, but don't start them. Only one device must be open.
+ void TwoStreams(AudioOutputDispatcher* dispatcher) {
+ 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();
+ WaitForCloseTimer(kTestCloseDelayMs);
+ }
+
+ // Open() method failed.
+ void OpenFailed(AudioOutputDispatcher* dispatcher) {
+ 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();
+ WaitForCloseTimer(kTestCloseDelayMs);
+ }
+
MessageLoop message_loop_;
- scoped_refptr<AudioOutputDispatcher> dispatcher_;
+ scoped_refptr<AudioOutputDispatcherImpl> dispatcher_impl_;
+ scoped_refptr<AudioOutputMixer> mixer_;
base::TimeDelta pause_delay_;
MockAudioManager manager_;
MockAudioSourceCallback callback_;
};
TEST_F(AudioOutputProxyTest, CreateAndClose) {
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_);
+ AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_impl_);
proxy->Close();
}
-TEST_F(AudioOutputProxyTest, OpenAndClose) {
- MockAudioOutputStream stream;
+TEST_F(AudioOutputProxyTest, CreateAndClose_Mixer) {
+ AudioOutputProxy* proxy = new AudioOutputProxy(mixer_);
+ proxy->Close();
+}
- EXPECT_CALL(manager(), MakeAudioOutputStream(_))
- .WillOnce(Return(&stream));
- EXPECT_CALL(stream, Open())
- .WillOnce(Return(true));
- EXPECT_CALL(stream, Close())
- .Times(1);
+TEST_F(AudioOutputProxyTest, OpenAndClose) {
+ OpenAndClose(dispatcher_impl_);
+}
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_);
- EXPECT_TRUE(proxy->Open());
- proxy->Close();
+TEST_F(AudioOutputProxyTest, OpenAndClose_Mixer) {
+ OpenAndClose(mixer_);
}
// Create a stream, and verify that it is closed after kTestCloseDelayMs.
@@ -148,7 +275,7 @@ TEST_F(AudioOutputProxyTest, CreateAndWait) {
EXPECT_CALL(stream, Close())
.Times(1);
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_);
+ AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_impl_);
EXPECT_TRUE(proxy->Open());
// Simulate a delay.
@@ -162,87 +289,28 @@ TEST_F(AudioOutputProxyTest, CreateAndWait) {
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();
+ StartAndStop(dispatcher_impl_);
+}
- proxy->Close();
+TEST_F(AudioOutputProxyTest, StartAndStop_Mixer) {
+ StartAndStop(mixer_);
}
-// 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();
-
- // Wait for StreamStopped() to post StopStreamTask().
- base::PlatformThread::Sleep(pause_delay_ * 2);
- message_loop_.RunAllPending();
-
- // Wait for the close timer to fire.
- base::PlatformThread::Sleep(
- base::TimeDelta::FromMilliseconds(kTestCloseDelayMs) * 2);
- message_loop_.RunAllPending();
-
- // Verify expectation before calling Close().
- Mock::VerifyAndClear(&stream);
+ CloseAfterStop(dispatcher_impl_);
+}
- proxy->Close();
+TEST_F(AudioOutputProxyTest, CloseAfterStop_Mixer) {
+ CloseAfterStop(mixer_);
}
-// 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);
+ TwoStreams(dispatcher_impl_);
+}
- AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher_);
- AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher_);
- EXPECT_TRUE(proxy1->Open());
- EXPECT_TRUE(proxy2->Open());
- proxy1->Close();
- proxy2->Close();
+TEST_F(AudioOutputProxyTest, TwoStreams_Mixer) {
+ TwoStreams(mixer_);
}
// Two streams: verify that second stream is allocated when the first
@@ -273,8 +341,8 @@ TEST_F(AudioOutputProxyTest, TwoStreams_OnePlaying) {
EXPECT_CALL(stream2, Close())
.Times(1);
- AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher_);
- AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher_);
+ AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher_impl_);
+ AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher_impl_);
EXPECT_TRUE(proxy1->Open());
EXPECT_TRUE(proxy2->Open());
@@ -286,6 +354,39 @@ TEST_F(AudioOutputProxyTest, TwoStreams_OnePlaying) {
proxy2->Close();
}
+// Two streams: verify that only one device will be created.
+TEST_F(AudioOutputProxyTest, TwoStreams_OnePlaying_Mixer) {
+ MockAudioOutputStream stream;
+
+ InitDispatcher(base::TimeDelta::FromMilliseconds(kTestCloseDelayMs));
+
+ 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* proxy1 = new AudioOutputProxy(mixer_);
+ AudioOutputProxy* proxy2 = new AudioOutputProxy(mixer_);
+ EXPECT_TRUE(proxy1->Open());
+ EXPECT_TRUE(proxy2->Open());
+
+ proxy1->Start(&callback_);
+ proxy1->Stop();
+
+ proxy1->Close();
+ proxy2->Close();
+ WaitForCloseTimer(kTestCloseDelayMs);
+}
+
// Two streams, both are playing. Dispatcher should not open a third stream.
TEST_F(AudioOutputProxyTest, TwoStreams_BothPlaying) {
MockAudioOutputStream stream1;
@@ -319,8 +420,8 @@ TEST_F(AudioOutputProxyTest, TwoStreams_BothPlaying) {
EXPECT_CALL(stream2, Close())
.Times(1);
- AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher_);
- AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher_);
+ AudioOutputProxy* proxy1 = new AudioOutputProxy(dispatcher_impl_);
+ AudioOutputProxy* proxy2 = new AudioOutputProxy(dispatcher_impl_);
EXPECT_TRUE(proxy1->Open());
EXPECT_TRUE(proxy2->Open());
@@ -333,20 +434,47 @@ TEST_F(AudioOutputProxyTest, TwoStreams_BothPlaying) {
proxy2->Close();
}
-// Open() method failed.
-TEST_F(AudioOutputProxyTest, OpenFailed) {
+// Two streams, both are playing. Still have to use single device.
+TEST_F(AudioOutputProxyTest, TwoStreams_BothPlaying_Mixer) {
MockAudioOutputStream stream;
+ InitDispatcher(base::TimeDelta::FromMilliseconds(kTestCloseDelayMs));
+
EXPECT_CALL(manager(), MakeAudioOutputStream(_))
.WillOnce(Return(&stream));
+
EXPECT_CALL(stream, Open())
- .WillOnce(Return(false));
+ .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_FALSE(proxy->Open());
- proxy->Close();
+ AudioOutputProxy* proxy1 = new AudioOutputProxy(mixer_);
+ AudioOutputProxy* proxy2 = new AudioOutputProxy(mixer_);
+ EXPECT_TRUE(proxy1->Open());
+ EXPECT_TRUE(proxy2->Open());
+
+ proxy1->Start(&callback_);
+ proxy2->Start(&callback_);
+ proxy1->Stop();
+ proxy2->Stop();
+
+ proxy1->Close();
+ proxy2->Close();
+ WaitForCloseTimer(kTestCloseDelayMs);
+}
+
+TEST_F(AudioOutputProxyTest, OpenFailed) {
+ OpenFailed(dispatcher_impl_);
+}
+
+TEST_F(AudioOutputProxyTest, OpenFailed_Mixer) {
+ OpenFailed(mixer_);
}
// Start() method failed.
@@ -360,7 +488,7 @@ TEST_F(AudioOutputProxyTest, StartFailed) {
EXPECT_CALL(stream, Close())
.Times(1);
- AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_);
+ AudioOutputProxy* proxy = new AudioOutputProxy(dispatcher_impl_);
EXPECT_TRUE(proxy->Open());
// Simulate a delay.
@@ -385,4 +513,48 @@ TEST_F(AudioOutputProxyTest, StartFailed) {
proxy->Close();
}
+// Start() method failed.
+TEST_F(AudioOutputProxyTest, StartFailed_Mixer) {
+ MockAudioOutputStream stream;
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream, Close())
+ .Times(1);
+ EXPECT_CALL(stream, Start(_))
+ .Times(1);
+ EXPECT_CALL(stream, SetVolume(_))
+ .Times(1);
+ EXPECT_CALL(stream, Stop())
+ .Times(1);
+
+ AudioOutputProxy* proxy1 = new AudioOutputProxy(mixer_);
+ AudioOutputProxy* proxy2 = new AudioOutputProxy(mixer_);
+ EXPECT_TRUE(proxy1->Open());
+ EXPECT_TRUE(proxy2->Open());
+ proxy1->Start(&callback_);
+ proxy1->Stop();
+ proxy1->Close();
+ WaitForCloseTimer(kTestCloseDelayMs);
+
+ // Verify expectation before continueing.
+ 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);
+
+ proxy2->Start(&callback_);
+
+ Mock::VerifyAndClear(&callback_);
+
+ proxy2->Close();
+ WaitForCloseTimer(kTestCloseDelayMs);
+}
+
} // namespace media
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index be195c8..5a2a912 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -26,4 +26,7 @@ const char kUsePulseAudio[] = "use-pulseaudio";
// Set number of threads to use for video decoding.
const char kVideoThreads[] = "video-threads";
+// Enables browser-side audio mixer.
+const char kEnableAudioMixer[] = "enable-audio-mixer";
+
} // namespace switches
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index a306fde..f39c9d7 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -27,6 +27,8 @@ MEDIA_EXPORT extern const char kUsePulseAudio[];
MEDIA_EXPORT extern const char kVideoThreads[];
+MEDIA_EXPORT extern const char kEnableAudioMixer[];
+
} // namespace switches
#endif // MEDIA_BASE_MEDIA_SWITCHES_H_
diff --git a/media/media.gyp b/media/media.gyp
index 673f51e..1ccfa2b 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -47,6 +47,10 @@
'audio/audio_output_controller.h',
'audio/audio_output_dispatcher.cc',
'audio/audio_output_dispatcher.h',
+ 'audio/audio_output_dispatcher_impl.cc',
+ 'audio/audio_output_dispatcher_impl.h',
+ 'audio/audio_output_mixer.cc',
+ 'audio/audio_output_mixer.h',
'audio/audio_output_proxy.cc',
'audio/audio_output_proxy.h',
'audio/audio_parameters.cc',
diff --git a/tools/valgrind/tsan/suppressions_mac.txt b/tools/valgrind/tsan/suppressions_mac.txt
index 882139f..f9a2b57 100644
--- a/tools/valgrind/tsan/suppressions_mac.txt
+++ b/tools/valgrind/tsan/suppressions_mac.txt
@@ -247,7 +247,9 @@
bug_123112
ThreadSanitizer:Race
fun:media::AUAudioOutputStream::Stop
+ fun:media::AudioOutputDispatcherImpl::StopStream
fun:media::AudioOutputProxy::Stop
fun:media::AudioOutputController::DoStopCloseAndClearStream
fun:media::AudioOutputController::DoClose
+ fun:base::internal::RunnableAdapter::Run
}