| /* |
| * 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 "absl/memory/memory.h" |
| #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" |
| #include "system_wrappers/include/event_wrapper.h" |
| |
| #include <ApplicationServices/ApplicationServices.h> |
| #include <libkern/OSAtomic.h> // OSAtomicCompareAndSwap() |
| #include <mach/mach.h> // mach_task_self() |
| #include <sys/sysctl.h> // sysctlbyname() |
| |
| namespace webrtc { |
| |
| #define WEBRTC_CA_RETURN_ON_ERR(expr) \ |
| do { \ |
| err = expr; \ |
| if (err != noErr) { \ |
| logCAMsg(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 }; |
| |
| 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 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), |
| _stopEventRec(*EventWrapper::Create()), |
| _stopEvent(*EventWrapper::Create()), |
| _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_LOG(LS_INFO) << __FUNCTION__ << " created"; |
| |
| 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() { |
| RTC_LOG(LS_INFO) << __FUNCTION__ << " destroyed"; |
| |
| 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) { |
| 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; |
| } |
| |
| delete &_stopEvent; |
| delete &_stopEventRec; |
| } |
| |
| // ============================================================================ |
| // API |
| // ============================================================================ |
| |
| void AudioDeviceMac::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { |
| rtc::CritScope lock(&_critSect); |
| |
| _ptrAudioBuffer = audioBuffer; |
| |
| // inform the AudioBuffer about default settings for this implementation |
| _ptrAudioBuffer->SetRecordingSampleRate(N_REC_SAMPLES_PER_SEC); |
| _ptrAudioBuffer->SetPlayoutSampleRate(N_PLAY_SAMPLES_PER_SEC); |
| _ptrAudioBuffer->SetRecordingChannels(N_REC_CHANNELS); |
| _ptrAudioBuffer->SetPlayoutChannels(N_PLAY_CHANNELS); |
| } |
| |
| int32_t AudioDeviceMac::ActiveAudioLayer( |
| AudioDeviceModule::AudioLayer& audioLayer) const { |
| audioLayer = AudioDeviceModule::kPlatformDefaultAudio; |
| return 0; |
| } |
| |
| AudioDeviceGeneric::InitStatus AudioDeviceMac::Init() { |
| rtc::CritScope lock(&_critSect); |
| |
| if (_initialized) { |
| return InitStatus::OK; |
| } |
| |
| OSStatus err = noErr; |
| |
| _isShutDown = false; |
| |
| // PortAudio ring buffers require an elementCount which is a power of two. |
| if (_renderBufData == NULL) { |
| UInt32 powerOfTwo = 1; |
| while (powerOfTwo < PLAY_BUF_SIZE_IN_SAMPLES) { |
| powerOfTwo <<= 1; |
| } |
| _renderBufSizeSamples = powerOfTwo; |
| _renderBufData = new SInt16[_renderBufSizeSamples]; |
| } |
| |
| if (_paRenderBuffer == NULL) { |
| _paRenderBuffer = new PaUtilRingBuffer; |
| PaRingBufferSize bufSize = -1; |
| bufSize = PaUtil_InitializeRingBuffer( |
| _paRenderBuffer, sizeof(SInt16), _renderBufSizeSamples, _renderBufData); |
| if (bufSize == -1) { |
| 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; |
| PaRingBufferSize 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; |
| } |
| |
| _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(rtc::LS_ERROR, "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() { |
| rtc::CritScope 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() { |
| rtc::CritScope 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::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) { |
| rtc::CritScope lock(&_critSect); |
| |
| 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, 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); |
| 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"; |
| rtc::CritScope 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) { |
| 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 (MicrophoneIsAvailable(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"; |
| rtc::CritScope 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) { |
| 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 (SpeakerIsAvailable(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"; |
| rtc::CritScope lock(&_critSect); |
| |
| 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_.get()); |
| capture_worker_thread_.reset( |
| new rtc::PlatformThread(RunCapture, this, "CaptureWorkerThread")); |
| RTC_DCHECK(capture_worker_thread_.get()); |
| capture_worker_thread_->Start(); |
| capture_worker_thread_->SetPriority(rtc::kRealtimePriority); |
| |
| OSStatus err = noErr; |
| if (_twoDevices) { |
| WEBRTC_CA_RETURN_ON_ERR( |
| AudioDeviceStart(_inputDeviceID, _inDeviceIOProcID)); |
| } else if (!_playing) { |
| WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_inputDeviceID, _deviceIOProcID)); |
| } |
| |
| _recording = true; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceMac::StopRecording() { |
| RTC_LOG(LS_INFO) << "StopRecording"; |
| rtc::CritScope lock(&_critSect); |
| |
| if (!_recIsInitialized) { |
| return 0; |
| } |
| |
| OSStatus err = noErr; |
| int32_t captureDeviceIsAlive = AtomicGet32(&_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 |
| _critSect.Leave(); // Cannot be under lock, risk of deadlock |
| if (kEventTimeout == _stopEventRec.Wait(2000)) { |
| rtc::CritScope critScoped(&_critSect); |
| 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)); |
| } |
| _critSect.Enter(); |
| _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 |
| _critSect.Leave(); // Cannot be under lock, risk of deadlock |
| if (kEventTimeout == _stopEvent.Wait(2000)) { |
| rtc::CritScope critScoped(&_critSect); |
| 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)); |
| } |
| _critSect.Enter(); |
| _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. |
| 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() { |
| RTC_LOG(LS_INFO) << "StartPlayout"; |
| rtc::CritScope lock(&_critSect); |
| |
| if (!_playIsInitialized) { |
| return -1; |
| } |
| |
| if (_playing) { |
| return 0; |
| } |
| |
| RTC_DCHECK(!render_worker_thread_.get()); |
| render_worker_thread_.reset( |
| new rtc::PlatformThread(RunRender, this, "RenderWorkerThread")); |
| render_worker_thread_->Start(); |
| render_worker_thread_->SetPriority(rtc::kRealtimePriority); |
| |
| if (_twoDevices || !_recording) { |
| OSStatus err = noErr; |
| WEBRTC_CA_RETURN_ON_ERR(AudioDeviceStart(_outputDeviceID, _deviceIOProcID)); |
| } |
| _playing = true; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceMac::StopPlayout() { |
| RTC_LOG(LS_INFO) << "StopPlayout"; |
| rtc::CritScope 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)) { |
| rtc::CritScope critScoped(&_critSect); |
| 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)); |
| } |
| _critSect.Enter(); |
| _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. |
| AtomicSet32(&_renderDeviceIsAlive, 0); |
| if (render_worker_thread_.get()) { |
| _critSect.Leave(); |
| render_worker_thread_->Stop(); |
| render_worker_thread_.reset(); |
| _critSect.Enter(); |
| } |
| |
| WEBRTC_CA_LOG_WARN(AudioConverterDispose(_renderConverter)); |
| |
| // Remove listeners. |
| AudioObjectPropertyAddress propertyAddress = { |
| kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput, 0}; |
| WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener( |
| _outputDeviceID, &propertyAddress, &objectListenerProc, this)); |
| |
| propertyAddress.mSelector = kAudioDeviceProcessorOverload; |
| WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener( |
| _outputDeviceID, &propertyAddress, &objectListenerProc, this)); |
| |
| if (_macBookPro) { |
| Boolean hasProperty = |
| AudioObjectHasProperty(_outputDeviceID, &propertyAddress); |
| if (hasProperty) { |
| propertyAddress.mSelector = kAudioDevicePropertyDataSource; |
| WEBRTC_CA_LOG_WARN(AudioObjectRemovePropertyListener( |
| _outputDeviceID, &propertyAddress, &objectListenerProc, this)); |
| } |
| } |
| |
| _playIsInitialized = false; |
| _playing = false; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceMac::PlayoutDelay(uint16_t& delayMS) const { |
| int32_t renderDelayUs = AtomicGet32(&_renderDelayUs); |
| delayMS = |
| static_cast<uint16_t>(1e-3 * (renderDelayUs + _renderLatencyUs) + 0.5); |
| return 0; |
| } |
| |
| 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 = absl::make_unique<AudioDeviceID[]>(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, |
| 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) { |
| 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) { |
| 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) { |
| 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<UInt32>( |
| (_outStreamFormat.mSampleRate / 1000.0) * kPlayBufDelayFixed * |
| _outStreamFormat.mChannelsPerFrame * sizeof(Float32)); |
| if (_outStreamFormat.mFramesPerPacket != 0) { |
| if (bufByteCount % _outStreamFormat.mFramesPerPacket != 0) { |
| bufByteCount = (static_cast<UInt32>(bufByteCount / |
| _outStreamFormat.mFramesPerPacket) + |
| 1) * |
| _outStreamFormat.mFramesPerPacket; |
| } |
| } |
| |
| // Ensure the buffer size is within the range provided by the device. |
| AudioObjectPropertyAddress propertyAddress = { |
| kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, 0}; |
| propertyAddress.mSelector = kAudioDevicePropertyBufferSizeRange; |
| AudioValueRange range; |
| UInt32 size = sizeof(range); |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &range)); |
| if (range.mMinimum > bufByteCount) { |
| bufByteCount = range.mMinimum; |
| } else if (range.mMaximum < bufByteCount) { |
| bufByteCount = range.mMaximum; |
| } |
| |
| propertyAddress.mSelector = kAudioDevicePropertyBufferSize; |
| size = sizeof(bufByteCount); |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectSetPropertyData( |
| _outputDeviceID, &propertyAddress, 0, NULL, size, &bufByteCount)); |
| |
| // Get render device latency. |
| propertyAddress.mSelector = kAudioDevicePropertyLatency; |
| UInt32 latency = 0; |
| size = sizeof(UInt32); |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &latency)); |
| _renderLatencyUs = |
| static_cast<uint32_t>((1.0e6 * latency) / _outStreamFormat.mSampleRate); |
| |
| // Get render stream latency. |
| propertyAddress.mSelector = kAudioDevicePropertyStreams; |
| AudioStreamID stream = 0; |
| size = sizeof(AudioStreamID); |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &stream)); |
| propertyAddress.mSelector = kAudioStreamPropertyLatency; |
| size = sizeof(UInt32); |
| latency = 0; |
| WEBRTC_CA_RETURN_ON_ERR(AudioObjectGetPropertyData( |
| _outputDeviceID, &propertyAddress, 0, NULL, &size, &latency)); |
| _renderLatencyUs += |
| static_cast<uint32_t>((1.0e6 * latency) / _outStreamFormat.mSampleRate); |
| |
| 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)"; |
| AtomicSet32(&_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)"; |
| AtomicSet32(&_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<AudioDeviceMac*>(userData); |
| RTC_DCHECK(ptrThis != NULL); |
| |
| return ptrThis->implInConverterProc(numberDataPackets, data); |
| } |
| |
| OSStatus AudioDeviceMac::implDeviceIOProc(const AudioBufferList* inputData, |
| const AudioTimeStamp* inputTime, |
| AudioBufferList* outputData, |
| const AudioTimeStamp* outputTime) { |
| OSStatus err = noErr; |
| UInt64 outputTimeNs = AudioConvertHostTimeToNanos(outputTime->mHostTime); |
| UInt64 nowNs = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); |
| |
| if (!_twoDevices && _recording) { |
| implInDeviceIOProc(inputData, inputTime); |
| } |
| |
| // Check if we should close down audio device |
| // Double-checked locking optimization to remove locking overhead |
| if (_doStop) { |
| _critSect.Enter(); |
| if (_doStop) { |
| if (_twoDevices || (!_recording && !_playing)) { |
| // In the case of a shared device, the single driving ioProc |
| // is stopped here |
| WEBRTC_CA_LOG_ERR(AudioDeviceStop(_outputDeviceID, _deviceIOProcID)); |
| WEBRTC_CA_LOG_WARN( |
| AudioDeviceDestroyIOProcID(_outputDeviceID, _deviceIOProcID)); |
| if (err == noErr) { |
| RTC_LOG(LS_VERBOSE) << "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. |
| RTC_LOG(LS_ERROR) << "Error in AudioConverterFillComplexBuffer()"; |
| return 1; |
| } else { |
| logCAMsg(rtc::LS_ERROR, "Error in AudioConverterFillComplexBuffer()", |
| (const char*)&err); |
| return 1; |
| } |
| } |
| |
| PaRingBufferSize bufSizeSamples = |
| PaUtil_GetRingBufferReadAvailable(_paRenderBuffer); |
| |
| int32_t renderDelayUs = |
| static_cast<int32_t>(1e-3 * (outputTimeNs - nowNs) + 0.5); |
| renderDelayUs += static_cast<int32_t>( |
| (1.0e6 * bufSizeSamples) / _outDesiredFormat.mChannelsPerFrame / |
| _outDesiredFormat.mSampleRate + |
| 0.5); |
| |
| AtomicSet32(&_renderDelayUs, renderDelayUs); |
| |
| return 0; |
| } |
| |
| OSStatus AudioDeviceMac::implOutConverterProc(UInt32* numberDataPackets, |
| AudioBufferList* data) { |
| RTC_DCHECK(data->mNumberBuffers == 1); |
| PaRingBufferSize numSamples = |
| *numberDataPackets * _outDesiredFormat.mChannelsPerFrame; |
| |
| data->mBuffers->mNumberChannels = _outDesiredFormat.mChannelsPerFrame; |
| // Always give the converter as much as it wants, zero padding as required. |
| data->mBuffers->mDataByteSize = |
| *numberDataPackets * _outDesiredFormat.mBytesPerPacket; |
| data->mBuffers->mData = _renderConvertData; |
| memset(_renderConvertData, 0, sizeof(_renderConvertData)); |
| |
| PaUtil_ReadRingBuffer(_paRenderBuffer, _renderConvertData, numSamples); |
| |
| kern_return_t kernErr = semaphore_signal_all(_renderSemaphore); |
| if (kernErr != KERN_SUCCESS) { |
| 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) { |
| _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) { |
| RTC_LOG(LS_VERBOSE) << "Recording device stopped"; |
| } |
| |
| _doStopRec = false; |
| _stopEventRec.Set(); |
| _critSect.Leave(); |
| return 0; |
| } |
| _critSect.Leave(); |
| } |
| |
| if (!_recording) { |
| // Allow above checks to avoid a timeout on stopping capture. |
| return 0; |
| } |
| |
| PaRingBufferSize bufSizeSamples = |
| PaUtil_GetRingBufferReadAvailable(_paCaptureBuffer); |
| |
| int32_t captureDelayUs = |
| static_cast<int32_t>(1e-3 * (nowNs - inputTimeNs) + 0.5); |
| captureDelayUs += static_cast<int32_t>((1.0e6 * bufSizeSamples) / |
| _inStreamFormat.mChannelsPerFrame / |
| _inStreamFormat.mSampleRate + |
| 0.5); |
| |
| AtomicSet32(&_captureDelayUs, captureDelayUs); |
| |
| RTC_DCHECK(inputData->mNumberBuffers == 1); |
| PaRingBufferSize numSamples = inputData->mBuffers->mDataByteSize * |
| _inStreamFormat.mChannelsPerFrame / |
| _inStreamFormat.mBytesPerPacket; |
| PaUtil_WriteRingBuffer(_paCaptureBuffer, inputData->mBuffers->mData, |
| numSamples); |
| |
| kern_return_t kernErr = semaphore_signal_all(_captureSemaphore); |
| if (kernErr != KERN_SUCCESS) { |
| RTC_LOG(LS_ERROR) << "semaphore_signal_all() error: " << 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) { |
| RTC_LOG(LS_ERROR) << "semaphore_wait() error: " << kernErr; |
| } |
| } |
| |
| // Pass the read pointer directly to the converter to avoid a memcpy. |
| void* dummyPtr; |
| PaRingBufferSize dummySize; |
| PaUtil_GetRingBufferReadRegions(_paCaptureBuffer, numSamples, |
| &data->mBuffers->mData, &numSamples, |
| &dummyPtr, &dummySize); |
| PaUtil_AdvanceRingBufferReadIndex(_paCaptureBuffer, numSamples); |
| |
| data->mBuffers->mNumberChannels = _inStreamFormat.mChannelsPerFrame; |
| *numberDataPackets = numSamples / _inStreamFormat.mChannelsPerFrame; |
| data->mBuffers->mDataByteSize = |
| *numberDataPackets * _inStreamFormat.mBytesPerPacket; |
| |
| return 0; |
| } |
| |
| bool AudioDeviceMac::RunRender(void* ptrThis) { |
| return static_cast<AudioDeviceMac*>(ptrThis)->RenderWorkerThread(); |
| } |
| |
| bool AudioDeviceMac::RenderWorkerThread() { |
| PaRingBufferSize numSamples = |
| ENGINE_PLAY_BUF_SIZE_IN_SAMPLES * _outDesiredFormat.mChannelsPerFrame; |
| while (PaUtil_GetRingBufferWriteAvailable(_paRenderBuffer) - |
| _renderDelayOffsetSamples < |
| numSamples) { |
| mach_timespec_t timeout; |
| timeout.tv_sec = 0; |
| timeout.tv_nsec = TIMER_PERIOD_MS; |
| |
| kern_return_t kernErr = semaphore_timedwait(_renderSemaphore, timeout); |
| if (kernErr == KERN_OPERATION_TIMED_OUT) { |
| int32_t signal = AtomicGet32(&_renderDeviceIsAlive); |
| if (signal == 0) { |
| // The render device is no longer alive; stop the worker thread. |
| return false; |
| } |
| } else if (kernErr != KERN_SUCCESS) { |
| 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<SInt16>(sampleInt32); |
| } |
| } |
| |
| PaUtil_WriteRingBuffer(_paRenderBuffer, pPlayBuffer, nOutSamples); |
| |
| return true; |
| } |
| |
| bool AudioDeviceMac::RunCapture(void* ptrThis) { |
| return static_cast<AudioDeviceMac*>(ptrThis)->CaptureWorkerThread(); |
| } |
| |
| bool AudioDeviceMac::CaptureWorkerThread() { |
| OSStatus err = noErr; |
| UInt32 noRecSamples = |
| ENGINE_REC_BUF_SIZE_IN_SAMPLES * _inDesiredFormat.mChannelsPerFrame; |
| SInt16 recordBuffer[noRecSamples]; |
| UInt32 size = ENGINE_REC_BUF_SIZE_IN_SAMPLES; |
| |
| AudioBufferList engineBuffer; |
| engineBuffer.mNumberBuffers = 1; // Interleaved channels. |
| engineBuffer.mBuffers->mNumberChannels = _inDesiredFormat.mChannelsPerFrame; |
| engineBuffer.mBuffers->mDataByteSize = |
| _inDesiredFormat.mBytesPerPacket * noRecSamples; |
| engineBuffer.mBuffers->mData = recordBuffer; |
| |
| err = AudioConverterFillComplexBuffer(_captureConverter, inConverterProc, |
| this, &size, &engineBuffer, NULL); |
| if (err != noErr) { |
| if (err == 1) { |
| // This is our own error. |
| return false; |
| } else { |
| logCAMsg(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 = AtomicGet32(&_captureDelayUs); |
| int32_t renderDelayUs = AtomicGet32(&_renderDelayUs); |
| |
| msecOnPlaySide = |
| static_cast<int32_t>(1e-3 * (renderDelayUs + _renderLatencyUs) + 0.5); |
| msecOnRecordSide = |
| static_cast<int32_t>(1e-3 * (captureDelayUs + _captureLatencyUs) + 0.5); |
| |
| if (!_ptrAudioBuffer) { |
| 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 |