summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/audio/audio_manager.h12
-rw-r--r--media/audio/audio_manager_base.cc23
-rw-r--r--media/audio/audio_manager_base.h30
-rw-r--r--media/audio/audio_output_controller.cc86
-rw-r--r--media/audio/audio_output_controller.h54
-rw-r--r--media/audio/audio_output_controller_unittest.cc55
-rw-r--r--media/audio/mock_audio_manager.cc8
-rw-r--r--media/audio/mock_audio_manager.h5
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();