blob: 70236b8837df9efead7bffbbdc6673e02a1e3d3f [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 "webrtc/base/arraysize.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/platform_thread.h"
#include "webrtc/modules/audio_device/audio_device_config.h"
#include "webrtc/modules/audio_device/mac/audio_device_mac.h"
#include "webrtc/modules/audio_device/mac/portaudio/pa_ringbuffer.h"
#include "webrtc/system_wrappers/include/event_wrapper.h"
#include "webrtc/system_wrappers/include/trace.h"
#include <ApplicationServices/ApplicationServices.h>
#include <libkern/OSAtomic.h> // OSAtomicCompareAndSwap()
#include <mach/mach.h> // mach_task_self()
#include <sys/sysctl.h> // sysctlbyname()
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)
enum { MaxNumberDevices = 64 };
void AudioDeviceMac::AtomicSet32(int32_t* theValue, int32_t newValue) {
while (1) {
int32_t oldValue = *theValue;
if (OSAtomicCompareAndSwap32Barrier(oldValue, newValue, theValue) == true) {
return;
}
}
}
int32_t AudioDeviceMac::AtomicGet32(int32_t* theValue) {
while (1) {
int32_t value = *theValue;
if (OSAtomicCompareAndSwap32Barrier(value, value, theValue) == true) {
return value;
}
}
}
// CoreAudio errors are best interpreted as four character strings.
void AudioDeviceMac::logCAMsg(const TraceLevel level,
const TraceModule module,
const int32_t id,
const char* msg,
const char* err) {
RTC_DCHECK(msg != NULL);
RTC_DCHECK(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
}
AudioDeviceMac::AudioDeviceMac(const int32_t id)
: _ptrAudioBuffer(NULL),
_critSect(*CriticalSectionWrapper::CreateCriticalSection()),
_stopEventRec(*EventWrapper::Create()),
_stopEvent(*EventWrapper::Create()),
_id(id),
_mixerManager(id),
_inputDeviceIndex(0),
_outputDeviceIndex(0),
_inputDeviceID(kAudioObjectUnknown),
_outputDeviceID(kAudioObjectUnknown),
_inputDeviceIsSpecified(false),
_outputDeviceIsSpecified(false),
_recChannels(N_REC_CHANNELS),
_playChannels(N_PLAY_CHANNELS),
_captureBufData(NULL),
_renderBufData(NULL),
_playBufType(AudioDeviceModule::kFixedBufferSize),
_initialized(false),
_isShutDown(false),
_recording(false),
_playing(false),
_recIsInitialized(false),
_playIsInitialized(false),
_AGC(false),
_renderDeviceIsAlive(1),
_captureDeviceIsAlive(1),
_twoDevices(true),
_doStop(false),
_doStopRec(false),
_macBookPro(false),
_macBookProPanRight(false),
_captureLatencyUs(0),
_renderLatencyUs(0),
_captureDelayUs(0),
_renderDelayUs(0),
_renderDelayOffsetSamples(0),
_playBufDelayFixed(20),
_playWarning(0),
_playError(0),
_recWarning(0),
_recError(0),
_paCaptureBuffer(NULL),
_paRenderBuffer(NULL),
_captureBufSizeSamples(0),
_renderBufSizeSamples(0),
prev_key_state_(),
get_mic_volume_counter_ms_(0) {
WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, id, "%s created", __FUNCTION__);
RTC_DCHECK(&_stopEvent != NULL);
RTC_DCHECK(&_stopEventRec != NULL);
memset(_renderConvertData, 0, sizeof(_renderConvertData));
memset(&_outStreamFormat, 0, sizeof(AudioStreamBasicDescription));
memset(&_outDesiredFormat, 0, sizeof(AudioStreamBasicDescription));
memset(&_inStreamFormat, 0, sizeof(AudioStreamBasicDescription));
memset(&_inDesiredFormat, 0, sizeof(AudioStreamBasicDescription));
}
AudioDeviceMac::~AudioDeviceMac() {
WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s destroyed",
__FUNCTION__);
if (!_isShutDown) {
Terminate();
}
RTC_DCHECK(!capture_worker_thread_.get());
RTC_DCHECK(!render_worker_thread_.get());
if (_paRenderBuffer) {
delete _paRenderBuffer;
_paRenderBuffer = NULL;
}
if (_paCaptureBuffer) {
delete _paCaptureBuffer;
_paCaptureBuffer = NULL;
}
if (_renderBufData) {
delete[] _renderBufData;
_renderBufData = NULL;
}
if (_captureBufData) {
delete[] _captureBufData;
_captureBufData = NULL;
}
kern_return_t kernErr = KERN_SUCCESS;
kernErr = semaphore_destroy(mach_task_self(), _renderSemaphore);
if (kernErr != KERN_SUCCESS) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" semaphore_destroy() error: %d", kernErr);
}
kernErr = semaphore_destroy(mach_task_self(), _captureSemaphore);
if (kernErr != KERN_SUCCESS) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" semaphore_destroy() error: %d", kernErr);
}
delete &_stopEvent;
delete &_stopEventRec;
delete &_critSect;
}
// ============================================================================
// API
// ============================================================================
void AudioDeviceMac::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
CriticalSectionScoped lock(&_critSect);
_ptrAudioBuffer = audioBuffer;
// inform the AudioBuffer about default settings for this implementation
_ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC);
_ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC);
_ptrAudioBuffer->SetRecordingChannels(N_REC_CHANNELS);
_ptrAudioBuffer->SetPlayoutChannels(N_PLAY_CHANNELS);
}
int32_t AudioDeviceMac::ActiveAudioLayer(
AudioDeviceModule::AudioLayer& audioLayer) const {
audioLayer = AudioDeviceModule::kPlatformDefaultAudio;
return 0;
}
AudioDeviceGeneric::InitStatus AudioDeviceMac::Init() {
CriticalSectionScoped lock(&_critSect);
if (_initialized) {
return InitStatus::OK;
}
OSStatus err = noErr;
_isShutDown = false;
// PortAudio ring buffers require an elementCount which is a power of two.
if (_renderBufData == NULL) {
UInt32 powerOfTwo = 1;
while (powerOfTwo < PLAY_BUF_SIZE_IN_SAMPLES) {
powerOfTwo <<= 1;
}
_renderBufSizeSamples = powerOfTwo;
_renderBufData = new SInt16[_renderBufSizeSamples];
}
if (_paRenderBuffer == NULL) {
_paRenderBuffer = new PaUtilRingBuffer;
PaRingBufferSize bufSize = -1;
bufSize = PaUtil_InitializeRingBuffer(
_paRenderBuffer, sizeof(SInt16), _renderBufSizeSamples, _renderBufData);
if (bufSize == -1) {
WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
" PaUtil_InitializeRingBuffer() error");
return InitStatus::PLAYOUT_ERROR;
}
}
if (_captureBufData == NULL) {
UInt32 powerOfTwo = 1;
while (powerOfTwo < REC_BUF_SIZE_IN_SAMPLES) {
powerOfTwo <<= 1;
}
_captureBufSizeSamples = powerOfTwo;
_captureBufData = new Float32[_captureBufSizeSamples];
}
if (_paCaptureBuffer == NULL) {
_paCaptureBuffer = new PaUtilRingBuffer;
PaRingBufferSize bufSize = -1;
bufSize =
PaUtil_InitializeRingBuffer(_paCaptureBuffer, sizeof(Float32),
_captureBufSizeSamples, _captureBufData);
if (bufSize == -1) {
WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
" PaUtil_InitializeRingBuffer() error");
return InitStatus::RECORDING_ERROR;
}
}
kern_return_t kernErr = KERN_SUCCESS;
kernErr = semaphore_create(mach_task_self(), &_renderSemaphore,
SYNC_POLICY_FIFO, 0);
if (kernErr != KERN_SUCCESS) {
WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
" semaphore_create() error: %d", kernErr);
return InitStatus::OTHER_ERROR;
}
kernErr = semaphore_create(mach_task_self(), &_captureSemaphore,
SYNC_POLICY_FIFO, 0);
if (kernErr != KERN_SUCCESS) {
WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
" semaphore_create() error: %d", kernErr);
return InitStatus::OTHER_ERROR;
}
// Setting RunLoop to NULL here instructs HAL to manage its own thread for
// notifications. This was the default behaviour on OS X 10.5 and earlier,
// but now must be explicitly specified. HAL would otherwise try to use the
// main thread to issue notifications.
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
CFRunLoopRef runLoop = NULL;
UInt32 size = sizeof(CFRunLoopRef);
int aoerr = AudioObjectSetPropertyData(
kAudioObjectSystemObject, &propertyAddress, 0, NULL, size, &runLoop);
if (aoerr != noErr) {
LOG(LS_ERROR) << "Error in AudioObjectSetPropertyData: "
<< (const char*)&aoerr;
return InitStatus::OTHER_ERROR;
}
// Listen for any device changes.
propertyAddress.mSelector = kAudioHardwarePropertyDevices;
WEBRTC_CA_LOG_ERR(AudioObjectAddPropertyListener(
kAudioObjectSystemObject, &propertyAddress, &objectListenerProc, this));
// Determine if this is a MacBook Pro
_macBookPro = false;
_macBookProPanRight = false;
char buf[128];
size_t length = sizeof(buf);
memset(buf, 0, length);
int intErr = sysctlbyname("hw.model", buf, &length, NULL, 0);
if (intErr != 0) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" Error in sysctlbyname(): %d", err);
} else {
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " Hardware model: %s",
buf);
if (strncmp(buf, "MacBookPro", 10) == 0) {
_macBookPro = true;
}
}
_playWarning = 0;
_playError = 0;
_recWarning = 0;
_recError = 0;
get_mic_volume_counter_ms_ = 0;
_initialized = true;
return InitStatus::OK;
}
int32_t AudioDeviceMac::Terminate() {
if (!_initialized) {
return 0;
}
if (_recording) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" Recording must be stopped");
return -1;
}
if (_playing) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" Playback must be stopped");
return -1;
}
_critSect.Enter();
_mixerManager.Close();
OSStatus err = noErr;
int retVal = 0;
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(
kAudioObjectSystemObject, &propertyAddress, &objectListenerProc, this));
err = AudioHardwareUnload();
if (err != noErr) {
logCAMsg(kTraceError, kTraceAudioDevice, _id,
"Error in AudioHardwareUnload()", (const char*)&err);
retVal = -1;
}
_isShutDown = true;
_initialized = false;
_outputDeviceIsSpecified = false;
_inputDeviceIsSpecified = false;
_critSect.Leave();
return retVal;
}
bool AudioDeviceMac::Initialized() const {
return (_initialized);
}
int32_t AudioDeviceMac::SpeakerIsAvailable(bool& available) {
bool wasInitialized = _mixerManager.SpeakerIsInitialized();
// Make an attempt to open up the
// output mixer corresponding to the currently selected output device.
//
if (!wasInitialized && InitSpeaker() == -1) {
available = false;
return 0;
}
// Given that InitSpeaker was successful, we know that a valid speaker
// exists.
available = true;
// Close the initialized output mixer
//
if (!wasInitialized) {
_mixerManager.CloseSpeaker();
}
return 0;
}
int32_t AudioDeviceMac::InitSpeaker() {
CriticalSectionScoped lock(&_critSect);
if (_playing) {
return -1;
}
if (InitDevice(_outputDeviceIndex, _outputDeviceID, false) == -1) {
return -1;
}
if (_inputDeviceID == _outputDeviceID) {
_twoDevices = false;
} else {
_twoDevices = true;
}
if (_mixerManager.OpenSpeaker(_outputDeviceID) == -1) {
return -1;
}
return 0;
}
int32_t AudioDeviceMac::MicrophoneIsAvailable(bool& available) {
bool wasInitialized = _mixerManager.MicrophoneIsInitialized();
// Make an attempt to open up the
// input mixer corresponding to the currently selected output device.
//
if (!wasInitialized && InitMicrophone() == -1) {
available = false;
return 0;
}
// Given that InitMicrophone was successful, we know that a valid microphone
// exists.
available = true;
// Close the initialized input mixer
//
if (!wasInitialized) {
_mixerManager.CloseMicrophone();
}
return 0;
}
int32_t AudioDeviceMac::InitMicrophone() {
CriticalSectionScoped lock(&_critSect);
if (_recording) {
return -1;
}
if (InitDevice(_inputDeviceIndex, _inputDeviceID, true) == -1) {
return -1;
}
if (_inputDeviceID == _outputDeviceID) {
_twoDevices = false;
} else {
_twoDevices = true;
}
if (_mixerManager.OpenMicrophone(_inputDeviceID) == -1) {
return -1;
}
return 0;
}
bool AudioDeviceMac::SpeakerIsInitialized() const {
return (_mixerManager.SpeakerIsInitialized());
}
bool AudioDeviceMac::MicrophoneIsInitialized() const {
return (_mixerManager.MicrophoneIsInitialized());
}
int32_t AudioDeviceMac::SpeakerVolumeIsAvailable(bool& available) {
bool wasInitialized = _mixerManager.SpeakerIsInitialized();
// Make an attempt to open up the
// output mixer corresponding to the currently selected output device.
//
if (!wasInitialized && InitSpeaker() == -1) {
// If we end up here it means that the selected speaker has no volume
// control.
available = false;
return 0;
}
// Given that InitSpeaker was successful, we know that a volume control exists
//
available = true;
// Close the initialized output mixer
//
if (!wasInitialized) {
_mixerManager.CloseSpeaker();
}
return 0;
}
int32_t AudioDeviceMac::SetSpeakerVolume(uint32_t volume) {
return (_mixerManager.SetSpeakerVolume(volume));
}
int32_t AudioDeviceMac::SpeakerVolume(uint32_t& volume) const {
uint32_t level(0);
if (_mixerManager.SpeakerVolume(level) == -1) {
return -1;
}
volume = level;
return 0;
}
int32_t AudioDeviceMac::SetWaveOutVolume(uint16_t volumeLeft,
uint16_t volumeRight) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" API call not supported on this platform");
return -1;
}
int32_t AudioDeviceMac::WaveOutVolume(uint16_t& /*volumeLeft*/,
uint16_t& /*volumeRight*/) const {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" API call not supported on this platform");
return -1;
}
int32_t AudioDeviceMac::MaxSpeakerVolume(uint32_t& maxVolume) const {
uint32_t maxVol(0);
if (_mixerManager.MaxSpeakerVolume(maxVol) == -1) {
return -1;
}
maxVolume = maxVol;
return 0;
}
int32_t AudioDeviceMac::MinSpeakerVolume(uint32_t& minVolume) const {
uint32_t minVol(0);
if (_mixerManager.MinSpeakerVolume(minVol) == -1) {
return -1;
}
minVolume = minVol;
return 0;
}
int32_t AudioDeviceMac::SpeakerVolumeStepSize(uint16_t& stepSize) const {
uint16_t delta(0);
if (_mixerManager.SpeakerVolumeStepSize(delta) == -1) {
return -1;
}
stepSize = delta;
return 0;
}
int32_t AudioDeviceMac::SpeakerMuteIsAvailable(bool& available) {
bool isAvailable(false);
bool wasInitialized = _mixerManager.SpeakerIsInitialized();
// Make an attempt to open up the
// output mixer corresponding to the currently selected output device.
//
if (!wasInitialized && InitSpeaker() == -1) {
// If we end up here it means that the selected speaker has no volume
// control, hence it is safe to state that there is no mute control
// already at this stage.
available = false;
return 0;
}
// Check if the selected speaker has a mute control
//
_mixerManager.SpeakerMuteIsAvailable(isAvailable);
available = isAvailable;
// Close the initialized output mixer
//
if (!wasInitialized) {
_mixerManager.CloseSpeaker();
}
return 0;
}
int32_t AudioDeviceMac::SetSpeakerMute(bool enable) {
return (_mixerManager.SetSpeakerMute(enable));
}
int32_t AudioDeviceMac::SpeakerMute(bool& enabled) const {
bool muted(0);
if (_mixerManager.SpeakerMute(muted) == -1) {
return -1;
}
enabled = muted;
return 0;
}
int32_t AudioDeviceMac::MicrophoneMuteIsAvailable(bool& available) {
bool isAvailable(false);
bool wasInitialized = _mixerManager.MicrophoneIsInitialized();
// Make an attempt to open up the
// input mixer corresponding to the currently selected input device.
//
if (!wasInitialized && InitMicrophone() == -1) {
// If we end up here it means that the selected microphone has no volume
// control, hence it is safe to state that there is no boost control
// already at this stage.
available = false;
return 0;
}
// Check if the selected microphone has a mute control
//
_mixerManager.MicrophoneMuteIsAvailable(isAvailable);
available = isAvailable;
// Close the initialized input mixer
//
if (!wasInitialized) {
_mixerManager.CloseMicrophone();
}
return 0;
}
int32_t AudioDeviceMac::SetMicrophoneMute(bool enable) {
return (_mixerManager.SetMicrophoneMute(enable));
}
int32_t AudioDeviceMac::MicrophoneMute(bool& enabled) const {
bool muted(0);
if (_mixerManager.MicrophoneMute(muted) == -1) {
return -1;
}
enabled = muted;
return 0;
}
int32_t AudioDeviceMac::MicrophoneBoostIsAvailable(bool& available) {
bool isAvailable(false);
bool wasInitialized = _mixerManager.MicrophoneIsInitialized();
// Enumerate all avaliable microphone and make an attempt to open up the
// input mixer corresponding to the currently selected input device.
//
if (!wasInitialized && InitMicrophone() == -1) {
// If we end up here it means that the selected microphone has no volume
// control, hence it is safe to state that there is no boost control
// already at this stage.
available = false;
return 0;
}
// Check if the selected microphone has a boost control
//
_mixerManager.MicrophoneBoostIsAvailable(isAvailable);
available = isAvailable;
// Close the initialized input mixer
//
if (!wasInitialized) {
_mixerManager.CloseMicrophone();
}
return 0;
}
int32_t AudioDeviceMac::SetMicrophoneBoost(bool enable) {
return (_mixerManager.SetMicrophoneBoost(enable));
}
int32_t AudioDeviceMac::MicrophoneBoost(bool& enabled) const {
bool onOff(0);
if (_mixerManager.MicrophoneBoost(onOff) == -1) {
return -1;
}
enabled = onOff;
return 0;
}
int32_t AudioDeviceMac::StereoRecordingIsAvailable(bool& available) {
bool isAvailable(false);
bool wasInitialized = _mixerManager.MicrophoneIsInitialized();
if (!wasInitialized && InitMicrophone() == -1) {
// Cannot open the specified device
available = false;
return 0;
}
// Check if the selected microphone can record stereo
//
_mixerManager.StereoRecordingIsAvailable(isAvailable);
available = isAvailable;
// Close the initialized input mixer
//
if (!wasInitialized) {
_mixerManager.CloseMicrophone();
}
return 0;
}
int32_t AudioDeviceMac::SetStereoRecording(bool enable) {
if (enable)
_recChannels = 2;
else
_recChannels = 1;
return 0;
}
int32_t AudioDeviceMac::StereoRecording(bool& enabled) const {
if (_recChannels == 2)
enabled = true;
else
enabled = false;
return 0;
}
int32_t AudioDeviceMac::StereoPlayoutIsAvailable(bool& available) {
bool isAvailable(false);
bool wasInitialized = _mixerManager.SpeakerIsInitialized();
if (!wasInitialized && InitSpeaker() == -1) {
// Cannot open the specified device
available = false;
return 0;
}
// Check if the selected microphone can record stereo
//
_mixerManager.StereoPlayoutIsAvailable(isAvailable);
available = isAvailable;
// Close the initialized input mixer
//
if (!wasInitialized) {
_mixerManager.CloseSpeaker();
}
return 0;
}
int32_t AudioDeviceMac::SetStereoPlayout(bool enable) {
if (enable)
_playChannels = 2;
else
_playChannels = 1;
return 0;
}
int32_t AudioDeviceMac::StereoPlayout(bool& enabled) const {
if (_playChannels == 2)
enabled = true;
else
enabled = false;
return 0;
}
int32_t AudioDeviceMac::SetAGC(bool enable) {
_AGC = enable;
return 0;
}
bool AudioDeviceMac::AGC() const {
return _AGC;
}
int32_t AudioDeviceMac::MicrophoneVolumeIsAvailable(bool& available) {
bool wasInitialized = _mixerManager.MicrophoneIsInitialized();
// Make an attempt to open up the
// input mixer corresponding to the currently selected output device.
//
if (!wasInitialized && InitMicrophone() == -1) {
// If we end up here it means that the selected microphone has no volume
// control.
available = false;
return 0;
}
// Given that InitMicrophone was successful, we know that a volume control
// exists
//
available = true;
// Close the initialized input mixer
//
if (!wasInitialized) {
_mixerManager.CloseMicrophone();
}
return 0;
}
int32_t AudioDeviceMac::SetMicrophoneVolume(uint32_t volume) {
return (_mixerManager.SetMicrophoneVolume(volume));
}
int32_t AudioDeviceMac::MicrophoneVolume(uint32_t& volume) const {
uint32_t level(0);
if (_mixerManager.MicrophoneVolume(level) == -1) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" failed to retrive current microphone level");
return -1;
}
volume = level;
return 0;
}
int32_t AudioDeviceMac::MaxMicrophoneVolume(uint32_t& maxVolume) const {
uint32_t maxVol(0);
if (_mixerManager.MaxMicrophoneVolume(maxVol) == -1) {
return -1;
}
maxVolume = maxVol;
return 0;
}
int32_t AudioDeviceMac::MinMicrophoneVolume(uint32_t& minVolume) const {
uint32_t minVol(0);
if (_mixerManager.MinMicrophoneVolume(minVol) == -1) {
return -1;
}
minVolume = minVol;
return 0;
}
int32_t AudioDeviceMac::MicrophoneVolumeStepSize(uint16_t& stepSize) const {
uint16_t delta(0);
if (_mixerManager.MicrophoneVolumeStepSize(delta) == -1) {
return -1;
}
stepSize = delta;
return 0;
}
int16_t AudioDeviceMac::PlayoutDevices() {
AudioDeviceID playDevices[MaxNumberDevices];
return GetNumberDevices(kAudioDevicePropertyScopeOutput, playDevices,
MaxNumberDevices);
}
int32_t AudioDeviceMac::SetPlayoutDevice(uint16_t index) {
CriticalSectionScoped lock(&_critSect);
if (_playIsInitialized) {
return -1;
}
AudioDeviceID playDevices[MaxNumberDevices];
uint32_t nDevices = GetNumberDevices(kAudioDevicePropertyScopeOutput,
playDevices, MaxNumberDevices);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
" number of availiable waveform-audio output devices is %u",
nDevices);
if (index > (nDevices - 1)) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" device index is out of range [0,%u]", (nDevices - 1));
return -1;
}
_outputDeviceIndex = index;
_outputDeviceIsSpecified = true;
return 0;
}
int32_t AudioDeviceMac::SetPlayoutDevice(
AudioDeviceModule::WindowsDeviceType /*device*/) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"WindowsDeviceType not supported");
return -1;
}
int32_t AudioDeviceMac::PlayoutDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) {
const uint16_t nDevices(PlayoutDevices());
if ((index > (nDevices - 1)) || (name == NULL)) {
return -1;
}
memset(name, 0, kAdmMaxDeviceNameSize);
if (guid != NULL) {
memset(guid, 0, kAdmMaxGuidSize);
}
return GetDeviceName(kAudioDevicePropertyScopeOutput, index, name);
}
int32_t AudioDeviceMac::RecordingDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) {
const uint16_t nDevices(RecordingDevices());
if ((index > (nDevices - 1)) || (name == NULL)) {
return -1;
}
memset(name, 0, kAdmMaxDeviceNameSize);
if (guid != NULL) {
memset(guid, 0, kAdmMaxGuidSize);
}
return GetDeviceName(kAudioDevicePropertyScopeInput, index, name);
}
int16_t AudioDeviceMac::RecordingDevices() {
AudioDeviceID recDevices[MaxNumberDevices];
return GetNumberDevices(kAudioDevicePropertyScopeInput, recDevices,
MaxNumberDevices);
}
int32_t AudioDeviceMac::SetRecordingDevice(uint16_t index) {
if (_recIsInitialized) {
return -1;
}
AudioDeviceID recDevices[MaxNumberDevices];
uint32_t nDevices = GetNumberDevices(kAudioDevicePropertyScopeInput,
recDevices, MaxNumberDevices);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
" number of availiable waveform-audio input devices is %u",
nDevices);
if (index > (nDevices - 1)) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" device index is out of range [0,%u]", (nDevices - 1));
return -1;
}
_inputDeviceIndex = index;
_inputDeviceIsSpecified = true;
return 0;
}
int32_t AudioDeviceMac::SetRecordingDevice(
AudioDeviceModule::WindowsDeviceType /*device*/) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"WindowsDeviceType not supported");
return -1;
}
int32_t AudioDeviceMac::PlayoutIsAvailable(bool& available) {
available = true;
// Try to initialize the playout side
if (InitPlayout() == -1) {
available = false;
}
// We destroy the IOProc created by InitPlayout() in implDeviceIOProc().
// We must actually start playout here in order to have the IOProc
// deleted by calling StopPlayout().
if (StartPlayout() == -1) {
available = false;
}
// Cancel effect of initialization
if (StopPlayout() == -1) {
available = false;
}
return 0;
}
int32_t AudioDeviceMac::RecordingIsAvailable(bool& available) {
available = true;
// Try to initialize the recording side
if (InitRecording() == -1) {
available = false;
}
// We destroy the IOProc created by InitRecording() in implInDeviceIOProc().
// We must actually start recording here in order to have the IOProc
// deleted by calling StopRecording().
if (StartRecording() == -1) {
available = false;
}
// Cancel effect of initialization
if (StopRecording() == -1) {
available = false;
}
return 0;
}
int32_t AudioDeviceMac::InitPlayout() {
CriticalSectionScoped lock(&_critSect);
if (_playing) {
return -1;
}
if (!_outputDeviceIsSpecified) {
return -1;
}
if (_playIsInitialized) {
return 0;
}
// Initialize the speaker (devices might have been added or removed)
if (InitSpeaker() == -1) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" InitSpeaker() failed");
}
if (!MicrophoneIsInitialized()) {
// Make this call to check if we are using
// one or two devices (_twoDevices)
bool available = false;
if (MicrophoneIsAvailable(available) == -1) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" MicrophoneIsAvailable() failed");
}
}
PaUtil_FlushRingBuffer(_paRenderBuffer);
OSStatus err = noErr;
UInt32 size = 0;
_renderDelayOffsetSamples = 0;
_renderDelayUs = 0;
_renderLatencyUs = 0;
_renderDeviceIsAlive = 1;
_doStop = false;
// The internal microphone of a MacBook Pro is located under the left speaker
// grille. When the internal speakers are in use, we want to fully stereo
// pan to the right.
AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, 0};
if (_macBookPro) {
_macBookProPanRight = false;
Boolean hasProperty =
AudioObjectHasProperty(_outputDeviceID, &propertyAddress);
if (hasProperty) {
UInt32 dataSource = 0;
size = sizeof(dataSource);
WEBRTC_CA_LOG_WARN(AudioObjectGetPropertyData(
_outputDeviceID, &propertyAddress, 0, NULL, &size, &dataSource));
if (dataSource == 'ispk') {
_macBookProPanRight = true;
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"MacBook Pro using internal speakers; stereo"
" panning right");
} else {
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"MacBook Pro not using internal speakers");
}
// Add a listener to determine if the status changes.
WEBRTC_CA_LOG_WARN(AudioObjectAddPropertyListener(
_outputDeviceID, &propertyAddress, &objectListenerProc, this));
}
}
// Get current stream description
propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
memset(&_outStreamFormat, 0, sizeof(_outStreamFormat));
size = sizeof(_outStreamFormat);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_outputDeviceID, &propertyAddress, 0, NULL, &size, &_outStreamFormat));
if (_outStreamFormat.mFormatID != kAudioFormatLinearPCM) {
logCAMsg(kTraceError, kTraceAudioDevice, _id,
"Unacceptable output stream format -> mFormatID",
(const char*)&_outStreamFormat.mFormatID);
return -1;
}
if (_outStreamFormat.mChannelsPerFrame > N_DEVICE_CHANNELS) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"Too many channels on output device (mChannelsPerFrame = %d)",
_outStreamFormat.mChannelsPerFrame);
return -1;
}
if (_outStreamFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"Non-interleaved audio data is not supported.",
"AudioHardware streams should not have this format.");
return -1;
}
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "Ouput stream format:");
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"mSampleRate = %f, mChannelsPerFrame = %u",
_outStreamFormat.mSampleRate,
_outStreamFormat.mChannelsPerFrame);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"mBytesPerPacket = %u, mFramesPerPacket = %u",
_outStreamFormat.mBytesPerPacket,
_outStreamFormat.mFramesPerPacket);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"mBytesPerFrame = %u, mBitsPerChannel = %u",
_outStreamFormat.mBytesPerFrame,
_outStreamFormat.mBitsPerChannel);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "mFormatFlags = %u",
_outStreamFormat.mFormatFlags);
logCAMsg(kTraceInfo, kTraceAudioDevice, _id, "mFormatID",
(const char*)&_outStreamFormat.mFormatID);
// Our preferred format to work with.
if (_outStreamFormat.mChannelsPerFrame < 2) {
// Disable stereo playout when we only have one channel on the device.
_playChannels = 1;
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"Stereo playout unavailable on this device");
}
WEBRTC_CA_RETURN_ON_ERR(SetDesiredPlayoutFormat());
// Listen for format changes.
propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
WEBRTC_CA_RETURN_ON_ERR(AudioObjectAddPropertyListener(
_outputDeviceID, &propertyAddress, &objectListenerProc, this));
// Listen for processor overloads.
propertyAddress.mSelector = kAudioDeviceProcessorOverload;
WEBRTC_CA_LOG_WARN(AudioObjectAddPropertyListener(
_outputDeviceID, &propertyAddress, &objectListenerProc, this));
if (_twoDevices || !_recIsInitialized) {
WEBRTC_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(
_outputDeviceID, deviceIOProc, this, &_deviceIOProcID));
}
_playIsInitialized = true;
return 0;
}
int32_t AudioDeviceMac::InitRecording() {
CriticalSectionScoped lock(&_critSect);
if (_recording) {
return -1;
}
if (!_inputDeviceIsSpecified) {
return -1;
}
if (_recIsInitialized) {
return 0;
}
// Initialize the microphone (devices might have been added or removed)
if (InitMicrophone() == -1) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" InitMicrophone() failed");
}
if (!SpeakerIsInitialized()) {
// Make this call to check if we are using
// one or two devices (_twoDevices)
bool available = false;
if (SpeakerIsAvailable(available) == -1) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" SpeakerIsAvailable() failed");
}
}
OSStatus err = noErr;
UInt32 size = 0;
PaUtil_FlushRingBuffer(_paCaptureBuffer);
_captureDelayUs = 0;
_captureLatencyUs = 0;
_captureDeviceIsAlive = 1;
_doStopRec = false;
// Get current stream description
AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeInput, 0};
memset(&_inStreamFormat, 0, sizeof(_inStreamFormat));
size = sizeof(_inStreamFormat);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_inputDeviceID, &propertyAddress, 0, NULL, &size, &_inStreamFormat));
if (_inStreamFormat.mFormatID != kAudioFormatLinearPCM) {
logCAMsg(kTraceError, kTraceAudioDevice, _id,
"Unacceptable input stream format -> mFormatID",
(const char*)&_inStreamFormat.mFormatID);
return -1;
}
if (_inStreamFormat.mChannelsPerFrame > N_DEVICE_CHANNELS) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"Too many channels on input device (mChannelsPerFrame = %d)",
_inStreamFormat.mChannelsPerFrame);
return -1;
}
const int io_block_size_samples = _inStreamFormat.mChannelsPerFrame *
_inStreamFormat.mSampleRate / 100 *
N_BLOCKS_IO;
if (io_block_size_samples > _captureBufSizeSamples) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"Input IO block size (%d) is larger than ring buffer (%u)",
io_block_size_samples, _captureBufSizeSamples);
return -1;
}
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " Input stream format:");
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
" mSampleRate = %f, mChannelsPerFrame = %u",
_inStreamFormat.mSampleRate, _inStreamFormat.mChannelsPerFrame);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
" mBytesPerPacket = %u, mFramesPerPacket = %u",
_inStreamFormat.mBytesPerPacket,
_inStreamFormat.mFramesPerPacket);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
" mBytesPerFrame = %u, mBitsPerChannel = %u",
_inStreamFormat.mBytesPerFrame, _inStreamFormat.mBitsPerChannel);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " mFormatFlags = %u",
_inStreamFormat.mFormatFlags);
logCAMsg(kTraceInfo, kTraceAudioDevice, _id, "mFormatID",
(const char*)&_inStreamFormat.mFormatID);
// Our preferred format to work with
if (_inStreamFormat.mChannelsPerFrame >= 2 && (_recChannels == 2)) {
_inDesiredFormat.mChannelsPerFrame = 2;
} else {
// Disable stereo recording when we only have one channel on the device.
_inDesiredFormat.mChannelsPerFrame = 1;
_recChannels = 1;
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"Stereo recording unavailable on this device");
}
if (_ptrAudioBuffer) {
// Update audio buffer with the selected parameters
_ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC);
_ptrAudioBuffer->SetRecordingChannels((uint8_t)_recChannels);
}
_inDesiredFormat.mSampleRate = N_REC_SAMPLES_PER_SEC;
_inDesiredFormat.mBytesPerPacket =
_inDesiredFormat.mChannelsPerFrame * sizeof(SInt16);
_inDesiredFormat.mFramesPerPacket = 1;
_inDesiredFormat.mBytesPerFrame =
_inDesiredFormat.mChannelsPerFrame * sizeof(SInt16);
_inDesiredFormat.mBitsPerChannel = sizeof(SInt16) * 8;
_inDesiredFormat.mFormatFlags =
kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
#ifdef WEBRTC_ARCH_BIG_ENDIAN
_inDesiredFormat.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
_inDesiredFormat.mFormatID = kAudioFormatLinearPCM;
WEBRTC_CA_RETURN_ON_ERR(AudioConverterNew(&_inStreamFormat, &_inDesiredFormat,
&_captureConverter));
// First try to set buffer size to desired value (10 ms * N_BLOCKS_IO)
// TODO(xians): investigate this block.
UInt32 bufByteCount =
(UInt32)((_inStreamFormat.mSampleRate / 1000.0) * 10.0 * N_BLOCKS_IO *
_inStreamFormat.mChannelsPerFrame * sizeof(Float32));
if (_inStreamFormat.mFramesPerPacket != 0) {
if (bufByteCount % _inStreamFormat.mFramesPerPacket != 0) {
bufByteCount =
((UInt32)(bufByteCount / _inStreamFormat.mFramesPerPacket) + 1) *
_inStreamFormat.mFramesPerPacket;
}
}
// Ensure the buffer size is within the acceptable range provided by the
// device.
propertyAddress.mSelector = kAudioDevicePropertyBufferSizeRange;
AudioValueRange range;
size = sizeof(range);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_inputDeviceID, &propertyAddress, 0, NULL, &size, &range));
if (range.mMinimum > bufByteCount) {
bufByteCount = range.mMinimum;
} else if (range.mMaximum < bufByteCount) {
bufByteCount = range.mMaximum;
}
propertyAddress.mSelector = kAudioDevicePropertyBufferSize;
size = sizeof(bufByteCount);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData(
_inputDeviceID, &propertyAddress, 0, NULL, size, &bufByteCount));
// Get capture device latency
propertyAddress.mSelector = kAudioDevicePropertyLatency;
UInt32 latency = 0;
size = sizeof(UInt32);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_inputDeviceID, &propertyAddress, 0, NULL, &size, &latency));
_captureLatencyUs = (UInt32)((1.0e6 * latency) / _inStreamFormat.mSampleRate);
// Get capture stream latency
propertyAddress.mSelector = kAudioDevicePropertyStreams;
AudioStreamID stream = 0;
size = sizeof(AudioStreamID);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_inputDeviceID, &propertyAddress, 0, NULL, &size, &stream));
propertyAddress.mSelector = kAudioStreamPropertyLatency;
size = sizeof(UInt32);
latency = 0;
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_inputDeviceID, &propertyAddress, 0, NULL, &size, &latency));
_captureLatencyUs +=
(UInt32)((1.0e6 * latency) / _inStreamFormat.mSampleRate);
// Listen for format changes
// TODO(xians): should we be using kAudioDevicePropertyDeviceHasChanged?
propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
WEBRTC_CA_RETURN_ON_ERR(AudioObjectAddPropertyListener(
_inputDeviceID, &propertyAddress, &objectListenerProc, this));
// Listen for processor overloads
propertyAddress.mSelector = kAudioDeviceProcessorOverload;
WEBRTC_CA_LOG_WARN(AudioObjectAddPropertyListener(
_inputDeviceID, &propertyAddress, &objectListenerProc, this));
if (_twoDevices) {
WEBRTC_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(
_inputDeviceID, inDeviceIOProc, this, &_inDeviceIOProcID));
} else if (!_playIsInitialized) {
WEBRTC_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(
_inputDeviceID, deviceIOProc, this, &_deviceIOProcID));
}
// Mark recording side as initialized
_recIsInitialized = true;
return 0;
}
int32_t AudioDeviceMac::StartRecording() {
CriticalSectionScoped lock(&_critSect);
if (!_recIsInitialized) {
return -1;
}
if (_recording) {
return 0;
}
if (!_initialized) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" Recording worker thread has not been started");
return -1;
}
RTC_DCHECK(!capture_worker_thread_.get());
capture_worker_thread_.reset(
new rtc::PlatformThread(RunCapture, this, "CaptureWorkerThread"));
RTC_DCHECK(capture_worker_thread_.get());
capture_worker_thread_->Start();
capture_worker_thread_->SetPriority(rtc::kRealtimePriority);
OSStatus err = noErr;
if (_twoDevices) {
WEBRTC_CA_RETURN_ON_ERR(
AudioDeviceStart(_inputDeviceID, _inDeviceIOProcID));
} else if (!_playing) {
WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_inputDeviceID, _deviceIOProcID));
}
_recording = true;
return 0;
}
int32_t AudioDeviceMac::StopRecording() {
CriticalSectionScoped lock(&_critSect);
if (!_recIsInitialized) {
return 0;
}
OSStatus err = noErr;
// Stop device
int32_t captureDeviceIsAlive = AtomicGet32(&_captureDeviceIsAlive);
if (_twoDevices) {
if (_recording && captureDeviceIsAlive == 1) {
_recording = false;
_doStopRec = true; // Signal to io proc to stop audio device
_critSect.Leave(); // Cannot be under lock, risk of deadlock
if (kEventTimeout == _stopEventRec.Wait(2000)) {
CriticalSectionScoped critScoped(&_critSect);
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" Timed out stopping the capture IOProc. "
"We may have failed to detect a device removal.");
WEBRTC_CA_LOG_WARN(AudioDeviceStop(_inputDeviceID, _inDeviceIOProcID));
WEBRTC_CA_LOG_WARN(
AudioDeviceDestroyIOProcID(_inputDeviceID, _inDeviceIOProcID));
}
_critSect.Enter();
_doStopRec = false;
WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, " Recording stopped");
}
} else {
// We signal a stop for a shared device even when rendering has
// not yet ended. This is to ensure the IOProc will return early as
// intended (by checking |_recording|) before accessing
// resources we free below (e.g. the capture converter).
//
// In the case of a shared devcie, the IOProc will verify
// rendering has ended before stopping itself.
if (_recording && captureDeviceIsAlive == 1) {
_recording = false;
_doStop = true; // Signal to io proc to stop audio device
_critSect.Leave(); // Cannot be under lock, risk of deadlock
if (kEventTimeout == _stopEvent.Wait(2000)) {
CriticalSectionScoped critScoped(&_critSect);
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" Timed out stopping the shared IOProc. "
"We may have failed to detect a device removal.");
// We assume rendering on a shared device has stopped as well if
// the IOProc times out.
WEBRTC_CA_LOG_WARN(AudioDeviceStop(_outputDeviceID, _deviceIOProcID));
WEBRTC_CA_LOG_WARN(
AudioDeviceDestroyIOProcID(_outputDeviceID, _deviceIOProcID));
}
_critSect.Enter();
_doStop = false;
WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
" Recording stopped (shared)");
}
}
// Setting this signal will allow the worker thread to be stopped.
AtomicSet32(&_captureDeviceIsAlive, 0);
if (capture_worker_thread_.get()) {
_critSect.Leave();
capture_worker_thread_->Stop();
capture_worker_thread_.reset();
_critSect.Enter();
}
WEBRTC_CA_LOG_WARN(AudioConverterDispose(_captureConverter));
// Remove listeners.
AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeInput, 0};
WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(
_inputDeviceID, &propertyAddress, &objectListenerProc, this));
propertyAddress.mSelector = kAudioDeviceProcessorOverload;
WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(
_inputDeviceID, &propertyAddress, &objectListenerProc, this));
_recIsInitialized = false;
_recording = false;
return 0;
}
bool AudioDeviceMac::RecordingIsInitialized() const {
return (_recIsInitialized);
}
bool AudioDeviceMac::Recording() const {
return (_recording);
}
bool AudioDeviceMac::PlayoutIsInitialized() const {
return (_playIsInitialized);
}
int32_t AudioDeviceMac::StartPlayout() {
CriticalSectionScoped lock(&_critSect);
if (!_playIsInitialized) {
return -1;
}
if (_playing) {
return 0;
}
RTC_DCHECK(!render_worker_thread_.get());
render_worker_thread_.reset(
new rtc::PlatformThread(RunRender, this, "RenderWorkerThread"));
render_worker_thread_->Start();
render_worker_thread_->SetPriority(rtc::kRealtimePriority);
if (_twoDevices || !_recording) {
OSStatus err = noErr;
WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_outputDeviceID, _deviceIOProcID));
}
_playing = true;
return 0;
}
int32_t AudioDeviceMac::StopPlayout() {
CriticalSectionScoped lock(&_critSect);
if (!_playIsInitialized) {
return 0;
}
OSStatus err = noErr;
int32_t renderDeviceIsAlive = AtomicGet32(&_renderDeviceIsAlive);
if (_playing && renderDeviceIsAlive == 1) {
// We signal a stop for a shared device even when capturing has not
// yet ended. This is to ensure the IOProc will return early as
// intended (by checking |_playing|) before accessing resources we
// free below (e.g. the render converter).
//
// In the case of a shared device, the IOProc will verify capturing
// has ended before stopping itself.
_playing = false;
_doStop = true; // Signal to io proc to stop audio device
_critSect.Leave(); // Cannot be under lock, risk of deadlock
if (kEventTimeout == _stopEvent.Wait(2000)) {
CriticalSectionScoped critScoped(&_critSect);
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" Timed out stopping the render IOProc. "
"We may have failed to detect a device removal.");
// We assume capturing on a shared device has stopped as well if the
// IOProc times out.
WEBRTC_CA_LOG_WARN(AudioDeviceStop(_outputDeviceID, _deviceIOProcID));
WEBRTC_CA_LOG_WARN(
AudioDeviceDestroyIOProcID(_outputDeviceID, _deviceIOProcID));
}
_critSect.Enter();
_doStop = false;
WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "Playout stopped");
}
// Setting this signal will allow the worker thread to be stopped.
AtomicSet32(&_renderDeviceIsAlive, 0);
if (render_worker_thread_.get()) {
_critSect.Leave();
render_worker_thread_->Stop();
render_worker_thread_.reset();
_critSect.Enter();
}
WEBRTC_CA_LOG_WARN(AudioConverterDispose(_renderConverter));
// Remove listeners.
AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput, 0};
WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(
_outputDeviceID, &propertyAddress, &objectListenerProc, this));
propertyAddress.mSelector = kAudioDeviceProcessorOverload;
WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(
_outputDeviceID, &propertyAddress, &objectListenerProc, this));
if (_macBookPro) {
Boolean hasProperty =
AudioObjectHasProperty(_outputDeviceID, &propertyAddress);
if (hasProperty) {
propertyAddress.mSelector = kAudioDevicePropertyDataSource;
WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener(
_outputDeviceID, &propertyAddress, &objectListenerProc, this));
}
}
_playIsInitialized = false;
_playing = false;
return 0;
}
int32_t AudioDeviceMac::PlayoutDelay(uint16_t& delayMS) const {
int32_t renderDelayUs = AtomicGet32(&_renderDelayUs);
delayMS =
static_cast<uint16_t>(1e-3 * (renderDelayUs + _renderLatencyUs) + 0.5);
return 0;
}
int32_t AudioDeviceMac::RecordingDelay(uint16_t& delayMS) const {
int32_t captureDelayUs = AtomicGet32(&_captureDelayUs);
delayMS =
static_cast<uint16_t>(1e-3 * (captureDelayUs + _captureLatencyUs) + 0.5);
return 0;
}
bool AudioDeviceMac::Playing() const {
return (_playing);
}
int32_t AudioDeviceMac::SetPlayoutBuffer(
const AudioDeviceModule::BufferType type,
uint16_t sizeMS) {
if (type != AudioDeviceModule::kFixedBufferSize) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" Adaptive buffer size not supported on this platform");
return -1;
}
_playBufType = type;
_playBufDelayFixed = sizeMS;
return 0;
}
int32_t AudioDeviceMac::PlayoutBuffer(AudioDeviceModule::BufferType& type,
uint16_t& sizeMS) const {
type = _playBufType;
sizeMS = _playBufDelayFixed;
return 0;
}
// Not implemented for Mac.
int32_t AudioDeviceMac::CPULoad(uint16_t& /*load*/) const {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" API call not supported on this platform");
return -1;
}
bool AudioDeviceMac::PlayoutWarning() const {
return (_playWarning > 0);
}
bool AudioDeviceMac::PlayoutError() const {
return (_playError > 0);
}
bool AudioDeviceMac::RecordingWarning() const {
return (_recWarning > 0);
}
bool AudioDeviceMac::RecordingError() const {
return (_recError > 0);
}
void AudioDeviceMac::ClearPlayoutWarning() {
_playWarning = 0;
}
void AudioDeviceMac::ClearPlayoutError() {
_playError = 0;
}
void AudioDeviceMac::ClearRecordingWarning() {
_recWarning = 0;
}
void AudioDeviceMac::ClearRecordingError() {
_recError = 0;
}
// ============================================================================
// Private Methods
// ============================================================================
int32_t AudioDeviceMac::GetNumberDevices(const AudioObjectPropertyScope scope,
AudioDeviceID scopedDeviceIds[],
const uint32_t deviceListLength) {
OSStatus err = noErr;
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
UInt32 size = 0;
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyDataSize(
kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size));
if (size == 0) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "No devices");
return 0;
}
AudioDeviceID* deviceIds = (AudioDeviceID*)malloc(size);
UInt32 numberDevices = size / sizeof(AudioDeviceID);
AudioBufferList* bufferList = NULL;
UInt32 numberScopedDevices = 0;
// First check if there is a default device and list it
UInt32 hardwareProperty = 0;
if (scope == kAudioDevicePropertyScopeOutput) {
hardwareProperty = kAudioHardwarePropertyDefaultOutputDevice;
} else {
hardwareProperty = kAudioHardwarePropertyDefaultInputDevice;
}
AudioObjectPropertyAddress propertyAddressDefault = {
hardwareProperty, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
AudioDeviceID usedID;
UInt32 uintSize = sizeof(UInt32);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(kAudioObjectSystemObject,
&propertyAddressDefault, 0,
NULL, &uintSize, &usedID));
if (usedID != kAudioDeviceUnknown) {
scopedDeviceIds[numberScopedDevices] = usedID;
numberScopedDevices++;
} else {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
"GetNumberDevices(): Default device unknown");
}
// Then list the rest of the devices
bool listOK = true;
WEBRTC_CA_LOG_ERR(AudioObjectGetPropertyData(
kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size, deviceIds));
if (err != noErr) {
listOK = false;
} else {
propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
propertyAddress.mScope = scope;
propertyAddress.mElement = 0;
for (UInt32 i = 0; i < numberDevices; i++) {
// Check for input channels
WEBRTC_CA_LOG_ERR(AudioObjectGetPropertyDataSize(
deviceIds[i], &propertyAddress, 0, NULL, &size));
if (err == kAudioHardwareBadDeviceError) {
// This device doesn't actually exist; continue iterating.
continue;
} else if (err != noErr) {
listOK = false;
break;
}
bufferList = (AudioBufferList*)malloc(size);
WEBRTC_CA_LOG_ERR(AudioObjectGetPropertyData(
deviceIds[i], &propertyAddress, 0, NULL, &size, bufferList));
if (err != noErr) {
listOK = false;
break;
}
if (bufferList->mNumberBuffers > 0) {
if (numberScopedDevices >= deviceListLength) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"Device list is not long enough");
listOK = false;
break;
}
scopedDeviceIds[numberScopedDevices] = deviceIds[i];
numberScopedDevices++;
}
free(bufferList);
bufferList = NULL;
} // for
}
if (!listOK) {
if (deviceIds) {
free(deviceIds);
deviceIds = NULL;
}
if (bufferList) {
free(bufferList);
bufferList = NULL;
}
return -1;
}
// Happy ending
if (deviceIds) {
free(deviceIds);
deviceIds = NULL;
}
return numberScopedDevices;
}
int32_t AudioDeviceMac::GetDeviceName(const AudioObjectPropertyScope scope,
const uint16_t index,
char* name) {
OSStatus err = noErr;
UInt32 len = kAdmMaxDeviceNameSize;
AudioDeviceID deviceIds[MaxNumberDevices];
int numberDevices = GetNumberDevices(scope, deviceIds, MaxNumberDevices);
if (numberDevices < 0) {
return -1;
} else if (numberDevices == 0) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "No devices");
return -1;
}
// If the number is below the number of devices, assume it's "WEBRTC ID"
// otherwise assume it's a CoreAudio ID
AudioDeviceID usedID;
// Check if there is a default device
bool isDefaultDevice = false;
if (index == 0) {
UInt32 hardwareProperty = 0;
if (scope == kAudioDevicePropertyScopeOutput) {
hardwareProperty = kAudioHardwarePropertyDefaultOutputDevice;
} else {
hardwareProperty = kAudioHardwarePropertyDefaultInputDevice;
}
AudioObjectPropertyAddress propertyAddress = {
hardwareProperty, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
UInt32 size = sizeof(UInt32);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size, &usedID));
if (usedID == kAudioDeviceUnknown) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
"GetDeviceName(): Default device unknown");
} else {
isDefaultDevice = true;
}
}
AudioObjectPropertyAddress propertyAddress = {kAudioDevicePropertyDeviceName,
scope, 0};
if (isDefaultDevice) {
char devName[len];
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(usedID, &propertyAddress,
0, NULL, &len, devName));
sprintf(name, "default (%s)", devName);
} else {
if (index < numberDevices) {
usedID = deviceIds[index];
} else {
usedID = index;
}
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(usedID, &propertyAddress,
0, NULL, &len, name));
}
return 0;
}
int32_t AudioDeviceMac::InitDevice(const uint16_t userDeviceIndex,
AudioDeviceID& deviceId,
const bool isInput) {
OSStatus err = noErr;
UInt32 size = 0;
AudioObjectPropertyScope deviceScope;
AudioObjectPropertySelector defaultDeviceSelector;
AudioDeviceID deviceIds[MaxNumberDevices];
if (isInput) {
deviceScope = kAudioDevicePropertyScopeInput;
defaultDeviceSelector = kAudioHardwarePropertyDefaultInputDevice;
} else {
deviceScope = kAudioDevicePropertyScopeOutput;
defaultDeviceSelector = kAudioHardwarePropertyDefaultOutputDevice;
}
AudioObjectPropertyAddress propertyAddress = {
defaultDeviceSelector, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
// Get the actual device IDs
int numberDevices =
GetNumberDevices(deviceScope, deviceIds, MaxNumberDevices);
if (numberDevices < 0) {
return -1;
} else if (numberDevices == 0) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"InitDevice(): No devices");
return -1;
}
bool isDefaultDevice = false;
deviceId = kAudioDeviceUnknown;
if (userDeviceIndex == 0) {
// Try to use default system device
size = sizeof(AudioDeviceID);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size, &deviceId));
if (deviceId == kAudioDeviceUnknown) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" No default device exists");
} else {
isDefaultDevice = true;
}
}
if (!isDefaultDevice) {
deviceId = deviceIds[userDeviceIndex];
}
// Obtain device name and manufacturer for logging.
// Also use this as a test to ensure a user-set device ID is valid.
char devName[128];
char devManf[128];
memset(devName, 0, sizeof(devName));
memset(devManf, 0, sizeof(devManf));
propertyAddress.mSelector = kAudioDevicePropertyDeviceName;
propertyAddress.mScope = deviceScope;
propertyAddress.mElement = 0;
size = sizeof(devName);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(deviceId, &propertyAddress,
0, NULL, &size, devName));
propertyAddress.mSelector = kAudioDevicePropertyDeviceManufacturer;
size = sizeof(devManf);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(deviceId, &propertyAddress,
0, NULL, &size, devManf));
if (isInput) {
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " Input device: %s %s",
devManf, devName);
} else {
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " Output device: %s %s",
devManf, devName);
}
return 0;
}
OSStatus AudioDeviceMac::SetDesiredPlayoutFormat() {
// Our preferred format to work with.
_outDesiredFormat.mSampleRate = N_PLAY_SAMPLES_PER_SEC;
_outDesiredFormat.mChannelsPerFrame = _playChannels;
if (_ptrAudioBuffer) {
// Update audio buffer with the selected parameters.
_ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC);
_ptrAudioBuffer->SetPlayoutChannels((uint8_t)_playChannels);
}
_renderDelayOffsetSamples = _renderBufSizeSamples -
N_BUFFERS_OUT * ENGINE_PLAY_BUF_SIZE_IN_SAMPLES *
_outDesiredFormat.mChannelsPerFrame;
_outDesiredFormat.mBytesPerPacket =
_outDesiredFormat.mChannelsPerFrame * sizeof(SInt16);
// In uncompressed audio, a packet is one frame.
_outDesiredFormat.mFramesPerPacket = 1;
_outDesiredFormat.mBytesPerFrame =
_outDesiredFormat.mChannelsPerFrame * sizeof(SInt16);
_outDesiredFormat.mBitsPerChannel = sizeof(SInt16) * 8;
_outDesiredFormat.mFormatFlags =
kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
#ifdef WEBRTC_ARCH_BIG_ENDIAN
_outDesiredFormat.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
_outDesiredFormat.mFormatID = kAudioFormatLinearPCM;
OSStatus err = noErr;
WEBRTC_CA_RETURN_ON_ERR(AudioConverterNew(
&_outDesiredFormat, &_outStreamFormat, &_renderConverter));
// Try to set buffer size to desired value (_playBufDelayFixed).
UInt32 bufByteCount = static_cast<UInt32>(
(_outStreamFormat.mSampleRate / 1000.0) * _playBufDelayFixed *
_outStreamFormat.mChannelsPerFrame * sizeof(Float32));
if (_outStreamFormat.mFramesPerPacket != 0) {
if (bufByteCount % _outStreamFormat.mFramesPerPacket != 0) {
bufByteCount = (static_cast<UInt32>(bufByteCount /
_outStreamFormat.mFramesPerPacket) +
1) *
_outStreamFormat.mFramesPerPacket;
}
}
// Ensure the buffer size is within the range provided by the device.
AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, 0};
propertyAddress.mSelector = kAudioDevicePropertyBufferSizeRange;
AudioValueRange range;
UInt32 size = sizeof(range);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_outputDeviceID, &propertyAddress, 0, NULL, &size, &range));
if (range.mMinimum > bufByteCount) {
bufByteCount = range.mMinimum;
} else if (range.mMaximum < bufByteCount) {
bufByteCount = range.mMaximum;
}
propertyAddress.mSelector = kAudioDevicePropertyBufferSize;
size = sizeof(bufByteCount);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData(
_outputDeviceID, &propertyAddress, 0, NULL, size, &bufByteCount));
// Get render device latency.
propertyAddress.mSelector = kAudioDevicePropertyLatency;
UInt32 latency = 0;
size = sizeof(UInt32);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_outputDeviceID, &propertyAddress, 0, NULL, &size, &latency));
_renderLatencyUs =
static_cast<uint32_t>((1.0e6 * latency) / _outStreamFormat.mSampleRate);
// Get render stream latency.
propertyAddress.mSelector = kAudioDevicePropertyStreams;
AudioStreamID stream = 0;
size = sizeof(AudioStreamID);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_outputDeviceID, &propertyAddress, 0, NULL, &size, &stream));
propertyAddress.mSelector = kAudioStreamPropertyLatency;
size = sizeof(UInt32);
latency = 0;
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
_outputDeviceID, &propertyAddress, 0, NULL, &size, &latency));
_renderLatencyUs +=
static_cast<uint32_t>((1.0e6 * latency) / _outStreamFormat.mSampleRate);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
" initial playout status: _renderDelayOffsetSamples=%d,"
" _renderDelayUs=%d, _renderLatencyUs=%d",
_renderDelayOffsetSamples, _renderDelayUs, _renderLatencyUs);
return 0;
}
OSStatus AudioDeviceMac::objectListenerProc(
AudioObjectID objectId,
UInt32 numberAddresses,
const AudioObjectPropertyAddress addresses[],
void* clientData) {
AudioDeviceMac* ptrThis = (AudioDeviceMac*)clientData;
RTC_DCHECK(ptrThis != NULL);
ptrThis->implObjectListenerProc(objectId, numberAddresses, addresses);
// AudioObjectPropertyListenerProc functions are supposed to return 0
return 0;
}
OSStatus AudioDeviceMac::implObjectListenerProc(
const AudioObjectID objectId,
const UInt32 numberAddresses,
const AudioObjectPropertyAddress addresses[]) {
WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
"AudioDeviceMac::implObjectListenerProc()");
for (UInt32 i = 0; i < numberAddresses; i++) {
if (addresses[i].mSelector == kAudioHardwarePropertyDevices) {
HandleDeviceChange();
} else if (addresses[i].mSelector == kAudioDevicePropertyStreamFormat) {
HandleStreamFormatChange(objectId, addresses[i]);
} else if (addresses[i].mSelector == kAudioDevicePropertyDataSource) {
HandleDataSourceChange(objectId, addresses[i]);
} else if (addresses[i].mSelector == kAudioDeviceProcessorOverload) {
HandleProcessorOverload(addresses[i]);
}
}
return 0;
}
int32_t AudioDeviceMac::HandleDeviceChange() {
OSStatus err = noErr;
WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
"kAudioHardwarePropertyDevices");
// A device has changed. Check if our registered devices have been removed.
// Ensure the devices have been initialized, meaning the IDs are valid.
if (MicrophoneIsInitialized()) {
AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyDeviceIsAlive, kAudioDevicePropertyScopeInput, 0};
UInt32 deviceIsAlive = 1;
UInt32 size = sizeof(UInt32);
err = AudioObjectGetPropertyData(_inputDeviceID, &propertyAddress, 0, NULL,
&size, &deviceIsAlive);
if (err == kAudioHardwareBadDeviceError || deviceIsAlive == 0) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
"Capture device is not alive (probably removed)");
AtomicSet32(&_captureDeviceIsAlive, 0);
_mixerManager.CloseMicrophone();
if (_recError == 1) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" pending recording error exists");
}
_recError = 1; // triggers callback from module process thread
} else if (err != noErr) {
logCAMsg(kTraceError, kTraceAudioDevice, _id,
"Error in AudioDeviceGetProperty()", (const char*)&err);
return -1;
}
}
if (SpeakerIsInitialized()) {
AudioObjectPropertyAddress propertyAddress = {
kAudioDevicePropertyDeviceIsAlive, kAudioDevicePropertyScopeOutput, 0};
UInt32 deviceIsAlive = 1;
UInt32 size = sizeof(UInt32);
err = AudioObjectGetPropertyData(_outputDeviceID, &propertyAddress, 0, NULL,
&size, &deviceIsAlive);
if (err == kAudioHardwareBadDeviceError || deviceIsAlive == 0) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
"Render device is not alive (probably removed)");
AtomicSet32(&_renderDeviceIsAlive, 0);
_mixerManager.CloseSpeaker();
if (_playError == 1) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" pending playout error exists");
}
_playError = 1; // triggers callback from module process thread
} else if (err != noErr) {
logCAMsg(kTraceError, kTraceAudioDevice, _id,
"Error in AudioDeviceGetProperty()", (const char*)&err);
return -1;
}
}
return 0;
}
int32_t AudioDeviceMac::HandleStreamFormatChange(
const AudioObjectID objectId,
const AudioObjectPropertyAddress propertyAddress) {
OSStatus err = noErr;
WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "Stream format changed");
if (objectId != _inputDeviceID && objectId != _outputDeviceID) {
return 0;
}
// Get the new device format
AudioStreamBasicDescription streamFormat;
UInt32 size = sizeof(streamFormat);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
objectId, &propertyAddress, 0, NULL, &size, &streamFormat));
if (streamFormat.mFormatID != kAudioFormatLinearPCM) {
logCAMsg(kTraceError, kTraceAudioDevice, _id,
"Unacceptable input stream format -> mFormatID",
(const char*)&streamFormat.mFormatID);
return -1;
}
if (streamFormat.mChannelsPerFrame > N_DEVICE_CHANNELS) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"Too many channels on device (mChannelsPerFrame = %d)",
streamFormat.mChannelsPerFrame);
return -1;
}
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "Stream format:");
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"mSampleRate = %f, mChannelsPerFrame = %u",
streamFormat.mSampleRate, streamFormat.mChannelsPerFrame);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"mBytesPerPacket = %u, mFramesPerPacket = %u",
streamFormat.mBytesPerPacket, streamFormat.mFramesPerPacket);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"mBytesPerFrame = %u, mBitsPerChannel = %u",
streamFormat.mBytesPerFrame, streamFormat.mBitsPerChannel);
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "mFormatFlags = %u",
streamFormat.mFormatFlags);
logCAMsg(kTraceInfo, kTraceAudioDevice, _id, "mFormatID",
(const char*)&streamFormat.mFormatID);
if (propertyAddress.mScope == kAudioDevicePropertyScopeInput) {
const int io_block_size_samples = streamFormat.mChannelsPerFrame *
streamFormat.mSampleRate / 100 *
N_BLOCKS_IO;
if (io_block_size_samples > _captureBufSizeSamples) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
"Input IO block size (%d) is larger than ring buffer (%u)",
io_block_size_samples, _captureBufSizeSamples);
return -1;
}
memcpy(&_inStreamFormat, &streamFormat, sizeof(streamFormat));
if (_inStreamFormat.mChannelsPerFrame >= 2 && (_recChannels == 2)) {
_inDesiredFormat.mChannelsPerFrame = 2;
} else {
// Disable stereo recording when we only have one channel on the device.
_inDesiredFormat.mChannelsPerFrame = 1;
_recChannels = 1;
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"Stereo recording unavailable on this device");
}
if (_ptrAudioBuffer) {
// Update audio buffer with the selected parameters
_ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC);
_ptrAudioBuffer->SetRecordingChannels((uint8_t)_recChannels);
}
// Recreate the converter with the new format
// TODO(xians): make this thread safe
WEBRTC_CA_RETURN_ON_ERR(AudioConverterDispose(_captureConverter));
WEBRTC_CA_RETURN_ON_ERR(AudioConverterNew(&streamFormat, &_inDesiredFormat,
&_captureConverter));
} else {
memcpy(&_outStreamFormat, &streamFormat, sizeof(streamFormat));
// Our preferred format to work with
if (_outStreamFormat.mChannelsPerFrame < 2) {
_playChannels = 1;
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"Stereo playout unavailable on this device");
}
WEBRTC_CA_RETURN_ON_ERR(SetDesiredPlayoutFormat());
}
return 0;
}
int32_t AudioDeviceMac::HandleDataSourceChange(
const AudioObjectID objectId,
const AudioObjectPropertyAddress propertyAddress) {
OSStatus err = noErr;
if (_macBookPro &&
propertyAddress.mScope == kAudioDevicePropertyScopeOutput) {
WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "Data source changed");
_macBookProPanRight = false;
UInt32 dataSource = 0;
UInt32 size = sizeof(UInt32);
WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(
objectId, &propertyAddress, 0, NULL, &size, &dataSource));
if (dataSource == 'ispk') {
_macBookProPanRight = true;
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"MacBook Pro using internal speakers; stereo panning right");
} else {
WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id,
"MacBook Pro not using internal speakers");
}
}
return 0;
}
int32_t AudioDeviceMac::HandleProcessorOverload(
const AudioObjectPropertyAddress propertyAddress) {
// TODO(xians): we probably want to notify the user in some way of the
// overload. However, the Windows interpretations of these errors seem to
// be more severe than what ProcessorOverload is thrown for.
//
// We don't log the notification, as it's sent from the HAL's IO thread. We
// don't want to slow it down even further.
if (propertyAddress.mScope == kAudioDevicePropertyScopeInput) {
// WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, "Capture processor
// overload");
//_callback->ProblemIsReported(
// SndCardStreamObserver::ERecordingProblem);
} else {
// WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
// "Render processor overload");
//_callback->ProblemIsReported(
// SndCardStreamObserver::EPlaybackProblem);
}
return 0;
}
// ============================================================================
// Thread Methods
// ============================================================================
OSStatus AudioDeviceMac::deviceIOProc(AudioDeviceID,
const AudioTimeStamp*,
const AudioBufferList* inputData,
const AudioTimeStamp* inputTime,
AudioBufferList* outputData,
const AudioTimeStamp* outputTime,
void* clientData) {
AudioDeviceMac* ptrThis = (AudioDeviceMac*)clientData;
RTC_DCHECK(ptrThis != NULL);
ptrThis->implDeviceIOProc(inputData, inputTime, outputData, outputTime);
// AudioDeviceIOProc functions are supposed to return 0
return 0;
}
OSStatus AudioDeviceMac::outConverterProc(AudioConverterRef,
UInt32* numberDataPackets,
AudioBufferList* data,
AudioStreamPacketDescription**,
void* userData) {
AudioDeviceMac* ptrThis = (AudioDeviceMac*)userData;
RTC_DCHECK(ptrThis != NULL);
return ptrThis->implOutConverterProc(numberDataPackets, data);
}
OSStatus AudioDeviceMac::inDeviceIOProc(AudioDeviceID,
const AudioTimeStamp*,
const AudioBufferList* inputData,
const AudioTimeStamp* inputTime,
AudioBufferList*,
const AudioTimeStamp*,
void* clientData) {
AudioDeviceMac* ptrThis = (AudioDeviceMac*)clientData;
RTC_DCHECK(ptrThis != NULL);
ptrThis->implInDeviceIOProc(inputData, inputTime);
// AudioDeviceIOProc functions are supposed to return 0
return 0;
}
OSStatus AudioDeviceMac::inConverterProc(
AudioConverterRef,
UInt32* numberDataPackets,
AudioBufferList* data,
AudioStreamPacketDescription** /*dataPacketDescription*/,
void* userData) {
AudioDeviceMac* ptrThis = static_cast<AudioDeviceMac*>(userData);
RTC_DCHECK(ptrThis != NULL);
return ptrThis->implInConverterProc(numberDataPackets, data);
}
OSStatus AudioDeviceMac::implDeviceIOProc(const AudioBufferList* inputData,
const AudioTimeStamp* inputTime,
AudioBufferList* outputData,
const AudioTimeStamp* outputTime) {
OSStatus err = noErr;
UInt64 outputTimeNs = AudioConvertHostTimeToNanos(outputTime->mHostTime);
UInt64 nowNs = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
if (!_twoDevices && _recording) {
implInDeviceIOProc(inputData, inputTime);
}
// Check if we should close down audio device
// Double-checked locking optimization to remove locking overhead
if (_doStop) {
_critSect.Enter();
if (_doStop) {
if (_twoDevices || (!_recording && !_playing)) {
// In the case of a shared device, the single driving ioProc
// is stopped here
WEBRTC_CA_LOG_ERR(AudioDeviceStop(_outputDeviceID, _deviceIOProcID));
WEBRTC_CA_LOG_WARN(
AudioDeviceDestroyIOProcID(_outputDeviceID, _deviceIOProcID));
if (err == noErr) {
WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
" Playout or shared device stopped");
}
}
_doStop = false;
_stopEvent.Set();
_critSect.Leave();
return 0;
}
_critSect.Leave();
}
if (!_playing) {
// This can be the case when a shared device is capturing but not
// rendering. We allow the checks above before returning to avoid a
// timeout when capturing is stopped.
return 0;
}
RTC_DCHECK(_outStreamFormat.mBytesPerFrame != 0);
UInt32 size =
outputData->mBuffers->mDataByteSize / _outStreamFormat.mBytesPerFrame;
// TODO(xians): signal an error somehow?
err = AudioConverterFillComplexBuffer(_renderConverter, outConverterProc,
this, &size, outputData, NULL);
if (err != noErr) {
if (err == 1) {
// This is our own error.
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" Error in AudioConverterFillComplexBuffer()");
return 1;
} else {
logCAMsg(kTraceError, kTraceAudioDevice, _id,
"Error in AudioConverterFillComplexBuffer()", (const char*)&err);
return 1;
}
}
PaRingBufferSize bufSizeSamples =
PaUtil_GetRingBufferReadAvailable(_paRenderBuffer);
int32_t renderDelayUs =
static_cast<int32_t>(1e-3 * (outputTimeNs - nowNs) + 0.5);
renderDelayUs += static_cast<int32_t>(
(1.0e6 * bufSizeSamples) / _outDesiredFormat.mChannelsPerFrame /
_outDesiredFormat.mSampleRate +
0.5);
AtomicSet32(&_renderDelayUs, renderDelayUs);
return 0;
}
OSStatus AudioDeviceMac::implOutConverterProc(UInt32* numberDataPackets,
AudioBufferList* data) {
RTC_DCHECK(data->mNumberBuffers == 1);
PaRingBufferSize numSamples =
*numberDataPackets * _outDesiredFormat.mChannelsPerFrame;
data->mBuffers->mNumberChannels = _outDesiredFormat.mChannelsPerFrame;
// Always give the converter as much as it wants, zero padding as required.
data->mBuffers->mDataByteSize =
*numberDataPackets * _outDesiredFormat.mBytesPerPacket;
data->mBuffers->mData = _renderConvertData;
memset(_renderConvertData, 0, sizeof(_renderConvertData));
PaUtil_ReadRingBuffer(_paRenderBuffer, _renderConvertData, numSamples);
kern_return_t kernErr = semaphore_signal_all(_renderSemaphore);
if (kernErr != KERN_SUCCESS) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" semaphore_signal_all() error: %d", kernErr);
return 1;
}
return 0;
}
OSStatus AudioDeviceMac::implInDeviceIOProc(const AudioBufferList* inputData,
const AudioTimeStamp* inputTime) {
OSStatus err = noErr;
UInt64 inputTimeNs = AudioConvertHostTimeToNanos(inputTime->mHostTime);
UInt64 nowNs = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
// Check if we should close down audio device
// Double-checked locking optimization to remove locking overhead
if (_doStopRec) {
_critSect.Enter();
if (_doStopRec) {
// This will be signalled only when a shared device is not in use.
WEBRTC_CA_LOG_ERR(AudioDeviceStop(_inputDeviceID, _inDeviceIOProcID));
WEBRTC_CA_LOG_WARN(
AudioDeviceDestroyIOProcID(_inputDeviceID, _inDeviceIOProcID));
if (err == noErr) {
WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id,
" Recording device stopped");
}
_doStopRec = false;
_stopEventRec.Set();
_critSect.Leave();
return 0;
}
_critSect.Leave();
}
if (!_recording) {
// Allow above checks to avoid a timeout on stopping capture.
return 0;
}
PaRingBufferSize bufSizeSamples =
PaUtil_GetRingBufferReadAvailable(_paCaptureBuffer);
int32_t captureDelayUs =
static_cast<int32_t>(1e-3 * (nowNs - inputTimeNs) + 0.5);
captureDelayUs += static_cast<int32_t>((1.0e6 * bufSizeSamples) /
_inStreamFormat.mChannelsPerFrame /
_inStreamFormat.mSampleRate +
0.5);
AtomicSet32(&_captureDelayUs, captureDelayUs);
RTC_DCHECK(inputData->mNumberBuffers == 1);
PaRingBufferSize numSamples = inputData->mBuffers->mDataByteSize *
_inStreamFormat.mChannelsPerFrame /
_inStreamFormat.mBytesPerPacket;
PaUtil_WriteRingBuffer(_paCaptureBuffer, inputData->mBuffers->mData,
numSamples);
kern_return_t kernErr = semaphore_signal_all(_captureSemaphore);
if (kernErr != KERN_SUCCESS) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" semaphore_signal_all() error: %d", kernErr);
}
return err;
}
OSStatus AudioDeviceMac::implInConverterProc(UInt32* numberDataPackets,
AudioBufferList* data) {
RTC_DCHECK(data->mNumberBuffers == 1);
PaRingBufferSize numSamples =
*numberDataPackets * _inStreamFormat.mChannelsPerFrame;
while (PaUtil_GetRingBufferReadAvailable(_paCaptureBuffer) < numSamples) {
mach_timespec_t timeout;
timeout.tv_sec = 0;
timeout.tv_nsec = TIMER_PERIOD_MS;
kern_return_t kernErr = semaphore_timedwait(_captureSemaphore, timeout);
if (kernErr == KERN_OPERATION_TIMED_OUT) {
int32_t signal = AtomicGet32(&_captureDeviceIsAlive);
if (signal == 0) {
// The capture device is no longer alive; stop the worker thread.
*numberDataPackets = 0;
return 1;
}
} else if (kernErr != KERN_SUCCESS) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" semaphore_wait() error: %d", kernErr);
}
}
// Pass the read pointer directly to the converter to avoid a memcpy.
void* dummyPtr;
PaRingBufferSize dummySize;
PaUtil_GetRingBufferReadRegions(_paCaptureBuffer, numSamples,
&data->mBuffers->mData, &numSamples,
&dummyPtr, &dummySize);
PaUtil_AdvanceRingBufferReadIndex(_paCaptureBuffer, numSamples);
data->mBuffers->mNumberChannels = _inStreamFormat.mChannelsPerFrame;
*numberDataPackets = numSamples / _inStreamFormat.mChannelsPerFrame;
data->mBuffers->mDataByteSize =
*numberDataPackets * _inStreamFormat.mBytesPerPacket;
return 0;
}
bool AudioDeviceMac::RunRender(void* ptrThis) {
return static_cast<AudioDeviceMac*>(ptrThis)->RenderWorkerThread();
}
bool AudioDeviceMac::RenderWorkerThread() {
PaRingBufferSize numSamples =
ENGINE_PLAY_BUF_SIZE_IN_SAMPLES * _outDesiredFormat.mChannelsPerFrame;
while (PaUtil_GetRingBufferWriteAvailable(_paRenderBuffer) -
_renderDelayOffsetSamples <
numSamples) {
mach_timespec_t timeout;
timeout.tv_sec = 0;
timeout.tv_nsec = TIMER_PERIOD_MS;
kern_return_t kernErr = semaphore_timedwait(_renderSemaphore, timeout);
if (kernErr == KERN_OPERATION_TIMED_OUT) {
int32_t signal = AtomicGet32(&_renderDeviceIsAlive);
if (signal == 0) {
// The render device is no longer alive; stop the worker thread.
return false;
}
} else if (kernErr != KERN_SUCCESS) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" semaphore_timedwait() error: %d", kernErr);
}
}
int8_t playBuffer[4 * ENGINE_PLAY_BUF_SIZE_IN_SAMPLES];
if (!_ptrAudioBuffer) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" capture AudioBuffer is invalid");
return false;
}
// Ask for new PCM data to be played out using the AudioDeviceBuffer.
uint32_t nSamples =
_ptrAudioBuffer->RequestPlayoutData(ENGINE_PLAY_BUF_SIZE_IN_SAMPLES);
nSamples = _ptrAudioBuffer->GetPlayoutData(playBuffer);
if (nSamples != ENGINE_PLAY_BUF_SIZE_IN_SAMPLES) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" invalid number of output samples(%d)", nSamples);
}
uint32_t nOutSamples = nSamples * _outDesiredFormat.mChannelsPerFrame;
SInt16* pPlayBuffer = (SInt16*)&playBuffer;
if (_macBookProPanRight && (_playChannels == 2)) {
// Mix entirely into the right channel and zero the left channel.
SInt32 sampleInt32 = 0;
for (uint32_t sampleIdx = 0; sampleIdx < nOutSamples; sampleIdx += 2) {
sampleInt32 = pPlayBuffer[sampleIdx];
sampleInt32 += pPlayBuffer[sampleIdx + 1];
sampleInt32 /= 2;
if (sampleInt32 > 32767) {
sampleInt32 = 32767;
} else if (sampleInt32 < -32768) {
sampleInt32 = -32768;
}
pPlayBuffer[sampleIdx] = 0;
pPlayBuffer[sampleIdx + 1] = static_cast<SInt16>(sampleInt32);
}
}
PaUtil_WriteRingBuffer(_paRenderBuffer, pPlayBuffer, nOutSamples);
return true;
}
bool AudioDeviceMac::RunCapture(void* ptrThis) {
return static_cast<AudioDeviceMac*>(ptrThis)->CaptureWorkerThread();
}
bool AudioDeviceMac::CaptureWorkerThread() {
OSStatus err = noErr;
UInt32 noRecSamples =
ENGINE_REC_BUF_SIZE_IN_SAMPLES * _inDesiredFormat.mChannelsPerFrame;
SInt16 recordBuffer[noRecSamples];
UInt32 size = ENGINE_REC_BUF_SIZE_IN_SAMPLES;
AudioBufferList engineBuffer;
engineBuffer.mNumberBuffers = 1; // Interleaved channels.
engineBuffer.mBuffers->mNumberChannels = _inDesiredFormat.mChannelsPerFrame;
engineBuffer.mBuffers->mDataByteSize =
_inDesiredFormat.mBytesPerPacket * noRecSamples;
engineBuffer.mBuffers->mData = recordBuffer;
err = AudioConverterFillComplexBuffer(_captureConverter, inConverterProc,
this, &size, &engineBuffer, NULL);
if (err != noErr) {
if (err == 1) {
// This is our own error.
return false;
} else {
logCAMsg(kTraceError, kTraceAudioDevice, _id,
"Error in AudioConverterFillComplexBuffer()", (const char*)&err);
return false;
}
}
// TODO(xians): what if the returned size is incorrect?
if (size == ENGINE_REC_BUF_SIZE_IN_SAMPLES) {
uint32_t currentMicLevel(0);
uint32_t newMicLevel(0);
int32_t msecOnPlaySide;
int32_t msecOnRecordSide;
int32_t captureDelayUs = AtomicGet32(&_captureDelayUs);
int32_t renderDelayUs = AtomicGet32(&_renderDelayUs);
msecOnPlaySide =
static_cast<int32_t>(1e-3 * (renderDelayUs + _renderLatencyUs) + 0.5);
msecOnRecordSide =
static_cast<int32_t>(1e-3 * (captureDelayUs + _captureLatencyUs) + 0.5);
if (!_ptrAudioBuffer) {
WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
" capture AudioBuffer is invalid");
return false;
}
// store the recorded buffer (no action will be taken if the
// #recorded samples is not a full buffer)
_ptrAudioBuffer->SetRecordedBuffer((int8_t*)&recordBuffer, (uint32_t)size);
if (AGC()) {
// Use mod to ensure we check the volume on the first pass.
if (get_mic_volume_counter_ms_ % kGetMicVolumeIntervalMs == 0) {
get_mic_volume_counter_ms_ = 0;
// store current mic level in the audio buffer if AGC is enabled
if (MicrophoneVolume(currentMicLevel) == 0) {
// this call does not affect the actual microphone volume
_ptrAudioBuffer->SetCurrentMicLevel(currentMicLevel);
}
}
get_mic_volume_counter_ms_ += kBufferSizeMs;
}
_ptrAudioBuffer->SetVQEData(msecOnPlaySide, msecOnRecordSide, 0);
_ptrAudioBuffer->SetTypingStatus(KeyPressed());
// deliver recorded samples at specified sample rate, mic level etc.
// to the observer using callback
_ptrAudioBuffer->DeliverRecordedData();
if (AGC()) {
newMicLevel = _ptrAudioBuffer->NewMicLevel();
if (newMicLevel != 0) {
// The VQE will only deliver non-zero microphone levels when
// a change is needed.
// Set this new mic level (received from the observer as return
// value in the callback).
WEBRTC_TRACE(kTraceStream, kTraceAudioDevice, _id,
" AGC change of volume: old=%u => new=%u",
currentMicLevel, newMicLevel);
if (SetMicrophoneVolume(newMicLevel) == -1) {
WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id,
" the required modification of the microphone "
"volume failed");
}
}
}
}
return true;
}
bool AudioDeviceMac::KeyPressed() {
bool key_down = false;
// Loop through all Mac virtual key constant values.
for (unsigned int key_index = 0; key_index < arraysize(prev_key_state_);
++key_index) {
bool keyState =
CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, key_index);
// A false -> true change in keymap means a key is pressed.
key_down |= (keyState && !prev_key_state_[key_index]);
// Save current state.
prev_key_state_[key_index] = keyState;
}
return key_down;
}
} // namespace webrtc