blob: 942e7db3b329ac5043915050f9664f263dac2092 [file] [log] [blame]
/*
* 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(rtc::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(rtc::LS_ERROR, "Error in " #expr, (const char*)&err); \
} \
} while (0)
#define WEBRTC_CA_LOG_WARN(expr) \
do { \
err = expr; \
if (err != noErr) { \
logCAMsg(rtc::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 rtc::LoggingSeverity sev,
const char* msg,
const char* err) {
RTC_DCHECK(msg != NULL);
RTC_DCHECK(err != NULL);
RTC_DCHECK(sev == rtc::LS_ERROR || sev == rtc::LS_WARNING);
#ifdef WEBRTC_ARCH_BIG_ENDIAN
switch (sev) {
case rtc::LS_ERROR:
RTC_LOG(LS_ERROR) << msg << ": " << err[0] << err[1] << err[2] << err[3];
break;
case rtc::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 rtc::LS_ERROR:
RTC_LOG(LS_ERROR) << msg << ": " << err[3] << err[2] << err[1] << err[0];
break;
case rtc::LS_WARNING:
RTC_LOG(LS_WARNING) << msg << ": " << err[3] << err[2] << err[1]
<< err[0];
break;
default:
break;
}
#endif
}
} // namespace webrtc
// EOF