audio_device_listener_mac.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "media/audio/mac/audio_device_listener_mac.h" 6 7#include "base/bind.h" 8#include "base/files/file_path.h" 9#include "base/logging.h" 10#include "base/mac/libdispatch_task_runner.h" 11#include "base/mac/mac_logging.h" 12#include "base/mac/mac_util.h" 13#include "base/message_loop.h" 14#include "base/pending_task.h" 15#include "media/audio/mac/audio_low_latency_output_mac.h" 16 17namespace media { 18 19// TaskObserver which guarantees that dispatch queue operations such as property 20// listener callbacks are mutually exclusive to operations on the audio thread. 21// TODO(dalecurtis): Instead we should replace the main thread with a dispatch 22// queue. See http://crbug.com/158170. 23class ExclusiveDispatchQueueTaskObserver : public MessageLoop::TaskObserver { 24 public: 25 ExclusiveDispatchQueueTaskObserver() 26 : property_listener_queue_(new base::mac::LibDispatchTaskRunner( 27 "com.google.chrome.AudioPropertyListenerQueue")), 28 queue_(property_listener_queue_->GetDispatchQueue()), 29 message_loop_(MessageLoop::current()) { 30 // If we're currently on the thread, fire the suspend operation so we don't 31 // end up with an unbalanced resume. 32 if (message_loop_->message_loop_proxy()->BelongsToCurrentThread()) 33 SuspendDispatchQueue(); 34 35 message_loop_->AddTaskObserver(this); 36 } 37 38 virtual ~ExclusiveDispatchQueueTaskObserver() { 39 message_loop_->RemoveTaskObserver(this); 40 41 // If we're currently on the thread, fire the resume operation so we don't 42 // end up with an unbalanced suspend. 43 if (message_loop_->message_loop_proxy()->BelongsToCurrentThread()) 44 ResumeDispatchQueue(); 45 46 // This will hang if any listeners are still registered with the queue. 47 property_listener_queue_->Shutdown(); 48 } 49 50 virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE { 51 SuspendDispatchQueue(); 52 } 53 54 virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE { 55 ResumeDispatchQueue(); 56 } 57 58 dispatch_queue_t dispatch_queue() const { 59 return queue_; 60 } 61 62 private: 63 // Issue a synchronous suspend operation. Benchmarks on a retina 10.8.2 64 // machine show this takes < 20us on average. dispatch_suspend() is an 65 // asynchronous operation so we need to issue it inside of a synchronous block 66 // to ensure it completes before WillProccesTask() completes. 67 void SuspendDispatchQueue() { 68 dispatch_sync(queue_, ^{ 69 dispatch_suspend(queue_); 70 }); 71 } 72 73 // Issue an asynchronous resume operation. Benchmarks on a retina 10.8.2 74 // machine show this takes < 10us on average. 75 void ResumeDispatchQueue() { 76 dispatch_resume(queue_); 77 } 78 79 scoped_refptr<base::mac::LibDispatchTaskRunner> property_listener_queue_; 80 const dispatch_queue_t queue_; 81 MessageLoop* message_loop_; 82 83 DISALLOW_COPY_AND_ASSIGN(ExclusiveDispatchQueueTaskObserver); 84}; 85 86// Property address to monitor for device changes. 87const AudioObjectPropertyAddress 88AudioDeviceListenerMac::kDeviceChangePropertyAddress = { 89 kAudioHardwarePropertyDefaultOutputDevice, 90 kAudioObjectPropertyScopeGlobal, 91 kAudioObjectPropertyElementMaster 92}; 93 94// Callback from the system when the default device changes; this must be called 95// on the MessageLoop that created the AudioManager. 96// static 97OSStatus AudioDeviceListenerMac::OnDefaultDeviceChanged( 98 AudioObjectID object, UInt32 num_addresses, 99 const AudioObjectPropertyAddress addresses[], void* context) { 100 if (object != kAudioObjectSystemObject) 101 return noErr; 102 103 for (UInt32 i = 0; i < num_addresses; ++i) { 104 if (addresses[i].mSelector == kDeviceChangePropertyAddress.mSelector && 105 addresses[i].mScope == kDeviceChangePropertyAddress.mScope && 106 addresses[i].mElement == kDeviceChangePropertyAddress.mElement && 107 context) { 108 AudioDeviceListenerMac* p_this = 109 static_cast<AudioDeviceListenerMac*>(context); 110 // Device changes on Mac are risky, the OSX API is not thread safe, so 111 // only change devices if we have to. Again, see http://crbug.com/158170. 112 // TODO(crogers): Remove this once the AUHAL output driver is in. 113 int sample_rate = AUAudioOutputStream::HardwareSampleRate(); 114 if (p_this->current_sample_rate_ != sample_rate) { 115 p_this->current_sample_rate_ = sample_rate; 116 p_this->listener_cb_.Run(); 117 } 118 break; 119 } 120 } 121 122 return noErr; 123} 124 125AudioDeviceListenerMac::AudioDeviceListenerMac(const base::Closure& listener_cb) 126 : listener_block_(NULL), 127 add_listener_block_func_(NULL), 128 remove_listener_block_func_(NULL), 129 current_sample_rate_(AUAudioOutputStream::HardwareSampleRate()) { 130 // Device changes are hard, lets go shopping! Sadly OSX does not handle 131 // property listener callbacks in a thread safe manner. On 10.6 we can set 132 // kAudioHardwarePropertyRunLoop to account for this. On 10.7 this is broken 133 // and we need to create a dispatch queue to drive callbacks. However code 134 // running on the dispatch queue must be mutually exclusive with code running 135 // on the audio thread. ExclusiveDispatchQueueTaskObserver works around this 136 // by pausing and resuming the dispatch queue before and after each pumped 137 // task. This is not ideal and long term we should replace the audio thread 138 // on OSX with a dispatch queue. See http://crbug.com/158170 for discussion. 139 // TODO(dalecurtis): Does not fix the cases where 140 // GetDefaultOutputStreamParameters() are called by the browser process. 141 // These are one time events due to renderer side cache and thus unlikely to 142 // occur at the same time as a device callback. Should be fixed along with 143 // http://crbug.com/137326 using a forced PostTask. 144 if (base::mac::IsOSLionOrLater()) { 145 if (!LoadAudioObjectPropertyListenerBlockFunctions()) 146 return; 147 148 task_observer_.reset(new ExclusiveDispatchQueueTaskObserver()); 149 listener_block_ = Block_copy(^( 150 UInt32 num_addresses, const AudioObjectPropertyAddress addresses[]) { 151 OnDefaultDeviceChanged( 152 kAudioObjectSystemObject, num_addresses, addresses, this); 153 }); 154 155 OSStatus result = add_listener_block_func_( 156 kAudioObjectSystemObject, &kDeviceChangePropertyAddress, 157 task_observer_->dispatch_queue(), listener_block_); 158 159 if (result != noErr) { 160 task_observer_.reset(); 161 Block_release(listener_block_); 162 listener_block_ = NULL; 163 OSSTATUS_DLOG(ERROR, result) 164 << "AudioObjectAddPropertyListenerBlock() failed!"; 165 return; 166 } 167 } else { 168 const AudioObjectPropertyAddress kRunLoopAddress = { 169 kAudioHardwarePropertyRunLoop, 170 kAudioObjectPropertyScopeGlobal, 171 kAudioObjectPropertyElementMaster 172 }; 173 174 CFRunLoopRef run_loop = CFRunLoopGetCurrent(); 175 UInt32 size = sizeof(CFRunLoopRef); 176 OSStatus result = AudioObjectSetPropertyData( 177 kAudioObjectSystemObject, &kRunLoopAddress, 0, 0, size, &run_loop); 178 if (result != noErr) { 179 OSSTATUS_DLOG(ERROR, result) << "Failed to set property listener thread."; 180 return; 181 } 182 183 result = AudioObjectAddPropertyListener( 184 kAudioObjectSystemObject, &kDeviceChangePropertyAddress, 185 &AudioDeviceListenerMac::OnDefaultDeviceChanged, this); 186 187 if (result != noErr) { 188 OSSTATUS_DLOG(ERROR, result) 189 << "AudioObjectAddPropertyListener() failed!"; 190 return; 191 } 192 } 193 194 listener_cb_ = listener_cb; 195} 196 197AudioDeviceListenerMac::~AudioDeviceListenerMac() { 198 DCHECK(thread_checker_.CalledOnValidThread()); 199 if (listener_cb_.is_null()) 200 return; 201 202 if (task_observer_) { 203 OSStatus result = remove_listener_block_func_( 204 kAudioObjectSystemObject, &kDeviceChangePropertyAddress, 205 task_observer_->dispatch_queue(), listener_block_); 206 207 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) 208 << "AudioObjectRemovePropertyListenerBlock() failed!"; 209 210 // Task observer will wait for all outstanding callbacks to complete. 211 task_observer_.reset(); 212 Block_release(listener_block_); 213 return; 214 } 215 216 // Since we're running on the same CFRunLoop, there can be no outstanding 217 // callbacks in flight. 218 OSStatus result = AudioObjectRemovePropertyListener( 219 kAudioObjectSystemObject, &kDeviceChangePropertyAddress, 220 &AudioDeviceListenerMac::OnDefaultDeviceChanged, this); 221 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) 222 << "AudioObjectRemovePropertyListener() failed!"; 223} 224 225bool AudioDeviceListenerMac::LoadAudioObjectPropertyListenerBlockFunctions() { 226 // Dynamically load required block functions. 227 // TODO(dalecurtis): Remove once the deployment target is > 10.6. 228 std::string error; 229 base::NativeLibrary core_audio = base::LoadNativeLibrary(base::FilePath( 230 "/System/Library/Frameworks/CoreAudio.framework/Versions/Current/" 231 "CoreAudio"), &error); 232 if (!error.empty()) { 233 LOG(ERROR) << "Could not open CoreAudio library: " << error; 234 return false; 235 } 236 237 core_audio_lib_.Reset(core_audio); 238 add_listener_block_func_ = 239 reinterpret_cast<AudioObjectPropertyListenerBlockT>( 240 core_audio_lib_.GetFunctionPointer( 241 "AudioObjectAddPropertyListenerBlock")); 242 remove_listener_block_func_ = 243 reinterpret_cast<AudioObjectPropertyListenerBlockT>( 244 core_audio_lib_.GetFunctionPointer( 245 "AudioObjectRemovePropertyListenerBlock")); 246 247 // If either function failed to load, skip listener registration. 248 if (!add_listener_block_func_ || !remove_listener_block_func_) { 249 DLOG(ERROR) << "Failed to load audio property listener block functions!"; 250 return false; 251 } 252 253 return true; 254} 255 256} // namespace media 257