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