/* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/audio_device/mac/audio_device_mac.h" #include #include // mach_task_self() #include // sysctlbyname() #include #include "modules/audio_device/audio_device_config.h" #include "modules/third_party/portaudio/pa_ringbuffer.h" #include "rtc_base/arraysize.h" #include "rtc_base/checks.h" #include "rtc_base/platform_thread.h" #include "rtc_base/system/arch.h" namespace webrtc { #define WEBRTC_CA_RETURN_ON_ERR(expr) \ do { \ err = expr; \ if (err != noErr) { \ logCAMsg(rtc::LS_ERROR, "Error in " #expr, (const char*)&err); \ return -1; \ } \ } while (0) #define WEBRTC_CA_LOG_ERR(expr) \ do { \ err = expr; \ if (err != noErr) { \ logCAMsg(rtc::LS_ERROR, "Error in " #expr, (const char*)&err); \ } \ } while (0) #define WEBRTC_CA_LOG_WARN(expr) \ do { \ err = expr; \ if (err != noErr) { \ logCAMsg(rtc::LS_WARNING, "Error in " #expr, (const char*)&err); \ } \ } while (0) enum { MaxNumberDevices = 64 }; // CoreAudio errors are best interpreted as four character strings. void AudioDeviceMac::logCAMsg(const rtc::LoggingSeverity sev, const char* msg, const char* err) { RTC_DCHECK(msg != NULL); RTC_DCHECK(err != NULL); #ifdef WEBRTC_ARCH_BIG_ENDIAN switch (sev) { case rtc::LS_ERROR: RTC_LOG(LS_ERROR) << msg << ": " << err[0] << err[1] << err[2] << err[3]; break; case rtc::LS_WARNING: RTC_LOG(LS_WARNING) << msg << ": " << err[0] << err[1] << err[2] << err[3]; break; case rtc::LS_VERBOSE: RTC_LOG(LS_VERBOSE) << msg << ": " << err[0] << err[1] << err[2] << err[3]; break; default: break; } #else // We need to flip the characters in this case. switch (sev) { case rtc::LS_ERROR: RTC_LOG(LS_ERROR) << msg << ": " << err[3] << err[2] << err[1] << err[0]; break; case rtc::LS_WARNING: RTC_LOG(LS_WARNING) << msg << ": " << err[3] << err[2] << err[1] << err[0]; break; case rtc::LS_VERBOSE: RTC_LOG(LS_VERBOSE) << msg << ": " << err[3] << err[2] << err[1] << err[0]; break; default: break; } #endif } AudioDeviceMac::AudioDeviceMac() : _ptrAudioBuffer(NULL), _mixerManager(), _inputDeviceIndex(0), _outputDeviceIndex(0), _inputDeviceID(kAudioObjectUnknown), _outputDeviceID(kAudioObjectUnknown), _inputDeviceIsSpecified(false), _outputDeviceIsSpecified(false), _recChannels(N_REC_CHANNELS), _playChannels(N_PLAY_CHANNELS), _captureBufData(NULL), _renderBufData(NULL), _initialized(false), _isShutDown(false), _recording(false), _playing(false), _recIsInitialized(false), _playIsInitialized(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), _paCaptureBuffer(NULL), _paRenderBuffer(NULL), _captureBufSizeSamples(0), _renderBufSizeSamples(0), prev_key_state_() { RTC_DLOG(LS_INFO) << __FUNCTION__ << " created"; 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() { RTC_DLOG(LS_INFO) << __FUNCTION__ << " destroyed"; if (!_isShutDown) { Terminate(); } RTC_DCHECK(capture_worker_thread_.empty()); RTC_DCHECK(render_worker_thread_.empty()); 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) { RTC_LOG(LS_ERROR) << "semaphore_destroy() error: " << kernErr; } kernErr = semaphore_destroy(mach_task_self(), _captureSemaphore); if (kernErr != KERN_SUCCESS) { RTC_LOG(LS_ERROR) << "semaphore_destroy() error: " << kernErr; } } // ============================================================================ // API // ============================================================================ void AudioDeviceMac::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { MutexLock lock(&mutex_); _ptrAudioBuffer = audioBuffer; // inform the AudioBuffer about default settings for this implementation _ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC); _ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC); _ptrAudioBuffer->SetRecordingChannels(N_REC_CHANNELS); _ptrAudioBuffer->SetPlayoutChannels(N_PLAY_CHANNELS); } int32_t AudioDeviceMac::ActiveAudioLayer( AudioDeviceModule::AudioLayer& audioLayer) const { audioLayer = AudioDeviceModule::kPlatformDefaultAudio; return 0; } AudioDeviceGeneric::InitStatus AudioDeviceMac::Init() { MutexLock lock(&mutex_); if (_initialized) { return InitStatus::OK; } OSStatus err = noErr; _isShutDown = false; // PortAudio ring buffers require an elementCount which is a power of two. if (_renderBufData == NULL) { UInt32 powerOfTwo = 1; while (powerOfTwo < PLAY_BUF_SIZE_IN_SAMPLES) { powerOfTwo <<= 1; } _renderBufSizeSamples = powerOfTwo; _renderBufData = new SInt16[_renderBufSizeSamples]; } if (_paRenderBuffer == NULL) { _paRenderBuffer = new PaUtilRingBuffer; ring_buffer_size_t bufSize = -1; bufSize = PaUtil_InitializeRingBuffer( _paRenderBuffer, sizeof(SInt16), _renderBufSizeSamples, _renderBufData); if (bufSize == -1) { RTC_LOG(LS_ERROR) << "PaUtil_InitializeRingBuffer() error"; return InitStatus::PLAYOUT_ERROR; } } if (_captureBufData == NULL) { UInt32 powerOfTwo = 1; while (powerOfTwo < REC_BUF_SIZE_IN_SAMPLES) { powerOfTwo <<= 1; } _captureBufSizeSamples = powerOfTwo; _captureBufData = new Float32[_captureBufSizeSamples]; } if (_paCaptureBuffer == NULL) { _paCaptureBuffer = new PaUtilRingBuffer; ring_buffer_size_t bufSize = -1; bufSize = PaUtil_InitializeRingBuffer(_paCaptureBuffer, sizeof(Float32), _captureBufSizeSamples, _captureBufData); if (bufSize == -1) { RTC_LOG(LS_ERROR) << "PaUtil_InitializeRingBuffer() error"; return InitStatus::RECORDING_ERROR; } } kern_return_t kernErr = KERN_SUCCESS; kernErr = semaphore_create(mach_task_self(), &_renderSemaphore, SYNC_POLICY_FIFO, 0); if (kernErr != KERN_SUCCESS) { RTC_LOG(LS_ERROR) << "semaphore_create() error: " << kernErr; return InitStatus::OTHER_ERROR; } kernErr = semaphore_create(mach_task_self(), &_captureSemaphore, SYNC_POLICY_FIFO, 0); if (kernErr != KERN_SUCCESS) { RTC_LOG(LS_ERROR) << "semaphore_create() error: " << kernErr; return InitStatus::OTHER_ERROR; } // Setting RunLoop to NULL here instructs HAL to manage its own thread for // notifications. This was the default behaviour on OS X 10.5 and earlier, // but now must be explicitly specified. HAL would otherwise try to use the // main thread to issue notifications. AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; CFRunLoopRef runLoop = NULL; UInt32 size = sizeof(CFRunLoopRef); int aoerr = AudioObjectSetPropertyData( kAudioObjectSystemObject, &propertyAddress, 0, NULL, size, &runLoop); if (aoerr != noErr) { RTC_LOG(LS_ERROR) << "Error in AudioObjectSetPropertyData: " << (const char*)&aoerr; return InitStatus::OTHER_ERROR; } // Listen for any device changes. propertyAddress.mSelector = kAudioHardwarePropertyDevices; WEBRTC_CA_LOG_ERR(AudioObjectAddPropertyListener( kAudioObjectSystemObject, &propertyAddress, &objectListenerProc, this)); // Determine if this is a MacBook Pro _macBookPro = false; _macBookProPanRight = false; char buf[128]; size_t length = sizeof(buf); memset(buf, 0, length); int intErr = sysctlbyname("hw.model", buf, &length, NULL, 0); if (intErr != 0) { RTC_LOG(LS_ERROR) << "Error in sysctlbyname(): " << err; } else { RTC_LOG(LS_VERBOSE) << "Hardware model: " << buf; if (strncmp(buf, "MacBookPro", 10) == 0) { _macBookPro = true; } } _initialized = true; return InitStatus::OK; } int32_t AudioDeviceMac::Terminate() { if (!_initialized) { return 0; } if (_recording) { RTC_LOG(LS_ERROR) << "Recording must be stopped"; return -1; } if (_playing) { RTC_LOG(LS_ERROR) << "Playback must be stopped"; return -1; } MutexLock lock(&mutex_); _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(rtc::LS_ERROR, "Error in AudioHardwareUnload()", (const char*)&err); retVal = -1; } _isShutDown = true; _initialized = false; _outputDeviceIsSpecified = false; _inputDeviceIsSpecified = false; return retVal; } bool AudioDeviceMac::Initialized() const { return (_initialized); } int32_t AudioDeviceMac::SpeakerIsAvailable(bool& available) { MutexLock lock(&mutex_); return SpeakerIsAvailableLocked(available); } int32_t AudioDeviceMac::SpeakerIsAvailableLocked(bool& available) { bool wasInitialized = _mixerManager.SpeakerIsInitialized(); // Make an attempt to open up the // output mixer corresponding to the currently selected output device. // if (!wasInitialized && InitSpeakerLocked() == -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() { MutexLock lock(&mutex_); return InitSpeakerLocked(); } int32_t AudioDeviceMac::InitSpeakerLocked() { 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) { MutexLock lock(&mutex_); return MicrophoneIsAvailableLocked(available); } int32_t AudioDeviceMac::MicrophoneIsAvailableLocked(bool& available) { bool wasInitialized = _mixerManager.MicrophoneIsInitialized(); // Make an attempt to open up the // input mixer corresponding to the currently selected output device. // if (!wasInitialized && InitMicrophoneLocked() == -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() { MutexLock lock(&mutex_); return InitMicrophoneLocked(); } int32_t AudioDeviceMac::InitMicrophoneLocked() { 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::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::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::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::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) { RTC_LOG(LS_WARNING) << "failed to retrieve 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; } int16_t AudioDeviceMac::PlayoutDevices() { AudioDeviceID playDevices[MaxNumberDevices]; return GetNumberDevices(kAudioDevicePropertyScopeOutput, playDevices, MaxNumberDevices); } int32_t AudioDeviceMac::SetPlayoutDevice(uint16_t index) { MutexLock lock(&mutex_); if (_playIsInitialized) { return -1; } AudioDeviceID playDevices[MaxNumberDevices]; uint32_t nDevices = GetNumberDevices(kAudioDevicePropertyScopeOutput, playDevices, MaxNumberDevices); RTC_LOG(LS_VERBOSE) << "number of available waveform-audio output devices is " << nDevices; if (index > (nDevices - 1)) { RTC_LOG(LS_ERROR) << "device index is out of range [0," << (nDevices - 1) << "]"; return -1; } _outputDeviceIndex = index; _outputDeviceIsSpecified = true; return 0; } int32_t AudioDeviceMac::SetPlayoutDevice( AudioDeviceModule::WindowsDeviceType /*device*/) { RTC_LOG(LS_ERROR) << "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, rtc::ArrayView(name, kAdmMaxDeviceNameSize)); } 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, rtc::ArrayView(name, kAdmMaxDeviceNameSize)); } 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); RTC_LOG(LS_VERBOSE) << "number of available waveform-audio input devices is " << nDevices; if (index > (nDevices - 1)) { RTC_LOG(LS_ERROR) << "device index is out of range [0," << (nDevices - 1) << "]"; return -1; } _inputDeviceIndex = index; _inputDeviceIsSpecified = true; return 0; } int32_t AudioDeviceMac::SetRecordingDevice( AudioDeviceModule::WindowsDeviceType /*device*/) { RTC_LOG(LS_ERROR) << "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() { RTC_LOG(LS_INFO) << "InitPlayout"; MutexLock lock(&mutex_); if (_playing) { return -1; } if (!_outputDeviceIsSpecified) { return -1; } if (_playIsInitialized) { return 0; } // Initialize the speaker (devices might have been added or removed) if (InitSpeakerLocked() == -1) { RTC_LOG(LS_WARNING) << "InitSpeaker() failed"; } if (!MicrophoneIsInitialized()) { // Make this call to check if we are using // one or two devices (_twoDevices) bool available = false; if (MicrophoneIsAvailableLocked(available) == -1) { RTC_LOG(LS_WARNING) << "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; RTC_LOG(LS_VERBOSE) << "MacBook Pro using internal speakers; stereo panning right"; } else { RTC_LOG(LS_VERBOSE) << "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(rtc::LS_ERROR, "Unacceptable output stream format -> mFormatID", (const char*)&_outStreamFormat.mFormatID); return -1; } if (_outStreamFormat.mChannelsPerFrame > N_DEVICE_CHANNELS) { RTC_LOG(LS_ERROR) << "Too many channels on output device (mChannelsPerFrame = " << _outStreamFormat.mChannelsPerFrame << ")"; return -1; } if (_outStreamFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) { RTC_LOG(LS_ERROR) << "Non-interleaved audio data is not supported." "AudioHardware streams should not have this format."; return -1; } RTC_LOG(LS_VERBOSE) << "Ouput stream format:"; RTC_LOG(LS_VERBOSE) << "mSampleRate = " << _outStreamFormat.mSampleRate << ", mChannelsPerFrame = " << _outStreamFormat.mChannelsPerFrame; RTC_LOG(LS_VERBOSE) << "mBytesPerPacket = " << _outStreamFormat.mBytesPerPacket << ", mFramesPerPacket = " << _outStreamFormat.mFramesPerPacket; RTC_LOG(LS_VERBOSE) << "mBytesPerFrame = " << _outStreamFormat.mBytesPerFrame << ", mBitsPerChannel = " << _outStreamFormat.mBitsPerChannel; RTC_LOG(LS_VERBOSE) << "mFormatFlags = " << _outStreamFormat.mFormatFlags; logCAMsg(rtc::LS_VERBOSE, "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; RTC_LOG(LS_VERBOSE) << "Stereo playout unavailable on this device"; } WEBRTC_CA_RETURN_ON_ERR(SetDesiredPlayoutFormat()); // Listen for format changes. propertyAddress.mSelector = kAudioDevicePropertyStreamFormat; WEBRTC_CA_LOG_WARN(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() { RTC_LOG(LS_INFO) << "InitRecording"; MutexLock lock(&mutex_); if (_recording) { return -1; } if (!_inputDeviceIsSpecified) { return -1; } if (_recIsInitialized) { return 0; } // Initialize the microphone (devices might have been added or removed) if (InitMicrophoneLocked() == -1) { RTC_LOG(LS_WARNING) << "InitMicrophone() failed"; } if (!SpeakerIsInitialized()) { // Make this call to check if we are using // one or two devices (_twoDevices) bool available = false; if (SpeakerIsAvailableLocked(available) == -1) { RTC_LOG(LS_WARNING) << "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(rtc::LS_ERROR, "Unacceptable input stream format -> mFormatID", (const char*)&_inStreamFormat.mFormatID); return -1; } if (_inStreamFormat.mChannelsPerFrame > N_DEVICE_CHANNELS) { RTC_LOG(LS_ERROR) << "Too many channels on input device (mChannelsPerFrame = " << _inStreamFormat.mChannelsPerFrame << ")"; return -1; } const int io_block_size_samples = _inStreamFormat.mChannelsPerFrame * _inStreamFormat.mSampleRate / 100 * N_BLOCKS_IO; if (io_block_size_samples > _captureBufSizeSamples) { RTC_LOG(LS_ERROR) << "Input IO block size (" << io_block_size_samples << ") is larger than ring buffer (" << _captureBufSizeSamples << ")"; return -1; } RTC_LOG(LS_VERBOSE) << "Input stream format:"; RTC_LOG(LS_VERBOSE) << "mSampleRate = " << _inStreamFormat.mSampleRate << ", mChannelsPerFrame = " << _inStreamFormat.mChannelsPerFrame; RTC_LOG(LS_VERBOSE) << "mBytesPerPacket = " << _inStreamFormat.mBytesPerPacket << ", mFramesPerPacket = " << _inStreamFormat.mFramesPerPacket; RTC_LOG(LS_VERBOSE) << "mBytesPerFrame = " << _inStreamFormat.mBytesPerFrame << ", mBitsPerChannel = " << _inStreamFormat.mBitsPerChannel; RTC_LOG(LS_VERBOSE) << "mFormatFlags = " << _inStreamFormat.mFormatFlags; logCAMsg(rtc::LS_VERBOSE, "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; RTC_LOG(LS_VERBOSE) << "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_LOG_WARN(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() { RTC_LOG(LS_INFO) << "StartRecording"; MutexLock lock(&mutex_); if (!_recIsInitialized) { return -1; } if (_recording) { return 0; } if (!_initialized) { RTC_LOG(LS_ERROR) << "Recording worker thread has not been started"; return -1; } RTC_DCHECK(capture_worker_thread_.empty()); capture_worker_thread_ = rtc::PlatformThread::SpawnJoinable( [this] { while (CaptureWorkerThread()) { } }, "CaptureWorkerThread", rtc::ThreadAttributes().SetPriority(rtc::ThreadPriority::kRealtime)); 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() { RTC_LOG(LS_INFO) << "StopRecording"; MutexLock lock(&mutex_); if (!_recIsInitialized) { return 0; } OSStatus err = noErr; int32_t captureDeviceIsAlive = _captureDeviceIsAlive; if (_twoDevices && captureDeviceIsAlive == 1) { // Recording side uses its own dedicated device and IOProc. if (_recording) { _recording = false; _doStopRec = true; // Signal to io proc to stop audio device mutex_.Unlock(); // Cannot be under lock, risk of deadlock if (!_stopEventRec.Wait(TimeDelta::Seconds(2))) { MutexLock lockScoped(&mutex_); RTC_LOG(LS_WARNING) << "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)); } mutex_.Lock(); _doStopRec = false; RTC_LOG(LS_INFO) << "Recording stopped (input device)"; } else if (_recIsInitialized) { WEBRTC_CA_LOG_WARN( AudioDeviceDestroyIOProcID(_inputDeviceID, _inDeviceIOProcID)); RTC_LOG(LS_INFO) << "Recording uninitialized (input device)"; } } 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 mutex_.Unlock(); // Cannot be under lock, risk of deadlock if (!_stopEvent.Wait(TimeDelta::Seconds(2))) { MutexLock lockScoped(&mutex_); RTC_LOG(LS_WARNING) << "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)); } mutex_.Lock(); _doStop = false; RTC_LOG(LS_INFO) << "Recording stopped (shared device)"; } else if (_recIsInitialized && !_playing && !_playIsInitialized) { WEBRTC_CA_LOG_WARN( AudioDeviceDestroyIOProcID(_outputDeviceID, _deviceIOProcID)); RTC_LOG(LS_INFO) << "Recording uninitialized (shared device)"; } } // Setting this signal will allow the worker thread to be stopped. _captureDeviceIsAlive = 0; if (!capture_worker_thread_.empty()) { mutex_.Unlock(); capture_worker_thread_.Finalize(); mutex_.Lock(); } 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() { RTC_LOG(LS_INFO) << "StartPlayout"; MutexLock lock(&mutex_); if (!_playIsInitialized) { return -1; } if (_playing) { return 0; } RTC_DCHECK(render_worker_thread_.empty()); render_worker_thread_ = rtc::PlatformThread::SpawnJoinable( [this] { while (RenderWorkerThread()) { } }, "RenderWorkerThread", rtc::ThreadAttributes().SetPriority(rtc::ThreadPriority::kRealtime)); if (_twoDevices || !_recording) { OSStatus err = noErr; WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_outputDeviceID, _deviceIOProcID)); } _playing = true; return 0; } int32_t AudioDeviceMac::StopPlayout() { RTC_LOG(LS_INFO) << "StopPlayout"; MutexLock lock(&mutex_); if (!_playIsInitialized) { return 0; } OSStatus err = noErr; int32_t renderDeviceIsAlive = _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 mutex_.Unlock(); // Cannot be under lock, risk of deadlock if (!_stopEvent.Wait(TimeDelta::Seconds(2))) { MutexLock lockScoped(&mutex_); RTC_LOG(LS_WARNING) << "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)); } mutex_.Lock(); _doStop = false; RTC_LOG(LS_INFO) << "Playout stopped"; } else if (_twoDevices && _playIsInitialized) { WEBRTC_CA_LOG_WARN( AudioDeviceDestroyIOProcID(_outputDeviceID, _deviceIOProcID)); RTC_LOG(LS_INFO) << "Playout uninitialized (output device)"; } else if (!_twoDevices && _playIsInitialized && !_recIsInitialized) { WEBRTC_CA_LOG_WARN( AudioDeviceDestroyIOProcID(_outputDeviceID, _deviceIOProcID)); RTC_LOG(LS_INFO) << "Playout uninitialized (shared device)"; } // Setting this signal will allow the worker thread to be stopped. _renderDeviceIsAlive = 0; if (!render_worker_thread_.empty()) { mutex_.Unlock(); render_worker_thread_.Finalize(); mutex_.Lock(); } 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 = _renderDelayUs; delayMS = static_cast(1e-3 * (renderDelayUs + _renderLatencyUs) + 0.5); return 0; } bool AudioDeviceMac::Playing() const { return (_playing); } // ============================================================================ // 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) { RTC_LOG(LS_WARNING) << "No devices"; return 0; } UInt32 numberDevices = size / sizeof(AudioDeviceID); const auto deviceIds = std::make_unique(numberDevices); 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 { RTC_LOG(LS_WARNING) << "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.get())); 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) { RTC_LOG(LS_ERROR) << "Device list is not long enough"; listOK = false; break; } scopedDeviceIds[numberScopedDevices] = deviceIds[i]; numberScopedDevices++; } free(bufferList); bufferList = NULL; } // for } if (!listOK) { if (bufferList) { free(bufferList); bufferList = NULL; } return -1; } return numberScopedDevices; } int32_t AudioDeviceMac::GetDeviceName(const AudioObjectPropertyScope scope, const uint16_t index, rtc::ArrayView name) { OSStatus err = noErr; AudioDeviceID deviceIds[MaxNumberDevices]; int numberDevices = GetNumberDevices(scope, deviceIds, MaxNumberDevices); if (numberDevices < 0) { return -1; } else if (numberDevices == 0) { RTC_LOG(LS_ERROR) << "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) { RTC_LOG(LS_WARNING) << "GetDeviceName(): Default device unknown"; } else { isDefaultDevice = true; } } AudioObjectPropertyAddress propertyAddress = {kAudioDevicePropertyDeviceName, scope, 0}; if (isDefaultDevice) { std::array devName; UInt32 len = devName.size(); WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( usedID, &propertyAddress, 0, NULL, &len, devName.data())); rtc::SimpleStringBuilder ss(name); ss.AppendFormat("default (%s)", devName.data()); } else { if (index < numberDevices) { usedID = deviceIds[index]; } else { usedID = index; } UInt32 len = name.size(); WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( usedID, &propertyAddress, 0, NULL, &len, name.data())); } 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) { RTC_LOG(LS_ERROR) << "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) { RTC_LOG(LS_WARNING) << "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) { RTC_LOG(LS_INFO) << "Input device: " << devManf << " " << devName; } else { RTC_LOG(LS_INFO) << "Output device: " << 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 set to 20ms. const uint16_t kPlayBufDelayFixed = 20; UInt32 bufByteCount = static_cast( (_outStreamFormat.mSampleRate / 1000.0) * kPlayBufDelayFixed * _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); RTC_LOG(LS_VERBOSE) << "initial playout status: _renderDelayOffsetSamples=" << _renderDelayOffsetSamples << ", _renderDelayUs=" << _renderDelayUs << ", _renderLatencyUs=" << _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[]) { RTC_LOG(LS_VERBOSE) << "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; RTC_LOG(LS_VERBOSE) << "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) { RTC_LOG(LS_WARNING) << "Capture device is not alive (probably removed)"; _captureDeviceIsAlive = 0; _mixerManager.CloseMicrophone(); } else if (err != noErr) { logCAMsg(rtc::LS_ERROR, "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) { RTC_LOG(LS_WARNING) << "Render device is not alive (probably removed)"; _renderDeviceIsAlive = 0; _mixerManager.CloseSpeaker(); } else if (err != noErr) { logCAMsg(rtc::LS_ERROR, "Error in AudioDeviceGetProperty()", (const char*)&err); return -1; } } return 0; } int32_t AudioDeviceMac::HandleStreamFormatChange( const AudioObjectID objectId, const AudioObjectPropertyAddress propertyAddress) { OSStatus err = noErr; RTC_LOG(LS_VERBOSE) << "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(rtc::LS_ERROR, "Unacceptable input stream format -> mFormatID", (const char*)&streamFormat.mFormatID); return -1; } if (streamFormat.mChannelsPerFrame > N_DEVICE_CHANNELS) { RTC_LOG(LS_ERROR) << "Too many channels on device (mChannelsPerFrame = " << streamFormat.mChannelsPerFrame << ")"; return -1; } if (_ptrAudioBuffer && streamFormat.mChannelsPerFrame != _recChannels) { RTC_LOG(LS_ERROR) << "Changing channels not supported (mChannelsPerFrame = " << streamFormat.mChannelsPerFrame << ")"; return -1; } RTC_LOG(LS_VERBOSE) << "Stream format:"; RTC_LOG(LS_VERBOSE) << "mSampleRate = " << streamFormat.mSampleRate << ", mChannelsPerFrame = " << streamFormat.mChannelsPerFrame; RTC_LOG(LS_VERBOSE) << "mBytesPerPacket = " << streamFormat.mBytesPerPacket << ", mFramesPerPacket = " << streamFormat.mFramesPerPacket; RTC_LOG(LS_VERBOSE) << "mBytesPerFrame = " << streamFormat.mBytesPerFrame << ", mBitsPerChannel = " << streamFormat.mBitsPerChannel; RTC_LOG(LS_VERBOSE) << "mFormatFlags = " << streamFormat.mFormatFlags; logCAMsg(rtc::LS_VERBOSE, "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) { RTC_LOG(LS_ERROR) << "Input IO block size (" << io_block_size_samples << ") is larger than ring buffer (" << _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; RTC_LOG(LS_VERBOSE) << "Stereo recording unavailable on this device"; } // 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; RTC_LOG(LS_VERBOSE) << "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) { RTC_LOG(LS_VERBOSE) << "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; RTC_LOG(LS_VERBOSE) << "MacBook Pro using internal speakers; stereo panning right"; } else { RTC_LOG(LS_VERBOSE) << "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) { // RTC_LOG(LS_WARNING) << "Capture processor // overload"; //_callback->ProblemIsReported( // SndCardStreamObserver::ERecordingProblem); } else { // RTC_LOG(LS_WARNING) << "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) { MutexLock lock(&mutex_); 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) { RTC_LOG(LS_VERBOSE) << "Playout or shared device stopped"; } } _doStop = false; _stopEvent.Set(); return 0; } } 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. RTC_LOG(LS_ERROR) << "Error in AudioConverterFillComplexBuffer()"; return 1; } else { logCAMsg(rtc::LS_ERROR, "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); _renderDelayUs = renderDelayUs; return 0; } OSStatus AudioDeviceMac::implOutConverterProc(UInt32* numberDataPackets, AudioBufferList* data) { RTC_DCHECK(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) { RTC_LOG(LS_ERROR) << "semaphore_signal_all() error: " << 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) { MutexLock lock(&mutex_); 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) { RTC_LOG(LS_VERBOSE) << "Recording device stopped"; } _doStopRec = false; _stopEventRec.Set(); return 0; } } if (!_recording) { // Allow above checks to avoid a timeout on stopping capture. return 0; } 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); _captureDelayUs = captureDelayUs; RTC_DCHECK(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) { RTC_LOG(LS_ERROR) << "semaphore_signal_all() error: " << kernErr; } return err; } OSStatus AudioDeviceMac::implInConverterProc(UInt32* numberDataPackets, AudioBufferList* data) { RTC_DCHECK(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 = _captureDeviceIsAlive; if (signal == 0) { // The capture device is no longer alive; stop the worker thread. *numberDataPackets = 0; return 1; } } else if (kernErr != KERN_SUCCESS) { RTC_LOG(LS_ERROR) << "semaphore_wait() error: " << 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::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 = _renderDeviceIsAlive; if (signal == 0) { // The render device is no longer alive; stop the worker thread. return false; } } else if (kernErr != KERN_SUCCESS) { RTC_LOG(LS_ERROR) << "semaphore_timedwait() error: " << kernErr; } } int8_t playBuffer[4 * ENGINE_PLAY_BUF_SIZE_IN_SAMPLES]; if (!_ptrAudioBuffer) { RTC_LOG(LS_ERROR) << "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) { RTC_LOG(LS_ERROR) << "invalid number of output samples(" << 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::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(rtc::LS_ERROR, "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) { int32_t msecOnPlaySide; int32_t msecOnRecordSide; int32_t captureDelayUs = _captureDelayUs; int32_t renderDelayUs = _renderDelayUs; msecOnPlaySide = static_cast(1e-3 * (renderDelayUs + _renderLatencyUs) + 0.5); msecOnRecordSide = static_cast(1e-3 * (captureDelayUs + _captureLatencyUs) + 0.5); if (!_ptrAudioBuffer) { RTC_LOG(LS_ERROR) << "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); _ptrAudioBuffer->SetVQEData(msecOnPlaySide, msecOnRecordSide); _ptrAudioBuffer->SetTypingStatus(KeyPressed()); // deliver recorded samples at specified sample rate, mic level etc. // to the observer using callback _ptrAudioBuffer->DeliverRecordedData(); } 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