summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authordalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-11 23:46:48 +0000
committerdalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-11 23:46:48 +0000
commit411792516e3eb09fd525f11c848545b25879aae9 (patch)
tree44ca5f8b042e69be50c72e2f56afb0f9db55f792 /media
parentb22fedc8f02ea3a6c2e451c432ee7514e1b746c2 (diff)
downloadchromium_src-411792516e3eb09fd525f11c848545b25879aae9.zip
chromium_src-411792516e3eb09fd525f11c848545b25879aae9.tar.gz
chromium_src-411792516e3eb09fd525f11c848545b25879aae9.tar.bz2
Improve thread safety for device changes on OSX.
Device changes are hard, lets go shopping! Sadly OSX does not handle property listener callbacks in a thread safe manner. On 10.6 we can set kAudioHardwarePropertyRunLoop to account for this. On 10.7 this is broken and we need to create a dispatch queue to drive callbacks. However code running on the dispatch queue must be mutually exclusive with code running on the audio thread. ExclusiveDispatchQueueTaskObserver works around this by pausing and resuming the dispatch queue before and after each pumped task. Benchmarks on my retina 10.8.2 macbook pro show on average this takes < 30us in total. No performance issues were seen even using FakeAudioOutputStream which drives audio callbacks on the message loop. Complicating matters it the fact that the AudioProperty(Add|Remove)ListenerBlock functions are not available on 10.6... so we need to dynamically load them :-/ This is not ideal and long term we should replace the audio thread on OSX with a dispatch queue to avoid having to suspend/resume the queue; this is non-trivial and requires a significant refactoring of the AudioManager to support. BUG=158170 TEST=media_unittests. manual device changes. Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=176228 Review URL: https://codereview.chromium.org/11824018 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@176474 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/audio/audio_manager_base.cc7
-rw-r--r--media/audio/mac/audio_device_listener_mac.cc235
-rw-r--r--media/audio/mac/audio_device_listener_mac.h76
-rw-r--r--media/audio/mac/audio_device_listener_mac_unittest.cc87
-rw-r--r--media/audio/mac/audio_manager_mac.cc87
-rw-r--r--media/audio/mac/audio_manager_mac.h12
-rw-r--r--media/media.gyp3
7 files changed, 432 insertions, 75 deletions
diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc
index 458e53b..41c226d 100644
--- a/media/audio/audio_manager_base.cc
+++ b/media/audio/audio_manager_base.cc
@@ -54,7 +54,14 @@ AudioManagerBase::AudioManagerBase()
#if defined(OS_WIN)
audio_thread_->init_com_with_mta(true);
#endif
+#if defined(OS_MACOSX)
+ // On Mac, use a UI loop to get native message pump so that CoreAudio property
+ // listener callbacks fire.
+ CHECK(audio_thread_->StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_UI, 0)));
+#else
CHECK(audio_thread_->Start());
+#endif
message_loop_ = audio_thread_->message_loop_proxy();
}
diff --git a/media/audio/mac/audio_device_listener_mac.cc b/media/audio/mac/audio_device_listener_mac.cc
new file mode 100644
index 0000000..8aedf45
--- /dev/null
+++ b/media/audio/mac/audio_device_listener_mac.cc
@@ -0,0 +1,235 @@
+// Copyright (c) 2013 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/mac/audio_device_listener_mac.h"
+
+#include "base/file_path.h"
+#include "base/logging.h"
+#include "base/mac/libdispatch_task_runner.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/mac_util.h"
+#include "base/message_loop.h"
+
+namespace media {
+
+// TaskObserver which guarantees that dispatch queue operations such as property
+// listener callbacks are mutually exclusive to operations on the audio thread.
+// TODO(dalecurtis): Instead we should replace the main thread with a dispatch
+// queue. See http://crbug.com/158170.
+class ExclusiveDispatchQueueTaskObserver : public MessageLoop::TaskObserver {
+ public:
+ ExclusiveDispatchQueueTaskObserver()
+ : property_listener_queue_(new base::mac::LibDispatchTaskRunner(
+ "com.google.chrome.AudioPropertyListenerQueue")),
+ queue_(property_listener_queue_->GetDispatchQueue()),
+ message_loop_(MessageLoop::current()) {
+ // If we're currently on the thread, fire the suspend operation so we don't
+ // end up with an unbalanced resume.
+ if (message_loop_->message_loop_proxy()->BelongsToCurrentThread())
+ WillProcessTask(base::TimeTicks());
+
+ message_loop_->AddTaskObserver(this);
+ }
+
+ virtual ~ExclusiveDispatchQueueTaskObserver() {
+ message_loop_->RemoveTaskObserver(this);
+
+ // If we're currently on the thread, fire the resume operation so we don't
+ // end up with an unbalanced suspend.
+ if (message_loop_->message_loop_proxy()->BelongsToCurrentThread())
+ DidProcessTask(base::TimeTicks());
+
+ // This will hang if any listeners are still registered with the queue.
+ property_listener_queue_->Shutdown();
+ }
+
+ virtual void WillProcessTask(base::TimeTicks time_posted) OVERRIDE {
+ // Issue a synchronous suspend operation. Benchmarks on a retina 10.8.2
+ // machine show this takes < 20us on average. dispatch_suspend() is an
+ // asynchronous operation so we need to issue it inside of a synchronous
+ // block to ensure it completes before WillProccesTask() completes.
+ dispatch_sync(queue_, ^{
+ dispatch_suspend(queue_);
+ });
+ }
+
+ virtual void DidProcessTask(base::TimeTicks time_posted) OVERRIDE {
+ // Issue an asynchronous resume operation. Benchmarks on a retina 10.8.2
+ // machine show this takes < 10us on average.
+ dispatch_resume(queue_);
+ }
+
+ dispatch_queue_t dispatch_queue() const {
+ return queue_;
+ }
+
+ private:
+ scoped_refptr<base::mac::LibDispatchTaskRunner> property_listener_queue_;
+ const dispatch_queue_t queue_;
+ MessageLoop* message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExclusiveDispatchQueueTaskObserver);
+};
+
+// Property address to monitor for device changes.
+const AudioObjectPropertyAddress
+AudioDeviceListenerMac::kDeviceChangePropertyAddress = {
+ kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+};
+
+// Callback from the system when the default device changes; this must be called
+// on the MessageLoop that created the AudioManager.
+// static
+OSStatus AudioDeviceListenerMac::OnDefaultDeviceChanged(
+ AudioObjectID object, UInt32 num_addresses,
+ const AudioObjectPropertyAddress addresses[], void* context) {
+ if (object != kAudioObjectSystemObject)
+ return noErr;
+
+ for (UInt32 i = 0; i < num_addresses; ++i) {
+ if (addresses[i].mSelector == kDeviceChangePropertyAddress.mSelector &&
+ addresses[i].mScope == kDeviceChangePropertyAddress.mScope &&
+ addresses[i].mElement == kDeviceChangePropertyAddress.mElement &&
+ context) {
+ static_cast<AudioDeviceListenerMac*>(context)->listener_cb_.Run();
+ break;
+ }
+ }
+
+ return noErr;
+}
+
+AudioDeviceListenerMac::AudioDeviceListenerMac(const base::Closure& listener_cb)
+ : listener_block_(NULL),
+ add_listener_block_func_(NULL),
+ remove_listener_block_func_(NULL) {
+ // Device changes are hard, lets go shopping! Sadly OSX does not handle
+ // property listener callbacks in a thread safe manner. On 10.6 we can set
+ // kAudioHardwarePropertyRunLoop to account for this. On 10.7 this is broken
+ // and we need to create a dispatch queue to drive callbacks. However code
+ // running on the dispatch queue must be mutually exclusive with code running
+ // on the audio thread. ExclusiveDispatchQueueTaskObserver works around this
+ // by pausing and resuming the dispatch queue before and after each pumped
+ // task. This is not ideal and long term we should replace the audio thread
+ // on OSX with a dispatch queue. See http://crbug.com/158170 for discussion.
+ // TODO(dalecurtis): Does not fix the cases where GetAudioHardwareSampleRate()
+ // and GetAudioInputHardwareSampleRate() are called by the browser process.
+ // These are one time events due to renderer side cache and thus unlikely to
+ // occur at the same time as a device callback. Should be fixed along with
+ // http://crbug.com/137326 using a forced PostTask.
+ if (base::mac::IsOSLionOrLater()) {
+ if (!LoadAudioObjectPropertyListenerBlockFunctions())
+ return;
+
+ task_observer_.reset(new ExclusiveDispatchQueueTaskObserver());
+ listener_block_ = Block_copy(^(
+ UInt32 num_addresses, const AudioObjectPropertyAddress addresses[]) {
+ OnDefaultDeviceChanged(
+ kAudioObjectSystemObject, num_addresses, addresses, this);
+ });
+
+ OSStatus result = add_listener_block_func_(
+ kAudioObjectSystemObject, &kDeviceChangePropertyAddress,
+ task_observer_->dispatch_queue(), listener_block_);
+
+ if (result != noErr) {
+ task_observer_.reset();
+ Block_release(listener_block_);
+ listener_block_ = NULL;
+ OSSTATUS_DLOG(ERROR, result)
+ << "AudioObjectAddPropertyListenerBlock() failed!";
+ return;
+ }
+ } else {
+ const AudioObjectPropertyAddress kRunLoopAddress = {
+ kAudioHardwarePropertyRunLoop,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+
+ CFRunLoopRef run_loop = CFRunLoopGetCurrent();
+ UInt32 size = sizeof(CFRunLoopRef);
+ OSStatus result = AudioObjectSetPropertyData(
+ kAudioObjectSystemObject, &kRunLoopAddress, 0, 0, size, &run_loop);
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result) << "Failed to set property listener thread.";
+ return;
+ }
+
+ result = AudioObjectAddPropertyListener(
+ kAudioObjectSystemObject, &kDeviceChangePropertyAddress,
+ &AudioDeviceListenerMac::OnDefaultDeviceChanged, this);
+
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result)
+ << "AudioObjectAddPropertyListener() failed!";
+ return;
+ }
+ }
+
+ listener_cb_ = listener_cb;
+}
+
+AudioDeviceListenerMac::~AudioDeviceListenerMac() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (listener_cb_.is_null())
+ return;
+
+ if (task_observer_) {
+ OSStatus result = remove_listener_block_func_(
+ kAudioObjectSystemObject, &kDeviceChangePropertyAddress,
+ task_observer_->dispatch_queue(), listener_block_);
+
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "AudioObjectRemovePropertyListenerBlock() failed!";
+
+ // Task observer will wait for all outstanding callbacks to complete.
+ task_observer_.reset();
+ Block_release(listener_block_);
+ return;
+ }
+
+ // Since we're running on the same CFRunLoop, there can be no outstanding
+ // callbacks in flight.
+ OSStatus result = AudioObjectRemovePropertyListener(
+ kAudioObjectSystemObject, &kDeviceChangePropertyAddress,
+ &AudioDeviceListenerMac::OnDefaultDeviceChanged, this);
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "AudioObjectRemovePropertyListener() failed!";
+}
+
+bool AudioDeviceListenerMac::LoadAudioObjectPropertyListenerBlockFunctions() {
+ // Dynamically load required block functions.
+ // TODO(dalecurtis): Remove once the deployment target is > 10.6.
+ std::string error;
+ base::NativeLibrary core_audio = base::LoadNativeLibrary(FilePath(
+ "/System/Library/Frameworks/CoreAudio.framework/Versions/Current/"
+ "CoreAudio"), &error);
+ if (!error.empty()) {
+ LOG(ERROR) << "Could not open CoreAudio library: " << error;
+ return false;
+ }
+
+ core_audio_lib_.Reset(core_audio);
+ add_listener_block_func_ =
+ reinterpret_cast<AudioObjectPropertyListenerBlockT>(
+ core_audio_lib_.GetFunctionPointer(
+ "AudioObjectAddPropertyListenerBlock"));
+ remove_listener_block_func_ =
+ reinterpret_cast<AudioObjectPropertyListenerBlockT>(
+ core_audio_lib_.GetFunctionPointer(
+ "AudioObjectRemovePropertyListenerBlock"));
+
+ // If either function failed to load, skip listener registration.
+ if (!add_listener_block_func_ || !remove_listener_block_func_) {
+ DLOG(ERROR) << "Failed to load audio property listener block functions!";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace media
diff --git a/media/audio/mac/audio_device_listener_mac.h b/media/audio/mac/audio_device_listener_mac.h
new file mode 100644
index 0000000..d2d5870
--- /dev/null
+++ b/media/audio/mac/audio_device_listener_mac.h
@@ -0,0 +1,76 @@
+// 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.
+
+#ifndef MEDIA_AUDIO_MAC_AUDIO_DEVICE_LISTENER_MAC_H_
+#define MEDIA_AUDIO_MAC_AUDIO_DEVICE_LISTENER_MAC_H_
+
+#include <CoreAudio/AudioHardware.h>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/scoped_native_library.h"
+#include "base/threading/thread_checker.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class ExclusiveDispatchQueueTaskObserver;
+
+// AudioDeviceListenerMac facilitates thread safe execution of device listener
+// callbacks issued via CoreAudio. On 10.6 this means correctly binding the
+// CFRunLoop driving callbacks to the CFRunLoop AudioDeviceListenerMac is
+// constructed on. On 10.7 this means creating a dispatch queue to drive
+// callbacks and modifying the message loop AudioDeviceListenerMac to ensure
+// message loop tasks and dispatch queue tasks are executed in a mutually
+// exclusive manner. See http://crbug.com/158170 for more discussion.
+class MEDIA_EXPORT AudioDeviceListenerMac {
+ public:
+ // |listener_cb| will be called when a device change occurs; it's a permanent
+ // callback and must outlive AudioDeviceListenerMac.
+ explicit AudioDeviceListenerMac(const base::Closure& listener_cb);
+ ~AudioDeviceListenerMac();
+
+ private:
+ friend class AudioDeviceListenerMacTest;
+ static const AudioObjectPropertyAddress kDeviceChangePropertyAddress;
+
+ static OSStatus OnDefaultDeviceChanged(
+ AudioObjectID object, UInt32 num_addresses,
+ const AudioObjectPropertyAddress addresses[], void* context);
+
+ // Attempts to dynamically load the property listener block functions. Sets
+ // |core_audio_lib_|, |add_listener_block_func_|, and
+ // |remove_listener_block_func_|. Returns false on failure. If successful,
+ // the unload function must be called to avoid memory leaks.
+ bool LoadAudioObjectPropertyListenerBlockFunctions();
+
+ base::Closure listener_cb_;
+ scoped_ptr<ExclusiveDispatchQueueTaskObserver> task_observer_;
+ base::ScopedNativeLibrary core_audio_lib_;
+
+ // TODO(dalecurtis): Use the standard AudioObjectPropertyListenerBlock when
+ // the deployment target is > 10.6.
+ typedef void (^CrAudioObjectPropertyListenerBlock)(
+ UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[]);
+ CrAudioObjectPropertyListenerBlock listener_block_;
+
+ // Variables for storing dynamically loaded block functions.
+ typedef OSStatus(*AudioObjectPropertyListenerBlockT)(
+ AudioObjectID inObjectID,
+ const AudioObjectPropertyAddress* inAddress,
+ dispatch_queue_t inDispatchQueue,
+ CrAudioObjectPropertyListenerBlock inListener);
+ AudioObjectPropertyListenerBlockT add_listener_block_func_;
+ AudioObjectPropertyListenerBlockT remove_listener_block_func_;
+
+ // AudioDeviceListenerMac must be constructed and destructed on the same
+ // thread.
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDeviceListenerMac);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_AUDIO_DEVICE_LISTENER_MAC_H_
diff --git a/media/audio/mac/audio_device_listener_mac_unittest.cc b/media/audio/mac/audio_device_listener_mac_unittest.cc
new file mode 100644
index 0000000..1f884bc
--- /dev/null
+++ b/media/audio/mac/audio_device_listener_mac_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2013 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 <CoreAudio/AudioHardware.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "media/audio/mac/audio_device_listener_mac.h"
+#include "media/base/bind_to_loop.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class AudioDeviceListenerMacTest : public testing::Test {
+ public:
+ AudioDeviceListenerMacTest() {
+ // It's important to create the device listener from the message loop in
+ // order to ensure we don't end up with unbalanced TaskObserver calls.
+ message_loop_.PostTask(FROM_HERE, base::Bind(
+ &AudioDeviceListenerMacTest::CreateDeviceListener,
+ base::Unretained(this)));
+ message_loop_.RunUntilIdle();
+ }
+
+ virtual ~AudioDeviceListenerMacTest() {
+ // It's important to destroy the device listener from the message loop in
+ // order to ensure we don't end up with unbalanced TaskObserver calls.
+ message_loop_.PostTask(FROM_HERE, base::Bind(
+ &AudioDeviceListenerMacTest::DestroyDeviceListener,
+ base::Unretained(this)));
+ message_loop_.RunUntilIdle();
+ }
+
+ void CreateDeviceListener() {
+ // Force a post task using BindToLoop to ensure device listener internals
+ // are working correctly.
+ output_device_listener_.reset(new AudioDeviceListenerMac(BindToLoop(
+ message_loop_.message_loop_proxy(), base::Bind(
+ &AudioDeviceListenerMacTest::OnDeviceChange,
+ base::Unretained(this)))));
+ }
+
+ void DestroyDeviceListener() {
+ output_device_listener_.reset();
+ }
+
+ bool ListenerIsValid() {
+ return !output_device_listener_->listener_cb_.is_null();
+ }
+
+ // Simulate a device change where no output devices are available.
+ bool SimulateDefaultOutputDeviceChange() {
+ // Include multiple addresses to ensure only a single device change event
+ // occurs.
+ const AudioObjectPropertyAddress addresses[] = {
+ AudioDeviceListenerMac::kDeviceChangePropertyAddress,
+ { kAudioHardwarePropertyDevices,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster }
+ };
+
+ return noErr == output_device_listener_->OnDefaultDeviceChanged(
+ kAudioObjectSystemObject, 1, addresses, output_device_listener_.get());
+ }
+
+ MOCK_METHOD0(OnDeviceChange, void());
+
+ protected:
+ MessageLoop message_loop_;
+ scoped_ptr<AudioDeviceListenerMac> output_device_listener_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDeviceListenerMacTest);
+};
+
+// Simulate a device change events and ensure we get the right callbacks.
+TEST_F(AudioDeviceListenerMacTest, OutputDeviceChange) {
+ ASSERT_TRUE(ListenerIsValid());
+ EXPECT_CALL(*this, OnDeviceChange()).Times(1);
+ ASSERT_TRUE(SimulateDefaultOutputDeviceChange());
+ message_loop_.RunUntilIdle();
+}
+
+} // namespace media
diff --git a/media/audio/mac/audio_manager_mac.cc b/media/audio/mac/audio_manager_mac.cc
index e0300ed..288ac14 100644
--- a/media/audio/mac/audio_manager_mac.cc
+++ b/media/audio/mac/audio_manager_mac.cc
@@ -233,74 +233,20 @@ static AudioDeviceID GetAudioDeviceIdByUId(bool is_input,
return audio_device_id;
}
-// Property address to monitor for device changes.
-static const AudioObjectPropertyAddress kDeviceChangePropertyAddress = {
- kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
-};
-
-// Callback from the system when the default device changes; this must be called
-// on the MessageLoop that created the AudioManager.
-static OSStatus OnDefaultDeviceChangedCallback(
- AudioObjectID object,
- UInt32 num_addresses,
- const AudioObjectPropertyAddress addresses[],
- void* context) {
- if (object != kAudioObjectSystemObject)
- return noErr;
-
- for (UInt32 i = 0; i < num_addresses; ++i) {
- if (addresses[i].mSelector == kDeviceChangePropertyAddress.mSelector &&
- addresses[i].mScope == kDeviceChangePropertyAddress.mScope &&
- addresses[i].mElement == kDeviceChangePropertyAddress.mElement &&
- context) {
- static_cast<AudioManagerMac*>(context)->OnDeviceChange();
- break;
- }
- }
-
- return noErr;
-}
-
-AudioManagerMac::AudioManagerMac()
- : listener_registered_(false),
- creating_message_loop_(base::MessageLoopProxy::current()) {
+AudioManagerMac::AudioManagerMac() {
SetMaxOutputStreamsAllowed(kMaxOutputStreams);
- // AudioManagerMac is expected to be created by the root platform thread, this
- // is generally BrowserMainLoop, it's MessageLoop will drive the NSApplication
- // pump which in turn fires the property listener callbacks.
- if (!creating_message_loop_)
- return;
-
- OSStatus result = AudioObjectAddPropertyListener(
- kAudioObjectSystemObject,
- &kDeviceChangePropertyAddress,
- &OnDefaultDeviceChangedCallback,
- this);
-
- if (result != noErr) {
- OSSTATUS_DLOG(ERROR, result) << "AudioObjectAddPropertyListener() failed!";
- return;
- }
-
- listener_registered_ = true;
+ // Task must be posted last to avoid races from handing out "this" to the
+ // audio thread.
+ GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
+ &AudioManagerMac::CreateDeviceListener, base::Unretained(this)));
}
AudioManagerMac::~AudioManagerMac() {
- if (listener_registered_) {
- // TODO(dalecurtis): CHECK destruction happens on |creating_message_loop_|,
- // should be true, but currently several unit tests perform destruction in
- // odd places so we can't CHECK here currently.
- OSStatus result = AudioObjectRemovePropertyListener(
- kAudioObjectSystemObject,
- &kDeviceChangePropertyAddress,
- &OnDefaultDeviceChangedCallback,
- this);
- OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
- << "AudioObjectRemovePropertyListener() failed!";
- }
+ // It's safe to post a task here since Shutdown() will wait for all tasks to
+ // complete before returning.
+ GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
+ &AudioManagerMac::DestroyDeviceListener, base::Unretained(this)));
Shutdown();
}
@@ -389,14 +335,17 @@ AudioParameters AudioManagerMac::GetPreferredLowLatencyOutputStreamParameters(
input_params);
}
-void AudioManagerMac::OnDeviceChange() {
- // Post the task to the |creating_message_loop_| to execute our listener
- // callback. The callback is created using BindToLoop() so will hop over
- // to the audio thread upon execution.
- creating_message_loop_->PostTask(FROM_HERE, BindToLoop(
+void AudioManagerMac::CreateDeviceListener() {
+ DCHECK(GetMessageLoop()->BelongsToCurrentThread());
+ output_device_listener_.reset(new AudioDeviceListenerMac(BindToLoop(
GetMessageLoop(), base::Bind(
&AudioManagerMac::NotifyAllOutputDeviceChangeListeners,
- base::Unretained(this))));
+ base::Unretained(this)))));
+}
+
+void AudioManagerMac::DestroyDeviceListener() {
+ DCHECK(GetMessageLoop()->BelongsToCurrentThread());
+ output_device_listener_.reset();
}
AudioManager* CreateAudioManager() {
diff --git a/media/audio/mac/audio_manager_mac.h b/media/audio/mac/audio_manager_mac.h
index d8b6b2d..40ed54e 100644
--- a/media/audio/mac/audio_manager_mac.h
+++ b/media/audio/mac/audio_manager_mac.h
@@ -9,6 +9,7 @@
#include "base/compiler_specific.h"
#include "base/message_loop_proxy.h"
#include "media/audio/audio_manager_base.h"
+#include "media/audio/mac/audio_device_listener_mac.h"
namespace media {
@@ -37,16 +38,15 @@ class MEDIA_EXPORT AudioManagerMac : public AudioManagerBase {
virtual AudioParameters GetPreferredLowLatencyOutputStreamParameters(
const AudioParameters& input_params) OVERRIDE;
- // Called by an internal device change listener. Must be called on
- // |creating_message_loop_|.
- void OnDeviceChange();
-
protected:
virtual ~AudioManagerMac();
private:
- bool listener_registered_;
- scoped_refptr<base::MessageLoopProxy> creating_message_loop_;
+ // Helper methods for constructing AudioDeviceListenerMac on the audio thread.
+ void CreateDeviceListener();
+ void DestroyDeviceListener();
+
+ scoped_ptr<AudioDeviceListenerMac> output_device_listener_;
DISALLOW_COPY_AND_ASSIGN(AudioManagerMac);
};
diff --git a/media/media.gyp b/media/media.gyp
index feb1cc5..17447e5 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -112,6 +112,8 @@
'audio/linux/cras_input.h',
'audio/linux/cras_output.cc',
'audio/linux/cras_output.h',
+ 'audio/mac/audio_device_listener_mac.cc',
+ 'audio/mac/audio_device_listener_mac.h',
'audio/mac/audio_input_mac.cc',
'audio/mac/audio_input_mac.h',
'audio/mac/audio_low_latency_input_mac.cc',
@@ -668,6 +670,7 @@
'audio/fake_audio_output_stream_unittest.cc',
'audio/ios/audio_manager_ios_unittest.cc',
'audio/linux/alsa_output_unittest.cc',
+ 'audio/mac/audio_device_listener_mac_unittest.cc',
'audio/mac/audio_low_latency_input_mac_unittest.cc',
'audio/mac/audio_output_mac_unittest.cc',
'audio/simple_sources_unittest.cc',