blob: 9dc45f649a426868385884ce01f235fc1b5b077b [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/mac/audio_manager_mac.h"
#include <algorithm>
#include <limits>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/mac/mac_logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/macros.h"
#include "base/memory/free_deleter.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_observer.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/thread_checker.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/mac/audio_auhal_mac.h"
#include "media/audio/mac/audio_input_mac.h"
#include "media/audio/mac/audio_low_latency_input_mac.h"
#include "media/audio/mac/coreaudio_dispatch_override.h"
#include "media/audio/mac/scoped_audio_unit.h"
#include "media/base/audio_parameters.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/channel_layout.h"
#include "media/base/limits.h"
#include "media/base/mac/audio_latency_mac.h"
#include "media/base/media_switches.h"
namespace media {
// Maximum number of output streams that can be open simultaneously.
static const int kMaxOutputStreams = 50;
// Default sample-rate on most Apple hardware.
static const int kFallbackSampleRate = 44100;
// Helper method to construct AudioObjectPropertyAddress structure given
// property selector and scope. The property element is always set to
// kAudioObjectPropertyElementMaster.
static AudioObjectPropertyAddress GetAudioObjectPropertyAddress(
AudioObjectPropertySelector selector,
bool is_input) {
AudioObjectPropertyScope scope = is_input ? kAudioObjectPropertyScopeInput
: kAudioObjectPropertyScopeOutput;
AudioObjectPropertyAddress property_address = {
selector, scope, kAudioObjectPropertyElementMaster};
return property_address;
}
// Get IO buffer size range from HAL given device id and scope.
static OSStatus GetIOBufferFrameSizeRange(AudioDeviceID device_id,
bool is_input,
UInt32* minimum,
UInt32* maximum) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
AudioObjectPropertyAddress address = GetAudioObjectPropertyAddress(
kAudioDevicePropertyBufferFrameSizeRange, is_input);
AudioValueRange range = {0, 0};
UInt32 data_size = sizeof(AudioValueRange);
OSStatus error = AudioObjectGetPropertyData(device_id, &address, 0, NULL,
&data_size, &range);
if (error != noErr) {
OSSTATUS_DLOG(WARNING, error)
<< "Failed to query IO buffer size range for device: " << std::hex
<< device_id;
} else {
*minimum = range.mMinimum;
*maximum = range.mMaximum;
}
return error;
}
static bool HasAudioHardware(AudioObjectPropertySelector selector) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
AudioDeviceID output_device_id = kAudioObjectUnknown;
const AudioObjectPropertyAddress property_address = {
selector,
kAudioObjectPropertyScopeGlobal, // mScope
kAudioObjectPropertyElementMaster // mElement
};
UInt32 output_device_id_size = static_cast<UInt32>(sizeof(output_device_id));
OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property_address,
0, // inQualifierDataSize
NULL, // inQualifierData
&output_device_id_size,
&output_device_id);
return err == kAudioHardwareNoError &&
output_device_id != kAudioObjectUnknown;
}
static std::string GetAudioDeviceNameFromDeviceId(AudioDeviceID device_id,
bool is_input) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CFStringRef device_name = nullptr;
UInt32 data_size = sizeof(device_name);
AudioObjectPropertyAddress property_address = GetAudioObjectPropertyAddress(
kAudioDevicePropertyDeviceNameCFString, is_input);
OSStatus result = AudioObjectGetPropertyData(
device_id, &property_address, 0, nullptr, &data_size, &device_name);
std::string device;
if (result == noErr) {
device = base::SysCFStringRefToUTF8(device_name);
CFRelease(device_name);
}
return device;
}
// Retrieves information on audio devices, and prepends the default
// device to the list if the list is non-empty.
static void GetAudioDeviceInfo(bool is_input,
media::AudioDeviceNames* device_names) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
// Query the number of total devices.
AudioObjectPropertyAddress property_address = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 size = 0;
OSStatus result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
&property_address,
0,
NULL,
&size);
if (result || !size)
return;
int device_count = size / sizeof(AudioDeviceID);
// Get the array of device ids for all the devices, which includes both
// input devices and output devices.
std::unique_ptr<AudioDeviceID, base::FreeDeleter> devices(
static_cast<AudioDeviceID*>(malloc(size)));
AudioDeviceID* device_ids = devices.get();
result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property_address,
0,
NULL,
&size,
device_ids);
if (result)
return;
// Iterate over all available devices to gather information.
for (int i = 0; i < device_count; ++i) {
// Get the number of input or output channels of the device.
property_address.mScope = is_input ?
kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
property_address.mSelector = kAudioDevicePropertyStreams;
size = 0;
result = AudioObjectGetPropertyDataSize(device_ids[i],
&property_address,
0,
NULL,
&size);
if (result || !size)
continue;
// Get device UID.
CFStringRef uid = NULL;
size = sizeof(uid);
property_address.mSelector = kAudioDevicePropertyDeviceUID;
property_address.mScope = kAudioObjectPropertyScopeGlobal;
result = AudioObjectGetPropertyData(device_ids[i],
&property_address,
0,
NULL,
&size,
&uid);
if (result)
continue;
// Get device name.
CFStringRef name = NULL;
property_address.mSelector = kAudioObjectPropertyName;
property_address.mScope = kAudioObjectPropertyScopeGlobal;
result = AudioObjectGetPropertyData(device_ids[i],
&property_address,
0,
NULL,
&size,
&name);
if (result) {
if (uid)
CFRelease(uid);
continue;
}
// Store the device name and UID.
media::AudioDeviceName device_name;
device_name.device_name = base::SysCFStringRefToUTF8(name);
device_name.unique_id = base::SysCFStringRefToUTF8(uid);
device_names->push_back(device_name);
// We are responsible for releasing the returned CFObject. See the
// comment in the AudioHardware.h for constant
// kAudioDevicePropertyDeviceUID.
if (uid)
CFRelease(uid);
if (name)
CFRelease(name);
}
if (!device_names->empty()) {
// Prepend the default device to the list since we always want it to be
// on the top of the list for all platforms. There is no duplicate
// counting here since the default device has been abstracted out before.
device_names->push_front(media::AudioDeviceName::CreateDefault());
}
}
static AudioDeviceID GetAudioDeviceIdByUId(bool is_input,
const std::string& device_id) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
AudioObjectPropertyAddress property_address = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
AudioDeviceID audio_device_id = kAudioObjectUnknown;
UInt32 device_size = sizeof(audio_device_id);
OSStatus result = -1;
if (AudioDeviceDescription::IsDefaultDevice(device_id)) {
// Default Device.
property_address.mSelector = is_input ?
kAudioHardwarePropertyDefaultInputDevice :
kAudioHardwarePropertyDefaultOutputDevice;
result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property_address,
0,
0,
&device_size,
&audio_device_id);
} else {
// Non-default device.
base::ScopedCFTypeRef<CFStringRef> uid(
base::SysUTF8ToCFStringRef(device_id));
AudioValueTranslation value;
value.mInputData = &uid;
value.mInputDataSize = sizeof(CFStringRef);
value.mOutputData = &audio_device_id;
value.mOutputDataSize = device_size;
UInt32 translation_size = sizeof(AudioValueTranslation);
property_address.mSelector = kAudioHardwarePropertyDeviceForUID;
result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&property_address,
0,
0,
&translation_size,
&value);
}
if (result) {
OSSTATUS_DLOG(WARNING, result) << "Unable to query device " << device_id
<< " for AudioDeviceID";
}
return audio_device_id;
}
static bool GetDefaultDevice(AudioDeviceID* device, bool input) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(device);
// Obtain the AudioDeviceID of the default input or output AudioDevice.
AudioObjectPropertyAddress pa;
pa.mSelector = input ? kAudioHardwarePropertyDefaultInputDevice
: kAudioHardwarePropertyDefaultOutputDevice;
pa.mScope = kAudioObjectPropertyScopeGlobal;
pa.mElement = kAudioObjectPropertyElementMaster;
UInt32 size = sizeof(*device);
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0,
0, &size, device);
if ((result != kAudioHardwareNoError) || (*device == kAudioDeviceUnknown)) {
DLOG(ERROR) << "Error getting default AudioDevice.";
return false;
}
return true;
}
static bool GetDefaultOutputDevice(AudioDeviceID* device) {
return GetDefaultDevice(device, false);
}
// Returns the total number of channels on a device; regardless of what the
// device's preferred rendering layout looks like. Should only be used for the
// channel count when a device has more than kMaxConcurrentChannels.
static bool GetDeviceTotalChannelCount(AudioDeviceID device,
AudioObjectPropertyScope scope,
int* channels) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(channels);
// Get the stream configuration of the device in an AudioBufferList (with the
// buffer pointers set to nullptr) which describes the list of streams and the
// number of channels in each stream.
AudioObjectPropertyAddress pa = {kAudioDevicePropertyStreamConfiguration,
scope, kAudioObjectPropertyElementMaster};
UInt32 size;
OSStatus result = AudioObjectGetPropertyDataSize(device, &pa, 0, 0, &size);
if (result != noErr || !size)
return false;
std::unique_ptr<uint8_t[]> list_storage(new uint8_t[size]);
AudioBufferList* buffer_list =
reinterpret_cast<AudioBufferList*>(list_storage.get());
result = AudioObjectGetPropertyData(device, &pa, 0, 0, &size, buffer_list);
if (result != noErr)
return false;
// Determine number of channels based on the AudioBufferList.
// |mNumberBuffers] is the number of interleaved channels in the buffer.
// If the number is 1, the buffer is noninterleaved.
*channels = 0;
for (UInt32 i = 0; i < buffer_list->mNumberBuffers; ++i)
*channels += buffer_list->mBuffers[i].mNumberChannels;
DVLOG(1) << (scope == kAudioDevicePropertyScopeInput ? "Input" : "Output")
<< " total channels: " << *channels;
return true;
}
// Returns the channel layout for |device| as provided by the AudioUnit attached
// to that device matching |element|. Returns true if the count could be pulled
// from the AudioUnit successfully, false otherwise.
static bool GetDeviceChannels(AudioDeviceID device,
AUElement element,
int* channels) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(channels);
// If the device has more channels than possible for layouts to express, use
// the total count of channels on the device; as of this writing, macOS will
// only return up to 8 channels in any layout. To allow WebAudio to work with
// > 8 channel devices, we must use the total channel count instead of the
// channel count of the preferred layout.
int total_channel_count = 0;
if (GetDeviceTotalChannelCount(device,
element == AUElement::OUTPUT
? kAudioDevicePropertyScopeOutput
: kAudioDevicePropertyScopeInput,
&total_channel_count) &&
total_channel_count > kMaxConcurrentChannels) {
*channels = total_channel_count;
return true;
}
ScopedAudioUnit au(device, element);
if (!au.is_valid())
return false;
// Attempt to retrieve the channel layout from the AudioUnit.
//
// Note: We don't use kAudioDevicePropertyPreferredChannelLayout on the device
// because it is not available on all devices.
UInt32 size;
Boolean writable;
OSStatus result = AudioUnitGetPropertyInfo(
au.audio_unit(), kAudioUnitProperty_AudioChannelLayout,
kAudioUnitScope_Output, element, &size, &writable);
if (result != noErr) {
OSSTATUS_DLOG(ERROR, result)
<< "Failed to get property info for AudioUnit channel layout.";
return false;
}
std::unique_ptr<uint8_t[]> layout_storage(new uint8_t[size]);
AudioChannelLayout* layout =
reinterpret_cast<AudioChannelLayout*>(layout_storage.get());
result = AudioUnitGetProperty(au.audio_unit(),
kAudioUnitProperty_AudioChannelLayout,
kAudioUnitScope_Output, element, layout, &size);
if (result != noErr) {
OSSTATUS_LOG(ERROR, result) << "Failed to get AudioUnit channel layout.";
return false;
}
// We don't want to have to know about all channel layout tags, so force OSX
// to give us the channel descriptions from the bitmap or tag if necessary.
const AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
const bool is_bitmap = tag == kAudioChannelLayoutTag_UseChannelBitmap;
const AudioFormatPropertyID fa =
is_bitmap ? kAudioFormatProperty_ChannelLayoutForBitmap
: kAudioFormatProperty_ChannelLayoutForTag;
if (is_bitmap) {
result = AudioFormatGetPropertyInfo(fa, sizeof(UInt32),
&layout->mChannelBitmap, &size);
} else {
result = AudioFormatGetPropertyInfo(fa, sizeof(AudioChannelLayoutTag),
&tag, &size);
}
if (result != noErr || !size) {
OSSTATUS_DLOG(ERROR, result)
<< "Failed to get AudioFormat property info, size=" << size;
return false;
}
layout_storage.reset(new uint8_t[size]);
layout = reinterpret_cast<AudioChannelLayout*>(layout_storage.get());
if (is_bitmap) {
result = AudioFormatGetProperty(fa, sizeof(UInt32),
&layout->mChannelBitmap, &size, layout);
} else {
result = AudioFormatGetProperty(fa, sizeof(AudioChannelLayoutTag), &tag,
&size, layout);
}
if (result != noErr) {
OSSTATUS_DLOG(ERROR, result) << "Failed to get AudioFormat property.";
return false;
}
}
// There is no channel info for stereo, assume so for mono as well.
if (layout->mNumberChannelDescriptions <= 2) {
*channels = layout->mNumberChannelDescriptions;
} else {
*channels = 0;
for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) {
if (layout->mChannelDescriptions[i].mChannelLabel !=
kAudioChannelLabel_Unknown)
(*channels)++;
}
}
DVLOG(1) << (element == AUElement::OUTPUT ? "Output" : "Input")
<< " channels: " << *channels;
return true;
}
class AudioManagerMac::AudioPowerObserver : public base::PowerObserver {
public:
AudioPowerObserver()
: is_suspending_(false),
is_monitoring_(base::PowerMonitor::Get()),
num_resume_notifications_(0) {
// The PowerMonitor requires significant setup (a CFRunLoop and preallocated
// IO ports) so it's not available under unit tests. See the OSX impl of
// base::PowerMonitorDeviceSource for more details.
if (!is_monitoring_)
return;
base::PowerMonitor::Get()->AddObserver(this);
}
~AudioPowerObserver() override {
DCHECK(thread_checker_.CalledOnValidThread());
if (!is_monitoring_)
return;
base::PowerMonitor::Get()->RemoveObserver(this);
}
bool IsSuspending() const {
DCHECK(thread_checker_.CalledOnValidThread());
return is_suspending_;
}
size_t num_resume_notifications() const { return num_resume_notifications_; }
bool ShouldDeferStreamStart() const {
DCHECK(thread_checker_.CalledOnValidThread());
// Start() should be deferred if the system is in the middle of a suspend or
// has recently started the process of resuming.
return is_suspending_ || base::TimeTicks::Now() < earliest_start_time_;
}
bool IsOnBatteryPower() const {
DCHECK(thread_checker_.CalledOnValidThread());
return base::PowerMonitor::Get()->IsOnBatteryPower();
}
private:
void OnSuspend() override {
DCHECK(thread_checker_.CalledOnValidThread());
DVLOG(1) << "OnSuspend";
is_suspending_ = true;
}
void OnResume() override {
DCHECK(thread_checker_.CalledOnValidThread());
DVLOG(1) << "OnResume";
++num_resume_notifications_;
is_suspending_ = false;
earliest_start_time_ = base::TimeTicks::Now() +
base::TimeDelta::FromSeconds(kStartDelayInSecsForPowerEvents);
}
bool is_suspending_;
const bool is_monitoring_;
base::TimeTicks earliest_start_time_;
base::ThreadChecker thread_checker_;
size_t num_resume_notifications_;
DISALLOW_COPY_AND_ASSIGN(AudioPowerObserver);
};
AudioManagerMac::AudioManagerMac(std::unique_ptr<AudioThread> audio_thread,
AudioLogFactory* audio_log_factory)
: AudioManagerBase(std::move(audio_thread), audio_log_factory),
current_sample_rate_(0),
current_output_device_(kAudioDeviceUnknown),
in_shutdown_(false) {
SetMaxOutputStreamsAllowed(kMaxOutputStreams);
// Task must be posted last to avoid races from handing out "this" to the
// audio thread. Always PostTask even if we're on the right thread since
// AudioManager creation is on the startup path and this may be slow.
GetTaskRunner()->PostTask(
FROM_HERE, base::Bind(&AudioManagerMac::InitializeOnAudioThread,
base::Unretained(this)));
}
AudioManagerMac::~AudioManagerMac() = default;
void AudioManagerMac::ShutdownOnAudioThread() {
// We are now in shutdown mode. This flag disables MaybeChangeBufferSize()
// and IncreaseIOBufferSizeIfPossible() which both touches native Core Audio
// APIs and they can fail and disrupt tests during shutdown.
in_shutdown_ = true;
// Even if tasks to close the streams are enqueued, they would not run
// leading to CHECKs getting hit in the destructor about open streams. Close
// them explicitly here. crbug.com/608049.
for (auto iter = basic_input_streams_.begin();
iter != basic_input_streams_.end();) {
// Note: Closing the stream will invalidate the iterator.
// Increment the iterator before closing the stream.
AudioInputStream* stream = *iter++;
stream->Close();
}
for (auto iter = low_latency_input_streams_.begin();
iter != low_latency_input_streams_.end();) {
// Note: Closing the stream will invalidate the iterator.
// Increment the iterator before closing the stream.
AudioInputStream* stream = *iter++;
stream->Close();
}
CHECK(basic_input_streams_.empty());
CHECK(low_latency_input_streams_.empty());
AudioManagerBase::ShutdownOnAudioThread();
}
bool AudioManagerMac::HasAudioOutputDevices() {
return HasAudioHardware(kAudioHardwarePropertyDefaultOutputDevice);
}
bool AudioManagerMac::HasAudioInputDevices() {
return HasAudioHardware(kAudioHardwarePropertyDefaultInputDevice);
}
// static
int AudioManagerMac::HardwareSampleRateForDevice(AudioDeviceID device_id) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
Float64 nominal_sample_rate;
UInt32 info_size = sizeof(nominal_sample_rate);
static const AudioObjectPropertyAddress kNominalSampleRateAddress = {
kAudioDevicePropertyNominalSampleRate,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyData(device_id,
&kNominalSampleRateAddress,
0,
0,
&info_size,
&nominal_sample_rate);
if (result != noErr) {
OSSTATUS_DLOG(WARNING, result)
<< "Could not get default sample rate for device: " << device_id;
return 0;
}
return static_cast<int>(nominal_sample_rate);
}
// static
int AudioManagerMac::HardwareSampleRate() {
// Determine the default output device's sample-rate.
AudioDeviceID device_id = kAudioObjectUnknown;
if (!GetDefaultOutputDevice(&device_id))
return kFallbackSampleRate;
return HardwareSampleRateForDevice(device_id);
}
void AudioManagerMac::GetAudioInputDeviceNames(
media::AudioDeviceNames* device_names) {
DCHECK(device_names->empty());
GetAudioDeviceInfo(true, device_names);
}
void AudioManagerMac::GetAudioOutputDeviceNames(
media::AudioDeviceNames* device_names) {
DCHECK(device_names->empty());
GetAudioDeviceInfo(false, device_names);
}
AudioParameters AudioManagerMac::GetInputStreamParameters(
const std::string& device_id) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
AudioDeviceID device = GetAudioDeviceIdByUId(true, device_id);
if (device == kAudioObjectUnknown) {
DLOG(ERROR) << "Invalid device " << device_id;
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
CHANNEL_LAYOUT_STEREO, kFallbackSampleRate, 16,
ChooseBufferSize(true, kFallbackSampleRate));
}
int channels = 0;
ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
if (GetDeviceChannels(device, AUElement::INPUT, &channels) && channels <= 2) {
channel_layout = GuessChannelLayout(channels);
} else {
DLOG(ERROR) << "Failed to get the device channels, use stereo as default "
<< "for device " << device_id;
}
int sample_rate = HardwareSampleRateForDevice(device);
if (!sample_rate)
sample_rate = kFallbackSampleRate;
// Due to the sharing of the input and output buffer sizes, we need to choose
// the input buffer size based on the output sample rate. See
// http://crbug.com/154352.
const int buffer_size = ChooseBufferSize(true, sample_rate);
// TODO(xians): query the native channel layout for the specific device.
return AudioParameters(
AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
sample_rate, 16, buffer_size);
}
std::string AudioManagerMac::GetAssociatedOutputDeviceID(
const std::string& input_device_id) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
AudioDeviceID device = GetAudioDeviceIdByUId(true, input_device_id);
if (device == kAudioObjectUnknown)
return std::string();
UInt32 size = 0;
AudioObjectPropertyAddress pa = {
kAudioDevicePropertyRelatedDevices,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyDataSize(device, &pa, 0, 0, &size);
if (result || !size)
return std::string();
int device_count = size / sizeof(AudioDeviceID);
std::unique_ptr<AudioDeviceID, base::FreeDeleter> devices(
static_cast<AudioDeviceID*>(malloc(size)));
result = AudioObjectGetPropertyData(
device, &pa, 0, NULL, &size, devices.get());
if (result)
return std::string();
std::vector<std::string> associated_devices;
for (int i = 0; i < device_count; ++i) {
// Get the number of output channels of the device.
pa.mSelector = kAudioDevicePropertyStreams;
size = 0;
result = AudioObjectGetPropertyDataSize(devices.get()[i],
&pa,
0,
NULL,
&size);
if (result || !size)
continue; // Skip if there aren't any output channels.
// Get device UID.
CFStringRef uid = NULL;
size = sizeof(uid);
pa.mSelector = kAudioDevicePropertyDeviceUID;
result = AudioObjectGetPropertyData(devices.get()[i],
&pa,
0,
NULL,
&size,
&uid);
if (result || !uid)
continue;
std::string ret(base::SysCFStringRefToUTF8(uid));
CFRelease(uid);
associated_devices.push_back(ret);
}
// No matching device found.
if (associated_devices.empty())
return std::string();
// Return the device if there is only one associated device.
if (associated_devices.size() == 1)
return associated_devices[0];
// When there are multiple associated devices, we currently do not have a way
// to detect if a device (e.g. a digital output device) is actually connected
// to an endpoint, so we cannot randomly pick a device.
// We pick the device iff the associated device is the default output device.
const std::string default_device = GetDefaultOutputDeviceID();
for (std::vector<std::string>::const_iterator iter =
associated_devices.begin();
iter != associated_devices.end(); ++iter) {
if (default_device == *iter)
return *iter;
}
// Failed to figure out which is the matching device, return an empty string.
return std::string();
}
const char* AudioManagerMac::GetName() {
return "Mac";
}
AudioOutputStream* AudioManagerMac::MakeLinearOutputStream(
const AudioParameters& params,
const LogCallback& log_callback) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
return MakeLowLatencyOutputStream(params, std::string(), log_callback);
}
AudioOutputStream* AudioManagerMac::MakeLowLatencyOutputStream(
const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
bool device_listener_first_init = false;
// Lazily create the audio device listener on the first stream creation,
// even if getting an audio device fails. Otherwise, if we have 0 audio
// devices, the listener will never be initialized, and new valid devices
// will never be detected.
if (!output_device_listener_) {
// NOTE: Use BindToCurrentLoop() to ensure the callback is always PostTask'd
// even if OSX calls us on the right thread. Some CoreAudio drivers will
// fire the callbacks during stream creation, leading to re-entrancy issues
// otherwise. See http://crbug.com/349604
output_device_listener_.reset(
new AudioDeviceListenerMac(BindToCurrentLoop(base::Bind(
&AudioManagerMac::HandleDeviceChanges, base::Unretained(this)))));
device_listener_first_init = true;
}
AudioDeviceID device = GetAudioDeviceIdByUId(false, device_id);
if (device == kAudioObjectUnknown) {
DLOG(ERROR) << "Failed to open output device: " << device_id;
return NULL;
}
// Only set the device and sample rate if we just initialized the device
// listener.
if (device_listener_first_init) {
// Only set the current output device for the default device.
if (AudioDeviceDescription::IsDefaultDevice(device_id))
current_output_device_ = device;
// Just use the current sample rate since we don't allow non-native sample
// rates on OSX.
current_sample_rate_ = params.sample_rate();
}
AUHALStream* stream = new AUHALStream(this, params, device, log_callback);
output_streams_.push_back(stream);
return stream;
}
std::string AudioManagerMac::GetDefaultOutputDeviceID() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
AudioDeviceID device_id = kAudioObjectUnknown;
if (!GetDefaultOutputDevice(&device_id))
return std::string();
const AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyDeviceUID,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
CFStringRef device_uid = NULL;
UInt32 size = sizeof(device_uid);
OSStatus status = AudioObjectGetPropertyData(device_id,
&property_address,
0,
NULL,
&size,
&device_uid);
if (status != kAudioHardwareNoError || !device_uid)
return std::string();
std::string ret(base::SysCFStringRefToUTF8(device_uid));
CFRelease(device_uid);
return ret;
}
AudioInputStream* AudioManagerMac::MakeLinearInputStream(
const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
AudioInputStream* stream = new PCMQueueInAudioInputStream(this, params);
basic_input_streams_.push_back(stream);
return stream;
}
AudioInputStream* AudioManagerMac::MakeLowLatencyInputStream(
const AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
// Gets the AudioDeviceID that refers to the AudioInputDevice with the device
// unique id. This AudioDeviceID is used to set the device for Audio Unit.
AudioDeviceID audio_device_id = GetAudioDeviceIdByUId(true, device_id);
AUAudioInputStream* stream = NULL;
if (audio_device_id != kAudioObjectUnknown) {
stream =
new AUAudioInputStream(this, params, audio_device_id, log_callback);
low_latency_input_streams_.push_back(stream);
}
return stream;
}
AudioParameters AudioManagerMac::GetPreferredOutputStreamParameters(
const std::string& output_device_id,
const AudioParameters& input_params) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
const AudioDeviceID device = GetAudioDeviceIdByUId(false, output_device_id);
if (device == kAudioObjectUnknown) {
DLOG(ERROR) << "Invalid output device " << output_device_id;
return input_params.IsValid() ? input_params : AudioParameters(
AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO,
kFallbackSampleRate, 16, ChooseBufferSize(false, kFallbackSampleRate));
}
const bool has_valid_input_params = input_params.IsValid();
const int hardware_sample_rate = HardwareSampleRateForDevice(device);
// Allow pass through buffer sizes. If concurrent input and output streams
// exist, they will use the smallest buffer size amongst them. As such, each
// stream must be able to FIFO requests appropriately when this happens.
int buffer_size;
if (has_valid_input_params) {
// If passed in via the input_params we allow buffer sizes to go as
// low as the the kMinAudioBufferSize, ignoring what
// ChooseBufferSize() normally returns.
buffer_size =
std::min(static_cast<int>(limits::kMaxAudioBufferSize),
std::max(input_params.frames_per_buffer(),
static_cast<int>(limits::kMinAudioBufferSize)));
} else {
buffer_size = ChooseBufferSize(false, hardware_sample_rate);
}
int hardware_channels;
if (!GetDeviceChannels(device, AUElement::OUTPUT, &hardware_channels))
hardware_channels = 2;
// Use the input channel count and channel layout if possible. Let OSX take
// care of remapping the channels; this lets user specified channel layouts
// work correctly.
int output_channels = input_params.channels();
ChannelLayout channel_layout = input_params.channel_layout();
if (!has_valid_input_params || output_channels > hardware_channels) {
output_channels = hardware_channels;
channel_layout = GuessChannelLayout(output_channels);
if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED)
channel_layout = CHANNEL_LAYOUT_DISCRETE;
}
AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
hardware_sample_rate, 16, buffer_size);
params.set_channels_for_discrete(output_channels);
return params;
}
void AudioManagerMac::InitializeOnAudioThread() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
InitializeCoreAudioDispatchOverride();
power_observer_.reset(new AudioPowerObserver());
}
void AudioManagerMac::HandleDeviceChanges() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
const int new_sample_rate = HardwareSampleRate();
AudioDeviceID new_output_device;
GetDefaultOutputDevice(&new_output_device);
if (current_sample_rate_ == new_sample_rate &&
current_output_device_ == new_output_device) {
return;
}
current_sample_rate_ = new_sample_rate;
current_output_device_ = new_output_device;
NotifyAllOutputDeviceChangeListeners();
}
int AudioManagerMac::ChooseBufferSize(bool is_input, int sample_rate) {
// kMinAudioBufferSize is too small for the output side because
// CoreAudio can get into under-run if the renderer fails delivering data
// to the browser within the allowed time by the OS. The workaround is to
// use 256 samples as the default output buffer size for sample rates
// smaller than 96KHz.
// TODO(xians): Remove this workaround after WebAudio supports user defined
// buffer size. See https://github.com/WebAudio/web-audio-api/issues/348
// for details.
int buffer_size =
is_input ? limits::kMinAudioBufferSize : 2 * limits::kMinAudioBufferSize;
const int user_buffer_size = GetUserBufferSize();
buffer_size = user_buffer_size
? user_buffer_size
: GetMinAudioBufferSizeMacOS(buffer_size, sample_rate);
return buffer_size;
}
bool AudioManagerMac::IsSuspending() const {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
return power_observer_->IsSuspending();
}
bool AudioManagerMac::ShouldDeferStreamStart() const {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
return power_observer_->ShouldDeferStreamStart();
}
bool AudioManagerMac::IsOnBatteryPower() const {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
return power_observer_->IsOnBatteryPower();
}
size_t AudioManagerMac::GetNumberOfResumeNotifications() const {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
return power_observer_->num_resume_notifications();
}
bool AudioManagerMac::MaybeChangeBufferSize(AudioDeviceID device_id,
AudioUnit audio_unit,
AudioUnitElement element,
size_t desired_buffer_size,
bool* size_was_changed,
size_t* io_buffer_frame_size) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
if (in_shutdown_) {
DVLOG(1) << "Disabled since we are shutting down";
return false;
}
const bool is_input = (element == 1);
DVLOG(1) << "MaybeChangeBufferSize(id=0x" << std::hex << device_id
<< ", is_input=" << is_input << ", desired_buffer_size=" << std::dec
<< desired_buffer_size << ")";
*size_was_changed = false;
*io_buffer_frame_size = 0;
// Log the device name (and id) for debugging purposes.
std::string device_name = GetAudioDeviceNameFromDeviceId(device_id, is_input);
DVLOG(1) << "name: " << device_name << " (ID: 0x" << std::hex << device_id
<< ")";
// Get the current size of the I/O buffer for the specified device. The
// property is read on a global scope, hence using element 0. The default IO
// buffer size on Mac OSX for OS X 10.9 and later is 512 audio frames.
UInt32 buffer_size = 0;
UInt32 property_size = sizeof(buffer_size);
OSStatus result = AudioUnitGetProperty(
audio_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global,
0, &buffer_size, &property_size);
if (result != noErr) {
OSSTATUS_DLOG(ERROR, result)
<< "AudioUnitGetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
return false;
}
// Store the currently used (not changed yet) I/O buffer frame size.
*io_buffer_frame_size = buffer_size;
DVLOG(1) << "current IO buffer size: " << buffer_size;
DVLOG(1) << "#output streams: " << output_streams_.size();
DVLOG(1) << "#input streams: " << low_latency_input_streams_.size();
// Check if a buffer size change is required. If the caller asks for a
// reduced size (|desired_buffer_size| < |buffer_size|), the new lower size
// will be set. For larger buffer sizes, we have to perform some checks to
// see if the size can actually be changed. If there is any other active
// streams on the same device, either input or output, a larger size than
// their requested buffer size can't be set. The reason is that an existing
// stream can't handle buffer size larger than its requested buffer size.
// See http://crbug.com/428706 for a reason why.
if (buffer_size == desired_buffer_size)
return true;
if (desired_buffer_size > buffer_size) {
// Do NOT set the buffer size if there is another output stream using
// the same device with a smaller requested buffer size.
// Note, for the caller stream, its requested_buffer_size() will be the same
// as |desired_buffer_size|, so it won't return true due to comparing with
// itself.
for (auto* stream : output_streams_) {
if (stream->device_id() == device_id &&
stream->requested_buffer_size() < desired_buffer_size) {
return true;
}
}
// Do NOT set the buffer size if there is another input stream using
// the same device with a smaller buffer size.
for (auto* stream : low_latency_input_streams_) {
if (stream->device_id() == device_id &&
stream->requested_buffer_size() < desired_buffer_size) {
return true;
}
}
}
// In this scope we know that the IO buffer size should be modified. But
// first, verify that |desired_buffer_size| is within the valid range and
// modify the desired buffer size if it is outside this range.
// Note that, we have found that AudioUnitSetProperty(PropertyBufferFrameSize)
// does in fact do this limitation internally and report noErr even if the
// user tries to set an invalid size. As an example, asking for a size of
// 4410 will on most devices be limited to 4096 without any further notice.
UInt32 minimum, maximum;
GetIOBufferFrameSizeRange(device_id, is_input, &minimum, &maximum);
DVLOG(1) << "valid IO buffer size range: [" << minimum << ", " << maximum
<< "]";
buffer_size = desired_buffer_size;
if (buffer_size < minimum)
buffer_size = minimum;
else if (buffer_size > maximum)
buffer_size = maximum;
DVLOG(1) << "validated desired buffer size: " << buffer_size;
// Set new (and valid) I/O buffer size for the specified device. The property
// is set on a global scope, hence using element 0.
result = AudioUnitSetProperty(audio_unit, kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global, 0, &buffer_size,
sizeof(buffer_size));
OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
<< "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed. "
<< "Size:: " << buffer_size;
*size_was_changed = (result == noErr);
DVLOG_IF(1, result == noErr) << "IO buffer size changed to: " << buffer_size;
// Store the currently used (after a change) I/O buffer frame size.
*io_buffer_frame_size = buffer_size;
// If the size was changed, update the actual output buffer size used for the
// given device ID.
if (!is_input && (result == noErr)) {
output_io_buffer_size_map_[device_id] = buffer_size;
}
return (result == noErr);
}
// static
base::TimeDelta AudioManagerMac::GetHardwareLatency(
AudioUnit audio_unit,
AudioDeviceID device_id,
AudioObjectPropertyScope scope,
int sample_rate) {
if (!audio_unit || device_id == kAudioObjectUnknown) {
DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown";
return base::TimeDelta();
}
// Get audio unit latency.
Float64 audio_unit_latency_sec = 0.0;
UInt32 size = sizeof(audio_unit_latency_sec);
OSStatus result = AudioUnitGetProperty(audio_unit, kAudioUnitProperty_Latency,
kAudioUnitScope_Global, 0,
&audio_unit_latency_sec, &size);
OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
<< "Could not get audio unit latency";
// Get audio device latency.
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyLatency, scope, kAudioObjectPropertyElementMaster};
UInt32 device_latency_frames = 0;
size = sizeof(device_latency_frames);
result = AudioObjectGetPropertyData(device_id, &property_address, 0, nullptr,
&size, &device_latency_frames);
OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
<< "Could not get audio device latency.";
property_address.mSelector = kAudioStreamPropertyLatency;
UInt32 stream_latency_frames = 0;
size = sizeof(stream_latency_frames);
result = AudioObjectGetPropertyData(device_id, &property_address, 0, nullptr,
&size, &stream_latency_frames);
OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
<< "Could not get stream latency.";
return base::TimeDelta::FromSecondsD(audio_unit_latency_sec) +
AudioTimestampHelper::FramesToTime(
device_latency_frames + stream_latency_frames, sample_rate);
}
bool AudioManagerMac::IncreaseIOBufferSizeIfPossible(AudioDeviceID device_id) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
DVLOG(1) << "IncreaseIOBufferSizeIfPossible(id=0x" << std::hex << device_id
<< ")";
if (in_shutdown_) {
DVLOG(1) << "Disabled since we are shutting down";
return false;
}
// Start by storing the actual I/O buffer size. Then scan all active output
// streams using the specified |device_id| and find the minimum requested
// buffer size. In addition, store a reference to the audio unit of the first
// output stream using |device_id|.
DCHECK(!output_io_buffer_size_map_.empty());
// All active output streams use the same actual I/O buffer size given
// a unique device ID.
// TODO(henrika): it would also be possible to use AudioUnitGetProperty(...,
// kAudioDevicePropertyBufferFrameSize,...) instead of caching the actual
// buffer size but I have chosen to use the map instead to avoid possibly
// expensive Core Audio API calls and the risk of failure when asking while
// closing a stream.
const size_t& actual_size = output_io_buffer_size_map_[device_id];
AudioUnit audio_unit;
size_t min_requested_size = std::numeric_limits<std::size_t>::max();
for (auto* stream : output_streams_) {
if (stream->device_id() == device_id) {
if (min_requested_size == std::numeric_limits<std::size_t>::max()) {
// Store reference to the first audio unit using the specified ID.
audio_unit = stream->audio_unit();
}
if (stream->requested_buffer_size() < min_requested_size)
min_requested_size = stream->requested_buffer_size();
DVLOG(1) << "requested:" << stream->requested_buffer_size()
<< " actual: " << actual_size;
}
}
if (min_requested_size == std::numeric_limits<std::size_t>::max()) {
DVLOG(1) << "No action since there is no active stream for given device id";
return false;
}
// It is only possible to revert to a larger buffer size if the lowest
// requested is not in use. Example: if the actual I/O buffer size is 256 and
// at least one output stream has asked for 256 as its buffer size, we can't
// start using a larger I/O buffer size.
DCHECK_GE(min_requested_size, actual_size);
if (min_requested_size == actual_size) {
DVLOG(1) << "No action since lowest possible size is already in use: "
<< actual_size;
return false;
}
// It should now be safe to increase the I/O buffer size to a new (higher)
// value using the |min_requested_size|. Doing so will save system resources.
// All active output streams with the same |device_id| are affected by this
// change but it is only required to apply the change to one of the streams.
DVLOG(1) << "min_requested_size: " << min_requested_size;
bool size_was_changed = false;
size_t io_buffer_frame_size = 0;
bool result =
MaybeChangeBufferSize(device_id, audio_unit, 0, min_requested_size,
&size_was_changed, &io_buffer_frame_size);
DCHECK_EQ(io_buffer_frame_size, min_requested_size);
DCHECK(size_was_changed);
return result;
}
bool AudioManagerMac::AudioDeviceIsUsedForInput(AudioDeviceID device_id) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
if (!basic_input_streams_.empty()) {
// For Audio Queues and in the default case (Mac OS X), the audio comes
// from the system’s default audio input device as set by a user in System
// Preferences.
AudioDeviceID default_id;
GetDefaultDevice(&default_id, true);
if (default_id == device_id)
return true;
}
// Each low latency streams has its own device ID.
for (auto* stream : low_latency_input_streams_) {
if (stream->device_id() == device_id)
return true;
}
return false;
}
void AudioManagerMac::ReleaseOutputStream(AudioOutputStream* stream) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
output_streams_.remove(static_cast<AUHALStream*>(stream));
AudioManagerBase::ReleaseOutputStream(stream);
}
void AudioManagerMac::ReleaseOutputStreamUsingRealDevice(
AudioOutputStream* stream,
AudioDeviceID device_id) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
DVLOG(1) << "Closing output stream with id=0x" << std::hex << device_id;
DVLOG(1) << "requested_buffer_size: "
<< static_cast<AUHALStream*>(stream)->requested_buffer_size();
// Start by closing down the specified output stream.
output_streams_.remove(static_cast<AUHALStream*>(stream));
AudioManagerBase::ReleaseOutputStream(stream);
// Prevent attempt to alter buffer size if the released stream was the last
// output stream.
if (output_streams_.empty())
return;
if (!AudioDeviceIsUsedForInput(device_id)) {
// The current audio device is not used for input. See if it is possible to
// increase the IO buffer size (saves power) given the remaining output
// audio streams and their buffer size requirements.
IncreaseIOBufferSizeIfPossible(device_id);
}
}
void AudioManagerMac::ReleaseInputStream(AudioInputStream* stream) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
auto stream_it = std::find(basic_input_streams_.begin(),
basic_input_streams_.end(),
stream);
if (stream_it == basic_input_streams_.end())
low_latency_input_streams_.remove(static_cast<AUAudioInputStream*>(stream));
else
basic_input_streams_.erase(stream_it);
AudioManagerBase::ReleaseInputStream(stream);
}
std::unique_ptr<AudioManager> CreateAudioManager(
std::unique_ptr<AudioThread> audio_thread,
AudioLogFactory* audio_log_factory) {
return base::MakeUnique<AudioManagerMac>(std::move(audio_thread),
audio_log_factory);
}
} // namespace media