aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/audio_device/mac/audio_device_mac.cc
diff options
context:
space:
mode:
authorChih-hung Hsieh <chh@google.com>2015-12-01 17:07:48 +0000
committerandroid-build-merger <android-build-merger@google.com>2015-12-01 17:07:48 +0000
commita4acd9d6bc9b3b033d7d274316e75ee067df8d20 (patch)
tree672a185b294789cf991f385c3e395dd63bea9063 /webrtc/modules/audio_device/mac/audio_device_mac.cc
parent3681b90ba4fe7a27232dd3e27897d5d7ed9d651c (diff)
parentfe8b4a657979b49e1701bd92f6d5814a99e0b2be (diff)
downloadwebrtc-a4acd9d6bc9b3b033d7d274316e75ee067df8d20.tar.gz
Merge changes I7bbf776e,I1b827825
am: fe8b4a6579 * commit 'fe8b4a657979b49e1701bd92f6d5814a99e0b2be': (7237 commits) WIP: Changes after merge commit 'cb3f9bd' Make the nonlinear beamformer steerable Utilize bitrate above codec max to protect video. Enable VP9 internal resize by default. Filter overlapping RTP header extensions. Make VCMEncodedFrameCallback const. MediaCodecVideoEncoder: Add number of quality resolution downscales to Encoded callback. Remove redudant encoder rate calls. Create isolate files for nonparallel tests. Register header extensions in RtpRtcpObserver to avoid log spam. Make an enum class out of NetEqDecoder, and hide the neteq_decoders_ table ACM: Move NACK functionality inside NetEq Fix chromium-style warnings in webrtc/sound/. Create a 'webrtc_nonparallel_tests' target. Update scalability structure data according to updates in the RTP payload profile. audio_coding: rename interface -> include Rewrote perform_action_on_all_files to be parallell. Update reference indices according to updates in the RTP payload profile. Disable P2PTransport...TestFailoverControlledSide on Memcheck pass clangcl compile options to ignore warnings in gflags.cc ...
Diffstat (limited to 'webrtc/modules/audio_device/mac/audio_device_mac.cc')
-rw-r--r--webrtc/modules/audio_device/mac/audio_device_mac.cc3241
1 files changed, 3241 insertions, 0 deletions
diff --git a/webrtc/modules/audio_device/mac/audio_device_mac.cc b/webrtc/modules/audio_device/mac/audio_device_mac.cc
new file mode 100644
index 0000000000..db98675bf6
--- /dev/null
+++ b/webrtc/modules/audio_device/mac/audio_device_mac.cc
@@ -0,0 +1,3241 @@
+/*
+ * 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/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/thread_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_()
+{
+ 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;
+}
+
+int32_t AudioDeviceMac::Init()
+{
+
+ CriticalSectionScoped lock(&_critSect);
+
+ if (_initialized)
+ {
+ return 0;
+ }
+
+ 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 -1;
+ }
+ }
+
+ 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 -1;
+ }
+ }
+
+ 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 -1;
+ }
+
+ 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 -1;
+ }
+
+ // 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);
+ WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData(kAudioObjectSystemObject,
+ &propertyAddress, 0, NULL, size, &runLoop));
+
+ // 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;
+
+ _initialized = true;
+
+ return 0;
+}
+
+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_ =
+ ThreadWrapper::CreateThread(RunCapture, this, "CaptureWorkerThread");
+ RTC_DCHECK(capture_worker_thread_.get());
+ capture_worker_thread_->Start();
+ capture_worker_thread_->SetPriority(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_ =
+ ThreadWrapper::CreateThread(RunRender, this, "RenderWorkerThread");
+ render_worker_thread_->Start();
+ render_worker_thread_->SetPriority(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())
+ {
+ // 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);
+ }
+ }
+
+ _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