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/power_monitor/power_monitor.h"
15 #include "base/power_monitor/power_observer.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/threading/thread_checker.h"
18 #include "media/audio/audio_parameters.h"
19 #include "media/audio/mac/audio_auhal_mac.h"
20 #include "media/audio/mac/audio_input_mac.h"
21 #include "media/audio/mac/audio_low_latency_input_mac.h"
22 #include "media/base/bind_to_current_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 // Define bounds for for low-latency input and output streams.
33 static const int kMinimumInputOutputBufferSize
= 128;
34 static const int kMaximumInputOutputBufferSize
= 4096;
36 // Default sample-rate on most Apple hardware.
37 static const int kFallbackSampleRate
= 44100;
39 static bool HasAudioHardware(AudioObjectPropertySelector selector
) {
40 AudioDeviceID output_device_id
= kAudioObjectUnknown
;
41 const AudioObjectPropertyAddress property_address
= {
43 kAudioObjectPropertyScopeGlobal
, // mScope
44 kAudioObjectPropertyElementMaster
// mElement
46 UInt32 output_device_id_size
= static_cast<UInt32
>(sizeof(output_device_id
));
47 OSStatus err
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
49 0, // inQualifierDataSize
50 NULL
, // inQualifierData
51 &output_device_id_size
,
53 return err
== kAudioHardwareNoError
&&
54 output_device_id
!= kAudioObjectUnknown
;
57 // Retrieves information on audio devices, and prepends the default
58 // device to the list if the list is non-empty.
59 static void GetAudioDeviceInfo(bool is_input
,
60 media::AudioDeviceNames
* device_names
) {
61 // Query the number of total devices.
62 AudioObjectPropertyAddress property_address
= {
63 kAudioHardwarePropertyDevices
,
64 kAudioObjectPropertyScopeGlobal
,
65 kAudioObjectPropertyElementMaster
68 OSStatus result
= AudioObjectGetPropertyDataSize(kAudioObjectSystemObject
,
76 int device_count
= size
/ sizeof(AudioDeviceID
);
78 // Get the array of device ids for all the devices, which includes both
79 // input devices and output devices.
80 scoped_ptr
<AudioDeviceID
, base::FreeDeleter
>
81 devices(static_cast<AudioDeviceID
*>(malloc(size
)));
82 AudioDeviceID
* device_ids
= devices
.get();
83 result
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
92 // Iterate over all available devices to gather information.
93 for (int i
= 0; i
< device_count
; ++i
) {
94 // Get the number of input or output channels of the device.
95 property_address
.mScope
= is_input
?
96 kAudioDevicePropertyScopeInput
: kAudioDevicePropertyScopeOutput
;
97 property_address
.mSelector
= kAudioDevicePropertyStreams
;
99 result
= AudioObjectGetPropertyDataSize(device_ids
[i
],
108 CFStringRef uid
= NULL
;
110 property_address
.mSelector
= kAudioDevicePropertyDeviceUID
;
111 property_address
.mScope
= kAudioObjectPropertyScopeGlobal
;
112 result
= AudioObjectGetPropertyData(device_ids
[i
],
122 CFStringRef name
= NULL
;
123 property_address
.mSelector
= kAudioObjectPropertyName
;
124 property_address
.mScope
= kAudioObjectPropertyScopeGlobal
;
125 result
= AudioObjectGetPropertyData(device_ids
[i
],
137 // Store the device name and UID.
138 media::AudioDeviceName device_name
;
139 device_name
.device_name
= base::SysCFStringRefToUTF8(name
);
140 device_name
.unique_id
= base::SysCFStringRefToUTF8(uid
);
141 device_names
->push_back(device_name
);
143 // We are responsible for releasing the returned CFObject. See the
144 // comment in the AudioHardware.h for constant
145 // kAudioDevicePropertyDeviceUID.
152 if (!device_names
->empty()) {
153 // Prepend the default device to the list since we always want it to be
154 // on the top of the list for all platforms. There is no duplicate
155 // counting here since the default device has been abstracted out before.
156 media::AudioDeviceName name
;
157 name
.device_name
= AudioManagerBase::kDefaultDeviceName
;
158 name
.unique_id
= AudioManagerBase::kDefaultDeviceId
;
159 device_names
->push_front(name
);
163 static AudioDeviceID
GetAudioDeviceIdByUId(bool is_input
,
164 const std::string
& device_id
) {
165 AudioObjectPropertyAddress property_address
= {
166 kAudioHardwarePropertyDevices
,
167 kAudioObjectPropertyScopeGlobal
,
168 kAudioObjectPropertyElementMaster
170 AudioDeviceID audio_device_id
= kAudioObjectUnknown
;
171 UInt32 device_size
= sizeof(audio_device_id
);
172 OSStatus result
= -1;
174 if (device_id
== AudioManagerBase::kDefaultDeviceId
|| device_id
.empty()) {
176 property_address
.mSelector
= is_input
?
177 kAudioHardwarePropertyDefaultInputDevice
:
178 kAudioHardwarePropertyDefaultOutputDevice
;
180 result
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
187 // Non-default device.
188 base::ScopedCFTypeRef
<CFStringRef
> uid(
189 base::SysUTF8ToCFStringRef(device_id
));
190 AudioValueTranslation value
;
191 value
.mInputData
= &uid
;
192 value
.mInputDataSize
= sizeof(CFStringRef
);
193 value
.mOutputData
= &audio_device_id
;
194 value
.mOutputDataSize
= device_size
;
195 UInt32 translation_size
= sizeof(AudioValueTranslation
);
197 property_address
.mSelector
= kAudioHardwarePropertyDeviceForUID
;
198 result
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
207 OSSTATUS_DLOG(WARNING
, result
) << "Unable to query device " << device_id
208 << " for AudioDeviceID";
211 return audio_device_id
;
215 void StopStreams(std::list
<T
*>* streams
) {
216 for (typename
std::list
<T
*>::iterator it
= streams
->begin();
217 it
!= streams
->end();
219 // Stop() is safe to call multiple times, so it doesn't matter if a stream
220 // has already been stopped.
226 class AudioManagerMac::AudioPowerObserver
: public base::PowerObserver
{
229 : is_suspending_(false),
230 is_monitoring_(base::PowerMonitor::Get()) {
231 // The PowerMonitor requires signifcant setup (a CFRunLoop and preallocated
232 // IO ports) so it's not available under unit tests. See the OSX impl of
233 // base::PowerMonitorDeviceSource for more details.
236 base::PowerMonitor::Get()->AddObserver(this);
239 ~AudioPowerObserver() override
{
240 DCHECK(thread_checker_
.CalledOnValidThread());
243 base::PowerMonitor::Get()->RemoveObserver(this);
246 bool ShouldDeferStreamStart() {
247 DCHECK(thread_checker_
.CalledOnValidThread());
248 // Start() should be deferred if the system is in the middle of a suspend or
249 // has recently started the process of resuming.
250 return is_suspending_
|| base::TimeTicks::Now() < earliest_start_time_
;
254 void OnSuspend() override
{
255 DCHECK(thread_checker_
.CalledOnValidThread());
256 is_suspending_
= true;
259 void OnResume() override
{
260 DCHECK(thread_checker_
.CalledOnValidThread());
261 is_suspending_
= false;
262 earliest_start_time_
= base::TimeTicks::Now() +
263 base::TimeDelta::FromSeconds(kStartDelayInSecsForPowerEvents
);
267 const bool is_monitoring_
;
268 base::TimeTicks earliest_start_time_
;
269 base::ThreadChecker thread_checker_
;
271 DISALLOW_COPY_AND_ASSIGN(AudioPowerObserver
);
274 AudioManagerMac::AudioManagerMac(AudioLogFactory
* audio_log_factory
)
275 : AudioManagerBase(audio_log_factory
),
276 current_sample_rate_(0),
277 current_output_device_(kAudioDeviceUnknown
) {
278 SetMaxOutputStreamsAllowed(kMaxOutputStreams
);
280 // Task must be posted last to avoid races from handing out "this" to the
281 // audio thread. Always PostTask even if we're on the right thread since
282 // AudioManager creation is on the startup path and this may be slow.
283 GetTaskRunner()->PostTask(FROM_HERE
, base::Bind(
284 &AudioManagerMac::InitializeOnAudioThread
, base::Unretained(this)));
287 AudioManagerMac::~AudioManagerMac() {
288 if (GetTaskRunner()->BelongsToCurrentThread()) {
289 ShutdownOnAudioThread();
291 // It's safe to post a task here since Shutdown() will wait for all tasks to
292 // complete before returning.
293 GetTaskRunner()->PostTask(FROM_HERE
, base::Bind(
294 &AudioManagerMac::ShutdownOnAudioThread
, base::Unretained(this)));
300 bool AudioManagerMac::HasAudioOutputDevices() {
301 return HasAudioHardware(kAudioHardwarePropertyDefaultOutputDevice
);
304 bool AudioManagerMac::HasAudioInputDevices() {
305 return HasAudioHardware(kAudioHardwarePropertyDefaultInputDevice
);
308 // TODO(xians): There are several places on the OSX specific code which
309 // could benefit from these helper functions.
310 bool AudioManagerMac::GetDefaultInputDevice(AudioDeviceID
* device
) {
311 return GetDefaultDevice(device
, true);
314 bool AudioManagerMac::GetDefaultOutputDevice(AudioDeviceID
* device
) {
315 return GetDefaultDevice(device
, false);
318 bool AudioManagerMac::GetDefaultDevice(AudioDeviceID
* device
, bool input
) {
321 // Obtain the current output device selected by the user.
322 AudioObjectPropertyAddress pa
;
323 pa
.mSelector
= input
? kAudioHardwarePropertyDefaultInputDevice
:
324 kAudioHardwarePropertyDefaultOutputDevice
;
325 pa
.mScope
= kAudioObjectPropertyScopeGlobal
;
326 pa
.mElement
= kAudioObjectPropertyElementMaster
;
328 UInt32 size
= sizeof(*device
);
329 OSStatus result
= AudioObjectGetPropertyData(kAudioObjectSystemObject
,
336 if ((result
!= kAudioHardwareNoError
) || (*device
== kAudioDeviceUnknown
)) {
337 DLOG(ERROR
) << "Error getting default AudioDevice.";
344 bool AudioManagerMac::GetDefaultOutputChannels(int* channels
) {
345 AudioDeviceID device
;
346 if (!GetDefaultOutputDevice(&device
))
348 return GetDeviceChannels(device
, kAudioDevicePropertyScopeOutput
, channels
);
351 bool AudioManagerMac::GetDeviceChannels(AudioDeviceID device
,
352 AudioObjectPropertyScope scope
,
356 // Get stream configuration.
357 AudioObjectPropertyAddress pa
;
358 pa
.mSelector
= kAudioDevicePropertyStreamConfiguration
;
360 pa
.mElement
= kAudioObjectPropertyElementMaster
;
363 OSStatus result
= AudioObjectGetPropertyDataSize(device
, &pa
, 0, 0, &size
);
364 if (result
!= noErr
|| !size
)
368 scoped_ptr
<uint8
[]> list_storage(new uint8
[size
]);
369 AudioBufferList
& buffer_list
=
370 *reinterpret_cast<AudioBufferList
*>(list_storage
.get());
372 result
= AudioObjectGetPropertyData(device
, &pa
, 0, 0, &size
, &buffer_list
);
376 // Determine number of input channels.
377 int channels_per_frame
= buffer_list
.mNumberBuffers
> 0 ?
378 buffer_list
.mBuffers
[0].mNumberChannels
: 0;
379 if (channels_per_frame
== 1 && buffer_list
.mNumberBuffers
> 1) {
381 *channels
= buffer_list
.mNumberBuffers
;
384 *channels
= channels_per_frame
;
390 int AudioManagerMac::HardwareSampleRateForDevice(AudioDeviceID device_id
) {
391 Float64 nominal_sample_rate
;
392 UInt32 info_size
= sizeof(nominal_sample_rate
);
394 static const AudioObjectPropertyAddress kNominalSampleRateAddress
= {
395 kAudioDevicePropertyNominalSampleRate
,
396 kAudioObjectPropertyScopeGlobal
,
397 kAudioObjectPropertyElementMaster
399 OSStatus result
= AudioObjectGetPropertyData(device_id
,
400 &kNominalSampleRateAddress
,
404 &nominal_sample_rate
);
405 if (result
!= noErr
) {
406 OSSTATUS_DLOG(WARNING
, result
)
407 << "Could not get default sample rate for device: " << device_id
;
411 return static_cast<int>(nominal_sample_rate
);
414 int AudioManagerMac::HardwareSampleRate() {
415 // Determine the default output device's sample-rate.
416 AudioDeviceID device_id
= kAudioObjectUnknown
;
417 if (!GetDefaultOutputDevice(&device_id
))
418 return kFallbackSampleRate
;
420 return HardwareSampleRateForDevice(device_id
);
423 void AudioManagerMac::GetAudioInputDeviceNames(
424 media::AudioDeviceNames
* device_names
) {
425 DCHECK(device_names
->empty());
426 GetAudioDeviceInfo(true, device_names
);
429 void AudioManagerMac::GetAudioOutputDeviceNames(
430 media::AudioDeviceNames
* device_names
) {
431 DCHECK(device_names
->empty());
432 GetAudioDeviceInfo(false, device_names
);
435 AudioParameters
AudioManagerMac::GetInputStreamParameters(
436 const std::string
& device_id
) {
437 AudioDeviceID device
= GetAudioDeviceIdByUId(true, device_id
);
438 if (device
== kAudioObjectUnknown
) {
439 DLOG(ERROR
) << "Invalid device " << device_id
;
440 return AudioParameters(
441 AudioParameters::AUDIO_PCM_LOW_LATENCY
, CHANNEL_LAYOUT_STEREO
,
442 kFallbackSampleRate
, 16, ChooseBufferSize(true, kFallbackSampleRate
));
446 ChannelLayout channel_layout
= CHANNEL_LAYOUT_STEREO
;
447 if (GetDeviceChannels(device
, kAudioDevicePropertyScopeInput
, &channels
) &&
449 channel_layout
= GuessChannelLayout(channels
);
451 DLOG(ERROR
) << "Failed to get the device channels, use stereo as default "
452 << "for device " << device_id
;
455 int sample_rate
= HardwareSampleRateForDevice(device
);
457 sample_rate
= kFallbackSampleRate
;
459 // Due to the sharing of the input and output buffer sizes, we need to choose
460 // the input buffer size based on the output sample rate. See
461 // http://crbug.com/154352.
462 const int buffer_size
= ChooseBufferSize(true, sample_rate
);
464 // TODO(xians): query the native channel layout for the specific device.
465 return AudioParameters(
466 AudioParameters::AUDIO_PCM_LOW_LATENCY
, channel_layout
,
467 sample_rate
, 16, buffer_size
);
470 std::string
AudioManagerMac::GetAssociatedOutputDeviceID(
471 const std::string
& input_device_id
) {
472 AudioDeviceID device
= GetAudioDeviceIdByUId(true, input_device_id
);
473 if (device
== kAudioObjectUnknown
)
474 return std::string();
477 AudioObjectPropertyAddress pa
= {
478 kAudioDevicePropertyRelatedDevices
,
479 kAudioDevicePropertyScopeOutput
,
480 kAudioObjectPropertyElementMaster
482 OSStatus result
= AudioObjectGetPropertyDataSize(device
, &pa
, 0, 0, &size
);
484 return std::string();
486 int device_count
= size
/ sizeof(AudioDeviceID
);
487 scoped_ptr
<AudioDeviceID
, base::FreeDeleter
>
488 devices(static_cast<AudioDeviceID
*>(malloc(size
)));
489 result
= AudioObjectGetPropertyData(
490 device
, &pa
, 0, NULL
, &size
, devices
.get());
492 return std::string();
494 std::vector
<std::string
> associated_devices
;
495 for (int i
= 0; i
< device_count
; ++i
) {
496 // Get the number of output channels of the device.
497 pa
.mSelector
= kAudioDevicePropertyStreams
;
499 result
= AudioObjectGetPropertyDataSize(devices
.get()[i
],
505 continue; // Skip if there aren't any output channels.
508 CFStringRef uid
= NULL
;
510 pa
.mSelector
= kAudioDevicePropertyDeviceUID
;
511 result
= AudioObjectGetPropertyData(devices
.get()[i
],
520 std::string
ret(base::SysCFStringRefToUTF8(uid
));
522 associated_devices
.push_back(ret
);
525 // No matching device found.
526 if (associated_devices
.empty())
527 return std::string();
529 // Return the device if there is only one associated device.
530 if (associated_devices
.size() == 1)
531 return associated_devices
[0];
533 // When there are multiple associated devices, we currently do not have a way
534 // to detect if a device (e.g. a digital output device) is actually connected
535 // to an endpoint, so we cannot randomly pick a device.
536 // We pick the device iff the associated device is the default output device.
537 const std::string default_device
= GetDefaultOutputDeviceID();
538 for (std::vector
<std::string
>::const_iterator iter
=
539 associated_devices
.begin();
540 iter
!= associated_devices
.end(); ++iter
) {
541 if (default_device
== *iter
)
545 // Failed to figure out which is the matching device, return an emtpy string.
546 return std::string();
549 AudioOutputStream
* AudioManagerMac::MakeLinearOutputStream(
550 const AudioParameters
& params
) {
551 return MakeLowLatencyOutputStream(params
, std::string());
554 AudioOutputStream
* AudioManagerMac::MakeLowLatencyOutputStream(
555 const AudioParameters
& params
,
556 const std::string
& device_id
) {
557 AudioDeviceID device
= GetAudioDeviceIdByUId(false, device_id
);
558 if (device
== kAudioObjectUnknown
) {
559 DLOG(ERROR
) << "Failed to open output device: " << device_id
;
563 // Lazily create the audio device listener on the first stream creation.
564 if (!output_device_listener_
) {
565 // NOTE: Use BindToCurrentLoop() to ensure the callback is always PostTask'd
566 // even if OSX calls us on the right thread. Some CoreAudio drivers will
567 // fire the callbacks during stream creation, leading to re-entrancy issues
568 // otherwise. See http://crbug.com/349604
569 output_device_listener_
.reset(
570 new AudioDeviceListenerMac(BindToCurrentLoop(base::Bind(
571 &AudioManagerMac::HandleDeviceChanges
, base::Unretained(this)))));
572 // Only set the current output device for the default device.
573 if (device_id
== AudioManagerBase::kDefaultDeviceId
|| device_id
.empty())
574 current_output_device_
= device
;
575 // Just use the current sample rate since we don't allow non-native sample
577 current_sample_rate_
= params
.sample_rate();
580 AUHALStream
* stream
= new AUHALStream(this, params
, device
);
581 output_streams_
.push_back(stream
);
585 std::string
AudioManagerMac::GetDefaultOutputDeviceID() {
586 AudioDeviceID device_id
= kAudioObjectUnknown
;
587 if (!GetDefaultOutputDevice(&device_id
))
588 return std::string();
590 const AudioObjectPropertyAddress property_address
= {
591 kAudioDevicePropertyDeviceUID
,
592 kAudioObjectPropertyScopeGlobal
,
593 kAudioObjectPropertyElementMaster
595 CFStringRef device_uid
= NULL
;
596 UInt32 size
= sizeof(device_uid
);
597 OSStatus status
= AudioObjectGetPropertyData(device_id
,
603 if (status
!= kAudioHardwareNoError
|| !device_uid
)
604 return std::string();
606 std::string
ret(base::SysCFStringRefToUTF8(device_uid
));
607 CFRelease(device_uid
);
612 AudioInputStream
* AudioManagerMac::MakeLinearInputStream(
613 const AudioParameters
& params
, const std::string
& device_id
) {
614 DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR
, params
.format());
615 AudioInputStream
* stream
= new PCMQueueInAudioInputStream(this, params
);
616 basic_input_streams_
.push_back(stream
);
620 AudioInputStream
* AudioManagerMac::MakeLowLatencyInputStream(
621 const AudioParameters
& params
, const std::string
& device_id
) {
622 DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY
, params
.format());
623 // Gets the AudioDeviceID that refers to the AudioInputDevice with the device
624 // unique id. This AudioDeviceID is used to set the device for Audio Unit.
625 AudioDeviceID audio_device_id
= GetAudioDeviceIdByUId(true, device_id
);
626 AUAudioInputStream
* stream
= NULL
;
627 if (audio_device_id
!= kAudioObjectUnknown
) {
628 stream
= new AUAudioInputStream(this, params
, audio_device_id
);
629 low_latency_input_streams_
.push_back(stream
);
635 AudioParameters
AudioManagerMac::GetPreferredOutputStreamParameters(
636 const std::string
& output_device_id
,
637 const AudioParameters
& input_params
) {
638 const AudioDeviceID device
= GetAudioDeviceIdByUId(false, output_device_id
);
639 if (device
== kAudioObjectUnknown
) {
640 DLOG(ERROR
) << "Invalid output device " << output_device_id
;
641 return input_params
.IsValid() ? input_params
: AudioParameters(
642 AudioParameters::AUDIO_PCM_LOW_LATENCY
, CHANNEL_LAYOUT_STEREO
,
643 kFallbackSampleRate
, 16, ChooseBufferSize(false, kFallbackSampleRate
));
646 const bool has_valid_input_params
= input_params
.IsValid();
647 const int hardware_sample_rate
= HardwareSampleRateForDevice(device
);
649 // Allow pass through buffer sizes. If concurrent input and output streams
650 // exist, they will use the smallest buffer size amongst them. As such, each
651 // stream must be able to FIFO requests appropriately when this happens.
652 int buffer_size
= ChooseBufferSize(false, hardware_sample_rate
);
653 if (has_valid_input_params
) {
655 std::min(kMaximumInputOutputBufferSize
,
656 std::max(input_params
.frames_per_buffer(), buffer_size
));
659 int hardware_channels
;
660 if (!GetDeviceChannels(device
, kAudioDevicePropertyScopeOutput
,
661 &hardware_channels
)) {
662 hardware_channels
= 2;
665 // Use the input channel count and channel layout if possible. Let OSX take
666 // care of remapping the channels; this lets user specified channel layouts
668 int output_channels
= input_params
.channels();
669 ChannelLayout channel_layout
= input_params
.channel_layout();
670 if (!has_valid_input_params
|| output_channels
> hardware_channels
) {
671 output_channels
= hardware_channels
;
672 channel_layout
= GuessChannelLayout(output_channels
);
673 if (channel_layout
== CHANNEL_LAYOUT_UNSUPPORTED
)
674 channel_layout
= CHANNEL_LAYOUT_DISCRETE
;
677 return AudioParameters(
678 AudioParameters::AUDIO_PCM_LOW_LATENCY
, channel_layout
, output_channels
,
679 hardware_sample_rate
, 16, buffer_size
, AudioParameters::NO_EFFECTS
);
682 void AudioManagerMac::InitializeOnAudioThread() {
683 DCHECK(GetTaskRunner()->BelongsToCurrentThread());
684 power_observer_
.reset(new AudioPowerObserver());
687 void AudioManagerMac::ShutdownOnAudioThread() {
688 DCHECK(GetTaskRunner()->BelongsToCurrentThread());
689 output_device_listener_
.reset();
690 power_observer_
.reset();
692 // Since CoreAudio calls have to run on the UI thread and browser shutdown
693 // doesn't wait for outstanding tasks to complete, we may have input/output
694 // streams still running at shutdown.
696 // To avoid calls into destructed classes, we need to stop the OS callbacks
697 // by stopping the streams. Note: The streams are leaked since process
698 // destruction is imminent.
700 // See http://crbug.com/354139 for crash details.
701 StopStreams(&basic_input_streams_
);
702 StopStreams(&low_latency_input_streams_
);
703 StopStreams(&output_streams_
);
706 void AudioManagerMac::HandleDeviceChanges() {
707 DCHECK(GetTaskRunner()->BelongsToCurrentThread());
708 const int new_sample_rate
= HardwareSampleRate();
709 AudioDeviceID new_output_device
;
710 GetDefaultOutputDevice(&new_output_device
);
712 if (current_sample_rate_
== new_sample_rate
&&
713 current_output_device_
== new_output_device
)
716 current_sample_rate_
= new_sample_rate
;
717 current_output_device_
= new_output_device
;
718 NotifyAllOutputDeviceChangeListeners();
721 int AudioManagerMac::ChooseBufferSize(bool is_input
, int sample_rate
) {
722 // kMinimumInputOutputBufferSize is too small for the output side because
723 // CoreAudio can get into under-run if the renderer fails delivering data
724 // to the browser within the allowed time by the OS. The workaround is to
725 // use 256 samples as the default output buffer size for sample rates
726 // smaller than 96KHz.
727 // TODO(xians): Remove this workaround after WebAudio supports user defined
728 // buffer size. See https://github.com/WebAudio/web-audio-api/issues/348
730 int buffer_size
= is_input
?
731 kMinimumInputOutputBufferSize
: 2 * kMinimumInputOutputBufferSize
;
732 const int user_buffer_size
= GetUserBufferSize();
733 if (user_buffer_size
) {
734 buffer_size
= user_buffer_size
;
735 } else if (sample_rate
> 48000) {
736 // The default buffer size is too small for higher sample rates and may lead
737 // to glitching. Adjust upwards by multiples of the default size.
738 if (sample_rate
<= 96000)
739 buffer_size
= 2 * kMinimumInputOutputBufferSize
;
740 else if (sample_rate
<= 192000)
741 buffer_size
= 4 * kMinimumInputOutputBufferSize
;
747 bool AudioManagerMac::ShouldDeferStreamStart() {
748 DCHECK(GetTaskRunner()->BelongsToCurrentThread());
749 return power_observer_
->ShouldDeferStreamStart();
752 bool AudioManagerMac::MaybeChangeBufferSize(AudioDeviceID device_id
,
753 AudioUnit audio_unit
,
754 AudioUnitElement element
,
755 size_t desired_buffer_size
) {
756 UInt32 buffer_size
= 0;
757 UInt32 property_size
= sizeof(buffer_size
);
758 OSStatus result
= AudioUnitGetProperty(audio_unit
,
759 kAudioDevicePropertyBufferFrameSize
,
760 kAudioUnitScope_Output
,
764 if (result
!= noErr
) {
765 OSSTATUS_DLOG(ERROR
, result
)
766 << "AudioUnitGetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
770 // The lowest buffer size always wins. For larger buffer sizes, we have
771 // to perform some checks to see if the size can actually be changed.
772 // If there is any other active streams on the same device, either input or
773 // output, a larger size than their requested buffer size can't be set.
774 // The reason is that an existing stream can't handle buffer size larger
775 // than its requested buffer size.
777 // See http://crbug.com/428706 for a reason why.
778 if (desired_buffer_size
> buffer_size
) {
779 // Do NOT set the buffer size if there is another output stream using
780 // the same device with a smaller requested buffer size.
781 // Note, for the caller stream, its requested_buffer_size() will be the same
782 // as |desired_buffer_size|, so it won't return true due to comparing with
784 for (auto* stream
: output_streams_
) {
785 if (stream
->device_id() == device_id
&&
786 stream
->requested_buffer_size() < desired_buffer_size
) {
791 // Do NOT set the buffer size if there is another input stream using
792 // the same device with a smaller buffer size.
793 for (auto* stream
: low_latency_input_streams_
) {
794 if (stream
->device_id() == device_id
&&
795 stream
->requested_buffer_size() < desired_buffer_size
) {
801 buffer_size
= desired_buffer_size
;
802 result
= AudioUnitSetProperty(audio_unit
,
803 kAudioDevicePropertyBufferFrameSize
,
804 kAudioUnitScope_Output
,
807 sizeof(buffer_size
));
808 OSSTATUS_DLOG_IF(ERROR
, result
!= noErr
, result
)
809 << "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed. "
810 << "Size:: " << buffer_size
;
812 return (result
== noErr
);
815 void AudioManagerMac::ReleaseOutputStream(AudioOutputStream
* stream
) {
816 output_streams_
.remove(static_cast<AUHALStream
*>(stream
));
817 AudioManagerBase::ReleaseOutputStream(stream
);
820 void AudioManagerMac::ReleaseInputStream(AudioInputStream
* stream
) {
821 auto stream_it
= std::find(basic_input_streams_
.begin(),
822 basic_input_streams_
.end(),
824 if (stream_it
== basic_input_streams_
.end())
825 low_latency_input_streams_
.remove(static_cast<AUAudioInputStream
*>(stream
));
827 basic_input_streams_
.erase(stream_it
);
829 AudioManagerBase::ReleaseInputStream(stream
);
832 AudioManager
* CreateAudioManager(AudioLogFactory
* audio_log_factory
) {
833 return new AudioManagerMac(audio_log_factory
);