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.
5 #include "media/audio/mac/audio_manager_mac.h"
7 #include <CoreAudio/AudioHardware.h>
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/mac/mac_logging.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "media/audio/audio_parameters.h"
16 #include "media/audio/mac/audio_auhal_mac.h"
17 #include "media/audio/mac/audio_input_mac.h"
18 #include "media/audio/mac/audio_low_latency_input_mac.h"
19 #include "media/audio/mac/audio_low_latency_output_mac.h"
20 #include "media/audio/mac/audio_synchronized_mac.h"
21 #include "media/audio/mac/audio_unified_mac.h"
22 #include "media/base/bind_to_loop.h"
23 #include "media/base/channel_layout.h"
24 #include "media/base/limits.h"
25 #include "media/base/media_switches.h"
29 // Maximum number of output streams that can be open simultaneously.
30 static const int kMaxOutputStreams
= 50;
32 // Default buffer size in samples for low-latency input and output streams.
33 static const int kDefaultLowLatencyBufferSize
= 128;
35 // Default sample-rate on most Apple hardware.
36 static const int kFallbackSampleRate
= 44100;
38 static bool HasAudioHardware(AudioObjectPropertySelector selector
) {
39 AudioDeviceID output_device_id
= kAudioObjectUnknown
;
40 const AudioObjectPropertyAddress property_address
= {
42 kAudioObjectPropertyScopeGlobal
, // mScope
43 kAudioObjectPropertyElementMaster
// mElement
45 UInt32 output_device_id_size
= static_cast<UInt32
>(sizeof(output_device_id
));
46 OSStatus err
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
48 0, // inQualifierDataSize
49 NULL
, // inQualifierData
50 &output_device_id_size
,
52 return err
== kAudioHardwareNoError
&&
53 output_device_id
!= kAudioObjectUnknown
;
56 // Returns true if the default input device is the same as
57 // the default output device.
58 bool AudioManagerMac::HasUnifiedDefaultIO() {
59 AudioDeviceID input_id
, output_id
;
60 if (!GetDefaultInputDevice(&input_id
) || !GetDefaultOutputDevice(&output_id
))
63 return input_id
== output_id
;
66 // Retrieves information on audio devices, and prepends the default
67 // device to the list if the list is non-empty.
68 static void GetAudioDeviceInfo(bool is_input
,
69 media::AudioDeviceNames
* device_names
) {
70 // Query the number of total devices.
71 AudioObjectPropertyAddress property_address
= {
72 kAudioHardwarePropertyDevices
,
73 kAudioObjectPropertyScopeGlobal
,
74 kAudioObjectPropertyElementMaster
77 OSStatus result
= AudioObjectGetPropertyDataSize(kAudioObjectSystemObject
,
85 int device_count
= size
/ sizeof(AudioDeviceID
);
87 // Get the array of device ids for all the devices, which includes both
88 // input devices and output devices.
89 scoped_ptr_malloc
<AudioDeviceID
>
90 devices(reinterpret_cast<AudioDeviceID
*>(malloc(size
)));
91 AudioDeviceID
* device_ids
= devices
.get();
92 result
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
101 // Iterate over all available devices to gather information.
102 for (int i
= 0; i
< device_count
; ++i
) {
103 // Get the number of input or output channels of the device.
104 property_address
.mScope
= is_input
?
105 kAudioDevicePropertyScopeInput
: kAudioDevicePropertyScopeOutput
;
106 property_address
.mSelector
= kAudioDevicePropertyStreams
;
108 result
= AudioObjectGetPropertyDataSize(device_ids
[i
],
117 CFStringRef uid
= NULL
;
119 property_address
.mSelector
= kAudioDevicePropertyDeviceUID
;
120 property_address
.mScope
= kAudioObjectPropertyScopeGlobal
;
121 result
= AudioObjectGetPropertyData(device_ids
[i
],
131 CFStringRef name
= NULL
;
132 property_address
.mSelector
= kAudioObjectPropertyName
;
133 property_address
.mScope
= kAudioObjectPropertyScopeGlobal
;
134 result
= AudioObjectGetPropertyData(device_ids
[i
],
146 // Store the device name and UID.
147 media::AudioDeviceName device_name
;
148 device_name
.device_name
= base::SysCFStringRefToUTF8(name
);
149 device_name
.unique_id
= base::SysCFStringRefToUTF8(uid
);
150 device_names
->push_back(device_name
);
152 // We are responsible for releasing the returned CFObject. See the
153 // comment in the AudioHardware.h for constant
154 // kAudioDevicePropertyDeviceUID.
161 if (!device_names
->empty()) {
162 // Prepend the default device to the list since we always want it to be
163 // on the top of the list for all platforms. There is no duplicate
164 // counting here since the default device has been abstracted out before.
165 media::AudioDeviceName name
;
166 name
.device_name
= AudioManagerBase::kDefaultDeviceName
;
167 name
.unique_id
= AudioManagerBase::kDefaultDeviceId
;
168 device_names
->push_front(name
);
172 static AudioDeviceID
GetAudioDeviceIdByUId(bool is_input
,
173 const std::string
& device_id
) {
174 AudioObjectPropertyAddress property_address
= {
175 kAudioHardwarePropertyDevices
,
176 kAudioObjectPropertyScopeGlobal
,
177 kAudioObjectPropertyElementMaster
179 AudioDeviceID audio_device_id
= kAudioObjectUnknown
;
180 UInt32 device_size
= sizeof(audio_device_id
);
181 OSStatus result
= -1;
183 if (device_id
== AudioManagerBase::kDefaultDeviceId
|| device_id
.empty()) {
185 property_address
.mSelector
= is_input
?
186 kAudioHardwarePropertyDefaultInputDevice
:
187 kAudioHardwarePropertyDefaultOutputDevice
;
189 result
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
196 // Non-default device.
197 base::ScopedCFTypeRef
<CFStringRef
> uid(
198 base::SysUTF8ToCFStringRef(device_id
));
199 AudioValueTranslation value
;
200 value
.mInputData
= &uid
;
201 value
.mInputDataSize
= sizeof(CFStringRef
);
202 value
.mOutputData
= &audio_device_id
;
203 value
.mOutputDataSize
= device_size
;
204 UInt32 translation_size
= sizeof(AudioValueTranslation
);
206 property_address
.mSelector
= kAudioHardwarePropertyDeviceForUID
;
207 result
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
216 OSSTATUS_DLOG(WARNING
, result
) << "Unable to query device " << device_id
217 << " for AudioDeviceID";
220 return audio_device_id
;
223 AudioManagerMac::AudioManagerMac()
224 : current_sample_rate_(0) {
225 current_output_device_
= kAudioDeviceUnknown
;
227 SetMaxOutputStreamsAllowed(kMaxOutputStreams
);
229 // Task must be posted last to avoid races from handing out "this" to the
230 // audio thread. Always PostTask even if we're on the right thread since
231 // AudioManager creation is on the startup path and this may be slow.
232 GetMessageLoop()->PostTask(FROM_HERE
, base::Bind(
233 &AudioManagerMac::CreateDeviceListener
, base::Unretained(this)));
236 AudioManagerMac::~AudioManagerMac() {
237 if (GetMessageLoop()->BelongsToCurrentThread()) {
238 DestroyDeviceListener();
240 // It's safe to post a task here since Shutdown() will wait for all tasks to
241 // complete before returning.
242 GetMessageLoop()->PostTask(FROM_HERE
, base::Bind(
243 &AudioManagerMac::DestroyDeviceListener
, base::Unretained(this)));
249 bool AudioManagerMac::HasAudioOutputDevices() {
250 return HasAudioHardware(kAudioHardwarePropertyDefaultOutputDevice
);
253 bool AudioManagerMac::HasAudioInputDevices() {
254 return HasAudioHardware(kAudioHardwarePropertyDefaultInputDevice
);
257 // TODO(xians): There are several places on the OSX specific code which
258 // could benefit from these helper functions.
259 bool AudioManagerMac::GetDefaultInputDevice(
260 AudioDeviceID
* device
) {
261 return GetDefaultDevice(device
, true);
264 bool AudioManagerMac::GetDefaultOutputDevice(
265 AudioDeviceID
* device
) {
266 return GetDefaultDevice(device
, false);
269 bool AudioManagerMac::GetDefaultDevice(
270 AudioDeviceID
* device
, bool input
) {
273 // Obtain the current output device selected by the user.
274 AudioObjectPropertyAddress pa
;
275 pa
.mSelector
= input
? kAudioHardwarePropertyDefaultInputDevice
:
276 kAudioHardwarePropertyDefaultOutputDevice
;
277 pa
.mScope
= kAudioObjectPropertyScopeGlobal
;
278 pa
.mElement
= kAudioObjectPropertyElementMaster
;
280 UInt32 size
= sizeof(*device
);
282 OSStatus result
= AudioObjectGetPropertyData(
283 kAudioObjectSystemObject
,
290 if ((result
!= kAudioHardwareNoError
) || (*device
== kAudioDeviceUnknown
)) {
291 DLOG(ERROR
) << "Error getting default AudioDevice.";
298 bool AudioManagerMac::GetDefaultOutputChannels(
300 AudioDeviceID device
;
301 if (!GetDefaultOutputDevice(&device
))
304 return GetDeviceChannels(device
,
305 kAudioDevicePropertyScopeOutput
,
309 bool AudioManagerMac::GetDeviceChannels(
310 AudioDeviceID device
,
311 AudioObjectPropertyScope scope
,
315 // Get stream configuration.
316 AudioObjectPropertyAddress pa
;
317 pa
.mSelector
= kAudioDevicePropertyStreamConfiguration
;
319 pa
.mElement
= kAudioObjectPropertyElementMaster
;
322 OSStatus result
= AudioObjectGetPropertyDataSize(device
, &pa
, 0, 0, &size
);
323 if (result
!= noErr
|| !size
)
327 scoped_ptr
<uint8
[]> list_storage(new uint8
[size
]);
328 AudioBufferList
& buffer_list
=
329 *reinterpret_cast<AudioBufferList
*>(list_storage
.get());
331 result
= AudioObjectGetPropertyData(
341 // Determine number of input channels.
342 int channels_per_frame
= buffer_list
.mNumberBuffers
> 0 ?
343 buffer_list
.mBuffers
[0].mNumberChannels
: 0;
344 if (channels_per_frame
== 1 && buffer_list
.mNumberBuffers
> 1) {
346 *channels
= buffer_list
.mNumberBuffers
;
349 *channels
= channels_per_frame
;
355 int AudioManagerMac::HardwareSampleRateForDevice(AudioDeviceID device_id
) {
356 Float64 nominal_sample_rate
;
357 UInt32 info_size
= sizeof(nominal_sample_rate
);
359 static const AudioObjectPropertyAddress kNominalSampleRateAddress
= {
360 kAudioDevicePropertyNominalSampleRate
,
361 kAudioObjectPropertyScopeGlobal
,
362 kAudioObjectPropertyElementMaster
364 OSStatus result
= AudioObjectGetPropertyData(
366 &kNominalSampleRateAddress
,
370 &nominal_sample_rate
);
371 if (result
!= noErr
) {
372 OSSTATUS_DLOG(WARNING
, result
)
373 << "Could not get default sample rate for device: " << device_id
;
377 return static_cast<int>(nominal_sample_rate
);
380 int AudioManagerMac::HardwareSampleRate() {
381 // Determine the default output device's sample-rate.
382 AudioDeviceID device_id
= kAudioObjectUnknown
;
383 if (!GetDefaultOutputDevice(&device_id
))
384 return kFallbackSampleRate
;
386 return HardwareSampleRateForDevice(device_id
);
389 void AudioManagerMac::GetAudioInputDeviceNames(
390 media::AudioDeviceNames
* device_names
) {
391 DCHECK(device_names
->empty());
392 GetAudioDeviceInfo(true, device_names
);
395 void AudioManagerMac::GetAudioOutputDeviceNames(
396 media::AudioDeviceNames
* device_names
) {
397 DCHECK(device_names
->empty());
398 GetAudioDeviceInfo(false, device_names
);
401 AudioParameters
AudioManagerMac::GetInputStreamParameters(
402 const std::string
& device_id
) {
403 // Due to the sharing of the input and output buffer sizes, we need to choose
404 // the input buffer size based on the output sample rate. See
405 // http://crbug.com/154352.
406 const int buffer_size
= ChooseBufferSize(
407 AUAudioOutputStream::HardwareSampleRate());
409 AudioDeviceID device
= GetAudioDeviceIdByUId(true, device_id
);
410 if (device
== kAudioObjectUnknown
) {
411 DLOG(ERROR
) << "Invalid device " << device_id
;
412 return AudioParameters();
416 ChannelLayout channel_layout
= CHANNEL_LAYOUT_STEREO
;
417 if (GetDeviceChannels(device
, kAudioDevicePropertyScopeInput
, &channels
) &&
419 channel_layout
= GuessChannelLayout(channels
);
421 DLOG(ERROR
) << "Failed to get the device channels, use stereo as default "
422 << "for device " << device_id
;
425 int sample_rate
= HardwareSampleRateForDevice(device
);
427 sample_rate
= kFallbackSampleRate
;
429 // TODO(xians): query the native channel layout for the specific device.
430 return AudioParameters(
431 AudioParameters::AUDIO_PCM_LOW_LATENCY
, channel_layout
,
432 sample_rate
, 16, buffer_size
);
435 std::string
AudioManagerMac::GetAssociatedOutputDeviceID(
436 const std::string
& input_device_id
) {
437 AudioDeviceID device
= GetAudioDeviceIdByUId(true, input_device_id
);
438 if (device
== kAudioObjectUnknown
)
439 return std::string();
442 AudioObjectPropertyAddress pa
= {
443 kAudioDevicePropertyRelatedDevices
,
444 kAudioDevicePropertyScopeOutput
,
445 kAudioObjectPropertyElementMaster
447 OSStatus result
= AudioObjectGetPropertyDataSize(device
, &pa
, 0, 0, &size
);
449 return std::string();
451 int device_count
= size
/ sizeof(AudioDeviceID
);
452 scoped_ptr_malloc
<AudioDeviceID
>
453 devices(reinterpret_cast<AudioDeviceID
*>(malloc(size
)));
454 result
= AudioObjectGetPropertyData(
455 device
, &pa
, 0, NULL
, &size
, devices
.get());
457 return std::string();
459 for (int i
= 0; i
< device_count
; ++i
) {
460 // Get the number of output channels of the device.
461 pa
.mSelector
= kAudioDevicePropertyStreams
;
463 result
= AudioObjectGetPropertyDataSize(devices
.get()[i
],
469 continue; // Skip if there aren't any output channels.
472 CFStringRef uid
= NULL
;
474 pa
.mSelector
= kAudioDevicePropertyDeviceUID
;
475 result
= AudioObjectGetPropertyData(devices
.get()[i
],
484 std::string
ret(base::SysCFStringRefToUTF8(uid
));
489 // No matching device found.
490 return std::string();
493 AudioOutputStream
* AudioManagerMac::MakeLinearOutputStream(
494 const AudioParameters
& params
) {
495 return MakeLowLatencyOutputStream(params
, std::string(), std::string());
498 AudioOutputStream
* AudioManagerMac::MakeLowLatencyOutputStream(
499 const AudioParameters
& params
,
500 const std::string
& device_id
,
501 const std::string
& input_device_id
) {
502 // Handle basic output with no input channels.
503 if (params
.input_channels() == 0) {
504 AudioDeviceID device
= GetAudioDeviceIdByUId(false, device_id
);
505 if (device
== kAudioObjectUnknown
) {
506 DLOG(ERROR
) << "Failed to open output device: " << device_id
;
509 return new AUHALStream(this, params
, device
);
512 DLOG_IF(ERROR
, !device_id
.empty()) << "Not implemented!";
514 // TODO(xians): support more than stereo input.
515 if (params
.input_channels() != 2) {
516 // WebAudio is currently hard-coded to 2 channels so we should not
518 NOTREACHED() << "Only stereo input is currently supported!";
522 AudioDeviceID device
= kAudioObjectUnknown
;
523 if (HasUnifiedDefaultIO()) {
524 // For I/O, the simplest case is when the default input and output
525 // devices are the same.
526 GetDefaultOutputDevice(&device
);
527 LOG(INFO
) << "UNIFIED: default input and output devices are identical";
529 // Some audio hardware is presented as separate input and output devices
530 // even though they are really the same physical hardware and
531 // share the same "clock domain" at the lowest levels of the driver.
532 // A common of example of this is the "built-in" audio hardware:
533 // "Built-in Line Input"
535 // We would like to use an "aggregate" device for these situations, since
536 // CoreAudio will make the most efficient use of the shared "clock domain"
537 // so we get the lowest latency and use fewer threads.
538 device
= aggregate_device_manager_
.GetDefaultAggregateDevice();
539 if (device
!= kAudioObjectUnknown
)
540 LOG(INFO
) << "Using AGGREGATE audio device";
543 if (device
!= kAudioObjectUnknown
&&
544 input_device_id
== AudioManagerBase::kDefaultDeviceId
)
545 return new AUHALStream(this, params
, device
);
547 // Fallback to AudioSynchronizedStream which will handle completely
548 // different and arbitrary combinations of input and output devices
549 // even running at different sample-rates.
550 // kAudioDeviceUnknown translates to "use default" here.
551 // TODO(xians): consider tracking UMA stats on AUHALStream
552 // versus AudioSynchronizedStream.
553 AudioDeviceID audio_device_id
= GetAudioDeviceIdByUId(true, input_device_id
);
554 if (audio_device_id
== kAudioObjectUnknown
)
557 return new AudioSynchronizedStream(this,
560 kAudioDeviceUnknown
);
563 std::string
AudioManagerMac::GetDefaultOutputDeviceID() {
564 AudioDeviceID device_id
= kAudioObjectUnknown
;
565 if (!GetDefaultOutputDevice(&device_id
))
566 return std::string();
568 const AudioObjectPropertyAddress property_address
= {
569 kAudioDevicePropertyDeviceUID
,
570 kAudioObjectPropertyScopeGlobal
,
571 kAudioObjectPropertyElementMaster
573 CFStringRef device_uid
= NULL
;
574 UInt32 size
= sizeof(device_uid
);
575 OSStatus status
= AudioObjectGetPropertyData(device_id
,
581 if (status
!= kAudioHardwareNoError
|| !device_uid
)
582 return std::string();
584 std::string
ret(base::SysCFStringRefToUTF8(device_uid
));
585 CFRelease(device_uid
);
590 AudioInputStream
* AudioManagerMac::MakeLinearInputStream(
591 const AudioParameters
& params
, const std::string
& device_id
) {
592 DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR
, params
.format());
593 return new PCMQueueInAudioInputStream(this, params
);
596 AudioInputStream
* AudioManagerMac::MakeLowLatencyInputStream(
597 const AudioParameters
& params
, const std::string
& device_id
) {
598 DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY
, params
.format());
599 // Gets the AudioDeviceID that refers to the AudioInputDevice with the device
600 // unique id. This AudioDeviceID is used to set the device for Audio Unit.
601 AudioDeviceID audio_device_id
= GetAudioDeviceIdByUId(true, device_id
);
602 AudioInputStream
* stream
= NULL
;
603 if (audio_device_id
!= kAudioObjectUnknown
) {
604 // AUAudioInputStream needs to be fed the preferred audio output parameters
605 // of the matching device so that the buffer size of both input and output
606 // can be matched. See constructor of AUAudioInputStream for more.
607 const std::string
associated_output_device(
608 GetAssociatedOutputDeviceID(device_id
));
609 const AudioParameters output_params
=
610 GetPreferredOutputStreamParameters(
611 associated_output_device
.empty() ?
612 AudioManagerBase::kDefaultDeviceId
: associated_output_device
,
614 stream
= new AUAudioInputStream(this, params
, output_params
,
621 AudioParameters
AudioManagerMac::GetPreferredOutputStreamParameters(
622 const std::string
& output_device_id
,
623 const AudioParameters
& input_params
) {
624 AudioDeviceID device
= GetAudioDeviceIdByUId(false, output_device_id
);
625 if (device
== kAudioObjectUnknown
) {
626 DLOG(ERROR
) << "Invalid output device " << output_device_id
;
627 return AudioParameters();
630 int hardware_channels
= 2;
631 if (!GetDeviceChannels(device
, kAudioDevicePropertyScopeOutput
,
632 &hardware_channels
)) {
633 // Fallback to stereo.
634 hardware_channels
= 2;
637 ChannelLayout channel_layout
= GuessChannelLayout(hardware_channels
);
639 const int hardware_sample_rate
= HardwareSampleRateForDevice(device
);
640 const int buffer_size
= ChooseBufferSize(hardware_sample_rate
);
642 int input_channels
= 0;
643 if (input_params
.IsValid()) {
644 input_channels
= input_params
.input_channels();
646 if (input_channels
> 0) {
647 // TODO(xians): given the limitations of the AudioOutputStream
648 // back-ends used with synchronized I/O, we hard-code to stereo.
649 // Specifically, this is a limitation of AudioSynchronizedStream which
650 // can be removed as part of the work to consolidate these back-ends.
651 channel_layout
= CHANNEL_LAYOUT_STEREO
;
655 AudioParameters
params(
656 AudioParameters::AUDIO_PCM_LOW_LATENCY
,
659 hardware_sample_rate
,
663 if (channel_layout
== CHANNEL_LAYOUT_UNSUPPORTED
)
664 params
.SetDiscreteChannels(hardware_channels
);
669 void AudioManagerMac::CreateDeviceListener() {
670 DCHECK(GetMessageLoop()->BelongsToCurrentThread());
672 // Get a baseline for the sample-rate and current device,
673 // so we can intelligently handle device notifications only when necessary.
674 current_sample_rate_
= HardwareSampleRate();
675 if (!GetDefaultOutputDevice(¤t_output_device_
))
676 current_output_device_
= kAudioDeviceUnknown
;
678 output_device_listener_
.reset(new AudioDeviceListenerMac(base::Bind(
679 &AudioManagerMac::HandleDeviceChanges
, base::Unretained(this))));
682 void AudioManagerMac::DestroyDeviceListener() {
683 DCHECK(GetMessageLoop()->BelongsToCurrentThread());
684 output_device_listener_
.reset();
687 void AudioManagerMac::HandleDeviceChanges() {
688 if (!GetMessageLoop()->BelongsToCurrentThread()) {
689 GetMessageLoop()->PostTask(FROM_HERE
, base::Bind(
690 &AudioManagerMac::HandleDeviceChanges
, base::Unretained(this)));
694 int new_sample_rate
= HardwareSampleRate();
695 AudioDeviceID new_output_device
;
696 GetDefaultOutputDevice(&new_output_device
);
698 if (current_sample_rate_
== new_sample_rate
&&
699 current_output_device_
== new_output_device
)
702 current_sample_rate_
= new_sample_rate
;
703 current_output_device_
= new_output_device
;
704 NotifyAllOutputDeviceChangeListeners();
707 int AudioManagerMac::ChooseBufferSize(int output_sample_rate
) {
708 int buffer_size
= kDefaultLowLatencyBufferSize
;
709 const int user_buffer_size
= GetUserBufferSize();
710 if (user_buffer_size
) {
711 buffer_size
= user_buffer_size
;
712 } else if (output_sample_rate
> 48000) {
713 // The default buffer size is too small for higher sample rates and may lead
714 // to glitching. Adjust upwards by multiples of the default size.
715 if (output_sample_rate
<= 96000)
716 buffer_size
= 2 * kDefaultLowLatencyBufferSize
;
717 else if (output_sample_rate
<= 192000)
718 buffer_size
= 4 * kDefaultLowLatencyBufferSize
;
724 AudioManager
* CreateAudioManager() {
725 return new AudioManagerMac();