blob: 83c850e4922329f6ee874c5863eb74e7910c070a [file] [log] [blame]
/*
* Copyright (C) 2017-2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "CoreAudioCaptureDeviceManager.h"
#if ENABLE(MEDIA_STREAM) && PLATFORM(MAC)
#include "CoreAudioCaptureDevice.h"
#include "CoreAudioCaptureSource.h"
#include "Logging.h"
#include "RealtimeMediaSourceCenter.h"
#include <AudioUnit/AudioUnit.h>
#include <CoreMedia/CMSync.h>
#include <pal/spi/cf/CoreAudioSPI.h>
#include <wtf/Assertions.h>
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
#import <pal/cf/CoreMediaSoftLink.h>
namespace WebCore {
CoreAudioCaptureDeviceManager& CoreAudioCaptureDeviceManager::singleton()
{
static NeverDestroyed<CoreAudioCaptureDeviceManager> manager;
return manager;
}
const Vector<CaptureDevice>& CoreAudioCaptureDeviceManager::captureDevices()
{
coreAudioCaptureDevices();
return m_captureDevices;
}
std::optional<CaptureDevice> CoreAudioCaptureDeviceManager::captureDeviceWithPersistentID(CaptureDevice::DeviceType type, const String& deviceID)
{
ASSERT_UNUSED(type, type == CaptureDevice::DeviceType::Microphone);
for (auto& device : captureDevices()) {
if (device.persistentId() == deviceID)
return device;
}
return std::nullopt;
}
static bool deviceHasStreams(AudioObjectID deviceID, const AudioObjectPropertyAddress& address)
{
UInt32 dataSize = 0;
auto err = AudioObjectGetPropertyDataSize(deviceID, &address, 0, nullptr, &dataSize);
if (err || !dataSize)
return false;
auto bufferList = std::unique_ptr<AudioBufferList>((AudioBufferList*) ::operator new (dataSize));
memset(bufferList.get(), 0, dataSize);
err = AudioObjectGetPropertyData(deviceID, &address, 0, nullptr, &dataSize, bufferList.get());
return !err && bufferList->mNumberBuffers;
}
static bool deviceHasInputStreams(AudioObjectID deviceID)
{
AudioObjectPropertyAddress address = {
kAudioDevicePropertyStreamConfiguration,
kAudioDevicePropertyScopeInput,
#if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN)
kAudioObjectPropertyElementMain
#else
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
kAudioObjectPropertyElementMaster
ALLOW_DEPRECATED_DECLARATIONS_END
#endif
};
return deviceHasStreams(deviceID, address);
}
static bool deviceHasOutputStreams(AudioObjectID deviceID)
{
AudioObjectPropertyAddress address = {
kAudioDevicePropertyStreamConfiguration,
kAudioDevicePropertyScopeOutput,
#if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN)
kAudioObjectPropertyElementMain
#else
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
kAudioObjectPropertyElementMaster
ALLOW_DEPRECATED_DECLARATIONS_END
#endif
};
return deviceHasStreams(deviceID, address);
}
static bool isValidCaptureDevice(const CoreAudioCaptureDevice& device)
{
// Ignore output devices that have input only for echo cancellation.
AudioObjectPropertyAddress address = {
kAudioDevicePropertyTapEnabled,
kAudioDevicePropertyScopeOutput,
#if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN)
kAudioObjectPropertyElementMain
#else
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
kAudioObjectPropertyElementMaster
ALLOW_DEPRECATED_DECLARATIONS_END
#endif
};
if (AudioObjectHasProperty(device.deviceID(), &address)) {
RELEASE_LOG(WebRTC, "Ignoring output device that have input only for echo cancellation");
return false;
}
// Ignore non-aggregable devices.
UInt32 dataSize = 0;
address = {
kAudioObjectPropertyCreator,
kAudioObjectPropertyScopeGlobal,
#if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN)
kAudioObjectPropertyElementMain
#else
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
kAudioObjectPropertyElementMaster
ALLOW_DEPRECATED_DECLARATIONS_END
#endif
};
CFStringRef name = nullptr;
dataSize = sizeof(name);
AudioObjectGetPropertyData(device.deviceID(), &address, 0, nullptr, &dataSize, &name);
bool isNonAggregable = !name || !String(name).startsWith("com.apple.audio.CoreAudio");
if (name)
CFRelease(name);
if (isNonAggregable) {
RELEASE_LOG(WebRTC, "Ignoring output device that is non aggregable");
return false;
}
// Ignore unnamed devices and aggregate devices created by VPIO.
if (device.label().isEmpty()) {
RELEASE_LOG(WebRTC, "Ignoring output device that is unnamed");
return false;
}
if (device.label().startsWith("VPAUAggregateAudioDevice")) {
RELEASE_LOG(WebRTC, "Ignoring output VPAUAggregateAudioDevice device");
return false;
}
if (device.label().contains("WebexMediaAudioDevice")) {
RELEASE_LOG(WebRTC, "Ignoring webex audio device");
return false;
}
return true;
}
void CoreAudioCaptureDeviceManager::scheduleUpdateCaptureDevices()
{
if (m_wasRefreshAudioCaptureDevicesScheduled)
return;
m_wasRefreshAudioCaptureDevicesScheduled = true;
callOnMainThread([this] {
refreshAudioCaptureDevices(NotifyIfDevicesHaveChanged::Notify);
m_wasRefreshAudioCaptureDevicesScheduled = false;
});
}
Vector<CoreAudioCaptureDevice>& CoreAudioCaptureDeviceManager::coreAudioCaptureDevices()
{
static bool initialized;
if (!initialized) {
initialized = true;
refreshAudioCaptureDevices(NotifyIfDevicesHaveChanged::DoNotNotify);
auto listener = ^(UInt32 count, const AudioObjectPropertyAddress properties[]) {
bool notify = false;
for (UInt32 i = 0; i < count; ++i)
notify |= (properties[i].mSelector == kAudioHardwarePropertyDevices || properties[i].mSelector == kAudioHardwarePropertyDefaultInputDevice || properties[i].mSelector == kAudioHardwarePropertyDefaultOutputDevice);
if (notify)
CoreAudioCaptureDeviceManager::singleton().scheduleUpdateCaptureDevices();
};
AudioObjectPropertyAddress address = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
#if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN)
kAudioObjectPropertyElementMain
#else
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
kAudioObjectPropertyElementMaster
ALLOW_DEPRECATED_DECLARATIONS_END
#endif
};
auto err = AudioObjectAddPropertyListenerBlock(kAudioObjectSystemObject, &address, dispatch_get_main_queue(), listener);
if (err)
LOG_ERROR("CoreAudioCaptureDeviceManager::devices(%p) AudioObjectAddPropertyListener for kAudioHardwarePropertyDevices returned error %d (%.4s)", this, (int)err, (char*)&err);
address = {
kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
#if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN)
kAudioObjectPropertyElementMain
#else
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
kAudioObjectPropertyElementMaster
ALLOW_DEPRECATED_DECLARATIONS_END
#endif
};
err = AudioObjectAddPropertyListenerBlock(kAudioObjectSystemObject, &address, dispatch_get_main_queue(), listener);
if (err)
LOG_ERROR("CoreAudioCaptureDeviceManager::devices(%p) AudioObjectAddPropertyListener for kAudioHardwarePropertyDefaultInputDevice returned error %d (%.4s)", this, (int)err, (char*)&err);
address = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
#if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN)
kAudioObjectPropertyElementMain
#else
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
kAudioObjectPropertyElementMaster
ALLOW_DEPRECATED_DECLARATIONS_END
#endif
};
err = AudioObjectAddPropertyListenerBlock(kAudioObjectSystemObject, &address, dispatch_get_main_queue(), listener);
if (err)
LOG_ERROR("CoreAudioCaptureDeviceManager::devices(%p) AudioObjectAddPropertyListener for kAudioHardwarePropertyDefaultOutputDevice returned error %d (%.4s)", this, (int)err, (char*)&err);
}
return m_coreAudioCaptureDevices;
}
std::optional<CoreAudioCaptureDevice> CoreAudioCaptureDeviceManager::coreAudioDeviceWithUID(const String& deviceID)
{
for (auto& device : coreAudioCaptureDevices()) {
if (device.persistentId() == deviceID && device.enabled())
return device;
}
return std::nullopt;
}
static inline bool hasDevice(const Vector<CoreAudioCaptureDevice>& devices, uint32_t deviceID, CaptureDevice::DeviceType deviceType)
{
return std::any_of(devices.begin(), devices.end(), [&deviceID, deviceType](auto& device) {
return device.deviceID() == deviceID && device.type() == deviceType;
});
}
static inline Vector<CoreAudioCaptureDevice> computeAudioDeviceList()
{
AudioObjectPropertyAddress address = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
#if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN)
kAudioObjectPropertyElementMain
#else
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
kAudioObjectPropertyElementMaster
ALLOW_DEPRECATED_DECLARATIONS_END
#endif
};
UInt32 dataSize = 0;
auto err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &address, 0, nullptr, &dataSize);
if (err) {
RELEASE_LOG(WebRTC, "computeAudioDeviceList failed to get size of device list %d (%.4s)", (int)err, (char*)&err);
return { };
}
size_t deviceCount = dataSize / sizeof(AudioObjectID);
Vector<AudioObjectID> deviceIDs(deviceCount);
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, nullptr, &dataSize, deviceIDs.data());
if (err) {
RELEASE_LOG(WebRTC, "computeAudioDeviceList failed to get device list %d (%.4s)", (int)err, (char*)&err);
return { };
}
Vector<CoreAudioCaptureDevice> audioDevices;
// Microphones
for (size_t i = 0; i < deviceCount; i++) {
AudioObjectID deviceID = deviceIDs[i];
if (!deviceHasInputStreams(deviceID) || hasDevice(audioDevices, deviceID, CaptureDevice::DeviceType::Microphone))
continue;
auto microphoneDevice = CoreAudioCaptureDevice::create(deviceID, CaptureDevice::DeviceType::Microphone, { });
if (microphoneDevice && isValidCaptureDevice(microphoneDevice.value()))
audioDevices.append(WTFMove(microphoneDevice.value()));
}
// Speakers
for (size_t i = 0; i < deviceCount; i++) {
AudioObjectID deviceID = deviceIDs[i];
if (!deviceHasOutputStreams(deviceID) || hasDevice(audioDevices, deviceID, CaptureDevice::DeviceType::Speaker))
continue;
String groupID;
for (auto relatedDeviceID : CoreAudioCaptureDevice::relatedAudioDeviceIDs(deviceID)) {
for (auto& device : audioDevices) {
if (device.deviceID() == relatedDeviceID && device.type() == CaptureDevice::DeviceType::Microphone) {
groupID = device.persistentId();
break;
}
}
}
auto device = CoreAudioCaptureDevice::create(deviceID, CaptureDevice::DeviceType::Speaker, groupID);
if (device) {
// If there is no groupID, relate devices if the label is matching.
if (groupID.isNull()) {
for (auto& existingDevice : audioDevices) {
if (existingDevice.label() == device->label() && existingDevice.type() == CaptureDevice::DeviceType::Microphone) {
device->setGroupId(existingDevice.persistentId());
break;
}
}
}
audioDevices.append(WTFMove(device.value()));
}
}
return audioDevices;
}
void CoreAudioCaptureDeviceManager::refreshAudioCaptureDevices(NotifyIfDevicesHaveChanged notify)
{
ASSERT(isMainThread());
auto audioDevices = computeAudioDeviceList();
bool haveDeviceChanges = audioDevices.size() != m_coreAudioCaptureDevices.size();
if (!haveDeviceChanges) {
for (size_t cptr = 0; cptr < audioDevices.size(); ++cptr) {
auto& oldDevice = m_coreAudioCaptureDevices[cptr];
auto& newDevice = audioDevices[cptr];
if (newDevice.type() != oldDevice.type() || newDevice.deviceID() != oldDevice.deviceID() || newDevice.isDefault() != oldDevice.isDefault() || newDevice.enabled() != oldDevice.enabled() || newDevice.isDefault() != oldDevice.isDefault())
haveDeviceChanges = true;
}
}
if (!haveDeviceChanges)
return;
std::sort(audioDevices.begin(), audioDevices.end(), [] (auto& first, auto& second) -> bool {
return first.isDefault() && !second.isDefault();
});
m_coreAudioCaptureDevices = WTFMove(audioDevices);
m_captureDevices.clear();
m_speakerDevices.clear();
for (auto& device : m_coreAudioCaptureDevices) {
if (device.type() == CaptureDevice::DeviceType::Microphone)
m_captureDevices.append(device);
else
m_speakerDevices.append(device);
}
if (notify == NotifyIfDevicesHaveChanged::Notify) {
deviceChanged();
CoreAudioCaptureSourceFactory::singleton().devicesChanged(m_captureDevices);
}
}
} // namespace WebCore
#endif // ENABLE(MEDIA_STREAM) && PLATFORM(MAC)