| // 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 |