diff options
-rw-r--r-- | media/audio/audio_manager.h | 12 | ||||
-rw-r--r-- | media/audio/audio_manager_base.cc | 23 | ||||
-rw-r--r-- | media/audio/audio_manager_base.h | 30 | ||||
-rw-r--r-- | media/audio/audio_output_controller.cc | 86 | ||||
-rw-r--r-- | media/audio/audio_output_controller.h | 54 | ||||
-rw-r--r-- | media/audio/audio_output_controller_unittest.cc | 55 | ||||
-rw-r--r-- | media/audio/mock_audio_manager.cc | 8 | ||||
-rw-r--r-- | media/audio/mock_audio_manager.h | 5 |
8 files changed, 225 insertions, 48 deletions
diff --git a/media/audio/audio_manager.h b/media/audio/audio_manager.h index a34027f..3b61bde 100644 --- a/media/audio/audio_manager.h +++ b/media/audio/audio_manager.h @@ -112,6 +112,18 @@ class MEDIA_EXPORT AudioManager { // Returns message loop used for audio IO. virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() = 0; + // Allows clients to listen for device state changes; e.g. preferred sample + // rate or channel layout changes. The typical response to receiving this + // callback is to recreate the stream. + class AudioDeviceListener { + public: + virtual void OnDeviceChange() = 0; + }; + + virtual void AddOutputDeviceChangeListener(AudioDeviceListener* listener) = 0; + virtual void RemoveOutputDeviceChangeListener( + AudioDeviceListener* listener) = 0; + protected: AudioManager(); diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc index 8c09ebb..0a095ff 100644 --- a/media/audio/audio_manager_base.cc +++ b/media/audio/audio_manager_base.cc @@ -82,6 +82,7 @@ void AudioManagerBase::Init() { DCHECK(!audio_thread_.get()); audio_thread_.reset(new AudioThread("AudioThread")); CHECK(audio_thread_->Start()); + message_loop_ = audio_thread_->message_loop_proxy(); } string16 AudioManagerBase::GetAudioInputDeviceModel() { @@ -90,6 +91,8 @@ string16 AudioManagerBase::GetAudioInputDeviceModel() { scoped_refptr<base::MessageLoopProxy> AudioManagerBase::GetMessageLoop() { base::AutoLock lock(audio_thread_lock_); + // Don't return |message_loop_| here because we don't want any new tasks to + // come in once we've started tearing down the audio thread. return audio_thread_.get() ? audio_thread_->message_loop_proxy() : NULL; } @@ -170,7 +173,7 @@ AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy( NOTIMPLEMENTED(); return NULL; #else - DCHECK(GetMessageLoop()->BelongsToCurrentThread()); + DCHECK(message_loop_->BelongsToCurrentThread()); AudioOutputDispatchersMap::iterator it = output_dispatchers_.find(params); if (it != output_dispatchers_.end()) @@ -335,4 +338,22 @@ AudioParameters AudioManagerBase::GetPreferredLowLatencyOutputStreamParameters( #endif // defined(OS_IOS) } +void AudioManagerBase::AddOutputDeviceChangeListener( + AudioDeviceListener* listener) { + DCHECK(message_loop_->BelongsToCurrentThread()); + output_listeners_.AddObserver(listener); +} + +void AudioManagerBase::RemoveOutputDeviceChangeListener( + AudioDeviceListener* listener) { + DCHECK(message_loop_->BelongsToCurrentThread()); + output_listeners_.RemoveObserver(listener); +} + +void AudioManagerBase::NotifyAllOutputDeviceChangeListeners() { + DCHECK(message_loop_->BelongsToCurrentThread()); + DVLOG(1) << "Firing OnDeviceChange() notifications."; + FOR_EACH_OBSERVER(AudioDeviceListener, output_listeners_, OnDeviceChange()); +} + } // namespace media diff --git a/media/audio/audio_manager_base.h b/media/audio/audio_manager_base.h index 06c7068..8d06ab8 100644 --- a/media/audio/audio_manager_base.h +++ b/media/audio/audio_manager_base.h @@ -11,6 +11,7 @@ #include "base/atomic_ref_count.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" #include "base/synchronization/lock.h" #include "base/threading/thread.h" #include "media/audio/audio_manager.h" @@ -110,9 +111,18 @@ class MEDIA_EXPORT AudioManagerBase : public AudioManager { virtual AudioParameters GetPreferredLowLatencyOutputStreamParameters( const AudioParameters& input_params); + // Listeners will be notified on the AudioManager::GetMessageLoop() loop. + virtual void AddOutputDeviceChangeListener( + AudioDeviceListener* listener) OVERRIDE; + virtual void RemoveOutputDeviceChangeListener( + AudioDeviceListener* listener) OVERRIDE; + protected: AudioManagerBase(); + // TODO(dalecurtis): This must change to map both input and output parameters + // to a single dispatcher, otherwise on a device state change we'll just get + // the exact same invalid dispatcher. typedef std::map<AudioParameters, scoped_refptr<AudioOutputDispatcher>, AudioParameters::Compare> AudioOutputDispatchersMap; @@ -127,10 +137,10 @@ class MEDIA_EXPORT AudioManagerBase : public AudioManager { void SetMaxOutputStreamsAllowed(int max) { max_num_output_streams_ = max; } - // Thread used to interact with AudioOutputStreams created by this - // audio manger. - scoped_ptr<AudioThread> audio_thread_; - mutable base::Lock audio_thread_lock_; + // Called by each platform specific AudioManager to notify output state change + // listeners that a state change has occurred. Must be called from the audio + // thread. + void NotifyAllOutputDeviceChangeListeners(); // Map of cached AudioOutputDispatcher instances. Must only be touched // from the audio thread (no locking). @@ -154,6 +164,18 @@ class MEDIA_EXPORT AudioManagerBase : public AudioManager { // Number of currently open input streams. int num_input_streams_; + // Track output state change listeners. + ObserverList<AudioDeviceListener> output_listeners_; + + // Thread used to interact with audio streams created by this audio manager. + scoped_ptr<AudioThread> audio_thread_; + mutable base::Lock audio_thread_lock_; + + // The message loop of the audio thread this object runs on. Set on Init(). + // Used for internal tasks which run on the audio thread even after Shutdown() + // has been started and GetMessageLoop() starts returning NULL. + scoped_refptr<base::MessageLoopProxy> message_loop_; + DISALLOW_COPY_AND_ASSIGN(AudioManagerBase); }; diff --git a/media/audio/audio_output_controller.cc b/media/audio/audio_output_controller.cc index bbd88c8..f21da8f 100644 --- a/media/audio/audio_output_controller.cc +++ b/media/audio/audio_output_controller.cc @@ -23,15 +23,17 @@ namespace media { const int AudioOutputController::kPollNumAttempts = 3; const int AudioOutputController::kPollPauseInMilliseconds = 3; -AudioOutputController::AudioOutputController(EventHandler* handler, - SyncReader* sync_reader, - const AudioParameters& params) - : handler_(handler), +AudioOutputController::AudioOutputController(AudioManager* audio_manager, + EventHandler* handler, + const AudioParameters& params, + SyncReader* sync_reader) + : audio_manager_(audio_manager), + handler_(handler), stream_(NULL), volume_(1.0), state_(kEmpty), sync_reader_(sync_reader), - message_loop_(NULL), + message_loop_(audio_manager->GetMessageLoop()), number_polling_attempts_left_(0), params_(params), ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)) { @@ -39,9 +41,8 @@ AudioOutputController::AudioOutputController(EventHandler* handler, AudioOutputController::~AudioOutputController() { DCHECK_EQ(kClosed, state_); - DCHECK(message_loop_); - if (!message_loop_.get() || message_loop_->BelongsToCurrentThread()) { + if (message_loop_->BelongsToCurrentThread()) { DoStopCloseAndClearStream(NULL); } else { // http://crbug.com/120973 @@ -70,12 +71,11 @@ scoped_refptr<AudioOutputController> AudioOutputController::Create( // Starts the audio controller thread. scoped_refptr<AudioOutputController> controller(new AudioOutputController( - event_handler, sync_reader, params)); + audio_manager, event_handler, params, sync_reader)); - controller->message_loop_ = audio_manager->GetMessageLoop(); controller->message_loop_->PostTask(FROM_HERE, base::Bind( - &AudioOutputController::DoCreate, controller, - base::Unretained(audio_manager))); + &AudioOutputController::DoCreate, controller)); + return controller; } @@ -110,23 +110,26 @@ void AudioOutputController::SetVolume(double volume) { &AudioOutputController::DoSetVolume, this, volume)); } -void AudioOutputController::DoCreate(AudioManager* audio_manager) { +void AudioOutputController::DoCreate() { DCHECK(message_loop_->BelongsToCurrentThread()); // Close() can be called before DoCreate() is executed. if (state_ == kClosed) return; - DCHECK_EQ(kEmpty, state_); + DCHECK(state_ == kEmpty || state_ == kRecreating) << state_; DoStopCloseAndClearStream(NULL); - stream_ = audio_manager->MakeAudioOutputStreamProxy(params_); + stream_ = audio_manager_->MakeAudioOutputStreamProxy(params_); if (!stream_) { + state_ = kError; + // TODO(hclam): Define error types. handler_->OnError(this, 0); return; } if (!stream_->Open()) { + state_ = kError; DoStopCloseAndClearStream(NULL); // TODO(hclam): Define error types. @@ -134,14 +137,21 @@ void AudioOutputController::DoCreate(AudioManager* audio_manager) { return; } + // Everything started okay, so register for state change callbacks if we have + // not already done so. + if (state_ != kRecreating) + audio_manager_->AddOutputDeviceChangeListener(this); + // We have successfully opened the stream. Set the initial volume. stream_->SetVolume(volume_); // Finally set the state to kCreated. + State original_state = state_; state_ = kCreated; - // And then report we have been created. - handler_->OnCreated(this); + // And then report we have been created if we haven't done so already. + if (original_state != kRecreating) + handler_->OnCreated(this); } void AudioOutputController::DoPlay() { @@ -328,20 +338,58 @@ void AudioOutputController::OnError(AudioOutputStream* stream, int code) { &AudioOutputController::DoReportError, this, code)); } -void AudioOutputController::DoStopCloseAndClearStream(WaitableEvent *done) { +void AudioOutputController::DoStopCloseAndClearStream(WaitableEvent* done) { DCHECK(message_loop_->BelongsToCurrentThread()); // Allow calling unconditionally and bail if we don't have a stream_ to close. - if (stream_ != NULL) { + if (stream_) { stream_->Stop(); stream_->Close(); stream_ = NULL; + + audio_manager_->RemoveOutputDeviceChangeListener(this); + audio_manager_ = NULL; + weak_this_.InvalidateWeakPtrs(); } // Should be last in the method, do not touch "this" from here on. - if (done != NULL) + if (done) done->Signal(); } +void AudioOutputController::OnDeviceChange() { + DCHECK(message_loop_->BelongsToCurrentThread()); + + // We should always have a stream by this point. + CHECK(stream_); + + // Preserve the original state and shutdown the stream. + State original_state = state_; + stream_->Stop(); + stream_->Close(); + stream_ = NULL; + + // Recreate the stream, exit if we ran into an error. + state_ = kRecreating; + DoCreate(); + if (!stream_ || state_ == kError) + return; + + // Get us back to the original state or an equivalent state. + switch (original_state) { + case kStarting: + case kPlaying: + DoPlay(); + return; + case kCreated: + case kPausedWhenStarting: + case kPaused: + // From the outside these three states are equivalent. + return; + default: + NOTREACHED() << "Invalid original state."; + } +} + } // namespace media diff --git a/media/audio/audio_output_controller.h b/media/audio/audio_output_controller.h index d963cd0..3dae719 100644 --- a/media/audio/audio_output_controller.h +++ b/media/audio/audio_output_controller.h @@ -13,6 +13,7 @@ #include "media/audio/audio_io.h" #include "media/audio/audio_manager.h" #include "media/audio/simple_sources.h" +#include "media/base/media_export.h" namespace base { class WaitableEvent; @@ -44,28 +45,28 @@ class MessageLoop; // // * Initial state // +// At any time after reaching the Created state but before Closed / Error, the +// AudioOutputController may be notified of a device change via OnDeviceChange() +// and transition to the Recreating state. If OnDeviceChange() completes +// successfully the state will transition back to an equivalent pre-call state. +// E.g., if the state was Paused or PausedWhenStarting, the new state will be +// Created, since these states are all functionally equivalent and require a +// Play() call to continue to the next state. +// // The AudioOutputStream can request data from the AudioOutputController via the // AudioSourceCallback interface. AudioOutputController uses the SyncReader // passed to it via construction to synchronously fulfill this read request. // -// The audio manager thread is owned by the AudioManager that the -// AudioOutputController holds a reference to. When performing tasks on this -// thread, the controller must not add or release references to the -// AudioManager or itself (since it in turn holds a reference to the manager), -// for delayed tasks as it can slow down or even prevent normal shut down. -// So, for tasks on the audio thread, the controller uses WeakPtr which enables -// us to safely cancel pending polling tasks. -// -// AudioManager will take care of properly shutting down the audio manager -// thread. +// Since AudioOutputController uses AudioManager's message loop the controller +// uses WeakPtr to allow safe cancellation of pending tasks. // -#include "media/base/media_export.h" namespace media { class MEDIA_EXPORT AudioOutputController : public base::RefCountedThreadSafe<AudioOutputController>, - public AudioOutputStream::AudioSourceCallback { + public AudioOutputStream::AudioSourceCallback, + NON_EXPORTED_BASE(public AudioManager::AudioDeviceListener) { public: // An event handler that receives events from the AudioOutputController. The // following methods are called on the audio manager thread. @@ -112,13 +113,11 @@ class MEDIA_EXPORT AudioOutputController // Factory method for creating an AudioOutputController. // This also creates and opens an AudioOutputStream on the audio manager // thread, and if this is successful, the |event_handler| will receive an - // OnCreated() call from the same audio manager thread. + // OnCreated() call from the same audio manager thread. |audio_manager| must + // outlive AudioOutputController. static scoped_refptr<AudioOutputController> Create( - AudioManager* audio_manager, - EventHandler* event_handler, - const AudioParameters& params, - // External synchronous reader for audio controller. - SyncReader* sync_reader); + AudioManager* audio_manager, EventHandler* event_handler, + const AudioParameters& params, SyncReader* sync_reader); // Methods to control playback of the stream. @@ -144,8 +143,7 @@ class MEDIA_EXPORT AudioOutputController // Sets the volume of the audio output stream. void SetVolume(double volume); - /////////////////////////////////////////////////////////////////////////// - // AudioSourceCallback methods. + // AudioSourceCallback implementation. virtual int OnMoreData(AudioBus* dest, AudioBuffersState buffers_state) OVERRIDE; virtual int OnMoreIOData(AudioBus* source, @@ -154,6 +152,12 @@ class MEDIA_EXPORT AudioOutputController virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE; virtual void WaitTillDataReady() OVERRIDE; + // AudioDeviceListener implementation. When called AudioOutputController will + // shutdown the existing |stream_|, transition to the kRecreating state, + // create a new stream, and then transition back to an equivalent state prior + // to being called. + virtual void OnDeviceChange() OVERRIDE; + protected: // Internal state of the source. enum State { @@ -165,6 +169,7 @@ class MEDIA_EXPORT AudioOutputController kPaused, kClosed, kError, + kRecreating, }; friend class base::RefCountedThreadSafe<AudioOutputController>; @@ -175,12 +180,11 @@ class MEDIA_EXPORT AudioOutputController static const int kPollNumAttempts; static const int kPollPauseInMilliseconds; - AudioOutputController(EventHandler* handler, - SyncReader* sync_reader, - const AudioParameters& params); + AudioOutputController(AudioManager* audio_manager, EventHandler* handler, + const AudioParameters& params, SyncReader* sync_reader); // The following methods are executed on the audio manager thread. - void DoCreate(AudioManager* audio_manager); + void DoCreate(); void DoPlay(); void PollAndStartIfDataReady(); void DoPause(); @@ -196,6 +200,8 @@ class MEDIA_EXPORT AudioOutputController // Signals event when done if it is not NULL. void DoStopCloseAndClearStream(base::WaitableEvent *done); + AudioManager* audio_manager_; + // |handler_| may be called only if |state_| is not kClosed. EventHandler* handler_; AudioOutputStream* stream_; diff --git a/media/audio/audio_output_controller_unittest.cc b/media/audio/audio_output_controller_unittest.cc index c1c000a..fe29ce5 100644 --- a/media/audio/audio_output_controller_unittest.cc +++ b/media/audio/audio_output_controller_unittest.cc @@ -228,4 +228,59 @@ TEST_F(AudioOutputControllerTest, PlayPausePlayClose) { CloseAudioController(controller); } +// Ensure state change events are handled. +TEST_F(AudioOutputControllerTest, PlayStateChangeClose) { + scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); + if (!audio_manager->HasAudioOutputDevices()) + return; + + MockAudioOutputControllerEventHandler event_handler; + base::WaitableEvent event(false, false); + EXPECT_CALL(event_handler, OnCreated(NotNull())) + .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal)); + + // OnPlaying() will be called once normally and once after being recreated. + base::WaitableEvent play_event(false, false); + EXPECT_CALL(event_handler, OnPlaying(NotNull())) + .Times(2) + .WillRepeatedly(InvokeWithoutArgs( + &play_event, &base::WaitableEvent::Signal)); + + // OnPaused() should not be called during the state change event. + EXPECT_CALL(event_handler, OnPaused(NotNull())) + .Times(0); + + MockAudioOutputControllerSyncReader sync_reader; + EXPECT_CALL(sync_reader, UpdatePendingBytes(_)) + .Times(AtLeast(1)); + EXPECT_CALL(sync_reader, Read(_, _)) + .WillRepeatedly(DoAll(ClearBuffer(), SignalEvent(&event), Return(4))); + EXPECT_CALL(sync_reader, DataReady()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(sync_reader, Close()); + + AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, + kSampleRate, kBitsPerSample, kSamplesPerPacket); + scoped_refptr<AudioOutputController> controller = + AudioOutputController::Create( + audio_manager.get(), &event_handler, params, &sync_reader); + ASSERT_TRUE(controller.get()); + + // Wait for OnCreated() to be called. + event.Wait(); + + ASSERT_FALSE(play_event.IsSignaled()); + controller->Play(); + play_event.Wait(); + + // Force a state change and wait for the stream to come back to playing state. + play_event.Reset(); + audio_manager->GetMessageLoop()->PostTask(FROM_HERE, + base::Bind(&AudioOutputController::OnDeviceChange, controller)); + play_event.Wait(); + + // Now stop the controller. + CloseAudioController(controller); +} + } // namespace media diff --git a/media/audio/mock_audio_manager.cc b/media/audio/mock_audio_manager.cc index fe28c94..bb3d49c 100644 --- a/media/audio/mock_audio_manager.cc +++ b/media/audio/mock_audio_manager.cc @@ -69,4 +69,12 @@ scoped_refptr<base::MessageLoopProxy> MockAudioManager::GetMessageLoop() { void MockAudioManager::Init() { } +void MockAudioManager::AddOutputDeviceChangeListener( + AudioDeviceListener* listener) { +} + +void MockAudioManager::RemoveOutputDeviceChangeListener( + AudioDeviceListener* listener) { +} + } // namespace media. diff --git a/media/audio/mock_audio_manager.h b/media/audio/mock_audio_manager.h index 821a199..347ca50 100644 --- a/media/audio/mock_audio_manager.h +++ b/media/audio/mock_audio_manager.h @@ -52,6 +52,11 @@ class MockAudioManager : public media::AudioManager { virtual void Init() OVERRIDE; + virtual void AddOutputDeviceChangeListener( + AudioDeviceListener* listener) OVERRIDE; + virtual void RemoveOutputDeviceChangeListener( + AudioDeviceListener* listener) OVERRIDE; + private: virtual ~MockAudioManager(); |