blob: e0300ed8eb1959bb8cba6fa50773effee8a9d995 [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 <CoreAudio/AudioHardware.h>
#include <string>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/mac/mac_logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/sys_string_conversions.h"
#include "media/audio/audio_util.h"
#include "media/audio/mac/audio_input_mac.h"
#include "media/audio/mac/audio_low_latency_input_mac.h"
#include "media/audio/mac/audio_low_latency_output_mac.h"
#include "media/audio/mac/audio_output_mac.h"
#include "media/audio/mac/audio_synchronized_mac.h"
#include "media/audio/mac/audio_unified_mac.h"
#include "media/base/bind_to_loop.h"
#include "media/base/limits.h"
#include "media/base/media_switches.h"
namespace media {
// Maximum number of output streams that can be open simultaneously.
static const int kMaxOutputStreams = 50;
static bool HasAudioHardware(AudioObjectPropertySelector selector) {
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;
}
// Returns true if the default input device is the same as
// the default output device.
static bool HasUnifiedDefaultIO() {
AudioDeviceID input_id, output_id;
AudioObjectPropertyAddress pa;
pa.mSelector = kAudioHardwarePropertyDefaultInputDevice;
pa.mScope = kAudioObjectPropertyScopeGlobal;
pa.mElement = kAudioObjectPropertyElementMaster;
UInt32 size = sizeof(input_id);
// Get the default input.
OSStatus result = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&pa,
0,
0,
&size,
&input_id);
if (result != noErr)
return false;
// Get the default output.
pa.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
result = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&pa,
0,
0,
&size,
&output_id);
if (result != noErr)
return false;
return input_id == output_id;
}
static void GetAudioDeviceInfo(bool is_input,
media::AudioDeviceNames* device_names) {
DCHECK(device_names);
device_names->clear();
// 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.
scoped_ptr_malloc<AudioDeviceID>
devices(reinterpret_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);
}
}
static AudioDeviceID GetAudioDeviceIdByUId(bool is_input,
const std::string& device_id) {
AudioObjectPropertyAddress property_address = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
AudioDeviceID audio_device_id = kAudioObjectUnknown;
UInt32 device_size = sizeof(audio_device_id);
OSStatus result = -1;
if (device_id == AudioManagerBase::kDefaultDeviceId) {
// 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::mac::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;
}
// Property address to monitor for device changes.
static const AudioObjectPropertyAddress kDeviceChangePropertyAddress = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
// Callback from the system when the default device changes; this must be called
// on the MessageLoop that created the AudioManager.
static OSStatus OnDefaultDeviceChangedCallback(
AudioObjectID object,
UInt32 num_addresses,
const AudioObjectPropertyAddress addresses[],
void* context) {
if (object != kAudioObjectSystemObject)
return noErr;
for (UInt32 i = 0; i < num_addresses; ++i) {
if (addresses[i].mSelector == kDeviceChangePropertyAddress.mSelector &&
addresses[i].mScope == kDeviceChangePropertyAddress.mScope &&
addresses[i].mElement == kDeviceChangePropertyAddress.mElement &&
context) {
static_cast<AudioManagerMac*>(context)->OnDeviceChange();
break;
}
}
return noErr;
}
AudioManagerMac::AudioManagerMac()
: listener_registered_(false),
creating_message_loop_(base::MessageLoopProxy::current()) {
SetMaxOutputStreamsAllowed(kMaxOutputStreams);
// AudioManagerMac is expected to be created by the root platform thread, this
// is generally BrowserMainLoop, it's MessageLoop will drive the NSApplication
// pump which in turn fires the property listener callbacks.
if (!creating_message_loop_)
return;
OSStatus result = AudioObjectAddPropertyListener(
kAudioObjectSystemObject,
&kDeviceChangePropertyAddress,
&OnDefaultDeviceChangedCallback,
this);
if (result != noErr) {
OSSTATUS_DLOG(ERROR, result) << "AudioObjectAddPropertyListener() failed!";
return;
}
listener_registered_ = true;
}
AudioManagerMac::~AudioManagerMac() {
if (listener_registered_) {
// TODO(dalecurtis): CHECK destruction happens on |creating_message_loop_|,
// should be true, but currently several unit tests perform destruction in
// odd places so we can't CHECK here currently.
OSStatus result = AudioObjectRemovePropertyListener(
kAudioObjectSystemObject,
&kDeviceChangePropertyAddress,
&OnDefaultDeviceChangedCallback,
this);
OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
<< "AudioObjectRemovePropertyListener() failed!";
}
Shutdown();
}
bool AudioManagerMac::HasAudioOutputDevices() {
return HasAudioHardware(kAudioHardwarePropertyDefaultOutputDevice);
}
bool AudioManagerMac::HasAudioInputDevices() {
return HasAudioHardware(kAudioHardwarePropertyDefaultInputDevice);
}
void AudioManagerMac::GetAudioInputDeviceNames(
media::AudioDeviceNames* device_names) {
GetAudioDeviceInfo(true, device_names);
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.
media::AudioDeviceName name;
name.device_name = AudioManagerBase::kDefaultDeviceName;
name.unique_id = AudioManagerBase::kDefaultDeviceId;
device_names->push_front(name);
}
}
AudioOutputStream* AudioManagerMac::MakeLinearOutputStream(
const AudioParameters& params) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
return new PCMQueueOutAudioOutputStream(this, params);
}
AudioOutputStream* AudioManagerMac::MakeLowLatencyOutputStream(
const AudioParameters& params) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
// TODO(crogers): remove once we properly handle input device selection.
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableWebAudioInput)) {
if (HasUnifiedDefaultIO())
return new AudioHardwareUnifiedStream(this, params);
// kAudioDeviceUnknown translates to "use default" here.
return new AudioSynchronizedStream(this,
params,
kAudioDeviceUnknown,
kAudioDeviceUnknown);
}
return new AUAudioOutputStream(this, params);
}
AudioInputStream* AudioManagerMac::MakeLinearInputStream(
const AudioParameters& params, const std::string& device_id) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
return new PCMQueueInAudioInputStream(this, params);
}
AudioInputStream* AudioManagerMac::MakeLowLatencyInputStream(
const AudioParameters& params, const std::string& device_id) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
// Gets the AudioDeviceID that refers to the AudioOutputDevice with the device
// unique id. This AudioDeviceID is used to set the device for Audio Unit.
AudioDeviceID audio_device_id = GetAudioDeviceIdByUId(true, device_id);
AudioInputStream* stream = NULL;
if (audio_device_id != kAudioObjectUnknown)
stream = new AUAudioInputStream(this, params, audio_device_id);
return stream;
}
AudioParameters AudioManagerMac::GetPreferredLowLatencyOutputStreamParameters(
const AudioParameters& input_params) {
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableWebAudioInput)) {
// TODO(crogers): given the limitations of the AudioOutputStream
// back-ends used with kEnableWebAudioInput, we hard-code to stereo.
// Specifically, this is a limitation of AudioSynchronizedStream which
// can be removed as part of the work to consolidate these back-ends.
return AudioParameters(
AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO,
GetAudioHardwareSampleRate(), 16, GetAudioHardwareBufferSize());
}
return AudioManagerBase::GetPreferredLowLatencyOutputStreamParameters(
input_params);
}
void AudioManagerMac::OnDeviceChange() {
// Post the task to the |creating_message_loop_| to execute our listener
// callback. The callback is created using BindToLoop() so will hop over
// to the audio thread upon execution.
creating_message_loop_->PostTask(FROM_HERE, BindToLoop(
GetMessageLoop(), base::Bind(
&AudioManagerMac::NotifyAllOutputDeviceChangeListeners,
base::Unretained(this))));
}
AudioManager* CreateAudioManager() {
return new AudioManagerMac();
}
} // namespace media