/* * 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 #include // OSAtomicCompareAndSwap() #include // mach_task_self() #include // 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 (1e-3 * (renderDelayUs + _renderLatencyUs) + 0.5); return 0; } int32_t AudioDeviceMac::RecordingDelay(uint16_t& delayMS) const { int32_t captureDelayUs = AtomicGet32(&_captureDelayUs); delayMS = static_cast (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 ((_outStreamFormat.mSampleRate / 1000.0) * _playBufDelayFixed * _outStreamFormat.mChannelsPerFrame * sizeof(Float32)); if (_outStreamFormat.mFramesPerPacket != 0) { if (bufByteCount % _outStreamFormat.mFramesPerPacket != 0) { bufByteCount = (static_cast (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 ((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 ((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 (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 (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) { 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 (1e-3 * (nowNs - inputTimeNs) + 0.5); captureDelayUs += static_cast ((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 (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 (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) { 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 (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((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