blob: f9a969fc05493dbaf6856c74908f6dc05750373c [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/mac/core_audio_util_mac.h"
#include <IOKit/audio/IOAudioTypes.h>
#include <utility>
#include "base/mac/mac_logging.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
namespace media {
namespace core_audio_mac {
namespace {
AudioObjectPropertyScope InputOutputScope(bool is_input) {
return is_input ? kAudioObjectPropertyScopeInput
: kAudioObjectPropertyScopeOutput;
}
absl::optional<std::string> GetDeviceStringProperty(
AudioObjectID device_id,
AudioObjectPropertySelector property_selector) {
CFStringRef property_value = nullptr;
UInt32 size = sizeof(property_value);
AudioObjectPropertyAddress property_address = {
property_selector, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
OSStatus result = AudioObjectGetPropertyData(
device_id, &property_address, 0 /* inQualifierDataSize */,
nullptr /* inQualifierData */, &size, &property_value);
if (result != noErr) {
OSSTATUS_DLOG(WARNING, result)
<< "Failed to read string property " << property_selector
<< " for device " << device_id;
return absl::nullopt;
}
if (!property_value)
return absl::nullopt;
std::string device_property = base::SysCFStringRefToUTF8(property_value);
CFRelease(property_value);
return device_property;
}
absl::optional<uint32_t> GetDeviceUint32Property(
AudioObjectID device_id,
AudioObjectPropertySelector property_selector,
AudioObjectPropertyScope property_scope) {
AudioObjectPropertyAddress property_address = {
property_selector, property_scope, kAudioObjectPropertyElementMaster};
UInt32 property_value;
UInt32 size = sizeof(property_value);
OSStatus result = AudioObjectGetPropertyData(
device_id, &property_address, 0 /* inQualifierDataSize */,
nullptr /* inQualifierData */, &size, &property_value);
if (result != noErr)
return absl::nullopt;
return property_value;
}
uint32_t GetDevicePropertySize(AudioObjectID device_id,
AudioObjectPropertySelector property_selector,
AudioObjectPropertyScope property_scope) {
AudioObjectPropertyAddress property_address = {
property_selector, property_scope, kAudioObjectPropertyElementMaster};
UInt32 size = 0;
OSStatus result = AudioObjectGetPropertyDataSize(
device_id, &property_address, 0 /* inQualifierDataSize */,
nullptr /* inQualifierData */, &size);
if (result != noErr) {
OSSTATUS_DLOG(WARNING, result)
<< "Failed to read size of property " << property_selector
<< " for device " << device_id;
return 0;
}
return size;
}
std::vector<AudioObjectID> GetAudioObjectIDs(
AudioObjectID audio_object_id,
AudioObjectPropertySelector property_selector) {
AudioObjectPropertyAddress property_address = {
property_selector, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
UInt32 size = 0;
OSStatus result = AudioObjectGetPropertyDataSize(
audio_object_id, &property_address, 0 /* inQualifierDataSize */,
nullptr /* inQualifierData */, &size);
if (result != noErr) {
OSSTATUS_DLOG(WARNING, result)
<< "Failed to read size of property " << property_selector
<< " for device/object " << audio_object_id;
return {};
}
if (size == 0)
return {};
size_t device_count = size / sizeof(AudioObjectID);
// Get the array of device ids for all the devices, which includes both
// input devices and output devices.
std::vector<AudioObjectID> device_ids(device_count);
result = AudioObjectGetPropertyData(
audio_object_id, &property_address, 0 /* inQualifierDataSize */,
nullptr /* inQualifierData */, &size, device_ids.data());
if (result != noErr) {
OSSTATUS_DLOG(WARNING, result)
<< "Failed to read object IDs from property " << property_selector
<< " for device/object " << audio_object_id;
return {};
}
return device_ids;
}
absl::optional<std::string> GetDeviceName(AudioObjectID device_id) {
return GetDeviceStringProperty(device_id, kAudioObjectPropertyName);
}
absl::optional<std::string> GetDeviceModel(AudioObjectID device_id) {
return GetDeviceStringProperty(device_id, kAudioDevicePropertyModelUID);
}
bool ModelContainsVidPid(const std::string& model) {
return model.size() > 10 && model[model.size() - 5] == ':' &&
model[model.size() - 10] == ':';
}
std::string UsbVidPidFromModel(const std::string& model) {
return ModelContainsVidPid(model)
? base::ToLowerASCII(model.substr(model.size() - 9))
: std::string();
}
std::string TransportTypeToString(uint32_t transport_type) {
switch (transport_type) {
case kAudioDeviceTransportTypeBuiltIn:
return "Built-in";
case kAudioDeviceTransportTypeAggregate:
return "Aggregate";
case kAudioDeviceTransportTypeAutoAggregate:
return "AutoAggregate";
case kAudioDeviceTransportTypeVirtual:
return "Virtual";
case kAudioDeviceTransportTypePCI:
return "PCI";
case kAudioDeviceTransportTypeUSB:
return "USB";
case kAudioDeviceTransportTypeFireWire:
return "FireWire";
case kAudioDeviceTransportTypeBluetooth:
return "Bluetooth";
case kAudioDeviceTransportTypeBluetoothLE:
return "Bluetooth LE";
case kAudioDeviceTransportTypeHDMI:
return "HDMI";
case kAudioDeviceTransportTypeDisplayPort:
return "DisplayPort";
case kAudioDeviceTransportTypeAirPlay:
return "AirPlay";
case kAudioDeviceTransportTypeAVB:
return "AVB";
case kAudioDeviceTransportTypeThunderbolt:
return "Thunderbolt";
case kAudioDeviceTransportTypeUnknown:
default:
return std::string();
}
}
absl::optional<std::string> TranslateDeviceSource(AudioObjectID device_id,
UInt32 source_id,
bool is_input) {
CFStringRef source_name = nullptr;
AudioValueTranslation translation;
translation.mInputData = &source_id;
translation.mInputDataSize = sizeof(source_id);
translation.mOutputData = &source_name;
translation.mOutputDataSize = sizeof(source_name);
UInt32 translation_size = sizeof(AudioValueTranslation);
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyDataSourceNameForIDCFString,
InputOutputScope(is_input), kAudioObjectPropertyElementMaster};
OSStatus result = AudioObjectGetPropertyData(
device_id, &property_address, 0 /* inQualifierDataSize */,
nullptr /* inQualifierData */, &translation_size, &translation);
if (result)
return absl::nullopt;
std::string ret = base::SysCFStringRefToUTF8(source_name);
CFRelease(source_name);
return ret;
}
} // namespace
std::vector<AudioObjectID> GetAllAudioDeviceIDs() {
return GetAudioObjectIDs(kAudioObjectSystemObject,
kAudioHardwarePropertyDevices);
}
std::vector<AudioObjectID> GetRelatedDeviceIDs(AudioObjectID device_id) {
return GetAudioObjectIDs(device_id, kAudioDevicePropertyRelatedDevices);
}
absl::optional<std::string> GetDeviceUniqueID(AudioObjectID device_id) {
return GetDeviceStringProperty(device_id, kAudioDevicePropertyDeviceUID);
}
absl::optional<std::string> GetDeviceLabel(AudioObjectID device_id,
bool is_input) {
absl::optional<std::string> device_label;
absl::optional<uint32_t> source = GetDeviceSource(device_id, is_input);
if (source) {
device_label = TranslateDeviceSource(device_id, *source, is_input);
}
if (!device_label) {
device_label = GetDeviceName(device_id);
if (!device_label)
return absl::nullopt;
}
std::string suffix;
absl::optional<uint32_t> transport_type = GetDeviceTransportType(device_id);
if (transport_type) {
if (*transport_type == kAudioDeviceTransportTypeUSB) {
absl::optional<std::string> model = GetDeviceModel(device_id);
if (model) {
suffix = UsbVidPidFromModel(*model);
}
} else {
suffix = TransportTypeToString(*transport_type);
}
}
DCHECK(device_label);
if (!suffix.empty())
*device_label += " (" + suffix + ")";
return device_label;
}
uint32_t GetNumStreams(AudioObjectID device_id, bool is_input) {
return GetDevicePropertySize(device_id, kAudioDevicePropertyStreams,
InputOutputScope(is_input));
}
absl::optional<uint32_t> GetDeviceSource(AudioObjectID device_id,
bool is_input) {
return GetDeviceUint32Property(device_id, kAudioDevicePropertyDataSource,
InputOutputScope(is_input));
}
absl::optional<uint32_t> GetDeviceTransportType(AudioObjectID device_id) {
return GetDeviceUint32Property(device_id, kAudioDevicePropertyTransportType,
kAudioObjectPropertyScopeGlobal);
}
bool IsPrivateAggregateDevice(AudioObjectID device_id) {
// Don't try to access aggregate device properties unless |device_id| is
// really an aggregate device.
if (GetDeviceTransportType(device_id) != kAudioDeviceTransportTypeAggregate)
return false;
const AudioObjectPropertyAddress property_address = {
kAudioAggregateDevicePropertyComposition, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
CFDictionaryRef dictionary = nullptr;
UInt32 size = sizeof(dictionary);
OSStatus result = AudioObjectGetPropertyData(
device_id, &property_address, 0 /* inQualifierDataSize */,
nullptr /* inQualifierData */, &size, &dictionary);
if (result != noErr) {
OSSTATUS_LOG(WARNING, result) << "Failed to read property "
<< kAudioAggregateDevicePropertyComposition
<< " for device " << device_id;
return false;
}
DCHECK_EQ(CFGetTypeID(dictionary), CFDictionaryGetTypeID());
bool is_private = false;
CFTypeRef value = CFDictionaryGetValue(
dictionary, CFSTR(kAudioAggregateDeviceIsPrivateKey));
if (value && CFGetTypeID(value) == CFNumberGetTypeID()) {
int number = 0;
if (CFNumberGetValue(reinterpret_cast<CFNumberRef>(value), kCFNumberIntType,
&number)) {
is_private = number != 0;
}
}
CFRelease(dictionary);
return is_private;
}
bool IsInputDevice(AudioObjectID device_id) {
std::vector<AudioObjectID> streams =
GetAudioObjectIDs(device_id, kAudioDevicePropertyStreams);
int num_undefined_input_streams = 0;
int num_defined_input_streams = 0;
int num_output_streams = 0;
for (auto stream_id : streams) {
auto direction =
GetDeviceUint32Property(stream_id, kAudioStreamPropertyDirection,
kAudioObjectPropertyScopeGlobal);
if (!direction.has_value())
continue;
const UInt32 kDirectionOutput = 0;
const UInt32 kDirectionInput = 1;
if (direction == kDirectionOutput) {
++num_output_streams;
} else if (direction == kDirectionInput) {
// Filter input streams based on what terminal it claims to be attached
// to. Note that INPUT_UNDEFINED comes from a set of terminals declared
// in IOKit. CoreAudio defines a number of terminals in
// AudioHardwareBase.h but none of them match any of the values I've
// seen used in practice, though I've only tested a few devices.
auto terminal =
GetDeviceUint32Property(stream_id, kAudioStreamPropertyTerminalType,
kAudioObjectPropertyScopeGlobal);
if (terminal.has_value() && terminal == INPUT_UNDEFINED) {
++num_undefined_input_streams;
} else {
++num_defined_input_streams;
}
}
}
// I've only seen INPUT_UNDEFINED introduced by the VoiceProcessing AudioUnit,
// but to err on the side of caution, let's allow a device with only undefined
// input streams and no output streams as well.
return num_defined_input_streams > 0 ||
(num_undefined_input_streams > 0 && num_output_streams == 0);
}
bool IsOutputDevice(AudioObjectID device_id) {
return GetNumStreams(device_id, false) > 0;
}
} // namespace core_audio_mac
} // namespace media