| /* | |
| * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "modules/audio_device/mac/audio_mixer_manager_mac.h" | |
| #include <unistd.h> // getpid() | |
| #include "rtc_base/system/arch.h" | |
| namespace webrtc { | |
| #define WEBRTC_CA_RETURN_ON_ERR(expr) \ | |
| do { \ | |
| err = expr; \ | |
| if (err != noErr) { \ | |
| logCAMsg(::webrtc::LS_ERROR, "Error in " #expr, (const char*)&err); \ | |
| return -1; \ | |
| } \ | |
| } while (0) | |
| #define WEBRTC_CA_LOG_ERR(expr) \ | |
| do { \ | |
| err = expr; \ | |
| if (err != noErr) { \ | |
| logCAMsg(::webrtc::LS_ERROR, "Error in " #expr, (const char*)&err); \ | |
| } \ | |
| } while (0) | |
| #define WEBRTC_CA_LOG_WARN(expr) \ | |
| do { \ | |
| err = expr; \ | |
| if (err != noErr) { \ | |
| logCAMsg(::webrtc::LS_WARNING, "Error in " #expr, (const char*)&err); \ | |
| } \ | |
| } while (0) | |
| AudioMixerManagerMac::AudioMixerManagerMac() | |
| : _inputDeviceID(kAudioObjectUnknown), | |
| _outputDeviceID(kAudioObjectUnknown), | |
| _noInputChannels(0), | |
| _noOutputChannels(0) { | |
| RTC_DLOG(LS_INFO) << __FUNCTION__ << " created"; | |
| } | |
| AudioMixerManagerMac::~AudioMixerManagerMac() { | |
| RTC_DLOG(LS_INFO) << __FUNCTION__ << " destroyed"; | |
| Close(); | |
| } | |
| // ============================================================================ | |
| // PUBLIC METHODS | |
| // ============================================================================ | |
| int32_t AudioMixerManagerMac::Close() { | |
| RTC_DLOG(LS_VERBOSE) << __FUNCTION__; | |
| MutexLock lock(&mutex_); | |
| CloseSpeakerLocked(); | |
| CloseMicrophoneLocked(); | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::CloseSpeaker() { | |
| MutexLock lock(&mutex_); | |
| return CloseSpeakerLocked(); | |
| } | |
| int32_t AudioMixerManagerMac::CloseSpeakerLocked() { | |
| RTC_DLOG(LS_VERBOSE) << __FUNCTION__; | |
| _outputDeviceID = kAudioObjectUnknown; | |
| _noOutputChannels = 0; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::CloseMicrophone() { | |
| MutexLock lock(&mutex_); | |
| return CloseMicrophoneLocked(); | |
| } | |
| int32_t AudioMixerManagerMac::CloseMicrophoneLocked() { | |
| RTC_DLOG(LS_VERBOSE) << __FUNCTION__; | |
| _inputDeviceID = kAudioObjectUnknown; | |
| _noInputChannels = 0; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::OpenSpeaker(AudioDeviceID deviceID) { | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::OpenSpeaker(id=" << deviceID | |
| << ")"; | |
| MutexLock lock(&mutex_); | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| pid_t hogPid = -1; | |
| _outputDeviceID = deviceID; | |
| // Check which process, if any, has hogged the device. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyHogMode, kAudioDevicePropertyScopeOutput, 0}; | |
| // First, does it have the property? Aggregate devices don't. | |
| if (AudioObjectHasProperty(_outputDeviceID, &propertyAddress)) { | |
| size = sizeof(hogPid); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &hogPid)); | |
| if (hogPid == -1) { | |
| RTC_LOG(LS_VERBOSE) << "No process has hogged the output device"; | |
| } | |
| // getpid() is apparently "always successful" | |
| else if (hogPid == getpid()) { | |
| RTC_LOG(LS_VERBOSE) << "Our process has hogged the output device"; | |
| } else { | |
| RTC_LOG(LS_WARNING) << "Another process (pid = " | |
| << static_cast<int>(hogPid) | |
| << ") has hogged the output device"; | |
| return -1; | |
| } | |
| } | |
| // get number of channels from stream format | |
| propertyAddress.mSelector = kAudioDevicePropertyStreamFormat; | |
| // Get the stream format, to be able to read the number of channels. | |
| AudioStreamBasicDescription streamFormat; | |
| size = sizeof(AudioStreamBasicDescription); | |
| memset(&streamFormat, 0, size); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &streamFormat)); | |
| _noOutputChannels = streamFormat.mChannelsPerFrame; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::OpenMicrophone(AudioDeviceID deviceID) { | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::OpenMicrophone(id=" << deviceID | |
| << ")"; | |
| MutexLock lock(&mutex_); | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| pid_t hogPid = -1; | |
| _inputDeviceID = deviceID; | |
| // Check which process, if any, has hogged the device. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyHogMode, kAudioDevicePropertyScopeInput, 0}; | |
| size = sizeof(hogPid); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, &size, &hogPid)); | |
| if (hogPid == -1) { | |
| RTC_LOG(LS_VERBOSE) << "No process has hogged the input device"; | |
| } | |
| // getpid() is apparently "always successful" | |
| else if (hogPid == getpid()) { | |
| RTC_LOG(LS_VERBOSE) << "Our process has hogged the input device"; | |
| } else { | |
| RTC_LOG(LS_WARNING) << "Another process (pid = " << static_cast<int>(hogPid) | |
| << ") has hogged the input device"; | |
| return -1; | |
| } | |
| // get number of channels from stream format | |
| propertyAddress.mSelector = kAudioDevicePropertyStreamFormat; | |
| // Get the stream format, to be able to read the number of channels. | |
| AudioStreamBasicDescription streamFormat; | |
| size = sizeof(AudioStreamBasicDescription); | |
| memset(&streamFormat, 0, size); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, &size, &streamFormat)); | |
| _noInputChannels = streamFormat.mChannelsPerFrame; | |
| return 0; | |
| } | |
| bool AudioMixerManagerMac::SpeakerIsInitialized() const { | |
| RTC_DLOG(LS_INFO) << __FUNCTION__; | |
| return (_outputDeviceID != kAudioObjectUnknown); | |
| } | |
| bool AudioMixerManagerMac::MicrophoneIsInitialized() const { | |
| RTC_DLOG(LS_INFO) << __FUNCTION__; | |
| return (_inputDeviceID != kAudioObjectUnknown); | |
| } | |
| int32_t AudioMixerManagerMac::SetSpeakerVolume(uint32_t volume) { | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::SetSpeakerVolume(volume=" | |
| << volume << ")"; | |
| MutexLock lock(&mutex_); | |
| if (_outputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| bool success = false; | |
| // volume range is 0.0 - 1.0, convert from 0 -255 | |
| const Float32 vol = (Float32)(volume / 255.0); | |
| RTC_DCHECK(vol <= 1.0 && vol >= 0.0); | |
| // Does the capture device have a master volume control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, 0}; | |
| Boolean isSettable = false; | |
| err = AudioObjectIsPropertySettable(_outputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| size = sizeof(vol); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, size, &vol)); | |
| return 0; | |
| } | |
| // Otherwise try to set each channel. | |
| for (UInt32 i = 1; i <= _noOutputChannels; i++) { | |
| propertyAddress.mElement = i; | |
| isSettable = false; | |
| err = AudioObjectIsPropertySettable(_outputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| size = sizeof(vol); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, size, &vol)); | |
| } | |
| success = true; | |
| } | |
| if (!success) { | |
| RTC_LOG(LS_WARNING) << "Unable to set a volume on any output channel"; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::SpeakerVolume(uint32_t& volume) const { | |
| if (_outputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| unsigned int channels = 0; | |
| Float32 channelVol = 0; | |
| Float32 vol = 0; | |
| // Does the device have a master volume control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, 0}; | |
| Boolean hasProperty = | |
| AudioObjectHasProperty(_outputDeviceID, &propertyAddress); | |
| if (hasProperty) { | |
| size = sizeof(vol); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &vol)); | |
| // vol 0.0 to 1.0 -> convert to 0 - 255 | |
| volume = static_cast<uint32_t>(vol * 255 + 0.5); | |
| } else { | |
| // Otherwise get the average volume across channels. | |
| vol = 0; | |
| for (UInt32 i = 1; i <= _noOutputChannels; i++) { | |
| channelVol = 0; | |
| propertyAddress.mElement = i; | |
| hasProperty = AudioObjectHasProperty(_outputDeviceID, &propertyAddress); | |
| if (hasProperty) { | |
| size = sizeof(channelVol); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &channelVol)); | |
| vol += channelVol; | |
| channels++; | |
| } | |
| } | |
| if (channels == 0) { | |
| RTC_LOG(LS_WARNING) << "Unable to get a volume on any channel"; | |
| return -1; | |
| } | |
| RTC_DCHECK_GT(channels, 0); | |
| // vol 0.0 to 1.0 -> convert to 0 - 255 | |
| volume = static_cast<uint32_t>(255 * vol / channels + 0.5); | |
| } | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::SpeakerVolume() => vol=" << vol; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::MaxSpeakerVolume(uint32_t& maxVolume) const { | |
| if (_outputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| // volume range is 0.0 to 1.0 | |
| // we convert that to 0 - 255 | |
| maxVolume = 255; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::MinSpeakerVolume(uint32_t& minVolume) const { | |
| if (_outputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| // volume range is 0.0 to 1.0 | |
| // we convert that to 0 - 255 | |
| minVolume = 0; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::SpeakerVolumeIsAvailable(bool& available) { | |
| if (_outputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| // Does the capture device have a master volume control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, 0}; | |
| Boolean isSettable = false; | |
| err = AudioObjectIsPropertySettable(_outputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| available = true; | |
| return 0; | |
| } | |
| // Otherwise try to set each channel. | |
| for (UInt32 i = 1; i <= _noOutputChannels; i++) { | |
| propertyAddress.mElement = i; | |
| isSettable = false; | |
| err = AudioObjectIsPropertySettable(_outputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err != noErr || !isSettable) { | |
| available = false; | |
| RTC_LOG(LS_WARNING) << "Volume cannot be set for output channel " << i | |
| << ", err=" << err; | |
| return -1; | |
| } | |
| } | |
| available = true; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::SpeakerMuteIsAvailable(bool& available) { | |
| if (_outputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| // Does the capture device have a master mute control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput, 0}; | |
| Boolean isSettable = false; | |
| err = AudioObjectIsPropertySettable(_outputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| available = true; | |
| return 0; | |
| } | |
| // Otherwise try to set each channel. | |
| for (UInt32 i = 1; i <= _noOutputChannels; i++) { | |
| propertyAddress.mElement = i; | |
| isSettable = false; | |
| err = AudioObjectIsPropertySettable(_outputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err != noErr || !isSettable) { | |
| available = false; | |
| RTC_LOG(LS_WARNING) << "Mute cannot be set for output channel " << i | |
| << ", err=" << err; | |
| return -1; | |
| } | |
| } | |
| available = true; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::SetSpeakerMute(bool enable) { | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::SetSpeakerMute(enable=" | |
| << enable << ")"; | |
| MutexLock lock(&mutex_); | |
| if (_outputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| UInt32 mute = enable ? 1 : 0; | |
| bool success = false; | |
| // Does the render device have a master mute control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput, 0}; | |
| Boolean isSettable = false; | |
| err = AudioObjectIsPropertySettable(_outputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| size = sizeof(mute); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, size, &mute)); | |
| return 0; | |
| } | |
| // Otherwise try to set each channel. | |
| for (UInt32 i = 1; i <= _noOutputChannels; i++) { | |
| propertyAddress.mElement = i; | |
| isSettable = false; | |
| err = AudioObjectIsPropertySettable(_outputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| size = sizeof(mute); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, size, &mute)); | |
| } | |
| success = true; | |
| } | |
| if (!success) { | |
| RTC_LOG(LS_WARNING) << "Unable to set mute on any input channel"; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::SpeakerMute(bool& enabled) const { | |
| if (_outputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| unsigned int channels = 0; | |
| UInt32 channelMuted = 0; | |
| UInt32 muted = 0; | |
| // Does the device have a master volume control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput, 0}; | |
| Boolean hasProperty = | |
| AudioObjectHasProperty(_outputDeviceID, &propertyAddress); | |
| if (hasProperty) { | |
| size = sizeof(muted); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &muted)); | |
| // 1 means muted | |
| enabled = static_cast<bool>(muted); | |
| } else { | |
| // Otherwise check if all channels are muted. | |
| for (UInt32 i = 1; i <= _noOutputChannels; i++) { | |
| muted = 0; | |
| propertyAddress.mElement = i; | |
| hasProperty = AudioObjectHasProperty(_outputDeviceID, &propertyAddress); | |
| if (hasProperty) { | |
| size = sizeof(channelMuted); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &channelMuted)); | |
| muted = (muted && channelMuted); | |
| channels++; | |
| } | |
| } | |
| if (channels == 0) { | |
| RTC_LOG(LS_WARNING) << "Unable to get mute for any channel"; | |
| return -1; | |
| } | |
| RTC_DCHECK_GT(channels, 0); | |
| // 1 means muted | |
| enabled = static_cast<bool>(muted); | |
| } | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::SpeakerMute() => enabled=" | |
| << enabled; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::StereoPlayoutIsAvailable(bool& available) { | |
| if (_outputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| available = (_noOutputChannels == 2); | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::StereoRecordingIsAvailable(bool& available) { | |
| if (_inputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| available = (_noInputChannels == 2); | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::MicrophoneMuteIsAvailable(bool& available) { | |
| if (_inputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| // Does the capture device have a master mute control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyMute, kAudioDevicePropertyScopeInput, 0}; | |
| Boolean isSettable = false; | |
| err = AudioObjectIsPropertySettable(_inputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| available = true; | |
| return 0; | |
| } | |
| // Otherwise try to set each channel. | |
| for (UInt32 i = 1; i <= _noInputChannels; i++) { | |
| propertyAddress.mElement = i; | |
| isSettable = false; | |
| err = AudioObjectIsPropertySettable(_inputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err != noErr || !isSettable) { | |
| available = false; | |
| RTC_LOG(LS_WARNING) << "Mute cannot be set for output channel " << i | |
| << ", err=" << err; | |
| return -1; | |
| } | |
| } | |
| available = true; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::SetMicrophoneMute(bool enable) { | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::SetMicrophoneMute(enable=" | |
| << enable << ")"; | |
| MutexLock lock(&mutex_); | |
| if (_inputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| UInt32 mute = enable ? 1 : 0; | |
| bool success = false; | |
| // Does the capture device have a master mute control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyMute, kAudioDevicePropertyScopeInput, 0}; | |
| Boolean isSettable = false; | |
| err = AudioObjectIsPropertySettable(_inputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| size = sizeof(mute); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, size, &mute)); | |
| return 0; | |
| } | |
| // Otherwise try to set each channel. | |
| for (UInt32 i = 1; i <= _noInputChannels; i++) { | |
| propertyAddress.mElement = i; | |
| isSettable = false; | |
| err = AudioObjectIsPropertySettable(_inputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| size = sizeof(mute); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, size, &mute)); | |
| } | |
| success = true; | |
| } | |
| if (!success) { | |
| RTC_LOG(LS_WARNING) << "Unable to set mute on any input channel"; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::MicrophoneMute(bool& enabled) const { | |
| if (_inputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| unsigned int channels = 0; | |
| UInt32 channelMuted = 0; | |
| UInt32 muted = 0; | |
| // Does the device have a master volume control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyMute, kAudioDevicePropertyScopeInput, 0}; | |
| Boolean hasProperty = | |
| AudioObjectHasProperty(_inputDeviceID, &propertyAddress); | |
| if (hasProperty) { | |
| size = sizeof(muted); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, &size, &muted)); | |
| // 1 means muted | |
| enabled = static_cast<bool>(muted); | |
| } else { | |
| // Otherwise check if all channels are muted. | |
| for (UInt32 i = 1; i <= _noInputChannels; i++) { | |
| muted = 0; | |
| propertyAddress.mElement = i; | |
| hasProperty = AudioObjectHasProperty(_inputDeviceID, &propertyAddress); | |
| if (hasProperty) { | |
| size = sizeof(channelMuted); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, &size, &channelMuted)); | |
| muted = (muted && channelMuted); | |
| channels++; | |
| } | |
| } | |
| if (channels == 0) { | |
| RTC_LOG(LS_WARNING) << "Unable to get mute for any channel"; | |
| return -1; | |
| } | |
| RTC_DCHECK_GT(channels, 0); | |
| // 1 means muted | |
| enabled = static_cast<bool>(muted); | |
| } | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::MicrophoneMute() => enabled=" | |
| << enabled; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::MicrophoneVolumeIsAvailable(bool& available) { | |
| if (_inputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| // Does the capture device have a master volume control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput, 0}; | |
| Boolean isSettable = false; | |
| err = AudioObjectIsPropertySettable(_inputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| available = true; | |
| return 0; | |
| } | |
| // Otherwise try to set each channel. | |
| for (UInt32 i = 1; i <= _noInputChannels; i++) { | |
| propertyAddress.mElement = i; | |
| isSettable = false; | |
| err = AudioObjectIsPropertySettable(_inputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err != noErr || !isSettable) { | |
| available = false; | |
| RTC_LOG(LS_WARNING) << "Volume cannot be set for input channel " << i | |
| << ", err=" << err; | |
| return -1; | |
| } | |
| } | |
| available = true; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::SetMicrophoneVolume(uint32_t volume) { | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::SetMicrophoneVolume(volume=" | |
| << volume << ")"; | |
| MutexLock lock(&mutex_); | |
| if (_inputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| bool success = false; | |
| // volume range is 0.0 - 1.0, convert from 0 - 255 | |
| const Float32 vol = (Float32)(volume / 255.0); | |
| RTC_DCHECK(vol <= 1.0 && vol >= 0.0); | |
| // Does the capture device have a master volume control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput, 0}; | |
| Boolean isSettable = false; | |
| err = AudioObjectIsPropertySettable(_inputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| size = sizeof(vol); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, size, &vol)); | |
| return 0; | |
| } | |
| // Otherwise try to set each channel. | |
| for (UInt32 i = 1; i <= _noInputChannels; i++) { | |
| propertyAddress.mElement = i; | |
| isSettable = false; | |
| err = AudioObjectIsPropertySettable(_inputDeviceID, &propertyAddress, | |
| &isSettable); | |
| if (err == noErr && isSettable) { | |
| size = sizeof(vol); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, size, &vol)); | |
| } | |
| success = true; | |
| } | |
| if (!success) { | |
| RTC_LOG(LS_WARNING) << "Unable to set a level on any input channel"; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::MicrophoneVolume(uint32_t& volume) const { | |
| if (_inputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| OSStatus err = noErr; | |
| UInt32 size = 0; | |
| unsigned int channels = 0; | |
| Float32 channelVol = 0; | |
| Float32 volFloat32 = 0; | |
| // Does the device have a master volume control? | |
| // If so, use it exclusively. | |
| AudioObjectPropertyAddress propertyAddress = { | |
| kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput, 0}; | |
| Boolean hasProperty = | |
| AudioObjectHasProperty(_inputDeviceID, &propertyAddress); | |
| if (hasProperty) { | |
| size = sizeof(volFloat32); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, &size, &volFloat32)); | |
| // vol 0.0 to 1.0 -> convert to 0 - 255 | |
| volume = static_cast<uint32_t>(volFloat32 * 255 + 0.5); | |
| } else { | |
| // Otherwise get the average volume across channels. | |
| volFloat32 = 0; | |
| for (UInt32 i = 1; i <= _noInputChannels; i++) { | |
| channelVol = 0; | |
| propertyAddress.mElement = i; | |
| hasProperty = AudioObjectHasProperty(_inputDeviceID, &propertyAddress); | |
| if (hasProperty) { | |
| size = sizeof(channelVol); | |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( | |
| _inputDeviceID, &propertyAddress, 0, NULL, &size, &channelVol)); | |
| volFloat32 += channelVol; | |
| channels++; | |
| } | |
| } | |
| if (channels == 0) { | |
| RTC_LOG(LS_WARNING) << "Unable to get a level on any channel"; | |
| return -1; | |
| } | |
| RTC_DCHECK_GT(channels, 0); | |
| // vol 0.0 to 1.0 -> convert to 0 - 255 | |
| volume = static_cast<uint32_t>(255 * volFloat32 / channels + 0.5); | |
| } | |
| RTC_LOG(LS_VERBOSE) << "AudioMixerManagerMac::MicrophoneVolume() => vol=" | |
| << volume; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::MaxMicrophoneVolume(uint32_t& maxVolume) const { | |
| if (_inputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| // volume range is 0.0 to 1.0 | |
| // we convert that to 0 - 255 | |
| maxVolume = 255; | |
| return 0; | |
| } | |
| int32_t AudioMixerManagerMac::MinMicrophoneVolume(uint32_t& minVolume) const { | |
| if (_inputDeviceID == kAudioObjectUnknown) { | |
| RTC_LOG(LS_WARNING) << "device ID has not been set"; | |
| return -1; | |
| } | |
| // volume range is 0.0 to 1.0 | |
| // we convert that to 0 - 10 | |
| minVolume = 0; | |
| return 0; | |
| } | |
| // ============================================================================ | |
| // Private Methods | |
| // ============================================================================ | |
| // CoreAudio errors are best interpreted as four character strings. | |
| void AudioMixerManagerMac::logCAMsg(const LoggingSeverity sev, | |
| const char* msg, | |
| const char* err) { | |
| RTC_DCHECK(msg != NULL); | |
| RTC_DCHECK(err != NULL); | |
| RTC_DCHECK(sev == LS_ERROR || sev == LS_WARNING); | |
| #ifdef WEBRTC_ARCH_BIG_ENDIAN | |
| switch (sev) { | |
| case LS_ERROR: | |
| RTC_LOG(LS_ERROR) << msg << ": " << err[0] << err[1] << err[2] << err[3]; | |
| break; | |
| case LS_WARNING: | |
| RTC_LOG(LS_WARNING) << msg << ": " << err[0] << err[1] << err[2] | |
| << err[3]; | |
| break; | |
| default: | |
| break; | |
| } | |
| #else | |
| // We need to flip the characters in this case. | |
| switch (sev) { | |
| case LS_ERROR: | |
| RTC_LOG(LS_ERROR) << msg << ": " << err[3] << err[2] << err[1] << err[0]; | |
| break; | |
| case LS_WARNING: | |
| RTC_LOG(LS_WARNING) << msg << ": " << err[3] << err[2] << err[1] | |
| << err[0]; | |
| break; | |
| default: | |
| break; | |
| } | |
| #endif | |
| } | |
| } // namespace webrtc | |
| // EOF |