/* * Copyright (c) 2011 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 "audio_device_utility.h" #include "audio_device_mac.h" #include "audio_device_config.h" #include "event_wrapper.h" #include "trace.h" #include "thread_wrapper.h" #include #include // sysctlbyname() #include // mach_task_self() #include // OSAtomicCompareAndSwap() #include "portaudio/pa_ringbuffer.h" 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) { WebRtc_Word32 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 WebRtc_Word32 id, const char *msg, const char *err) { assert(msg != NULL); assert(err != NULL); #ifdef WEBRTC_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 WebRtc_Word32 id) : _ptrAudioBuffer(NULL), _critSect(*CriticalSectionWrapper::CreateCriticalSection()), _critSectCb(*CriticalSectionWrapper::CreateCriticalSection()), _stopEventRec(*EventWrapper::Create()), _stopEvent(*EventWrapper::Create()), _captureWorkerThread(NULL), _renderWorkerThread(NULL), _captureWorkerThreadId(0), _renderWorkerThreadId(0), _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), _startRec(false), _stopRec(false), _stopPlay(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) { WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, id, "%s created", __FUNCTION__); assert(&_stopEvent != NULL); assert(&_stopEventRec != NULL); memset(_renderConvertData, 0, sizeof(_renderConvertData)); } AudioDeviceMac::~AudioDeviceMac() { WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, "%s destroyed", __FUNCTION__); if (!_isShutDown) { Terminate(); } if (_captureWorkerThread) { delete _captureWorkerThread; _captureWorkerThread = NULL; } if (_renderWorkerThread) { delete _renderWorkerThread; _renderWorkerThread = NULL; } 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; delete &_critSectCb; } // ============================================================================ // API // ============================================================================ void AudioDeviceMac::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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); } WebRtc_Word32 AudioDeviceMac::ActiveAudioLayer( AudioDeviceModule::AudioLayer& audioLayer) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); audioLayer = AudioDeviceModule::kPlatformDefaultAudio; return 0; } WebRtc_Word32 AudioDeviceMac::Init() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; ring_buffer_size_t 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; ring_buffer_size_t bufSize = -1; bufSize = PaUtil_InitializeRingBuffer(_paCaptureBuffer, sizeof(Float32), _captureBufSizeSamples, _captureBufData); if (bufSize == -1) { WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, " PaUtil_InitializeRingBuffer() error"); return -1; } } if (_renderWorkerThread == NULL) { _renderWorkerThread = ThreadWrapper::CreateThread(RunRender, this, kRealtimePriority, "RenderWorkerThread"); if (_renderWorkerThread == NULL) { WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, " Render CreateThread() error"); return -1; } } if (_captureWorkerThread == NULL) { _captureWorkerThread = ThreadWrapper::CreateThread(RunCapture, this, kRealtimePriority, "CaptureWorkerThread"); if (_captureWorkerThread == NULL) { WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, " Capture CreateThread() 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; } WebRtc_Word32 AudioDeviceMac::Terminate() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } _critSect.Leave(); _isShutDown = true; _initialized = false; _outputDeviceIsSpecified = false; _inputDeviceIsSpecified = false; return retVal; } bool AudioDeviceMac::Initialized() const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); return (_initialized); } WebRtc_Word32 AudioDeviceMac::SpeakerIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::InitSpeaker() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::MicrophoneIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::InitMicrophone() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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 { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); return (_mixerManager.SpeakerIsInitialized()); } bool AudioDeviceMac::MicrophoneIsInitialized() const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); return (_mixerManager.MicrophoneIsInitialized()); } WebRtc_Word32 AudioDeviceMac::SpeakerVolumeIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::SetSpeakerVolume(WebRtc_UWord32 volume) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetSpeakerVolume(volume=%u)", volume); return (_mixerManager.SetSpeakerVolume(volume)); } WebRtc_Word32 AudioDeviceMac::SpeakerVolume(WebRtc_UWord32& volume) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); WebRtc_UWord32 level(0); if (_mixerManager.SpeakerVolume(level) == -1) { return -1; } volume = level; return 0; } WebRtc_Word32 AudioDeviceMac::SetWaveOutVolume(WebRtc_UWord16 volumeLeft, WebRtc_UWord16 volumeRight) { WEBRTC_TRACE( kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetWaveOutVolume(volumeLeft=%u, volumeRight=%u)", volumeLeft, volumeRight); WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " API call not supported on this platform"); return -1; } WebRtc_Word32 AudioDeviceMac::WaveOutVolume(WebRtc_UWord16& /*volumeLeft*/, WebRtc_UWord16& /*volumeRight*/) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " API call not supported on this platform"); return -1; } WebRtc_Word32 AudioDeviceMac::MaxSpeakerVolume(WebRtc_UWord32& maxVolume) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); WebRtc_UWord32 maxVol(0); if (_mixerManager.MaxSpeakerVolume(maxVol) == -1) { return -1; } maxVolume = maxVol; return 0; } WebRtc_Word32 AudioDeviceMac::MinSpeakerVolume(WebRtc_UWord32& minVolume) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); WebRtc_UWord32 minVol(0); if (_mixerManager.MinSpeakerVolume(minVol) == -1) { return -1; } minVolume = minVol; return 0; } WebRtc_Word32 AudioDeviceMac::SpeakerVolumeStepSize(WebRtc_UWord16& stepSize) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); WebRtc_UWord16 delta(0); if (_mixerManager.SpeakerVolumeStepSize(delta) == -1) { return -1; } stepSize = delta; return 0; } WebRtc_Word32 AudioDeviceMac::SpeakerMuteIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::SetSpeakerMute(bool enable) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetSpeakerMute(enable=%u)", enable); return (_mixerManager.SetSpeakerMute(enable)); } WebRtc_Word32 AudioDeviceMac::SpeakerMute(bool& enabled) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); bool muted(0); if (_mixerManager.SpeakerMute(muted) == -1) { return -1; } enabled = muted; return 0; } WebRtc_Word32 AudioDeviceMac::MicrophoneMuteIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::SetMicrophoneMute(bool enable) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceWindowsWave::SetMicrophoneMute(enable=%u)", enable); return (_mixerManager.SetMicrophoneMute(enable)); } WebRtc_Word32 AudioDeviceMac::MicrophoneMute(bool& enabled) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); bool muted(0); if (_mixerManager.MicrophoneMute(muted) == -1) { return -1; } enabled = muted; return 0; } WebRtc_Word32 AudioDeviceMac::MicrophoneBoostIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::SetMicrophoneBoost(bool enable) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetMicrophoneBoost(enable=%u)", enable); return (_mixerManager.SetMicrophoneBoost(enable)); } WebRtc_Word32 AudioDeviceMac::MicrophoneBoost(bool& enabled) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); bool onOff(0); if (_mixerManager.MicrophoneBoost(onOff) == -1) { return -1; } enabled = onOff; return 0; } WebRtc_Word32 AudioDeviceMac::StereoRecordingIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::SetStereoRecording(bool enable) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetStereoRecording(enable=%u)", enable); if (enable) _recChannels = 2; else _recChannels = 1; return 0; } WebRtc_Word32 AudioDeviceMac::StereoRecording(bool& enabled) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); if (_recChannels == 2) enabled = true; else enabled = false; return 0; } WebRtc_Word32 AudioDeviceMac::StereoPlayoutIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::SetStereoPlayout(bool enable) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetStereoPlayout(enable=%u)", enable); if (enable) _playChannels = 2; else _playChannels = 1; return 0; } WebRtc_Word32 AudioDeviceMac::StereoPlayout(bool& enabled) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); if (_playChannels == 2) enabled = true; else enabled = false; return 0; } WebRtc_Word32 AudioDeviceMac::SetAGC(bool enable) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetAGC(enable=%d)", enable); _AGC = enable; return 0; } bool AudioDeviceMac::AGC() const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); return _AGC; } WebRtc_Word32 AudioDeviceMac::MicrophoneVolumeIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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; } WebRtc_Word32 AudioDeviceMac::SetMicrophoneVolume(WebRtc_UWord32 volume) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetMicrophoneVolume(volume=%u)", volume); return (_mixerManager.SetMicrophoneVolume(volume)); } WebRtc_Word32 AudioDeviceMac::MicrophoneVolume(WebRtc_UWord32& volume) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); WebRtc_UWord32 level(0); if (_mixerManager.MicrophoneVolume(level) == -1) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " failed to retrive current microphone level"); return -1; } volume = level; return 0; } WebRtc_Word32 AudioDeviceMac::MaxMicrophoneVolume(WebRtc_UWord32& maxVolume) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); WebRtc_UWord32 maxVol(0); if (_mixerManager.MaxMicrophoneVolume(maxVol) == -1) { return -1; } maxVolume = maxVol; return 0; } WebRtc_Word32 AudioDeviceMac::MinMicrophoneVolume(WebRtc_UWord32& minVolume) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); WebRtc_UWord32 minVol(0); if (_mixerManager.MinMicrophoneVolume(minVol) == -1) { return -1; } minVolume = minVol; return 0; } WebRtc_Word32 AudioDeviceMac::MicrophoneVolumeStepSize(WebRtc_UWord16& stepSize) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); WebRtc_UWord16 delta(0); if (_mixerManager.MicrophoneVolumeStepSize(delta) == -1) { return -1; } stepSize = delta; return 0; } WebRtc_Word16 AudioDeviceMac::PlayoutDevices() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); AudioDeviceID playDevices[MaxNumberDevices]; return GetNumberDevices(kAudioDevicePropertyScopeOutput, playDevices, MaxNumberDevices); } WebRtc_Word32 AudioDeviceMac::SetPlayoutDevice(WebRtc_UWord16 index) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetPlayoutDevice(index=%u)", index); if (_playIsInitialized) { return -1; } AudioDeviceID playDevices[MaxNumberDevices]; WebRtc_UWord32 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; } WebRtc_Word32 AudioDeviceMac::SetPlayoutDevice( AudioDeviceModule::WindowsDeviceType /*device*/) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WindowsDeviceType not supported"); return -1; } WebRtc_Word32 AudioDeviceMac::PlayoutDeviceName( WebRtc_UWord16 index, WebRtc_Word8 name[kAdmMaxDeviceNameSize], WebRtc_Word8 guid[kAdmMaxGuidSize]) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::PlayoutDeviceName(index=%u)", index); const WebRtc_UWord16 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); } WebRtc_Word32 AudioDeviceMac::RecordingDeviceName( WebRtc_UWord16 index, WebRtc_Word8 name[kAdmMaxDeviceNameSize], WebRtc_Word8 guid[kAdmMaxGuidSize]) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::RecordingDeviceName(index=%u)", index); const WebRtc_UWord16 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); } WebRtc_Word16 AudioDeviceMac::RecordingDevices() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); AudioDeviceID recDevices[MaxNumberDevices]; return GetNumberDevices(kAudioDevicePropertyScopeInput, recDevices, MaxNumberDevices); } WebRtc_Word32 AudioDeviceMac::SetRecordingDevice(WebRtc_UWord16 index) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetRecordingDevice(index=%u)", index); if (_recIsInitialized) { return -1; } AudioDeviceID recDevices[MaxNumberDevices]; WebRtc_UWord32 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; } WebRtc_Word32 AudioDeviceMac::SetRecordingDevice(AudioDeviceModule::WindowsDeviceType /*device*/) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, "WindowsDeviceType not supported"); return -1; } WebRtc_Word32 AudioDeviceMac::PlayoutIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); available = false; // Try to initialize the playout side WebRtc_Word32 res = InitPlayout(); // Cancel effect of initialization StopPlayout(); if (res != -1) { available = true; } return 0; } WebRtc_Word32 AudioDeviceMac::RecordingIsAvailable(bool& available) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); available = false; // Try to initialize the recording side WebRtc_Word32 res = InitRecording(); // Cancel effect of initialization StopRecording(); if (res != -1) { available = true; } return 0; } WebRtc_Word32 AudioDeviceMac::InitPlayout() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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 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, mChannelsPerFrame = %u", _outStreamFormat.mFormatFlags, _outStreamFormat.mChannelsPerFrame); logCAMsg(kTraceInfo, kTraceAudioDevice, _id, "mFormatID", (const char *) &_outStreamFormat.mFormatID); // Our preferred format to work with _outDesiredFormat.mSampleRate = N_PLAY_SAMPLES_PER_SEC; if (_outStreamFormat.mChannelsPerFrame >= 2 && (_playChannels == 2)) { _outDesiredFormat.mChannelsPerFrame = 2; } else { // Disable stereo playout when we only have one channel on the device. _outDesiredFormat.mChannelsPerFrame = 1; _playChannels = 1; WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "Stereo playout unavailable on this device"); } if (_ptrAudioBuffer) { // Update audio buffer with the selected parameters _ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC); _ptrAudioBuffer->SetPlayoutChannels((WebRtc_UWord8) _playChannels); } _renderDelayOffsetSamples = _renderBufSizeSamples - N_BUFFERS_OUT * ENGINE_PLAY_BUF_SIZE_IN_SAMPLES * _outDesiredFormat.mChannelsPerFrame; _outDesiredFormat.mBytesPerPacket = _outDesiredFormat.mChannelsPerFrame * sizeof(SInt16); _outDesiredFormat.mFramesPerPacket = 1; // In uncompressed audio, // a packet is one frame. _outDesiredFormat.mBytesPerFrame = _outDesiredFormat.mChannelsPerFrame * sizeof(SInt16); _outDesiredFormat.mBitsPerChannel = sizeof(SInt16) * 8; _outDesiredFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; #ifdef WEBRTC_BIG_ENDIAN _outDesiredFormat.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; #endif _outDesiredFormat.mFormatID = kAudioFormatLinearPCM; WEBRTC_CA_RETURN_ON_ERR(AudioConverterNew(&_outDesiredFormat, &_outStreamFormat, &_renderConverter)); // First try to set buffer size to desired value (_playBufDelayFixed) UInt32 bufByteCount = (UInt32)((_outStreamFormat.mSampleRate / 1000.0) * _playBufDelayFixed * _outStreamFormat.mChannelsPerFrame * sizeof(Float32)); if (_outStreamFormat.mFramesPerPacket != 0) { if (bufByteCount % _outStreamFormat.mFramesPerPacket != 0) { bufByteCount = ((UInt32)(bufByteCount / _outStreamFormat.mFramesPerPacket) + 1) * _outStreamFormat.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(_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 = (WebRtc_UWord32) ((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 += (WebRtc_UWord32) ((1.0e6 * latency) / _outStreamFormat.mSampleRate); // 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) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceCreateIOProcID != NULL) { WEBRTC_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(_outputDeviceID, deviceIOProc, this, &_deviceIOProcID)); } else { #endif WEBRTC_CA_RETURN_ON_ERR(AudioDeviceAddIOProc(_outputDeviceID, deviceIOProc, this)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif } // Mark playout side as initialized _playIsInitialized = true; WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, " initial playout status: _renderDelayOffsetSamples=%d," " _renderDelayUs=%d, _renderLatencyUs=%d", _renderDelayOffsetSamples, _renderDelayUs, _renderLatencyUs); return 0; } WebRtc_Word32 AudioDeviceMac::InitRecording() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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 device (mChannelsPerFrame = %d)", _inStreamFormat.mChannelsPerFrame); 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, mChannelsPerFrame = %u", _inStreamFormat.mFormatFlags, _inStreamFormat.mChannelsPerFrame); 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((WebRtc_UWord8) _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_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) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceCreateIOProcID != NULL) { WEBRTC_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(_inputDeviceID, inDeviceIOProc, this, &_inDeviceIOProcID)); } else { #endif WEBRTC_CA_RETURN_ON_ERR(AudioDeviceAddIOProc(_inputDeviceID, inDeviceIOProc, this)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif } else if (!_playIsInitialized) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceCreateIOProcID != NULL) { WEBRTC_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(_inputDeviceID, deviceIOProc, this, &_deviceIOProcID)); } else { #endif WEBRTC_CA_RETURN_ON_ERR(AudioDeviceAddIOProc(_inputDeviceID, deviceIOProc, this)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif } // Mark recording side as initialized _recIsInitialized = true; return 0; } WebRtc_Word32 AudioDeviceMac::StartRecording() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); if (!_recIsInitialized) { return -1; } if (_recording) { return 0; } if (!_initialized) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Recording worker thread has not been started"); return -1; } OSStatus err = noErr; unsigned int threadID(0); if (_captureWorkerThread != NULL) { _captureWorkerThread->Start(threadID); } _captureWorkerThreadId = threadID; if (_twoDevices) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceCreateIOProcID != NULL) { WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_inputDeviceID, _inDeviceIOProcID)); } else { #endif WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_inputDeviceID, inDeviceIOProc)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif } else if (!_playing) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceCreateIOProcID != NULL) { WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_inputDeviceID, _deviceIOProcID)); } else { #endif WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_inputDeviceID, deviceIOProc)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif } _recording = true; return 0; } WebRtc_Word32 AudioDeviceMac::StopRecording() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); CriticalSectionScoped lock(_critSect); if (!_recIsInitialized) { return 0; } OSStatus err = noErr; // Stop device int32_t captureDeviceIsAlive = AtomicGet32(&_captureDeviceIsAlive); if (_twoDevices) { if (_recording && captureDeviceIsAlive == 1) { _doStopRec = true; // Signal to io proc to stop audio device _critSect.Leave(); // Cannot be under lock, risk of deadlock if (kEventTimeout == _stopEventRec.Wait(2000)) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Timed out stopping the capture IOProc. " "We likely failed to detect a device removal."); } _critSect.Enter(); _doStopRec = false; WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, " Recording stopped"); } #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceDestroyIOProcID != NULL) { WEBRTC_CA_LOG_WARN(AudioDeviceDestroyIOProcID(_inputDeviceID, _inDeviceIOProcID)); } else { #endif WEBRTC_CA_LOG_WARN(AudioDeviceRemoveIOProc(_inputDeviceID, inDeviceIOProc)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif } else if (!_playing) { // Stop the shared device if playing has stopped as well. if (_recording && captureDeviceIsAlive == 1) { _doStop = true; // Signal to io proc to stop audio device _critSect.Leave(); // Cannot be under lock, risk of deadlock if (kEventTimeout == _stopEvent.Wait(2000)) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Timed out stopping the shared IOProc. " "We likely failed to detect a device removal."); } _critSect.Enter(); _doStop = false; WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, " Recording stopped (shared)"); } #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceDestroyIOProcID != NULL) { WEBRTC_CA_LOG_WARN(AudioDeviceDestroyIOProcID(_inputDeviceID, _deviceIOProcID)); } else { #endif WEBRTC_CA_LOG_WARN(AudioDeviceRemoveIOProc(_inputDeviceID, deviceIOProc)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif } // Setting this signal will allow the worker thread to be stopped. AtomicSet32(&_captureDeviceIsAlive, 0); _critSect.Leave(); if (_captureWorkerThread != NULL) { if (!_captureWorkerThread->Stop()) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Timed out waiting for the render worker thread to " "stop."); } } _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 { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); return (_recIsInitialized); } bool AudioDeviceMac::Recording() const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); return (_recording); } bool AudioDeviceMac::PlayoutIsInitialized() const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); return (_playIsInitialized); } WebRtc_Word32 AudioDeviceMac::StartPlayout() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); if (!_playIsInitialized) { return -1; } if (_playing) { return 0; } OSStatus err = noErr; unsigned int threadID(0); if (_renderWorkerThread != NULL) { _renderWorkerThread->Start(threadID); } _renderWorkerThreadId = threadID; if (_twoDevices || !_recording) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceCreateIOProcID != NULL) { WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_outputDeviceID, _deviceIOProcID)); } else { #endif WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_outputDeviceID, deviceIOProc)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif } _playing = true; return 0; } WebRtc_Word32 AudioDeviceMac::StopPlayout() { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); CriticalSectionScoped lock(_critSect); if (!_playIsInitialized) { return 0; } OSStatus err = noErr; int32_t renderDeviceIsAlive = AtomicGet32(&_renderDeviceIsAlive); if (_twoDevices || !_recording) { // Stop the shared device if recording has stopped as well. if (_playing && renderDeviceIsAlive == 1) { _doStop = true; // Signal to io proc to stop audio device _critSect.Leave(); // Cannot be under lock, risk of deadlock if (kEventTimeout == _stopEvent.Wait(2000)) { WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, " Timed out stopping the render IOProc. " "We likely failed to detect a device removal."); } _critSect.Enter(); _doStop = false; WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, "Playout stopped"); } #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceDestroyIOProcID != NULL) { WEBRTC_CA_LOG_WARN(AudioDeviceDestroyIOProcID(_outputDeviceID, _deviceIOProcID)); } else { #endif WEBRTC_CA_LOG_WARN(AudioDeviceRemoveIOProc(_outputDeviceID, deviceIOProc)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif } // Setting this signal will allow the worker thread to be stopped. AtomicSet32(&_renderDeviceIsAlive, 0); _critSect.Leave(); if (_renderWorkerThread != NULL) { if (!_renderWorkerThread->Stop()) { WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, " Timed out waiting for the render worker thread to " "stop."); } } _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; } WebRtc_Word32 AudioDeviceMac::PlayoutDelay(WebRtc_UWord16& delayMS) const { int32_t renderDelayUs = AtomicGet32(&_renderDelayUs); delayMS = static_cast (1e-3 * (renderDelayUs + _renderLatencyUs) + 0.5); return 0; } WebRtc_Word32 AudioDeviceMac::RecordingDelay(WebRtc_UWord16& delayMS) const { int32_t captureDelayUs = AtomicGet32(&_captureDelayUs); delayMS = static_cast (1e-3 * (captureDelayUs + _captureLatencyUs) + 0.5); return 0; } bool AudioDeviceMac::Playing() const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); return (_playing); } WebRtc_Word32 AudioDeviceMac::SetPlayoutBuffer( const AudioDeviceModule::BufferType type, WebRtc_UWord16 sizeMS) { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "AudioDeviceMac::SetPlayoutBuffer(type=%u, sizeMS=%u)", type, 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; } WebRtc_Word32 AudioDeviceMac::PlayoutBuffer( AudioDeviceModule::BufferType& type, WebRtc_UWord16& sizeMS) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); type = _playBufType; sizeMS = _playBufDelayFixed; return 0; } // Not implemented for Mac. WebRtc_Word32 AudioDeviceMac::CPULoad(WebRtc_UWord16& /*load*/) const { WEBRTC_TRACE(kTraceModuleCall, kTraceAudioDevice, _id, "%s", __FUNCTION__); 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 // ============================================================================ WebRtc_Word32 AudioDeviceMac::GetNumberDevices(const AudioObjectPropertyScope scope, AudioDeviceID scopedDeviceIds[], const WebRtc_UWord32 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; } WebRtc_Word32 AudioDeviceMac::GetDeviceName(const AudioObjectPropertyScope scope, const WebRtc_UWord16 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; } WebRtc_Word32 AudioDeviceMac::InitDevice(const WebRtc_UWord16 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::objectListenerProc( AudioObjectID objectId, UInt32 numberAddresses, const AudioObjectPropertyAddress addresses[], void* clientData) { AudioDeviceMac *ptrThis = (AudioDeviceMac *) clientData; assert(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; } WebRtc_Word32 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; } WebRtc_Word32 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, mChannelsPerFrame = %u", streamFormat.mFormatFlags, streamFormat.mChannelsPerFrame); logCAMsg(kTraceInfo, kTraceAudioDevice, _id, "mFormatID", (const char *) &streamFormat.mFormatID); if (propertyAddress.mScope == kAudioDevicePropertyScopeInput) { 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((WebRtc_UWord8) _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)); if (_outStreamFormat.mChannelsPerFrame >= 2 && (_playChannels == 2)) { _outDesiredFormat.mChannelsPerFrame = 2; } else { // Disable stereo playout when we only have one channel on the device. _outDesiredFormat.mChannelsPerFrame = 1; _playChannels = 1; WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, "Stereo playout unavailable on this device"); } if (_ptrAudioBuffer) { // Update audio buffer with the selected parameters _ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC); _ptrAudioBuffer->SetPlayoutChannels((WebRtc_UWord8) _playChannels); } _renderDelayOffsetSamples = _renderBufSizeSamples - N_BUFFERS_OUT * ENGINE_PLAY_BUF_SIZE_IN_SAMPLES * _outDesiredFormat.mChannelsPerFrame; // Recreate the converter with the new format // TODO(xians): make this thread safe WEBRTC_CA_RETURN_ON_ERR(AudioConverterDispose(_renderConverter)); WEBRTC_CA_RETURN_ON_ERR(AudioConverterNew(&_outDesiredFormat, &streamFormat, &_renderConverter)); } return 0; } WebRtc_Word32 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; } WebRtc_Word32 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; assert(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; assert(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; assert(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 (userData); assert(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) { // This case is for stop play only or stop play+rec // (in that case out and in device ids are equal) #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceCreateIOProcID != NULL) { WEBRTC_CA_LOG_ERR(AudioDeviceStop(_outputDeviceID, _deviceIOProcID)); } else { #endif WEBRTC_CA_LOG_ERR(AudioDeviceStop(_outputDeviceID, deviceIOProc)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif 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; } assert(_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; } } ring_buffer_size_t bufSizeSamples = PaUtil_GetRingBufferReadAvailable(_paRenderBuffer); int32_t renderDelayUs = static_cast (1e-3 * (outputTimeNs - nowNs) + 0.5); renderDelayUs += static_cast ((1.0e6 * bufSizeSamples) / _outDesiredFormat.mChannelsPerFrame / _outDesiredFormat.mSampleRate + 0.5); AtomicSet32(&_renderDelayUs, renderDelayUs); return 0; } OSStatus AudioDeviceMac::implOutConverterProc(UInt32 *numberDataPackets, AudioBufferList *data) { assert(data->mNumberBuffers == 1); ring_buffer_size_t 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()); if (!_recording) { return 0; } // Check if we should close down audio device // Double-checked locking optimization to remove locking overhead if (_doStopRec) { _critSect.Enter(); if (_doStopRec) { // This case is for stop rec only #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 if (AudioDeviceCreateIOProcID != NULL) { WEBRTC_CA_LOG_ERR(AudioDeviceStop(_inputDeviceID, _inDeviceIOProcID)); } else { #endif WEBRTC_CA_LOG_ERR(AudioDeviceStop(_inputDeviceID, inDeviceIOProc)); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 } #endif if (err == noErr) { WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, " Recording device stopped"); } _doStopRec = false; _stopEventRec.Set(); _critSect.Leave(); return 0; } _critSect.Leave(); } ring_buffer_size_t bufSizeSamples = PaUtil_GetRingBufferReadAvailable(_paCaptureBuffer); int32_t captureDelayUs = static_cast (1e-3 * (nowNs - inputTimeNs) + 0.5); captureDelayUs += static_cast ((1.0e6 * bufSizeSamples) / _inStreamFormat.mChannelsPerFrame / _inStreamFormat.mSampleRate + 0.5); AtomicSet32(&_captureDelayUs, captureDelayUs); assert(inputData->mNumberBuffers == 1); ring_buffer_size_t 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) { assert(data->mNumberBuffers == 1); ring_buffer_size_t 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; ring_buffer_size_t 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 (ptrThis)->RenderWorkerThread(); } bool AudioDeviceMac::RenderWorkerThread() { ring_buffer_size_t 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); } } WebRtc_Word8 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. WebRtc_UWord32 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); } WebRtc_UWord32 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 (WebRtc_UWord32 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 (sampleInt32); } } PaUtil_WriteRingBuffer(_paRenderBuffer, pPlayBuffer, nOutSamples); return true; } bool AudioDeviceMac::RunCapture(void* ptrThis) { return static_cast (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) { WebRtc_UWord32 currentMicLevel(0); WebRtc_UWord32 newMicLevel(0); WebRtc_Word32 msecOnPlaySide; WebRtc_Word32 msecOnRecordSide; int32_t captureDelayUs = AtomicGet32(&_captureDelayUs); int32_t renderDelayUs = AtomicGet32(&_renderDelayUs); msecOnPlaySide = static_cast (1e-3 * (renderDelayUs + _renderLatencyUs) + 0.5); msecOnRecordSide = static_cast (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((WebRtc_Word8*) &recordBuffer, (WebRtc_UWord32) 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); // 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; } } // namespace webrtc