/* * 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 "webrtc/modules/audio_device/mac/audio_mixer_manager_mac.h" #include "webrtc/system_wrappers/include/trace.h" #include // getpid() namespace webrtc { #define WEBRTC_CA_RETURN_ON_ERR(expr) \ do { \ err = expr; \ if (err != noErr) { \ logCAMsg(kTraceError, kTraceAudioDevice, _id, "Error in " #expr, \ (const char*) & err); \ return -1; \ } \ } while (0) #define WEBRTC_CA_LOG_ERR(expr) \ do { \ err = expr; \ if (err != noErr) { \ logCAMsg(kTraceError, kTraceAudioDevice, _id, "Error in " #expr, \ (const char*) & err); \ } \ } while (0) #define WEBRTC_CA_LOG_WARN(expr) \ do { \ err = expr; \ if (err != noErr) { \ logCAMsg(kTraceWarning, kTraceAudioDevice, _id, "Error in " #expr, \ (const char*) & err); \ } \ } while (0) AudioMixerManagerMac::AudioMixerManagerMac(const int32_t id) : _critSect(*CriticalSectionWrapper::CreateCriticalSection()), _id(id), _inputDeviceID(kAudioObjectUnknown), _outputDeviceID(kAudioObjectUnknown), _noInputChannels(0), _noOutputChannels(0) { WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s constructed", __FUNCTION__); } AudioMixerManagerMac::~AudioMixerManagerMac() { WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s destructed", __FUNCTION__); Close(); delete &_critSect; } // ============================================================================ // PUBLIC METHODS // ============================================================================ int32_t AudioMixerManagerMac::Close() { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%s", __FUNCTION__); CriticalSectionScoped lock(&_critSect); CloseSpeaker(); CloseMicrophone(); return 0; } int32_t AudioMixerManagerMac::CloseSpeaker() { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%s", __FUNCTION__); CriticalSectionScoped lock(&_critSect); _outputDeviceID = kAudioObjectUnknown; _noOutputChannels = 0; return 0; } int32_t AudioMixerManagerMac::CloseMicrophone() { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "%s", __FUNCTION__); CriticalSectionScoped lock(&_critSect); _inputDeviceID = kAudioObjectUnknown; _noInputChannels = 0; return 0; } int32_t AudioMixerManagerMac::OpenSpeaker(AudioDeviceID deviceID) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "AudioMixerManagerMac::OpenSpeaker(id=%d)", deviceID); CriticalSectionScoped lock(&_critSect); 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}; size = sizeof(hogPid); WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( _outputDeviceID, &propertyAddress, 0, NULL, &size, &hogPid)); if (hogPid == -1) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " No process has hogged the input device"); } // getpid() is apparently "always successful" else if (hogPid == getpid()) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " Our process has hogged the input device"); } else { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Another process (pid = %d) has hogged the input device", static_cast(hogPid)); 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) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "AudioMixerManagerMac::OpenMicrophone(id=%d)", deviceID); CriticalSectionScoped lock(&_critSect); 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) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " No process has hogged the input device"); } // getpid() is apparently "always successful" else if (hogPid == getpid()) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " Our process has hogged the input device"); } else { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Another process (pid = %d) has hogged the input device", static_cast(hogPid)); 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 { WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s", __FUNCTION__); return (_outputDeviceID != kAudioObjectUnknown); } bool AudioMixerManagerMac::MicrophoneIsInitialized() const { WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s", __FUNCTION__); return (_inputDeviceID != kAudioObjectUnknown); } int32_t AudioMixerManagerMac::SetSpeakerVolume(uint32_t volume) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "AudioMixerManagerMac::SetSpeakerVolume(volume=%u)", volume); CriticalSectionScoped lock(&_critSect); if (_outputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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); assert(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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Unable to set a volume on any output channel"); return -1; } return 0; } int32_t AudioMixerManagerMac::SpeakerVolume(uint32_t& volume) const { if (_outputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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(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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Unable to get a volume on any channel"); return -1; } assert(channels > 0); // vol 0.0 to 1.0 -> convert to 0 - 255 volume = static_cast(255 * vol / channels + 0.5); } WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " AudioMixerManagerMac::SpeakerVolume() => vol=%i", vol); return 0; } int32_t AudioMixerManagerMac::MaxSpeakerVolume(uint32_t& maxVolume) const { if (_outputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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::SpeakerVolumeStepSize(uint16_t& stepSize) const { if (_outputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " device ID has not been set"); return -1; } // volume range is 0.0 to 1.0 // we convert that to 0 - 255 stepSize = 1; return 0; } int32_t AudioMixerManagerMac::SpeakerVolumeIsAvailable(bool& available) { if (_outputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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; WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Volume cannot be set for output channel %d, err=%d", i, err); return -1; } } available = true; return 0; } int32_t AudioMixerManagerMac::SpeakerMuteIsAvailable(bool& available) { if (_outputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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; WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Mute cannot be set for output channel %d, err=%d", i, err); return -1; } } available = true; return 0; } int32_t AudioMixerManagerMac::SetSpeakerMute(bool enable) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "AudioMixerManagerMac::SetSpeakerMute(enable=%u)", enable); CriticalSectionScoped lock(&_critSect); if (_outputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Unable to set mute on any input channel"); return -1; } return 0; } int32_t AudioMixerManagerMac::SpeakerMute(bool& enabled) const { if (_outputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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(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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Unable to get mute for any channel"); return -1; } assert(channels > 0); // 1 means muted enabled = static_cast(muted); } WEBRTC_TRACE( kTraceInfo, kTraceAudioDevice, _id, " AudioMixerManagerMac::SpeakerMute() => enabled=%d, enabled"); return 0; } int32_t AudioMixerManagerMac::StereoPlayoutIsAvailable(bool& available) { if (_outputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " device ID has not been set"); return -1; } available = (_noOutputChannels == 2); return 0; } int32_t AudioMixerManagerMac::StereoRecordingIsAvailable(bool& available) { if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " device ID has not been set"); return -1; } available = (_noInputChannels == 2); return 0; } int32_t AudioMixerManagerMac::MicrophoneMuteIsAvailable(bool& available) { if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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; WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Mute cannot be set for output channel %d, err=%d", i, err); return -1; } } available = true; return 0; } int32_t AudioMixerManagerMac::SetMicrophoneMute(bool enable) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "AudioMixerManagerMac::SetMicrophoneMute(enable=%u)", enable); CriticalSectionScoped lock(&_critSect); if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Unable to set mute on any input channel"); return -1; } return 0; } int32_t AudioMixerManagerMac::MicrophoneMute(bool& enabled) const { if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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(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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Unable to get mute for any channel"); return -1; } assert(channels > 0); // 1 means muted enabled = static_cast(muted); } WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " AudioMixerManagerMac::MicrophoneMute() => enabled=%d", enabled); return 0; } int32_t AudioMixerManagerMac::MicrophoneBoostIsAvailable(bool& available) { if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " device ID has not been set"); return -1; } available = false; // No AudioObjectPropertySelector value for Mic Boost return 0; } int32_t AudioMixerManagerMac::SetMicrophoneBoost(bool enable) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "AudioMixerManagerMac::SetMicrophoneBoost(enable=%u)", enable); CriticalSectionScoped lock(&_critSect); if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " device ID has not been set"); return -1; } // Ensure that the selected microphone has a valid boost control. bool available(false); MicrophoneBoostIsAvailable(available); if (!available) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " it is not possible to enable microphone boost"); return -1; } // It is assumed that the call above fails! return 0; } int32_t AudioMixerManagerMac::MicrophoneBoost(bool& enabled) const { if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " device ID has not been set"); return -1; } // Microphone boost cannot be enabled on this platform! enabled = false; return 0; } int32_t AudioMixerManagerMac::MicrophoneVolumeIsAvailable(bool& available) { if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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; WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Volume cannot be set for input channel %d, err=%d", i, err); return -1; } } available = true; return 0; } int32_t AudioMixerManagerMac::SetMicrophoneVolume(uint32_t volume) { WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "AudioMixerManagerMac::SetMicrophoneVolume(volume=%u)", volume); CriticalSectionScoped lock(&_critSect); if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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); assert(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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Unable to set a level on any input channel"); return -1; } return 0; } int32_t AudioMixerManagerMac::MicrophoneVolume(uint32_t& volume) const { if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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(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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Unable to get a level on any channel"); return -1; } assert(channels > 0); // vol 0.0 to 1.0 -> convert to 0 - 255 volume = static_cast(255 * volFloat32 / channels + 0.5); } WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " AudioMixerManagerMac::MicrophoneVolume() => vol=%u", volume); return 0; } int32_t AudioMixerManagerMac::MaxMicrophoneVolume(uint32_t& maxVolume) const { if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " 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; } int32_t AudioMixerManagerMac::MicrophoneVolumeStepSize( uint16_t& stepSize) const { if (_inputDeviceID == kAudioObjectUnknown) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " device ID has not been set"); return -1; } // volume range is 0.0 to 1.0 // we convert that to 0 - 10 stepSize = 1; return 0; } // ============================================================================ // Private Methods // ============================================================================ // CoreAudio errors are best interpreted as four character strings. void AudioMixerManagerMac::logCAMsg(const TraceLevel level, const TraceModule module, const int32_t id, const char* msg, const char* err) { assert(msg != NULL); assert(err != NULL); #ifdef WEBRTC_ARCH_BIG_ENDIAN WEBRTC_TRACE(level, module, id, "%s: %.4s", msg, err); #else // We need to flip the characters in this case. WEBRTC_TRACE(level, module, id, "%s: %.1s%.1s%.1s%.1s", msg, err + 3, err + 2, err + 1, err); #endif } } // namespace webrtc // EOF