blob: 6ec6a5b008faec2a381a85c8771f4e4d92488a1f [file] [log] [blame]
// Copyright 2012 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/audio_manager_mac.h"
#include <limits>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/apple/foundation_util.h"
#include "base/apple/osstatus_logging.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/containers/heap_array.h"
#include "base/functional/bind.h"
#include "base/mac/mac_util.h"
#include "base/memory/free_deleter.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_observer.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "media/audio/apple/audio_auhal.h"
#include "media/audio/apple/audio_input.h"
#include "media/audio/apple/audio_low_latency_input.h"
#include "media/audio/apple/scoped_audio_unit.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/mac/audio_loopback_input_mac.h"
#include "media/audio/mac/core_audio_util_mac.h"
#include "media/audio/mac/coreaudio_dispatch_override.h"
#include "media/audio/mac/screen_capture_kit_swizzler.h"
#include "media/base/audio_parameters.h"
#include "media/base/audio_timestamp_helper.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 {
BASE_FEATURE(kMonitorOutputSampleRateChangesMac,
"MonitorOutputSampleRateChangesMac",
base::FEATURE_ENABLED_BY_DEFAULT);
// 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;
static bool GetOutputDeviceChannelsAndLayout(AudioUnit audio_unit,
int* channels,
ChannelLayout* channel_layout);
// Helper method to construct AudioObjectPropertyAddress structure given
// property selector and scope. The property element is always set to
// kAudioObjectPropertyElementMain.
static AudioObjectPropertyAddress GetAudioObjectPropertyAddress(
AudioObjectPropertySelector selector,
bool is_input) {
AudioObjectPropertyScope scope = is_input ? kAudioObjectPropertyScopeInput
: kAudioObjectPropertyScopeOutput;
AudioObjectPropertyAddress property_address = {
selector, scope, kAudioObjectPropertyElementMain};
return property_address;
}
static const AudioObjectPropertyAddress kNoiseReductionPropertyAddress = {
'nzca', kAudioDevicePropertyScopeInput, kAudioObjectPropertyElementMain};
// 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 result = AudioObjectGetPropertyData(device_id, &address, 0, NULL,
&data_size, &range);
if (result != noErr) {
OSSTATUS_DLOG(WARNING, result)
<< "Failed to query IO buffer size range for device: " << std::hex
<< device_id;
} else {
*minimum = range.mMinimum;
*maximum = range.mMaximum;
}
return result;
}
static bool HasAudioHardware(AudioObjectPropertySelector selector) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
AudioDeviceID output_device_id = kAudioObjectUnknown;
const AudioObjectPropertyAddress property_address = {
selector,
kAudioObjectPropertyScopeGlobal, // mScope
kAudioObjectPropertyElementMain // 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());
std::vector<AudioObjectID> device_ids =
core_audio_mac::GetAllAudioDeviceIDs();
for (AudioObjectID device_id : device_ids) {
const bool is_valid_for_direction =
(is_input ? core_audio_mac::IsInputDevice(device_id)
: core_audio_mac::IsOutputDevice(device_id));
if (!is_valid_for_direction) {
continue;
}
std::optional<std::string> unique_id =
core_audio_mac::GetDeviceUniqueID(device_id);
if (!unique_id) {
continue;
}
std::optional<std::string> label =
core_audio_mac::GetDeviceLabel(device_id, is_input);
if (!label) {
continue;
}
// Filter out aggregate devices, e.g. those that get created by using
// kAudioUnitSubType_VoiceProcessingIO.
if (core_audio_mac::IsPrivateAggregateDevice(device_id)) {
continue;
}
device_names->emplace_back(std::move(*label), std::move(*unique_id));
}
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());
}
}
AudioDeviceID AudioManagerMac::GetAudioDeviceIdByUId(
bool is_input,
const std::string& device_id) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
AudioObjectPropertyAddress property_address = {
kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain};
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::apple::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 = kAudioObjectPropertyElementMain;
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;
}
bool AudioManagerMac::GetDefaultInputDevice(AudioDeviceID* input_device) {
return GetDefaultDevice(input_device, true);
}
bool AudioManagerMac::GetDefaultOutputDevice(AudioDeviceID* output_device) {
return GetDefaultDevice(output_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, kAudioObjectPropertyElementMain};
UInt32 size;
OSStatus result = AudioObjectGetPropertyDataSize(device, &pa, 0, 0, &size);
if (result != noErr || !size) {
return false;
}
auto list_storage = base::HeapArray<uint8_t>::Uninit(size);
AudioBufferList* buffer_list =
reinterpret_cast<AudioBufferList*>(list_storage.data());
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) << __FUNCTION__
<< (scope == kAudioDevicePropertyScopeInput ? " Input" : " Output")
<< " total channels: " << *channels;
return true;
}
// Returns the channel count from the |audio_unit|'s stream format for input
// scope / input element or output scope / output element.
static bool GetAudioUnitStreamFormatChannelCount(AudioUnit audio_unit,
AUElement element,
int* channels) {
AudioStreamBasicDescription stream_format;
UInt32 size = sizeof(stream_format);
OSStatus result =
AudioUnitGetProperty(audio_unit, kAudioUnitProperty_StreamFormat,
element == AUElement::OUTPUT ? kAudioUnitScope_Output
: kAudioUnitScope_Input,
element, &stream_format, &size);
if (result != noErr) {
OSSTATUS_DLOG(ERROR, result) << "Failed to get AudioUnit stream format.";
return false;
}
*channels = stream_format.mChannelsPerFrame;
return true;
}
// Returns the `channels` for `device` as provided by the AudioUnit attached
// to that input device. Returns true if the `channels` could be pulled
// from the AudioUnit successfully, otherwise return false and `channels` is
// untouched.
static bool GetInputDeviceChannels(AudioDeviceID device, int* channels) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(channels);
// For input, get the channel count directly from the AudioUnit's stream
// format.
// TODO(crbug.com/41361558): Find out if we can use channel layout on
// input element, or confirm that we can't.
ScopedAudioUnit au(device, AUElement::INPUT);
if (!au.is_valid()) {
return false;
}
if (!GetAudioUnitStreamFormatChannelCount(au.audio_unit(), AUElement::INPUT,
channels)) {
return false;
}
DVLOG(2) << __FUNCTION__ << " Input channels: " << *channels;
return true;
}
// Returns the `channels` and `channel_layout` for `device` as provided by the
// AudioUnit attached to that output device. Returns true if the `channels` and
// `channel_layout` could be pulled from the AudioUnit successfully, otherwise
// return false and `channels` and `channel_layout` are untouched.
static bool GetOutputDeviceChannelsAndLayout(AudioDeviceID device,
int* channels,
ChannelLayout* channel_layout) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(channels);
CHECK(channel_layout);
// 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, kAudioDevicePropertyScopeOutput,
&total_channel_count) &&
total_channel_count > kMaxConcurrentChannels) {
*channels = total_channel_count;
*channel_layout = CHANNEL_LAYOUT_DISCRETE;
} else {
ScopedAudioUnit au(device, AUElement::OUTPUT);
if (!au.is_valid()) {
return false;
}
if (!GetOutputDeviceChannelsAndLayout(au.audio_unit(), channels,
channel_layout)) {
return false;
}
}
DVLOG(2) << __FUNCTION__ << " Output channels: " << *channels
<< ", channel layout: " << ChannelLayoutToString(*channel_layout);
return true;
}
static bool GetOutputDeviceChannelsAndLayout(AudioUnit audio_unit,
int* channels,
ChannelLayout* channel_layout) {
// Attempt to retrieve the channel layout from the AudioUnit.
std::unique_ptr<ScopedAudioChannelLayout> scoped_device_layout =
AudioManagerApple::GetOutputDeviceChannelLayout(audio_unit);
if (!scoped_device_layout) {
DLOG(ERROR) << "Failed to retrieve output device channel layout.";
return false;
}
AudioChannelLayout* device_layout = scoped_device_layout->layout();
// There is no channel info for stereo, assume so for mono as well.
if (device_layout->mNumberChannelDescriptions == 1 ||
device_layout->mNumberChannelDescriptions == 2) {
*channels = device_layout->mNumberChannelDescriptions;
*channel_layout =
*channels == 2 ? CHANNEL_LAYOUT_STEREO : CHANNEL_LAYOUT_MONO;
return true;
}
*channels = 0;
// use `CHANNEL_LAYOUT_DISCRETE` as the default layout if we can't
// find out a matched one.
*channel_layout = CHANNEL_LAYOUT_DISCRETE;
std::vector<Channels> channels_to_match;
for (UInt32 i = 0; i < device_layout->mNumberChannelDescriptions; i++) {
AudioChannelLabel label =
device_layout->mChannelDescriptions[i].mChannelLabel;
if (label == kAudioChannelLabel_Unknown) {
continue;
}
(*channels)++;
Channels channel;
if (AudioChannelLabelToChannel(label, &channel)) {
channels_to_match.push_back(channel);
}
}
if (*channels == 0 ||
*channels != static_cast<int>(channels_to_match.size())) {
return true;
}
for (int i = 0; i <= ChannelLayout::CHANNEL_LAYOUT_MAX; i++) {
ChannelLayout layout = static_cast<ChannelLayout>(i);
if (ChannelLayoutToChannelCount(layout) != *channels) {
continue;
}
bool matched = true;
for (const auto& channel : channels_to_match) {
auto channel_order = ChannelOrder(layout, channel);
if (channel_order == -1) {
matched = false;
break;
}
}
if (matched) {
*channel_layout = layout;
return true;
}
}
return true;
}
class AudioManagerMac::AudioPowerObserver : public base::PowerSuspendObserver {
public:
AudioPowerObserver()
: is_suspending_(false),
is_monitoring_(base::PowerMonitor::IsInitialized()),
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::AddPowerSuspendObserver(this);
}
AudioPowerObserver(const AudioPowerObserver&) = delete;
AudioPowerObserver& operator=(const AudioPowerObserver&) = delete;
~AudioPowerObserver() override {
DCHECK(thread_checker_.CalledOnValidThread());
if (!is_monitoring_) {
return;
}
base::PowerMonitor::RemovePowerSuspendObserver(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::IsOnBatteryPower();
}
private:
void OnSuspend() override {
DCHECK(thread_checker_.CalledOnValidThread());
DVLOG(1) << "AudioPowerObserver::" << __FUNCTION__;
is_suspending_ = true;
}
void OnResume() override {
DCHECK(thread_checker_.CalledOnValidThread());
DVLOG(1) << "AudioPowerObserver::" << __FUNCTION__;
++num_resume_notifications_;
is_suspending_ = false;
earliest_start_time_ =
base::TimeTicks::Now() + base::Seconds(kStartDelayInSecsForPowerEvents);
}
bool is_suspending_;
const bool is_monitoring_;
base::TimeTicks earliest_start_time_;
base::ThreadChecker thread_checker_;
size_t num_resume_notifications_;
};
AudioManagerMac::AudioManagerMac(std::unique_ptr<AudioThread> audio_thread,
AudioLogFactory* audio_log_factory)
: AudioManagerApple(std::move(audio_thread), audio_log_factory),
current_sample_rate_(0),
current_output_device_(kAudioDeviceUnknown),
in_shutdown_(false),
weak_ptr_factory_(this) {
SetMaxOutputStreamsAllowed(kMaxOutputStreams);
// PostTask since AudioManager creation may be on the startup path and this
// may be slow.
GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&AudioManagerMac::InitializeOnAudioThread,
weak_ptr_factory_.GetWeakPtr()));
}
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.
CloseAllInputStreams();
CHECK(basic_input_streams_.empty());
CHECK(low_latency_input_streams_.empty());
// Deinitialize power observer on audio thread, since it's initialized on the
// audio thread. Typically, constructor/destructor and
// InitializeOnAudioThread/ShutdownOnAudioThread are all run on the main
// thread, but this might not be true in testing.
power_observer_.reset();
AudioManagerBase::ShutdownOnAudioThread();
}
std::vector<AudioObjectID> AudioManagerMac::GetAllAudioDeviceIDs() {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
return core_audio_mac::GetAllAudioDeviceIDs();
}
std::vector<AudioObjectID> AudioManagerMac::GetRelatedNonBluetoothDeviceIDs(
AudioObjectID device_id) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
return core_audio_mac::GetRelatedDeviceIDs(device_id);
}
std::vector<AudioObjectID> AudioManagerMac::GetRelatedBluetoothDeviceIDs(
AudioObjectID device_id) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
std::vector<AudioObjectID> result_ids;
// Get unique ID of input device which would be used to match with unique IDs
// of all other devices.
std::optional<std::string> input_unique_id = GetDeviceUniqueID(device_id);
if (!input_unique_id) {
return result_ids;
}
// Get the base name from the unique ID by removing :input/:output from it.
// A bluetooth audio input device uniqueID is of the format
// "F3-A2-14-A9-1D-F8:input", while the corresponding output device uniqueID
// is of the format "F3-A2-14-A9-1D-F8:output".
std::vector<std::string> trimmed_input_vector =
SplitString(input_unique_id.value(), ":", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
if (trimmed_input_vector.empty()) {
return result_ids;
}
std::string& trimmed_input_unique_id = trimmed_input_vector[0];
// Iterate through all device IDs and match the unique IDs base to find the
// related devices.
for (const auto& id : GetAllAudioDeviceIDs()) {
std::optional<std::string> unique_id = GetDeviceUniqueID(id);
if (!unique_id) {
continue;
}
std::vector<std::string> trimmed_vector =
SplitString(unique_id.value(), ":", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
if (trimmed_vector.empty()) {
continue;
}
std::string& trimmed_id = trimmed_vector[0];
if (trimmed_id == trimmed_input_unique_id) {
result_ids.push_back(id);
}
}
return result_ids;
}
std::vector<AudioObjectID> AudioManagerMac::GetRelatedDeviceIDs(
AudioObjectID device_id) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
std::optional<uint32_t> transport_type = GetDeviceTransportType(device_id);
if (transport_type && *transport_type == kAudioDeviceTransportTypeBluetooth) {
return GetRelatedBluetoothDeviceIDs(device_id);
}
return GetRelatedNonBluetoothDeviceIDs(device_id);
}
std::optional<std::string> AudioManagerMac::GetDeviceUniqueID(
AudioObjectID device_id) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
return core_audio_mac::GetDeviceUniqueID(device_id);
}
std::optional<uint32_t> AudioManagerMac::GetDeviceTransportType(
AudioObjectID device_id) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
return core_audio_mac::GetDeviceTransportType(device_id);
}
bool AudioManagerMac::HasAudioOutputDevices() {
return HasAudioHardware(kAudioHardwarePropertyDefaultOutputDevice);
}
bool AudioManagerMac::HasAudioInputDevices() {
return HasAudioHardware(kAudioHardwarePropertyDefaultInputDevice);
}
// static
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());
if (AudioDeviceDescription::IsLoopbackDevice(device_id)) {
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(), kLoopbackSampleRate,
ChooseBufferSize(true, kLoopbackSampleRate));
}
AudioDeviceID device = GetAudioDeviceIdByUId(true, device_id);
if (device == kAudioObjectUnknown) {
DLOG(ERROR) << "Invalid device " << device_id;
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(), kFallbackSampleRate,
ChooseBufferSize(true, kFallbackSampleRate));
}
int channels = 0;
ChannelLayoutConfig channel_layout_config = ChannelLayoutConfig::Stereo();
if (GetInputDeviceChannels(device, &channels) && channels <= 2) {
channel_layout_config = ChannelLayoutConfig::Guess(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(grunell): query the native channel layout for the specific device.
AudioParameters params(
AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout_config,
sample_rate, buffer_size,
AudioParameters::HardwareCapabilities(
GetMinAudioBufferSizeMacOS(limits::kMinAudioBufferSize, sample_rate),
limits::kMaxAudioBufferSize));
if (DeviceSupportsAmbientNoiseReduction(device)) {
params.set_effects(AudioParameters::NOISE_SUPPRESSION);
}
// VoiceProcessingIO cannot be used on aggregate devices, since it creates an
// aggregate device itself. It also only runs in mono, but we allow upmixing
// to stereo since we can't claim a device works either in stereo without echo
// cancellation or mono with echo cancellation.
if ((params.channel_layout() == CHANNEL_LAYOUT_MONO ||
params.channel_layout() == CHANNEL_LAYOUT_STEREO) &&
GetDeviceTransportType(device) != kAudioDeviceTransportTypeAggregate) {
params.set_effects(params.effects() |
AudioParameters::EXPERIMENTAL_ECHO_CANCELLER);
}
return params;
}
std::string AudioManagerMac::GetAssociatedOutputDeviceID(
const std::string& input_device_unique_id) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
AudioObjectID input_device_id =
GetAudioDeviceIdByUId(true, input_device_unique_id);
if (input_device_id == kAudioObjectUnknown) {
return std::string();
}
std::vector<AudioObjectID> related_device_ids =
GetRelatedDeviceIDs(input_device_id);
// Defined as a set as device IDs might be duplicated in
// GetRelatedDeviceIDs().
base::flat_set<AudioObjectID> related_output_device_ids;
for (AudioObjectID device_id : related_device_ids) {
if (core_audio_mac::GetNumStreams(device_id, false /* is_input */) > 0) {
related_output_device_ids.insert(device_id);
}
}
// Return the device ID if there is only one associated device.
// 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.
if (related_output_device_ids.size() == 1) {
std::optional<std::string> related_unique_id =
GetDeviceUniqueID(*related_output_device_ids.begin());
if (related_unique_id) {
return std::move(*related_unique_id);
}
}
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 base::BindPostTaskToCurrentDefault() 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_ = AudioDeviceListenerMac::Create(
base::BindPostTaskToCurrentDefault(base::BindRepeating(
&AudioManagerMac::HandleDeviceChanges, base::Unretained(this))),
/*monitor_sample_rate_changes=*/
base::FeatureList::IsEnabled(kMonitorOutputSampleRateChangesMac),
/*monitor_default_input=*/false,
/*monitor_addition_removal=*/false,
/*monitor_sources=*/false);
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_.insert(stream);
return stream;
}
std::string AudioManagerMac::GetDefaultOutputDeviceID() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
return GetDefaultDeviceID(false /* is_input */);
}
std::string AudioManagerMac::GetDefaultInputDeviceID() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
return GetDefaultDeviceID(true /* is_input */);
}
std::string AudioManagerMac::GetDefaultDeviceID(bool is_input) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
AudioDeviceID device_id = kAudioObjectUnknown;
if (!GetDefaultDevice(&device_id, is_input)) {
return std::string();
}
const AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyDeviceUID, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain};
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_.insert(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());
if (AudioDeviceDescription::IsLoopbackDevice(device_id)) {
screen_capture_kit_swizzler_ = SwizzleScreenCaptureKit();
return CreateSCKAudioInputStream(
params, device_id, log_callback,
base::BindRepeating(&AudioManagerBase::ReleaseInputStream,
base::Unretained(this)));
}
// 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);
if (audio_device_id == kAudioObjectUnknown) {
return nullptr;
}
VoiceProcessingMode voice_processing_mode =
(params.effects() & AudioParameters::ECHO_CANCELLER)
? VoiceProcessingMode::kEnabled
: VoiceProcessingMode::kDisabled;
auto* stream = new AUAudioInputStream(this, params, audio_device_id,
log_callback, voice_processing_mode);
low_latency_input_streams_.insert(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,
ChannelLayoutConfig::Stereo(),
kFallbackSampleRate,
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) {
// Ensure the latency asked for is maintained, even if the sample rate is
// changed here.
const int scaled_buffer_size = input_params.frames_per_buffer() *
hardware_sample_rate /
input_params.sample_rate();
// 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(scaled_buffer_size,
static_cast<int>(limits::kMinAudioBufferSize)));
} else {
buffer_size = ChooseBufferSize(false, hardware_sample_rate);
}
int hardware_channels;
ChannelLayout hardware_channel_layout;
if (!GetOutputDeviceChannelsAndLayout(device, &hardware_channels,
&hardware_channel_layout)) {
hardware_channels = 2;
hardware_channel_layout = CHANNEL_LAYOUT_STEREO;
}
// 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 output_channel_layout = input_params.channel_layout();
if (!has_valid_input_params || output_channels > hardware_channels) {
output_channels = hardware_channels;
output_channel_layout = hardware_channel_layout;
}
AudioParameters params(
AudioParameters::AUDIO_PCM_LOW_LATENCY,
{output_channel_layout, output_channels}, hardware_sample_rate,
buffer_size,
AudioParameters::HardwareCapabilities(
GetMinAudioBufferSizeMacOS(limits::kMinAudioBufferSize,
hardware_sample_rate),
limits::kMaxAudioBufferSize));
return params;
}
void AudioManagerMac::InitializeOnAudioThread() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
InitializeCoreAudioDispatchOverride();
power_observer_ = std::make_unique<AudioPowerObserver>();
}
void AudioManagerMac::HandleDeviceChanges() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
AudioDeviceID new_output_device;
GetDefaultOutputDevice(&new_output_device);
const int new_sample_rate = HardwareSampleRateForDevice(new_output_device);
if (current_sample_rate_ == new_sample_rate &&
current_output_device_ == new_output_device) {
return;
}
DVLOG(1) << __func__
<< " device changed: " << (current_sample_rate_ != new_sample_rate)
<< " current sample rate: " << current_sample_rate_
<< " new sample rate: " << new_sample_rate;
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();
}
base::TimeDelta AudioManagerMac::GetDeferStreamStartTimeout() const {
if (ShouldDeferStreamStart()) {
return base::Seconds(AudioManagerMac::kStartDelayInSecsForPowerEvents);
}
return base::TimeDelta();
}
void AudioManagerMac::StopAmplitudePeakTrace() {
TraceAmplitudePeak(/*trace_start=*/false);
}
double AudioManagerMac::GetMaxInputVolume(AudioDeviceID device_id) {
// Verify that we have a valid device.
if (device_id == kAudioObjectUnknown) {
LOG(ERROR) << "Device ID is unknown";
return 0.0;
}
// The master channel is 0, Left and right are channels 1 and 2.
// Query if any of the master, left or right channels has volume control.
for (int channel = 0; channel <= GetNumberOfChannelsForDevice(device_id);
++channel) {
// If the volume is settable, the valid volume range is [0.0, 1.0].
if (IsVolumeSettableOnChannel(device_id, channel)) {
return 1.0;
}
}
// Volume control is not available for the audio stream.
return 0.0;
}
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) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
if (in_shutdown_) {
DVLOG(1) << __FUNCTION__ << " Disabled since we are shutting down";
return false;
}
const bool is_input = (element == 1);
DVLOG(1) << __FUNCTION__ << " (id=0x" << std::hex << device_id
<< ", is_input=" << is_input << ", desired_buffer_size=" << std::dec
<< desired_buffer_size << ")";
// Log the device name (and id) for debugging purposes.
std::string device_name = GetAudioDeviceNameFromDeviceId(device_id, is_input);
DVLOG(1) << __FUNCTION__ << " 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,
AUElement::OUTPUT, &buffer_size, &property_size);
if (result != noErr) {
OSSTATUS_DLOG(ERROR, result)
<< "AudioUnitGetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
return false;
}
DVLOG(1) << __FUNCTION__ << " current IO buffer size: " << buffer_size;
DVLOG(1) << __FUNCTION__ << " #output streams: " << output_streams_.size();
DVLOG(1) << __FUNCTION__
<< " #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 = buffer_size;
UInt32 maximum = buffer_size;
result = GetIOBufferFrameSizeRange(device_id, is_input, &minimum, &maximum);
if (result != noErr) {
// OS error is logged in GetIOBufferFrameSizeRange().
return false;
}
DVLOG(1) << __FUNCTION__ << " 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;
DVLOG_IF(1, result == noErr)
<< __FUNCTION__ << " IO buffer size changed to: " << buffer_size;
// Store the currently used (after a change) I/O buffer frame size.
return result == noErr;
}
bool AudioManagerMac::DeviceSupportsAmbientNoiseReduction(
AudioDeviceID device_id) {
return AudioObjectHasProperty(device_id, &kNoiseReductionPropertyAddress);
}
bool AudioManagerMac::SuppressNoiseReduction(AudioDeviceID device_id) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
DCHECK(DeviceSupportsAmbientNoiseReduction(device_id));
NoiseReductionState& state = device_noise_reduction_states_[device_id];
if (state.suppression_count == 0) {
UInt32 initially_enabled = 0;
UInt32 size = sizeof(initially_enabled);
OSStatus result =
AudioObjectGetPropertyData(device_id, &kNoiseReductionPropertyAddress,
0, nullptr, &size, &initially_enabled);
if (result != noErr) {
return false;
}
if (initially_enabled) {
const UInt32 disable = 0;
result =
AudioObjectSetPropertyData(device_id, &kNoiseReductionPropertyAddress,
0, nullptr, sizeof(disable), &disable);
if (result != noErr) {
OSSTATUS_DLOG(WARNING, result)
<< "Failed to disable ambient noise reduction for device: "
<< std::hex << device_id;
}
state.initial_state = NoiseReductionState::ENABLED;
} else {
state.initial_state = NoiseReductionState::DISABLED;
}
}
// Only increase the counter if suppression succeeded or is already active.
++state.suppression_count;
return true;
}
void AudioManagerMac::UnsuppressNoiseReduction(AudioDeviceID device_id) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
NoiseReductionState& state = device_noise_reduction_states_[device_id];
DCHECK_NE(state.suppression_count, 0);
--state.suppression_count;
if (state.suppression_count == 0) {
if (state.initial_state == NoiseReductionState::ENABLED) {
const UInt32 enable = 1;
OSStatus result =
AudioObjectSetPropertyData(device_id, &kNoiseReductionPropertyAddress,
0, nullptr, sizeof(enable), &enable);
if (result != noErr) {
OSSTATUS_DLOG(WARNING, result)
<< "Failed to re-enable ambient noise reduction for device: "
<< std::hex << device_id;
}
}
}
}
void AudioManagerMac::ReleaseOutputStream(AudioOutputStream* stream) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
CHECK(stream);
auto it = output_streams_.find(static_cast<AUHALStream*>(stream));
if (it != output_streams_.end()) {
output_streams_.erase(it);
}
AudioManagerBase::ReleaseOutputStream(stream);
}
void AudioManagerMac::ReleaseInputStream(AudioInputStream* stream) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
auto stream_it = basic_input_streams_.find(stream);
if (stream_it != basic_input_streams_.end()) {
basic_input_streams_.erase(stream_it);
} else {
auto it = low_latency_input_streams_.find(
static_cast<AUAudioInputStream*>(stream));
if (it != low_latency_input_streams_.end()) {
low_latency_input_streams_.erase(
static_cast<AUAudioInputStream*>(stream));
}
}
AudioManagerBase::ReleaseInputStream(stream);
}
std::unique_ptr<AudioManager> CreateAudioManager(
std::unique_ptr<AudioThread> audio_thread,
AudioLogFactory* audio_log_factory) {
return std::make_unique<AudioManagerMac>(std::move(audio_thread),
audio_log_factory);
}
// static
bool AudioManagerMac::IsVolumeSettableOnChannel(AudioDeviceID device_id,
int channel) {
Boolean is_settable = false;
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput,
static_cast<UInt32>(channel)};
OSStatus result =
AudioObjectIsPropertySettable(device_id, &property_address, &is_settable);
return (result == noErr) ? is_settable : false;
}
void AudioManagerMac::SetInputVolume(AudioDeviceID device_id, double volume) {
CHECK_GE(volume, 0.0);
CHECK_LE(volume, 1.0);
// Verify that we have a valid device.
if (device_id == kAudioObjectUnknown) {
LOG(ERROR) << "Device ID is unknown";
return;
}
Float32 volume_float32 = static_cast<Float32>(volume);
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMain};
// Try to set the volume for master volume channel.
if (IsVolumeSettableOnChannel(device_id, kAudioObjectPropertyElementMain)) {
OSStatus result =
AudioObjectSetPropertyData(device_id, &property_address, 0, nullptr,
sizeof(volume_float32), &volume_float32);
if (result != noErr) {
DLOG(WARNING) << "Failed to set volume to " << volume_float32;
}
return;
}
// The master channel is 0, Left and right are channels 1 and 2.
// There is no master volume control, try to set volume for each channel.
[[maybe_unused]] int successful_channels = 0;
for (int channel = 1; channel <= GetNumberOfChannelsForDevice(device_id);
++channel) {
property_address.mElement = static_cast<UInt32>(channel);
if (IsVolumeSettableOnChannel(device_id, channel)) {
OSStatus result =
AudioObjectSetPropertyData(device_id, &property_address, 0, NULL,
sizeof(volume_float32), &volume_float32);
if (result == noErr) {
++successful_channels;
}
}
}
DLOG_IF(WARNING, successful_channels == 0)
<< "Failed to set volume to " << volume_float32;
}
double AudioManagerMac::GetInputVolume(AudioDeviceID device_id) {
// Verify that we have a valid device.
if (device_id == kAudioObjectUnknown) {
LOG(ERROR) << "Device ID is unknown";
return 0.0;
}
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMain};
if (AudioObjectHasProperty(device_id, &property_address)) {
// The device supports master volume control, get the volume from the
// master channel.
Float32 volume_float32 = 0.0;
UInt32 size = sizeof(volume_float32);
OSStatus result = AudioObjectGetPropertyData(
device_id, &property_address, 0, nullptr, &size, &volume_float32);
if (result == noErr) {
return static_cast<double>(volume_float32);
}
return 0.0;
} else {
// There is no master volume control, try to get the average volume of
// all the channels.
Float32 volume_float32 = 0.0;
int successful_channels = 0;
for (int i = 1; i <= GetNumberOfChannelsForDevice(device_id); ++i) {
property_address.mElement = static_cast<UInt32>(i);
if (AudioObjectHasProperty(device_id, &property_address)) {
Float32 channel_volume = 0;
UInt32 size = sizeof(channel_volume);
OSStatus result = AudioObjectGetPropertyData(
device_id, &property_address, 0, nullptr, &size, &channel_volume);
if (result == noErr) {
volume_float32 += channel_volume;
++successful_channels;
}
}
}
// Get the average volume of the channels.
if (successful_channels != 0) {
return static_cast<double>(volume_float32 / successful_channels);
}
}
DLOG(WARNING) << "Failed to get volume";
return 0.0;
}
bool AudioManagerMac::IsInputMuted(AudioDeviceID device_id) {
// Verify that we have a valid device.
DCHECK_NE(device_id, kAudioObjectUnknown) << "Device ID is unknown";
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyMute, kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMain};
if (!AudioObjectHasProperty(device_id, &property_address)) {
DLOG(ERROR) << "Device does not support checking master mute state";
return false;
}
UInt32 muted = 0;
UInt32 size = sizeof(muted);
OSStatus result = AudioObjectGetPropertyData(device_id, &property_address, 0,
nullptr, &size, &muted);
DLOG_IF(WARNING, result != noErr) << "Failed to get mute state";
return result == noErr && muted != 0;
}
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,
kAudioObjectPropertyElementMain};
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
<< ", returing fallback sample rate " << kFallbackSampleRate;
return kFallbackSampleRate;
}
return static_cast<int>(nominal_sample_rate);
}
// static
AudioDeviceID AudioManagerMac::FindFirstOutputSubdevice(
AudioDeviceID aggregate_device_id) {
const AudioObjectPropertyAddress property_address = {
kAudioAggregateDevicePropertyFullSubDeviceList,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
base::apple::ScopedCFTypeRef<CFArrayRef> subdevices;
UInt32 size = sizeof(subdevices);
OSStatus result = AudioObjectGetPropertyData(
aggregate_device_id, &property_address, 0 /* inQualifierDataSize */,
nullptr /* inQualifierData */, &size, subdevices.InitializeInto());
if (result != noErr) {
OSSTATUS_LOG(WARNING, result)
<< "Failed to read property "
<< kAudioAggregateDevicePropertyFullSubDeviceList << " for device "
<< aggregate_device_id;
return kAudioObjectUnknown;
}
AudioDeviceID output_subdevice_id = kAudioObjectUnknown;
DCHECK_EQ(CFGetTypeID(subdevices.get()), CFArrayGetTypeID());
const CFIndex count = CFArrayGetCount(subdevices.get());
for (CFIndex i = 0; i < count; ++i) {
CFStringRef value = base::apple::CFCast<CFStringRef>(
CFArrayGetValueAtIndex(subdevices.get(), i));
if (value) {
std::string uid = base::SysCFStringRefToUTF8(value);
output_subdevice_id = AudioManagerMac::GetAudioDeviceIdByUId(false, uid);
if (output_subdevice_id != kAudioObjectUnknown &&
core_audio_mac::GetNumStreams(output_subdevice_id, false) > 0) {
return output_subdevice_id;
}
}
}
return kAudioObjectUnknown;
}
OSStatus AudioManagerMac::GetInputDeviceStreamFormat(
AudioUnit audio_unit,
AudioStreamBasicDescription* input_format) {
DCHECK(audio_unit);
UInt32 property_size = sizeof(*input_format);
// Get the audio stream data format on the input scope of the input element
// since it is connected to the current input device.
return AudioUnitGetProperty(audio_unit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, AUElement::INPUT,
input_format, &property_size);
}
// static
int AudioManagerMac::GetNumberOfChannelsForDevice(AudioDeviceID device_id) {
// The master channel is 0, Left and right are channels 1 and 2. And the
// master channel is not counted.
// Get the stream format, to be able to read the number of channels.
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMain};
AudioStreamBasicDescription stream_format;
UInt32 size = sizeof(stream_format);
OSStatus result = AudioObjectGetPropertyData(device_id, &property_address, 0,
nullptr, &size, &stream_format);
if (result != noErr) {
DLOG(WARNING) << "Could not get stream format";
return 0;
}
return static_cast<int>(stream_format.mChannelsPerFrame);
}
} // namespace media