audio_manager_mac.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 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 <CoreAudio/AudioHardware.h> 6 7#include <string> 8 9#include "base/bind.h" 10#include "base/command_line.h" 11#include "base/mac/mac_logging.h" 12#include "base/mac/scoped_cftyperef.h" 13#include "base/sys_string_conversions.h" 14#include "media/audio/mac/audio_input_mac.h" 15#include "media/audio/mac/audio_low_latency_input_mac.h" 16#include "media/audio/mac/audio_low_latency_output_mac.h" 17#include "media/audio/mac/audio_manager_mac.h" 18#include "media/audio/mac/audio_output_mac.h" 19#include "media/audio/mac/audio_synchronized_mac.h" 20#include "media/audio/mac/audio_unified_mac.h" 21#include "media/base/bind_to_loop.h" 22#include "media/base/limits.h" 23#include "media/base/media_switches.h" 24 25namespace media { 26 27// Maximum number of output streams that can be open simultaneously. 28static const int kMaxOutputStreams = 50; 29 30static bool HasAudioHardware(AudioObjectPropertySelector selector) { 31 AudioDeviceID output_device_id = kAudioObjectUnknown; 32 const AudioObjectPropertyAddress property_address = { 33 selector, 34 kAudioObjectPropertyScopeGlobal, // mScope 35 kAudioObjectPropertyElementMaster // mElement 36 }; 37 UInt32 output_device_id_size = static_cast<UInt32>(sizeof(output_device_id)); 38 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, 39 &property_address, 40 0, // inQualifierDataSize 41 NULL, // inQualifierData 42 &output_device_id_size, 43 &output_device_id); 44 return err == kAudioHardwareNoError && 45 output_device_id != kAudioObjectUnknown; 46} 47 48// Returns true if the default input device is the same as 49// the default output device. 50static bool HasUnifiedDefaultIO() { 51 AudioDeviceID input_id, output_id; 52 53 AudioObjectPropertyAddress pa; 54 pa.mSelector = kAudioHardwarePropertyDefaultInputDevice; 55 pa.mScope = kAudioObjectPropertyScopeGlobal; 56 pa.mElement = kAudioObjectPropertyElementMaster; 57 UInt32 size = sizeof(input_id); 58 59 // Get the default input. 60 OSStatus result = AudioObjectGetPropertyData( 61 kAudioObjectSystemObject, 62 &pa, 63 0, 64 0, 65 &size, 66 &input_id); 67 68 if (result != noErr) 69 return false; 70 71 // Get the default output. 72 pa.mSelector = kAudioHardwarePropertyDefaultOutputDevice; 73 result = AudioObjectGetPropertyData( 74 kAudioObjectSystemObject, 75 &pa, 76 0, 77 0, 78 &size, 79 &output_id); 80 81 if (result != noErr) 82 return false; 83 84 return input_id == output_id; 85} 86 87static void GetAudioDeviceInfo(bool is_input, 88 media::AudioDeviceNames* device_names) { 89 DCHECK(device_names); 90 device_names->clear(); 91 92 // Query the number of total devices. 93 AudioObjectPropertyAddress property_address = { 94 kAudioHardwarePropertyDevices, 95 kAudioObjectPropertyScopeGlobal, 96 kAudioObjectPropertyElementMaster 97 }; 98 UInt32 size = 0; 99 OSStatus result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, 100 &property_address, 101 0, 102 NULL, 103 &size); 104 if (result || !size) 105 return; 106 107 int device_count = size / sizeof(AudioDeviceID); 108 109 // Get the array of device ids for all the devices, which includes both 110 // input devices and output devices. 111 scoped_ptr_malloc<AudioDeviceID> 112 devices(reinterpret_cast<AudioDeviceID*>(malloc(size))); 113 AudioDeviceID* device_ids = devices.get(); 114 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, 115 &property_address, 116 0, 117 NULL, 118 &size, 119 device_ids); 120 if (result) 121 return; 122 123 // Iterate over all available devices to gather information. 124 for (int i = 0; i < device_count; ++i) { 125 // Get the number of input or output channels of the device. 126 property_address.mScope = is_input ? 127 kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; 128 property_address.mSelector = kAudioDevicePropertyStreams; 129 size = 0; 130 result = AudioObjectGetPropertyDataSize(device_ids[i], 131 &property_address, 132 0, 133 NULL, 134 &size); 135 if (result || !size) 136 continue; 137 138 // Get device UID. 139 CFStringRef uid = NULL; 140 size = sizeof(uid); 141 property_address.mSelector = kAudioDevicePropertyDeviceUID; 142 property_address.mScope = kAudioObjectPropertyScopeGlobal; 143 result = AudioObjectGetPropertyData(device_ids[i], 144 &property_address, 145 0, 146 NULL, 147 &size, 148 &uid); 149 if (result) 150 continue; 151 152 // Get device name. 153 CFStringRef name = NULL; 154 property_address.mSelector = kAudioObjectPropertyName; 155 property_address.mScope = kAudioObjectPropertyScopeGlobal; 156 result = AudioObjectGetPropertyData(device_ids[i], 157 &property_address, 158 0, 159 NULL, 160 &size, 161 &name); 162 if (result) { 163 if (uid) 164 CFRelease(uid); 165 continue; 166 } 167 168 // Store the device name and UID. 169 media::AudioDeviceName device_name; 170 device_name.device_name = base::SysCFStringRefToUTF8(name); 171 device_name.unique_id = base::SysCFStringRefToUTF8(uid); 172 device_names->push_back(device_name); 173 174 // We are responsible for releasing the returned CFObject. See the 175 // comment in the AudioHardware.h for constant 176 // kAudioDevicePropertyDeviceUID. 177 if (uid) 178 CFRelease(uid); 179 if (name) 180 CFRelease(name); 181 } 182} 183 184static AudioDeviceID GetAudioDeviceIdByUId(bool is_input, 185 const std::string& device_id) { 186 AudioObjectPropertyAddress property_address = { 187 kAudioHardwarePropertyDevices, 188 kAudioObjectPropertyScopeGlobal, 189 kAudioObjectPropertyElementMaster 190 }; 191 AudioDeviceID audio_device_id = kAudioObjectUnknown; 192 UInt32 device_size = sizeof(audio_device_id); 193 OSStatus result = -1; 194 195 if (device_id == AudioManagerBase::kDefaultDeviceId) { 196 // Default Device. 197 property_address.mSelector = is_input ? 198 kAudioHardwarePropertyDefaultInputDevice : 199 kAudioHardwarePropertyDefaultOutputDevice; 200 201 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, 202 &property_address, 203 0, 204 0, 205 &device_size, 206 &audio_device_id); 207 } else { 208 // Non-default device. 209 base::mac::ScopedCFTypeRef<CFStringRef> 210 uid(base::SysUTF8ToCFStringRef(device_id)); 211 AudioValueTranslation value; 212 value.mInputData = &uid; 213 value.mInputDataSize = sizeof(CFStringRef); 214 value.mOutputData = &audio_device_id; 215 value.mOutputDataSize = device_size; 216 UInt32 translation_size = sizeof(AudioValueTranslation); 217 218 property_address.mSelector = kAudioHardwarePropertyDeviceForUID; 219 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, 220 &property_address, 221 0, 222 0, 223 &translation_size, 224 &value); 225 } 226 227 if (result) { 228 OSSTATUS_DLOG(WARNING, result) << "Unable to query device " << device_id 229 << " for AudioDeviceID"; 230 } 231 232 return audio_device_id; 233} 234 235// Property address to monitor for device changes. 236static const AudioObjectPropertyAddress kDeviceChangePropertyAddress = { 237 kAudioHardwarePropertyDefaultOutputDevice, 238 kAudioObjectPropertyScopeGlobal, 239 kAudioObjectPropertyElementMaster 240}; 241 242// Callback from the system when the default device changes. This can be called 243// either on the main thread or on an audio thread managed by the system 244// depending on what kAudioHardwarePropertyRunLoop is set to. 245static OSStatus OnDefaultDeviceChangedCallback( 246 AudioObjectID object, 247 UInt32 num_addresses, 248 const AudioObjectPropertyAddress addresses[], 249 void* context) { 250 if (object != kAudioObjectSystemObject) 251 return noErr; 252 253 for (UInt32 i = 0; i < num_addresses; ++i) { 254 if (addresses[i].mSelector == kDeviceChangePropertyAddress.mSelector && 255 addresses[i].mScope == kDeviceChangePropertyAddress.mScope && 256 addresses[i].mElement == kDeviceChangePropertyAddress.mElement && 257 context) { 258 static_cast<base::Closure*>(context)->Run(); 259 } 260 } 261 262 return noErr; 263} 264 265AudioManagerMac::AudioManagerMac() { 266 SetMaxOutputStreamsAllowed(kMaxOutputStreams); 267 268 // Register a callback for device changes. 269 listener_cb_ = BindToLoop(GetMessageLoop(), base::Bind( 270 &AudioManagerMac::NotifyAllOutputDeviceChangeListeners, 271 base::Unretained(this))); 272 273 OSStatus result = AudioObjectAddPropertyListener( 274 kAudioObjectSystemObject, 275 &kDeviceChangePropertyAddress, 276 &OnDefaultDeviceChangedCallback, 277 &listener_cb_); 278 279 if (result != noErr) { 280 OSSTATUS_DLOG(ERROR, result) << "AudioObjectAddPropertyListener() failed!"; 281 listener_cb_.Reset(); 282 } 283} 284 285AudioManagerMac::~AudioManagerMac() { 286 if (!listener_cb_.is_null()) { 287 OSStatus result = AudioObjectRemovePropertyListener( 288 kAudioObjectSystemObject, 289 &kDeviceChangePropertyAddress, 290 &OnDefaultDeviceChangedCallback, 291 &listener_cb_); 292 OSSTATUS_DLOG_IF(ERROR, result != noErr, result) 293 << "AudioObjectRemovePropertyListener() failed!"; 294 } 295 296 Shutdown(); 297} 298 299bool AudioManagerMac::HasAudioOutputDevices() { 300 return HasAudioHardware(kAudioHardwarePropertyDefaultOutputDevice); 301} 302 303bool AudioManagerMac::HasAudioInputDevices() { 304 return HasAudioHardware(kAudioHardwarePropertyDefaultInputDevice); 305} 306 307void AudioManagerMac::GetAudioInputDeviceNames( 308 media::AudioDeviceNames* device_names) { 309 GetAudioDeviceInfo(true, device_names); 310 if (!device_names->empty()) { 311 // Prepend the default device to the list since we always want it to be 312 // on the top of the list for all platforms. There is no duplicate 313 // counting here since the default device has been abstracted out before. 314 media::AudioDeviceName name; 315 name.device_name = AudioManagerBase::kDefaultDeviceName; 316 name.unique_id = AudioManagerBase::kDefaultDeviceId; 317 device_names->push_front(name); 318 } 319} 320 321AudioOutputStream* AudioManagerMac::MakeLinearOutputStream( 322 const AudioParameters& params) { 323 DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); 324 return new PCMQueueOutAudioOutputStream(this, params); 325} 326 327AudioOutputStream* AudioManagerMac::MakeLowLatencyOutputStream( 328 const AudioParameters& params) { 329 DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); 330 331 // TODO(crogers): remove once we properly handle input device selection. 332 if (CommandLine::ForCurrentProcess()->HasSwitch( 333 switches::kEnableWebAudioInput)) { 334 if (HasUnifiedDefaultIO()) 335 return new AudioHardwareUnifiedStream(this, params); 336 337 // kAudioDeviceUnknown translates to "use default" here. 338 return new AudioSynchronizedStream(this, 339 params, 340 kAudioDeviceUnknown, 341 kAudioDeviceUnknown); 342 } 343 344 return new AUAudioOutputStream(this, params); 345} 346 347AudioInputStream* AudioManagerMac::MakeLinearInputStream( 348 const AudioParameters& params, const std::string& device_id) { 349 DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); 350 return new PCMQueueInAudioInputStream(this, params); 351} 352 353AudioInputStream* AudioManagerMac::MakeLowLatencyInputStream( 354 const AudioParameters& params, const std::string& device_id) { 355 DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); 356 // Gets the AudioDeviceID that refers to the AudioOutputDevice with the device 357 // unique id. This AudioDeviceID is used to set the device for Audio Unit. 358 AudioDeviceID audio_device_id = GetAudioDeviceIdByUId(true, device_id); 359 AudioInputStream* stream = NULL; 360 if (audio_device_id != kAudioObjectUnknown) 361 stream = new AUAudioInputStream(this, params, audio_device_id); 362 363 return stream; 364} 365 366AudioManager* CreateAudioManager() { 367 return new AudioManagerMac(); 368} 369 370} // namespace media 371