diff options
author | enal@chromium.org <enal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-19 17:44:10 +0000 |
---|---|---|
committer | enal@chromium.org <enal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-19 17:44:10 +0000 |
commit | 08c1c1399c69de70485abcbc80ba9884f8d41224 (patch) | |
tree | c27b9f6d0608d3750fbfbab7d2b1c7cdf62e7f28 | |
parent | 3ae7a56eddef4427b218c2151bb331a02a5cc394 (diff) | |
download | chromium_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.cc | 18 | ||||
-rw-r--r-- | media/audio/audio_manager_base.h | 1 | ||||
-rw-r--r-- | media/audio/audio_output_dispatcher.cc | 154 | ||||
-rw-r--r-- | media/audio/audio_output_dispatcher.h | 105 | ||||
-rw-r--r-- | media/audio/audio_output_dispatcher_impl.cc | 200 | ||||
-rw-r--r-- | media/audio/audio_output_dispatcher_impl.h | 102 | ||||
-rw-r--r-- | media/audio/audio_output_mixer.cc | 251 | ||||
-rw-r--r-- | media/audio/audio_output_mixer.h | 92 | ||||
-rw-r--r-- | media/audio/audio_output_proxy.cc | 23 | ||||
-rw-r--r-- | media/audio/audio_output_proxy.h | 4 | ||||
-rw-r--r-- | media/audio/audio_output_proxy_unittest.cc | 372 | ||||
-rw-r--r-- | media/base/media_switches.cc | 3 | ||||
-rw-r--r-- | media/base/media_switches.h | 2 | ||||
-rw-r--r-- | media/media.gyp | 4 | ||||
-rw-r--r-- | tools/valgrind/tsan/suppressions_mac.txt | 2 |
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 } |