#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include "media/audio/mac/audio_manager_mac.h"
#include <algorithm>
#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/metrics/histogram_functions.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_observer.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/audio_features.h"
#include "media/audio/mac/audio_loopback_input_mac.h"
#include "media/audio/mac/avfoundation_output_stream.h"
#include "media/audio/mac/core_audio_util_mac.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/media_switches.h"
namespace media {
namespace {
bool IsCatapLoopbackAudioEnabledForDevice(const std::string& device_id) {
if (!IsMacCatapSystemLoopbackCaptureSupported()) {
return false;
}
if (device_id == AudioDeviceDescription::kLoopbackWithMuteDeviceIdCast) {
return base::FeatureList::IsEnabled(kMacCatapLoopbackAudioForCast);
}
if (device_id == AudioDeviceDescription::kLoopbackAllDevicesId) {
return base::FeatureList::IsEnabled(kSystemLoopbackAsAecReference);
}
return base::FeatureList::IsEnabled(kMacCatapLoopbackAudioForScreenShare);
}
}
static const int kMaxOutputStreams = 50;
static const int kFallbackSampleRate = 44100;
static bool GetOutputDeviceChannelsAndLayout(AudioUnit audio_unit,
int* channels,
ChannelLayout* channel_layout);
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};
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,
kAudioObjectPropertyElementMain
};
UInt32 output_device_id_size = static_cast<UInt32>(sizeof(output_device_id));
OSStatus err =
AudioObjectGetPropertyData(kAudioObjectSystemObject, &property_address,
0,
NULL,
&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;
}
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;
}
if (core_audio_mac::IsPrivateAggregateDevice(device_id)) {
continue;
}
device_names->emplace_back(std::move(*label), std::move(*unique_id));
}
if (!device_names->empty()) {
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)) {
property_address.mSelector =
is_input ? kAudioHardwarePropertyDefaultInputDevice
: kAudioHardwarePropertyDefaultOutputDevice;
result =
AudioObjectGetPropertyData(kAudioObjectSystemObject, &property_address,
0, 0, &device_size, &audio_device_id);
} else {
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;
}
bool AudioManagerMac::GetDefaultInputDevice(AudioDeviceID* input_device) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(input_device);
std::optional<AudioDeviceID> device =
core_audio_mac::GetDefaultDevice(true);
if (!device) {
return false;
}
*input_device = *device;
return true;
}
bool AudioManagerMac::GetDefaultOutputDevice(AudioDeviceID* output_device) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(output_device);
std::optional<AudioDeviceID> device =
core_audio_mac::GetDefaultDevice(false);
if (!device) {
return false;
}
*output_device = *device;
return true;
}
static bool GetDeviceTotalChannelCount(AudioDeviceID device,
AudioObjectPropertyScope scope,
int* channels) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(channels);
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;
}
*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;
}
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;
}
static bool GetInputDeviceChannels(AudioDeviceID device, int* channels) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(channels);
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;
}
static bool GetOutputDeviceChannelsAndLayout(AudioDeviceID device,
int* channels,
ChannelLayout* channel_layout) {
DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
CHECK(channels);
CHECK(channel_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) {
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();
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;
*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::GetInstance()->IsInitialized()),
num_resume_notifications_(0) {
if (!is_monitoring_) {
return;
}
base::PowerMonitor::GetInstance()->AddPowerSuspendObserver(this);
}
AudioPowerObserver(const AudioPowerObserver&) = delete;
AudioPowerObserver& operator=(const AudioPowerObserver&) = delete;
~AudioPowerObserver() override {
DCHECK(thread_checker_.CalledOnValidThread());
if (!is_monitoring_) {
return;
}
base::PowerMonitor::GetInstance()->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());
return is_suspending_ || base::TimeTicks::Now() < earliest_start_time_;
}
bool IsOnBatteryPower() const {
DCHECK(thread_checker_.CalledOnValidThread());
return base::PowerMonitor::GetInstance()->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);
GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&AudioManagerMac::InitializeOnAudioThread,
weak_ptr_factory_.GetWeakPtr()));
}
AudioManagerMac::~AudioManagerMac() = default;
void AudioManagerMac::ShutdownOnAudioThread() {
in_shutdown_ = true;
CloseAllInputStreams();
CHECK(basic_input_streams_.empty());
CHECK(low_latency_input_streams_.empty());
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;
std::optional<std::string> input_unique_id = GetDeviceUniqueID(device_id);
if (!input_unique_id) {
return result_ids;
}
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];
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);
}
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)) {
if (IsCatapLoopbackAudioEnabledForDevice(device_id)) {
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(), kLoopbackSampleRate,
kCatapLoopbackDefaultFramesPerBuffer);
} else {
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(), kLoopbackSampleRate,
kSckLoopbackFramesPerBuffer);
}
}
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;
}
const int buffer_size = ChooseBufferSize(true, sample_rate);
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);
}
base::UmaHistogramBoolean(
"Media.Audio.Mac.NoiseSuppressionAvailable",
params.effects() & AudioParameters::NOISE_SUPPRESSION);
if (AUAudioInputStream::IsEchoCancellationSupported(device, params)) {
params.set_effects(params.effects() | AudioParameters::ECHO_CANCELLER);
params.set_effects(params.effects() |
AudioParameters::AUTOMATIC_GAIN_CONTROL);
}
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);
base::flat_set<AudioObjectID> related_output_device_ids;
for (AudioObjectID device_id : related_device_ids) {
if (core_audio_mac::GetNumStreams(device_id, false ) > 0) {
related_output_device_ids.insert(device_id);
}
}
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 std::string_view 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;
if (!output_device_listener_) {
output_device_listener_ = AudioDeviceListenerMac::Create(
base::BindPostTaskToCurrentDefault(
base::BindRepeating(&AudioManagerMac::HandleDeviceChanges,
weak_ptr_factory_.GetWeakPtr())),
true,
false,
false,
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;
}
if (device_listener_first_init) {
if (AudioDeviceDescription::IsDefaultDevice(device_id)) {
current_output_device_ = device;
}
current_sample_rate_ = params.sample_rate();
}
if (base::FeatureList::IsEnabled(features::kMacAVFoundationPlayback) &&
params.latency_tag() == AudioLatency::Type::kPlayback) {
DVLOG(1) << __func__ << ": Creating AVFoundationOutputStream for "
<< ChannelLayoutToString(params.channel_layout()) << " layout.";
auto* stream = new AVFoundationOutputStream(this, params, device_id);
return stream;
}
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 );
}
std::string AudioManagerMac::GetDefaultInputDeviceID() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
return GetDefaultDeviceID(true );
}
std::string AudioManagerMac::GetDefaultDeviceID(bool is_input) {
DCHECK(GetTaskRunner()->BelongsToCurrentThread());
std::optional<AudioDeviceID> device_id =
core_audio_mac::GetDefaultDevice(is_input);
if (!device_id) {
return std::string();
}
return core_audio_mac::GetDeviceUniqueID(*device_id).value_or(std::string());
}
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)) {
if (IsCatapLoopbackAudioEnabledForDevice(device_id)) {
return CreateCatapAudioInputStream(
params, device_id, log_callback,
base::BindOnce(&AudioManagerBase::ReleaseInputStream,
base::Unretained(this)));
}
screen_capture_kit_swizzler_ = SwizzleScreenCaptureKit();
return CreateSCKAudioInputStream(
params, device_id, log_callback,
base::BindRepeating(&AudioManagerBase::ReleaseInputStream,
base::Unretained(this)));
}
AudioDeviceID audio_device_id = GetAudioDeviceIdByUId(true, device_id);
if (audio_device_id == kAudioObjectUnknown) {
return nullptr;
}
auto* stream =
new AUAudioInputStream(this, params, audio_device_id, log_callback);
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);
int buffer_size;
if (has_valid_input_params) {
const int scaled_buffer_size = input_params.frames_per_buffer() *
hardware_sample_rate /
input_params.sample_rate();
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;
}
int output_channels = input_params.channels();
ChannelLayout output_channel_layout = input_params.channel_layout();
const bool use_avf_streams =
base::FeatureList::IsEnabled(features::kMacAVFoundationPlayback) &&
input_params.latency_tag() == AudioLatency::Type::kPlayback;
if (!has_valid_input_params ||
(output_channels > hardware_channels && !use_avf_streams)) {
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());
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) {
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(false);
}
double AudioManagerMac::GetMaxInputVolume(AudioDeviceID device_id) {
if (device_id == kAudioObjectUnknown) {
LOG(ERROR) << "Device ID is unknown";
return 0.0;
}
for (int channel = 0; channel <= GetNumberOfChannelsForDevice(device_id);
++channel) {
if (IsVolumeSettableOnChannel(device_id, channel)) {
return 1.0;
}
}
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 << ")";
std::string device_name = GetAudioDeviceNameFromDeviceId(device_id, is_input);
DVLOG(1) << __FUNCTION__ << " name: " << device_name << " (ID: 0x" << std::hex
<< device_id << ")";
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();
if (buffer_size == desired_buffer_size) {
return true;
}
if (desired_buffer_size > buffer_size) {
for (auto* stream : output_streams_) {
if (stream->device_id() == device_id &&
stream->requested_buffer_size() < desired_buffer_size) {
return true;
}
}
for (auto* stream : low_latency_input_streams_) {
if (stream->device_id() == device_id &&
stream->requested_buffer_size() < desired_buffer_size) {
return true;
}
}
}
UInt32 minimum = buffer_size;
UInt32 maximum = buffer_size;
result = GetIOBufferFrameSizeRange(device_id, is_input, &minimum, &maximum);
if (result != noErr) {
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;
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;
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;
}
}
++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);
}
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);
if (device_id == kAudioObjectUnknown) {
LOG(ERROR) << "Device ID is unknown";
return;
}
Float32 volume_float32 = static_cast<Float32>(volume);
AudioObjectPropertyAddress property_address = {
kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMain};
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;
}
[[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) {
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)) {
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 {
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;
}
}
}
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) {
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
<< ", returning fallback sample rate " << kFallbackSampleRate;
return kFallbackSampleRate;
}
return static_cast<int>(nominal_sample_rate);
}
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 ,
nullptr , &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;
}
int AudioManagerMac::GetMinAudioBufferSizeMacOS(int min_buffer_size,
int sample_rate) {
int buffer_size = min_buffer_size;
if (sample_rate > 48000) {
if (sample_rate <= 96000) {
buffer_size = 2 * limits::kMinAudioBufferSize;
} else if (sample_rate <= 192000) {
buffer_size = 4 * limits::kMinAudioBufferSize;
}
}
DCHECK_EQ(limits::kMaxWebAudioBufferSize % buffer_size, 0);
return buffer_size;
}
OSStatus AudioManagerMac::GetInputDeviceStreamFormat(
AudioUnit audio_unit,
AudioStreamBasicDescription* input_format) {
DCHECK(audio_unit);
UInt32 property_size = sizeof(*input_format);
return AudioUnitGetProperty(audio_unit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, AUElement::INPUT,
input_format, &property_size);
}
int AudioManagerMac::GetNumberOfChannelsForDevice(AudioDeviceID device_id) {
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);
}
}