diff options
author | dalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-11 01:39:08 +0000 |
---|---|---|
committer | dalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-11 01:39:08 +0000 |
commit | 930512c170b552b3f718ad947a05ec306ce6271d (patch) | |
tree | f0c9dfd8a43a447b7eac1fd921e26caa238c6cc4 /media | |
parent | f0e4ea57f792dc5eac03519b4c7707b22e28f2c8 (diff) | |
download | chromium_src-930512c170b552b3f718ad947a05ec306ce6271d.zip chromium_src-930512c170b552b3f718ad947a05ec306ce6271d.tar.gz chromium_src-930512c170b552b3f718ad947a05ec306ce6271d.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.
Review URL: https://codereview.chromium.org/11824018
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@176228 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/audio_manager_base.cc | 7 | ||||
-rw-r--r-- | media/audio/mac/audio_device_listener_mac.cc | 243 | ||||
-rw-r--r-- | media/audio/mac/audio_device_listener_mac.h | 59 | ||||
-rw-r--r-- | media/audio/mac/audio_device_listener_mac_unittest.cc | 82 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.cc | 87 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.h | 12 | ||||
-rw-r--r-- | media/audio/win/audio_manager_win.h | 2 | ||||
-rw-r--r-- | media/media.gyp | 3 |
8 files changed, 419 insertions, 76 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..a868e05 --- /dev/null +++ b/media/audio/mac/audio_device_listener_mac.cc @@ -0,0 +1,243 @@ +// 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" +#include "base/native_library.h" + +namespace media { + +// The following three variables are used for dynamically loading the required +// AudioObject(Add|Remove)PropertyListenerBlock functions on 10.7+ while keeping +// a 10.6 deployment target. See http://crbug.com/158170 for details on why. +// TODO(dalecurtis): Remove once the deployment target is > 10.6. +static const char kCoreAudioFrameworkPath[] = + "/System/Library/Frameworks/CoreAudio.framework/Versions/Current/CoreAudio"; + +typedef OSStatus(*AudioObjectPropertyListenerBlockT)( + AudioObjectID inObjectID, + const AudioObjectPropertyAddress* inAddress, + dispatch_queue_t inDispatchQueue, + CrAudioObjectPropertyListenerBlock inListener); + +static AudioObjectPropertyListenerBlockT g_add_listener_block_func = NULL; +static AudioObjectPropertyListenerBlockT g_remove_listener_block_func = NULL; + +// 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); +}; + +static bool LoadAudioObjectPropertyListenerBlockFunctions() { + if (g_add_listener_block_func && g_remove_listener_block_func) + return true; + + // 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(kCoreAudioFrameworkPath), &error); + if (!error.empty()) { + LOG(ERROR) << "Could not open CoreAudio library: " << error; + return false; + } + + g_add_listener_block_func = + reinterpret_cast<AudioObjectPropertyListenerBlockT>( + base::GetFunctionPointerFromNativeLibrary( + core_audio, "AudioObjectAddPropertyListenerBlock")); + g_remove_listener_block_func = + reinterpret_cast<AudioObjectPropertyListenerBlockT>( + base::GetFunctionPointerFromNativeLibrary( + core_audio, "AudioObjectRemovePropertyListenerBlock")); + + // If either function failed to load, skip listener registration. + if (!g_add_listener_block_func || !g_remove_listener_block_func) { + g_add_listener_block_func = NULL; + g_remove_listener_block_func = NULL; + return false; + } + + return true; +} + +// 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) { + if (!LoadAudioObjectPropertyListenerBlockFunctions()) + return; + + // 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 our 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. + OSStatus result; + if (base::mac::IsOSLionOrLater()) { + task_observer_.reset(new ExclusiveDispatchQueueTaskObserver()); + listener_block_ = Block_copy(^( + UInt32 num_addresses, const AudioObjectPropertyAddress addresses[]) { + OnDefaultDeviceChanged( + kAudioObjectSystemObject, num_addresses, addresses, this); + }); + result = g_add_listener_block_func( + kAudioObjectSystemObject, &kDeviceChangePropertyAddress, + task_observer_->dispatch_queue(), listener_block_); + } else { + const AudioObjectPropertyAddress kRunLoopAddress = { + kAudioHardwarePropertyRunLoop, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + CFRunLoopRef run_loop = CFRunLoopGetCurrent(); + UInt32 size = sizeof(CFRunLoopRef); + 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) { + task_observer_.reset(); + 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 = g_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!"; +} + +} // 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..b944817 --- /dev/null +++ b/media/audio/mac/audio_device_listener_mac.h @@ -0,0 +1,59 @@ +// 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/threading/thread_checker.h" +#include "media/base/media_export.h" + +namespace media { + +// TODO(dalecurtis): Use the standard AudioObjectPropertyListenerBlock when +// the deployment target is > 10.6. +typedef void (^CrAudioObjectPropertyListenerBlock)( + UInt32 num_addresses, const AudioObjectPropertyAddress addresses[]); + +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); + + base::Closure listener_cb_; + CrAudioObjectPropertyListenerBlock listener_block_; + scoped_ptr<ExclusiveDispatchQueueTaskObserver> task_observer_; + + // 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..cff3b05 --- /dev/null +++ b/media/audio/mac/audio_device_listener_mac_unittest.cc @@ -0,0 +1,82 @@ +// 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(); + } + + // 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) { + 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/audio/win/audio_manager_win.h b/media/audio/win/audio_manager_win.h index 4ce2fbc..61bd8a6 100644 --- a/media/audio/win/audio_manager_win.h +++ b/media/audio/win/audio_manager_win.h @@ -1,4 +1,4 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// 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. diff --git a/media/media.gyp b/media/media.gyp index 05b35cc..963369d 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -111,6 +111,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', @@ -667,6 +669,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', |