| /* |
| * 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 <assert.h> |
| |
| #include "webrtc/base/checks.h" |
| #include "webrtc/base/logging.h" |
| #include "webrtc/modules/audio_device/audio_device_config.h" |
| #include "webrtc/modules/audio_device/linux/audio_device_pulse_linux.h" |
| #include "webrtc/system_wrappers/include/event_wrapper.h" |
| #include "webrtc/system_wrappers/include/trace.h" |
| |
| webrtc_adm_linux_pulse::PulseAudioSymbolTable PaSymbolTable; |
| |
| // Accesses Pulse functions through our late-binding symbol table instead of |
| // directly. This way we don't have to link to libpulse, which means our binary |
| // will work on systems that don't have it. |
| #define LATE(sym) \ |
| LATESYM_GET(webrtc_adm_linux_pulse::PulseAudioSymbolTable, &PaSymbolTable, sym) |
| |
| namespace webrtc |
| { |
| |
| AudioDeviceLinuxPulse::AudioDeviceLinuxPulse(const int32_t id) : |
| _ptrAudioBuffer(NULL), |
| _critSect(*CriticalSectionWrapper::CreateCriticalSection()), |
| _timeEventRec(*EventWrapper::Create()), |
| _timeEventPlay(*EventWrapper::Create()), |
| _recStartEvent(*EventWrapper::Create()), |
| _playStartEvent(*EventWrapper::Create()), |
| _id(id), |
| _mixerManager(id), |
| _inputDeviceIndex(0), |
| _outputDeviceIndex(0), |
| _inputDeviceIsSpecified(false), |
| _outputDeviceIsSpecified(false), |
| sample_rate_hz_(0), |
| _recChannels(1), |
| _playChannels(1), |
| _playBufType(AudioDeviceModule::kFixedBufferSize), |
| _initialized(false), |
| _recording(false), |
| _playing(false), |
| _recIsInitialized(false), |
| _playIsInitialized(false), |
| _startRec(false), |
| _stopRec(false), |
| _startPlay(false), |
| _stopPlay(false), |
| _AGC(false), |
| update_speaker_volume_at_startup_(false), |
| _playBufDelayFixed(20), |
| _sndCardPlayDelay(0), |
| _sndCardRecDelay(0), |
| _writeErrors(0), |
| _playWarning(0), |
| _playError(0), |
| _recWarning(0), |
| _recError(0), |
| _deviceIndex(-1), |
| _numPlayDevices(0), |
| _numRecDevices(0), |
| _playDeviceName(NULL), |
| _recDeviceName(NULL), |
| _playDisplayDeviceName(NULL), |
| _recDisplayDeviceName(NULL), |
| _playBuffer(NULL), |
| _playbackBufferSize(0), |
| _playbackBufferUnused(0), |
| _tempBufferSpace(0), |
| _recBuffer(NULL), |
| _recordBufferSize(0), |
| _recordBufferUsed(0), |
| _tempSampleData(NULL), |
| _tempSampleDataSize(0), |
| _configuredLatencyPlay(0), |
| _configuredLatencyRec(0), |
| _paDeviceIndex(-1), |
| _paStateChanged(false), |
| _paMainloop(NULL), |
| _paMainloopApi(NULL), |
| _paContext(NULL), |
| _recStream(NULL), |
| _playStream(NULL), |
| _recStreamFlags(0), |
| _playStreamFlags(0) |
| { |
| WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, id, |
| "%s created", __FUNCTION__); |
| |
| memset(_paServerVersion, 0, sizeof(_paServerVersion)); |
| memset(&_playBufferAttr, 0, sizeof(_playBufferAttr)); |
| memset(&_recBufferAttr, 0, sizeof(_recBufferAttr)); |
| memset(_oldKeyState, 0, sizeof(_oldKeyState)); |
| } |
| |
| AudioDeviceLinuxPulse::~AudioDeviceLinuxPulse() |
| { |
| WEBRTC_TRACE(kTraceMemory, kTraceAudioDevice, _id, |
| "%s destroyed", __FUNCTION__); |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| Terminate(); |
| |
| if (_recBuffer) |
| { |
| delete [] _recBuffer; |
| _recBuffer = NULL; |
| } |
| if (_playBuffer) |
| { |
| delete [] _playBuffer; |
| _playBuffer = NULL; |
| } |
| if (_playDeviceName) |
| { |
| delete [] _playDeviceName; |
| _playDeviceName = NULL; |
| } |
| if (_recDeviceName) |
| { |
| delete [] _recDeviceName; |
| _recDeviceName = NULL; |
| } |
| |
| delete &_recStartEvent; |
| delete &_playStartEvent; |
| delete &_timeEventRec; |
| delete &_timeEventPlay; |
| delete &_critSect; |
| } |
| |
| void AudioDeviceLinuxPulse::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| _ptrAudioBuffer = audioBuffer; |
| |
| // Inform the AudioBuffer about default settings for this implementation. |
| // Set all values to zero here since the actual settings will be done by |
| // InitPlayout and InitRecording later. |
| _ptrAudioBuffer->SetRecordingSampleRate(0); |
| _ptrAudioBuffer->SetPlayoutSampleRate(0); |
| _ptrAudioBuffer->SetRecordingChannels(0); |
| _ptrAudioBuffer->SetPlayoutChannels(0); |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // ActiveAudioLayer |
| // ---------------------------------------------------------------------------- |
| |
| int32_t AudioDeviceLinuxPulse::ActiveAudioLayer( |
| AudioDeviceModule::AudioLayer& audioLayer) const |
| { |
| audioLayer = AudioDeviceModule::kLinuxPulseAudio; |
| return 0; |
| } |
| |
| AudioDeviceGeneric::InitStatus AudioDeviceLinuxPulse::Init() { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (_initialized) { |
| return InitStatus::OK; |
| } |
| |
| // Initialize PulseAudio |
| if (InitPulseAudio() < 0) { |
| LOG(LS_ERROR) << "failed to initialize PulseAudio"; |
| if (TerminatePulseAudio() < 0) { |
| LOG(LS_ERROR) << "failed to terminate PulseAudio"; |
| } |
| return InitStatus::OTHER_ERROR; |
| } |
| |
| _playWarning = 0; |
| _playError = 0; |
| _recWarning = 0; |
| _recError = 0; |
| |
| // Get X display handle for typing detection |
| _XDisplay = XOpenDisplay(NULL); |
| if (!_XDisplay) { |
| LOG(LS_WARNING) |
| << "failed to open X display, typing detection will not work"; |
| } |
| |
| // RECORDING |
| _ptrThreadRec.reset(new rtc::PlatformThread( |
| RecThreadFunc, this, "webrtc_audio_module_rec_thread")); |
| |
| _ptrThreadRec->Start(); |
| _ptrThreadRec->SetPriority(rtc::kRealtimePriority); |
| |
| // PLAYOUT |
| _ptrThreadPlay.reset(new rtc::PlatformThread( |
| PlayThreadFunc, this, "webrtc_audio_module_play_thread")); |
| _ptrThreadPlay->Start(); |
| _ptrThreadPlay->SetPriority(rtc::kRealtimePriority); |
| |
| _initialized = true; |
| |
| return InitStatus::OK; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::Terminate() |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!_initialized) |
| { |
| return 0; |
| } |
| |
| _mixerManager.Close(); |
| |
| // RECORDING |
| if (_ptrThreadRec) |
| { |
| rtc::PlatformThread* tmpThread = _ptrThreadRec.release(); |
| |
| _timeEventRec.Set(); |
| tmpThread->Stop(); |
| delete tmpThread; |
| } |
| |
| // PLAYOUT |
| if (_ptrThreadPlay) |
| { |
| rtc::PlatformThread* tmpThread = _ptrThreadPlay.release(); |
| |
| _timeEventPlay.Set(); |
| tmpThread->Stop(); |
| delete tmpThread; |
| } |
| |
| // Terminate PulseAudio |
| if (TerminatePulseAudio() < 0) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to terminate PulseAudio"); |
| return -1; |
| } |
| |
| if (_XDisplay) |
| { |
| XCloseDisplay(_XDisplay); |
| _XDisplay = NULL; |
| } |
| |
| _initialized = false; |
| _outputDeviceIsSpecified = false; |
| _inputDeviceIsSpecified = false; |
| |
| return 0; |
| } |
| |
| bool AudioDeviceLinuxPulse::Initialized() const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_initialized); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::InitSpeaker() |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (_playing) |
| { |
| return -1; |
| } |
| |
| if (!_outputDeviceIsSpecified) |
| { |
| return -1; |
| } |
| |
| // check if default device |
| if (_outputDeviceIndex == 0) |
| { |
| uint16_t deviceIndex = 0; |
| GetDefaultDeviceInfo(false, NULL, deviceIndex); |
| _paDeviceIndex = deviceIndex; |
| } else |
| { |
| // get the PA device index from |
| // the callback |
| _deviceIndex = _outputDeviceIndex; |
| |
| // get playout devices |
| PlayoutDevices(); |
| } |
| |
| // the callback has now set the _paDeviceIndex to |
| // the PulseAudio index of the device |
| if (_mixerManager.OpenSpeaker(_paDeviceIndex) == -1) |
| { |
| return -1; |
| } |
| |
| // clear _deviceIndex |
| _deviceIndex = -1; |
| _paDeviceIndex = -1; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::InitMicrophone() |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (_recording) |
| { |
| return -1; |
| } |
| |
| if (!_inputDeviceIsSpecified) |
| { |
| return -1; |
| } |
| |
| // Check if default device |
| if (_inputDeviceIndex == 0) |
| { |
| uint16_t deviceIndex = 0; |
| GetDefaultDeviceInfo(true, NULL, deviceIndex); |
| _paDeviceIndex = deviceIndex; |
| } else |
| { |
| // Get the PA device index from |
| // the callback |
| _deviceIndex = _inputDeviceIndex; |
| |
| // get recording devices |
| RecordingDevices(); |
| } |
| |
| // The callback has now set the _paDeviceIndex to |
| // the PulseAudio index of the device |
| if (_mixerManager.OpenMicrophone(_paDeviceIndex) == -1) |
| { |
| return -1; |
| } |
| |
| // Clear _deviceIndex |
| _deviceIndex = -1; |
| _paDeviceIndex = -1; |
| |
| return 0; |
| } |
| |
| bool AudioDeviceLinuxPulse::SpeakerIsInitialized() const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_mixerManager.SpeakerIsInitialized()); |
| } |
| |
| bool AudioDeviceLinuxPulse::MicrophoneIsInitialized() const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_mixerManager.MicrophoneIsInitialized()); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SpeakerVolumeIsAvailable(bool& available) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| 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 volume control exists. |
| available = true; |
| |
| // Close the initialized output mixer |
| if (!wasInitialized) |
| { |
| _mixerManager.CloseSpeaker(); |
| } |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetSpeakerVolume(uint32_t volume) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!_playing) { |
| // Only update the volume if it's been set while we weren't playing. |
| update_speaker_volume_at_startup_ = true; |
| } |
| return (_mixerManager.SetSpeakerVolume(volume)); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SpeakerVolume(uint32_t& volume) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| uint32_t level(0); |
| |
| if (_mixerManager.SpeakerVolume(level) == -1) |
| { |
| return -1; |
| } |
| |
| volume = level; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetWaveOutVolume( |
| uint16_t volumeLeft, |
| uint16_t volumeRight) |
| { |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::WaveOutVolume( |
| uint16_t& /*volumeLeft*/, |
| uint16_t& /*volumeRight*/) const |
| { |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MaxSpeakerVolume( |
| uint32_t& maxVolume) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| uint32_t maxVol(0); |
| |
| if (_mixerManager.MaxSpeakerVolume(maxVol) == -1) |
| { |
| return -1; |
| } |
| |
| maxVolume = maxVol; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MinSpeakerVolume( |
| uint32_t& minVolume) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| uint32_t minVol(0); |
| |
| if (_mixerManager.MinSpeakerVolume(minVol) == -1) |
| { |
| return -1; |
| } |
| |
| minVolume = minVol; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SpeakerVolumeStepSize( |
| uint16_t& stepSize) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| uint16_t delta(0); |
| |
| if (_mixerManager.SpeakerVolumeStepSize(delta) == -1) |
| { |
| return -1; |
| } |
| |
| stepSize = delta; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SpeakerMuteIsAvailable(bool& available) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| 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 AudioDeviceLinuxPulse::SetSpeakerMute(bool enable) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_mixerManager.SetSpeakerMute(enable)); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SpeakerMute(bool& enabled) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| bool muted(0); |
| if (_mixerManager.SpeakerMute(muted) == -1) |
| { |
| return -1; |
| } |
| |
| enabled = muted; |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MicrophoneMuteIsAvailable(bool& available) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| 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 AudioDeviceLinuxPulse::SetMicrophoneMute(bool enable) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_mixerManager.SetMicrophoneMute(enable)); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MicrophoneMute(bool& enabled) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| bool muted(0); |
| if (_mixerManager.MicrophoneMute(muted) == -1) |
| { |
| return -1; |
| } |
| |
| enabled = muted; |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MicrophoneBoostIsAvailable(bool& available) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| bool isAvailable(false); |
| bool wasInitialized = _mixerManager.MicrophoneIsInitialized(); |
| |
| // Enumerate all avaliable microphone and make an attempt to open up the |
| // input mixer corresponding to the currently selected input device. |
| // |
| if (!wasInitialized && InitMicrophone() == -1) |
| { |
| // If we end up here it means that the selected microphone has no |
| // volume control, hence it is safe to state that there is no |
| // boost control already at this stage. |
| available = false; |
| return 0; |
| } |
| |
| // Check if the selected microphone has a boost control |
| _mixerManager.MicrophoneBoostIsAvailable(isAvailable); |
| available = isAvailable; |
| |
| // Close the initialized input mixer |
| if (!wasInitialized) |
| { |
| _mixerManager.CloseMicrophone(); |
| } |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetMicrophoneBoost(bool enable) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_mixerManager.SetMicrophoneBoost(enable)); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MicrophoneBoost(bool& enabled) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| bool onOff(0); |
| |
| if (_mixerManager.MicrophoneBoost(onOff) == -1) |
| { |
| return -1; |
| } |
| |
| enabled = onOff; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::StereoRecordingIsAvailable(bool& available) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (_recChannels == 2 && _recording) { |
| available = true; |
| return 0; |
| } |
| |
| available = false; |
| bool wasInitialized = _mixerManager.MicrophoneIsInitialized(); |
| int error = 0; |
| |
| if (!wasInitialized && InitMicrophone() == -1) |
| { |
| // Cannot open the specified device |
| available = false; |
| return 0; |
| } |
| |
| // Check if the selected microphone can record stereo. |
| bool isAvailable(false); |
| error = _mixerManager.StereoRecordingIsAvailable(isAvailable); |
| if (!error) |
| available = isAvailable; |
| |
| // Close the initialized input mixer |
| if (!wasInitialized) |
| { |
| _mixerManager.CloseMicrophone(); |
| } |
| |
| return error; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetStereoRecording(bool enable) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (enable) |
| _recChannels = 2; |
| else |
| _recChannels = 1; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::StereoRecording(bool& enabled) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (_recChannels == 2) |
| enabled = true; |
| else |
| enabled = false; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::StereoPlayoutIsAvailable(bool& available) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (_playChannels == 2 && _playing) { |
| available = true; |
| return 0; |
| } |
| |
| available = false; |
| bool wasInitialized = _mixerManager.SpeakerIsInitialized(); |
| int error = 0; |
| |
| if (!wasInitialized && InitSpeaker() == -1) |
| { |
| // Cannot open the specified device. |
| return -1; |
| } |
| |
| // Check if the selected speaker can play stereo. |
| bool isAvailable(false); |
| error = _mixerManager.StereoPlayoutIsAvailable(isAvailable); |
| if (!error) |
| available = isAvailable; |
| |
| // Close the initialized input mixer |
| if (!wasInitialized) |
| { |
| _mixerManager.CloseSpeaker(); |
| } |
| |
| return error; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetStereoPlayout(bool enable) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (enable) |
| _playChannels = 2; |
| else |
| _playChannels = 1; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::StereoPlayout(bool& enabled) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (_playChannels == 2) |
| enabled = true; |
| else |
| enabled = false; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetAGC(bool enable) |
| { |
| CriticalSectionScoped lock(&_critSect); |
| _AGC = enable; |
| |
| return 0; |
| } |
| |
| bool AudioDeviceLinuxPulse::AGC() const |
| { |
| CriticalSectionScoped lock(&_critSect); |
| return _AGC; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MicrophoneVolumeIsAvailable( |
| bool& available) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| 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 AudioDeviceLinuxPulse::SetMicrophoneVolume(uint32_t volume) |
| { |
| return (_mixerManager.SetMicrophoneVolume(volume)); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MicrophoneVolume( |
| uint32_t& volume) const |
| { |
| |
| uint32_t level(0); |
| |
| if (_mixerManager.MicrophoneVolume(level) == -1) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " failed to retrive current microphone level"); |
| return -1; |
| } |
| |
| volume = level; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MaxMicrophoneVolume( |
| uint32_t& maxVolume) const |
| { |
| |
| uint32_t maxVol(0); |
| |
| if (_mixerManager.MaxMicrophoneVolume(maxVol) == -1) |
| { |
| return -1; |
| } |
| |
| maxVolume = maxVol; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MinMicrophoneVolume( |
| uint32_t& minVolume) const |
| { |
| |
| uint32_t minVol(0); |
| |
| if (_mixerManager.MinMicrophoneVolume(minVol) == -1) |
| { |
| return -1; |
| } |
| |
| minVolume = minVol; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::MicrophoneVolumeStepSize( |
| uint16_t& stepSize) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| uint16_t delta(0); |
| |
| if (_mixerManager.MicrophoneVolumeStepSize(delta) == -1) |
| { |
| return -1; |
| } |
| |
| stepSize = delta; |
| |
| return 0; |
| } |
| |
| int16_t AudioDeviceLinuxPulse::PlayoutDevices() |
| { |
| PaLock(); |
| |
| pa_operation* paOperation = NULL; |
| _numPlayDevices = 1; // init to 1 to account for "default" |
| |
| // get the whole list of devices and update _numPlayDevices |
| paOperation = LATE(pa_context_get_sink_info_list)(_paContext, |
| PaSinkInfoCallback, |
| this); |
| |
| WaitForOperationCompletion(paOperation); |
| |
| PaUnLock(); |
| |
| return _numPlayDevices; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetPlayoutDevice(uint16_t index) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (_playIsInitialized) |
| { |
| return -1; |
| } |
| |
| const uint16_t nDevices = PlayoutDevices(); |
| |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| " number of availiable output devices is %u", nDevices); |
| |
| if (index > (nDevices - 1)) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " device index is out of range [0,%u]", (nDevices - 1)); |
| return -1; |
| } |
| |
| _outputDeviceIndex = index; |
| _outputDeviceIsSpecified = true; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetPlayoutDevice( |
| AudioDeviceModule::WindowsDeviceType /*device*/) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "WindowsDeviceType not supported"); |
| return -1; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::PlayoutDeviceName( |
| uint16_t index, |
| char name[kAdmMaxDeviceNameSize], |
| char guid[kAdmMaxGuidSize]) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| const uint16_t nDevices = PlayoutDevices(); |
| |
| if ((index > (nDevices - 1)) || (name == NULL)) |
| { |
| return -1; |
| } |
| |
| memset(name, 0, kAdmMaxDeviceNameSize); |
| |
| if (guid != NULL) |
| { |
| memset(guid, 0, kAdmMaxGuidSize); |
| } |
| |
| // Check if default device |
| if (index == 0) |
| { |
| uint16_t deviceIndex = 0; |
| return GetDefaultDeviceInfo(false, name, deviceIndex); |
| } |
| |
| // Tell the callback that we want |
| // The name for this device |
| _playDisplayDeviceName = name; |
| _deviceIndex = index; |
| |
| // get playout devices |
| PlayoutDevices(); |
| |
| // clear device name and index |
| _playDisplayDeviceName = NULL; |
| _deviceIndex = -1; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::RecordingDeviceName( |
| uint16_t index, |
| char name[kAdmMaxDeviceNameSize], |
| char guid[kAdmMaxGuidSize]) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| const uint16_t nDevices(RecordingDevices()); |
| |
| if ((index > (nDevices - 1)) || (name == NULL)) |
| { |
| return -1; |
| } |
| |
| memset(name, 0, kAdmMaxDeviceNameSize); |
| |
| if (guid != NULL) |
| { |
| memset(guid, 0, kAdmMaxGuidSize); |
| } |
| |
| // Check if default device |
| if (index == 0) |
| { |
| uint16_t deviceIndex = 0; |
| return GetDefaultDeviceInfo(true, name, deviceIndex); |
| } |
| |
| // Tell the callback that we want |
| // the name for this device |
| _recDisplayDeviceName = name; |
| _deviceIndex = index; |
| |
| // Get recording devices |
| RecordingDevices(); |
| |
| // Clear device name and index |
| _recDisplayDeviceName = NULL; |
| _deviceIndex = -1; |
| |
| return 0; |
| } |
| |
| int16_t AudioDeviceLinuxPulse::RecordingDevices() |
| { |
| PaLock(); |
| |
| pa_operation* paOperation = NULL; |
| _numRecDevices = 1; // Init to 1 to account for "default" |
| |
| // Get the whole list of devices and update _numRecDevices |
| paOperation = LATE(pa_context_get_source_info_list)(_paContext, |
| PaSourceInfoCallback, |
| this); |
| |
| WaitForOperationCompletion(paOperation); |
| |
| PaUnLock(); |
| |
| return _numRecDevices; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetRecordingDevice(uint16_t index) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (_recIsInitialized) |
| { |
| return -1; |
| } |
| |
| const uint16_t nDevices(RecordingDevices()); |
| |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| " number of availiable input devices is %u", nDevices); |
| |
| if (index > (nDevices - 1)) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " device index is out of range [0,%u]", (nDevices - 1)); |
| return -1; |
| } |
| |
| _inputDeviceIndex = index; |
| _inputDeviceIsSpecified = true; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetRecordingDevice( |
| AudioDeviceModule::WindowsDeviceType /*device*/) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "WindowsDeviceType not supported"); |
| return -1; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::PlayoutIsAvailable(bool& available) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| available = false; |
| |
| // Try to initialize the playout side |
| int32_t res = InitPlayout(); |
| |
| // Cancel effect of initialization |
| StopPlayout(); |
| |
| if (res != -1) |
| { |
| available = true; |
| } |
| |
| return res; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::RecordingIsAvailable(bool& available) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| available = false; |
| |
| // Try to initialize the playout side |
| int32_t res = InitRecording(); |
| |
| // Cancel effect of initialization |
| StopRecording(); |
| |
| if (res != -1) |
| { |
| available = true; |
| } |
| |
| return res; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::InitPlayout() |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (_playing) |
| { |
| return -1; |
| } |
| |
| if (!_outputDeviceIsSpecified) |
| { |
| return -1; |
| } |
| |
| if (_playIsInitialized) |
| { |
| return 0; |
| } |
| |
| // Initialize the speaker (devices might have been added or removed) |
| if (InitSpeaker() == -1) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " InitSpeaker() failed"); |
| } |
| |
| // Set the play sample specification |
| pa_sample_spec playSampleSpec; |
| playSampleSpec.channels = _playChannels; |
| playSampleSpec.format = PA_SAMPLE_S16LE; |
| playSampleSpec.rate = sample_rate_hz_; |
| |
| // Create a new play stream |
| _playStream = LATE(pa_stream_new)(_paContext, "playStream", |
| &playSampleSpec, NULL); |
| |
| if (!_playStream) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to create play stream, err=%d", |
| LATE(pa_context_errno)(_paContext)); |
| return -1; |
| } |
| |
| // Provide the playStream to the mixer |
| _mixerManager.SetPlayStream(_playStream); |
| |
| if (_ptrAudioBuffer) |
| { |
| // Update audio buffer with the selected parameters |
| _ptrAudioBuffer->SetPlayoutSampleRate(sample_rate_hz_); |
| _ptrAudioBuffer->SetPlayoutChannels((uint8_t) _playChannels); |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " stream state %d\n", |
| LATE(pa_stream_get_state)(_playStream)); |
| |
| // Set stream flags |
| _playStreamFlags = (pa_stream_flags_t) (PA_STREAM_AUTO_TIMING_UPDATE |
| | PA_STREAM_INTERPOLATE_TIMING); |
| |
| if (_configuredLatencyPlay != WEBRTC_PA_NO_LATENCY_REQUIREMENTS) |
| { |
| // If configuring a specific latency then we want to specify |
| // PA_STREAM_ADJUST_LATENCY to make the server adjust parameters |
| // automatically to reach that target latency. However, that flag |
| // doesn't exist in Ubuntu 8.04 and many people still use that, |
| // so we have to check the protocol version of libpulse. |
| if (LATE(pa_context_get_protocol_version)(_paContext) |
| >= WEBRTC_PA_ADJUST_LATENCY_PROTOCOL_VERSION) |
| { |
| _playStreamFlags |= PA_STREAM_ADJUST_LATENCY; |
| } |
| |
| const pa_sample_spec *spec = |
| LATE(pa_stream_get_sample_spec)(_playStream); |
| if (!spec) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " pa_stream_get_sample_spec()"); |
| return -1; |
| } |
| |
| size_t bytesPerSec = LATE(pa_bytes_per_second)(spec); |
| uint32_t latency = bytesPerSec * |
| WEBRTC_PA_PLAYBACK_LATENCY_MINIMUM_MSECS / |
| WEBRTC_PA_MSECS_PER_SEC; |
| |
| // Set the play buffer attributes |
| _playBufferAttr.maxlength = latency; // num bytes stored in the buffer |
| _playBufferAttr.tlength = latency; // target fill level of play buffer |
| // minimum free num bytes before server request more data |
| _playBufferAttr.minreq = latency / WEBRTC_PA_PLAYBACK_REQUEST_FACTOR; |
| // prebuffer tlength before starting playout |
| _playBufferAttr.prebuf = _playBufferAttr.tlength - |
| _playBufferAttr.minreq; |
| |
| _configuredLatencyPlay = latency; |
| } |
| |
| // num samples in bytes * num channels |
| _playbackBufferSize = sample_rate_hz_ / 100 * 2 * _playChannels; |
| _playbackBufferUnused = _playbackBufferSize; |
| _playBuffer = new int8_t[_playbackBufferSize]; |
| |
| // Enable underflow callback |
| LATE(pa_stream_set_underflow_callback)(_playStream, |
| PaStreamUnderflowCallback, this); |
| |
| // Set the state callback function for the stream |
| LATE(pa_stream_set_state_callback)(_playStream, |
| PaStreamStateCallback, this); |
| |
| // Mark playout side as initialized |
| _playIsInitialized = true; |
| _sndCardPlayDelay = 0; |
| _sndCardRecDelay = 0; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::InitRecording() |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (_recording) |
| { |
| return -1; |
| } |
| |
| if (!_inputDeviceIsSpecified) |
| { |
| return -1; |
| } |
| |
| if (_recIsInitialized) |
| { |
| return 0; |
| } |
| |
| // Initialize the microphone (devices might have been added or removed) |
| if (InitMicrophone() == -1) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " InitMicrophone() failed"); |
| } |
| |
| // Set the rec sample specification |
| pa_sample_spec recSampleSpec; |
| recSampleSpec.channels = _recChannels; |
| recSampleSpec.format = PA_SAMPLE_S16LE; |
| recSampleSpec.rate = sample_rate_hz_; |
| |
| // Create a new rec stream |
| _recStream = LATE(pa_stream_new)(_paContext, "recStream", &recSampleSpec, |
| NULL); |
| if (!_recStream) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to create rec stream, err=%d", |
| LATE(pa_context_errno)(_paContext)); |
| return -1; |
| } |
| |
| // Provide the recStream to the mixer |
| _mixerManager.SetRecStream(_recStream); |
| |
| if (_ptrAudioBuffer) |
| { |
| // Update audio buffer with the selected parameters |
| _ptrAudioBuffer->SetRecordingSampleRate(sample_rate_hz_); |
| _ptrAudioBuffer->SetRecordingChannels((uint8_t) _recChannels); |
| } |
| |
| if (_configuredLatencyRec != WEBRTC_PA_NO_LATENCY_REQUIREMENTS) |
| { |
| _recStreamFlags = (pa_stream_flags_t) (PA_STREAM_AUTO_TIMING_UPDATE |
| | PA_STREAM_INTERPOLATE_TIMING); |
| |
| // If configuring a specific latency then we want to specify |
| // PA_STREAM_ADJUST_LATENCY to make the server adjust parameters |
| // automatically to reach that target latency. However, that flag |
| // doesn't exist in Ubuntu 8.04 and many people still use that, |
| // so we have to check the protocol version of libpulse. |
| if (LATE(pa_context_get_protocol_version)(_paContext) |
| >= WEBRTC_PA_ADJUST_LATENCY_PROTOCOL_VERSION) |
| { |
| _recStreamFlags |= PA_STREAM_ADJUST_LATENCY; |
| } |
| |
| const pa_sample_spec *spec = |
| LATE(pa_stream_get_sample_spec)(_recStream); |
| if (!spec) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " pa_stream_get_sample_spec(rec)"); |
| return -1; |
| } |
| |
| size_t bytesPerSec = LATE(pa_bytes_per_second)(spec); |
| uint32_t latency = bytesPerSec |
| * WEBRTC_PA_LOW_CAPTURE_LATENCY_MSECS / WEBRTC_PA_MSECS_PER_SEC; |
| |
| // Set the rec buffer attributes |
| // Note: fragsize specifies a maximum transfer size, not a minimum, so |
| // it is not possible to force a high latency setting, only a low one. |
| _recBufferAttr.fragsize = latency; // size of fragment |
| _recBufferAttr.maxlength = latency + bytesPerSec |
| * WEBRTC_PA_CAPTURE_BUFFER_EXTRA_MSECS / WEBRTC_PA_MSECS_PER_SEC; |
| |
| _configuredLatencyRec = latency; |
| } |
| |
| _recordBufferSize = sample_rate_hz_ / 100 * 2 * _recChannels; |
| _recordBufferUsed = 0; |
| _recBuffer = new int8_t[_recordBufferSize]; |
| |
| // Enable overflow callback |
| LATE(pa_stream_set_overflow_callback)(_recStream, |
| PaStreamOverflowCallback, |
| this); |
| |
| // Set the state callback function for the stream |
| LATE(pa_stream_set_state_callback)(_recStream, |
| PaStreamStateCallback, |
| this); |
| |
| // Mark recording side as initialized |
| _recIsInitialized = true; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::StartRecording() |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!_recIsInitialized) |
| { |
| return -1; |
| } |
| |
| if (_recording) |
| { |
| return 0; |
| } |
| |
| // Set state to ensure that the recording starts from the audio thread. |
| _startRec = true; |
| |
| // The audio thread will signal when recording has started. |
| _timeEventRec.Set(); |
| if (kEventTimeout == _recStartEvent.Wait(10000)) |
| { |
| { |
| CriticalSectionScoped lock(&_critSect); |
| _startRec = false; |
| } |
| StopRecording(); |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to activate recording"); |
| return -1; |
| } |
| |
| { |
| CriticalSectionScoped lock(&_critSect); |
| if (_recording) |
| { |
| // The recording state is set by the audio thread after recording |
| // has started. |
| } else |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to activate recording"); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::StopRecording() |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| CriticalSectionScoped lock(&_critSect); |
| |
| if (!_recIsInitialized) |
| { |
| return 0; |
| } |
| |
| if (_recStream == NULL) |
| { |
| return -1; |
| } |
| |
| _recIsInitialized = false; |
| _recording = false; |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " stopping recording"); |
| |
| // Stop Recording |
| PaLock(); |
| |
| DisableReadCallback(); |
| LATE(pa_stream_set_overflow_callback)(_recStream, NULL, NULL); |
| |
| // Unset this here so that we don't get a TERMINATED callback |
| LATE(pa_stream_set_state_callback)(_recStream, NULL, NULL); |
| |
| if (LATE(pa_stream_get_state)(_recStream) != PA_STREAM_UNCONNECTED) |
| { |
| // Disconnect the stream |
| if (LATE(pa_stream_disconnect)(_recStream) != PA_OK) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to disconnect rec stream, err=%d\n", |
| LATE(pa_context_errno)(_paContext)); |
| PaUnLock(); |
| return -1; |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " disconnected recording"); |
| } |
| |
| LATE(pa_stream_unref)(_recStream); |
| _recStream = NULL; |
| |
| PaUnLock(); |
| |
| // Provide the recStream to the mixer |
| _mixerManager.SetRecStream(_recStream); |
| |
| if (_recBuffer) |
| { |
| delete [] _recBuffer; |
| _recBuffer = NULL; |
| } |
| |
| return 0; |
| } |
| |
| bool AudioDeviceLinuxPulse::RecordingIsInitialized() const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_recIsInitialized); |
| } |
| |
| bool AudioDeviceLinuxPulse::Recording() const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_recording); |
| } |
| |
| bool AudioDeviceLinuxPulse::PlayoutIsInitialized() const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_playIsInitialized); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::StartPlayout() |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!_playIsInitialized) |
| { |
| return -1; |
| } |
| |
| if (_playing) |
| { |
| return 0; |
| } |
| |
| // Set state to ensure that playout starts from the audio thread. |
| { |
| CriticalSectionScoped lock(&_critSect); |
| _startPlay = true; |
| } |
| |
| // Both |_startPlay| and |_playing| needs protction since they are also |
| // accessed on the playout thread. |
| |
| // The audio thread will signal when playout has started. |
| _timeEventPlay.Set(); |
| if (kEventTimeout == _playStartEvent.Wait(10000)) |
| { |
| { |
| CriticalSectionScoped lock(&_critSect); |
| _startPlay = false; |
| } |
| StopPlayout(); |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to activate playout"); |
| return -1; |
| } |
| |
| { |
| CriticalSectionScoped lock(&_critSect); |
| if (_playing) |
| { |
| // The playing state is set by the audio thread after playout |
| // has started. |
| } else |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to activate playing"); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::StopPlayout() |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| CriticalSectionScoped lock(&_critSect); |
| |
| if (!_playIsInitialized) |
| { |
| return 0; |
| } |
| |
| if (_playStream == NULL) |
| { |
| return -1; |
| } |
| |
| _playIsInitialized = false; |
| _playing = false; |
| _sndCardPlayDelay = 0; |
| _sndCardRecDelay = 0; |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " stopping playback"); |
| |
| // Stop Playout |
| PaLock(); |
| |
| DisableWriteCallback(); |
| LATE(pa_stream_set_underflow_callback)(_playStream, NULL, NULL); |
| |
| // Unset this here so that we don't get a TERMINATED callback |
| LATE(pa_stream_set_state_callback)(_playStream, NULL, NULL); |
| |
| if (LATE(pa_stream_get_state)(_playStream) != PA_STREAM_UNCONNECTED) |
| { |
| // Disconnect the stream |
| if (LATE(pa_stream_disconnect)(_playStream) != PA_OK) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to disconnect play stream, err=%d", |
| LATE(pa_context_errno)(_paContext)); |
| PaUnLock(); |
| return -1; |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " disconnected playback"); |
| } |
| |
| LATE(pa_stream_unref)(_playStream); |
| _playStream = NULL; |
| |
| PaUnLock(); |
| |
| // Provide the playStream to the mixer |
| _mixerManager.SetPlayStream(_playStream); |
| |
| if (_playBuffer) |
| { |
| delete [] _playBuffer; |
| _playBuffer = NULL; |
| } |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::PlayoutDelay(uint16_t& delayMS) const |
| { |
| CriticalSectionScoped lock(&_critSect); |
| delayMS = (uint16_t) _sndCardPlayDelay; |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::RecordingDelay(uint16_t& delayMS) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| delayMS = (uint16_t) _sndCardRecDelay; |
| return 0; |
| } |
| |
| bool AudioDeviceLinuxPulse::Playing() const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| return (_playing); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::SetPlayoutBuffer( |
| const AudioDeviceModule::BufferType type, |
| uint16_t sizeMS) |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (type != AudioDeviceModule::kFixedBufferSize) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Adaptive buffer size not supported on this platform"); |
| return -1; |
| } |
| |
| _playBufType = type; |
| _playBufDelayFixed = sizeMS; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::PlayoutBuffer( |
| AudioDeviceModule::BufferType& type, |
| uint16_t& sizeMS) const |
| { |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); |
| type = _playBufType; |
| sizeMS = _playBufDelayFixed; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::CPULoad(uint16_t& /*load*/) const |
| { |
| |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " API call not supported on this platform"); |
| return -1; |
| } |
| |
| bool AudioDeviceLinuxPulse::PlayoutWarning() const |
| { |
| CriticalSectionScoped lock(&_critSect); |
| return (_playWarning > 0); |
| } |
| |
| bool AudioDeviceLinuxPulse::PlayoutError() const |
| { |
| CriticalSectionScoped lock(&_critSect); |
| return (_playError > 0); |
| } |
| |
| bool AudioDeviceLinuxPulse::RecordingWarning() const |
| { |
| CriticalSectionScoped lock(&_critSect); |
| return (_recWarning > 0); |
| } |
| |
| bool AudioDeviceLinuxPulse::RecordingError() const |
| { |
| CriticalSectionScoped lock(&_critSect); |
| return (_recError > 0); |
| } |
| |
| void AudioDeviceLinuxPulse::ClearPlayoutWarning() |
| { |
| CriticalSectionScoped lock(&_critSect); |
| _playWarning = 0; |
| } |
| |
| void AudioDeviceLinuxPulse::ClearPlayoutError() |
| { |
| CriticalSectionScoped lock(&_critSect); |
| _playError = 0; |
| } |
| |
| void AudioDeviceLinuxPulse::ClearRecordingWarning() |
| { |
| CriticalSectionScoped lock(&_critSect); |
| _recWarning = 0; |
| } |
| |
| void AudioDeviceLinuxPulse::ClearRecordingError() |
| { |
| CriticalSectionScoped lock(&_critSect); |
| _recError = 0; |
| } |
| |
| // ============================================================================ |
| // Private Methods |
| // ============================================================================ |
| |
| void AudioDeviceLinuxPulse::PaContextStateCallback(pa_context *c, void *pThis) |
| { |
| static_cast<AudioDeviceLinuxPulse*> (pThis)-> |
| PaContextStateCallbackHandler(c); |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // PaSinkInfoCallback |
| // ---------------------------------------------------------------------------- |
| |
| void AudioDeviceLinuxPulse::PaSinkInfoCallback(pa_context */*c*/, |
| const pa_sink_info *i, int eol, |
| void *pThis) |
| { |
| static_cast<AudioDeviceLinuxPulse*> (pThis)->PaSinkInfoCallbackHandler( |
| i, eol); |
| } |
| |
| void AudioDeviceLinuxPulse::PaSourceInfoCallback(pa_context */*c*/, |
| const pa_source_info *i, |
| int eol, void *pThis) |
| { |
| static_cast<AudioDeviceLinuxPulse*> (pThis)->PaSourceInfoCallbackHandler( |
| i, eol); |
| } |
| |
| void AudioDeviceLinuxPulse::PaServerInfoCallback(pa_context */*c*/, |
| const pa_server_info *i, |
| void *pThis) |
| { |
| static_cast<AudioDeviceLinuxPulse*> (pThis)-> |
| PaServerInfoCallbackHandler(i); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamStateCallback(pa_stream *p, void *pThis) |
| { |
| static_cast<AudioDeviceLinuxPulse*> (pThis)-> |
| PaStreamStateCallbackHandler(p); |
| } |
| |
| void AudioDeviceLinuxPulse::PaContextStateCallbackHandler(pa_context *c) |
| { |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " context state cb"); |
| |
| pa_context_state_t state = LATE(pa_context_get_state)(c); |
| switch (state) |
| { |
| case PA_CONTEXT_UNCONNECTED: |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " unconnected"); |
| break; |
| case PA_CONTEXT_CONNECTING: |
| case PA_CONTEXT_AUTHORIZING: |
| case PA_CONTEXT_SETTING_NAME: |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " no state"); |
| break; |
| case PA_CONTEXT_FAILED: |
| case PA_CONTEXT_TERMINATED: |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " failed"); |
| _paStateChanged = true; |
| LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); |
| break; |
| case PA_CONTEXT_READY: |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " ready"); |
| _paStateChanged = true; |
| LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); |
| break; |
| } |
| } |
| |
| void AudioDeviceLinuxPulse::PaSinkInfoCallbackHandler(const pa_sink_info *i, |
| int eol) |
| { |
| if (eol) |
| { |
| // Signal that we are done |
| LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); |
| return; |
| } |
| |
| if (_numPlayDevices == _deviceIndex) |
| { |
| // Convert the device index to the one of the sink |
| _paDeviceIndex = i->index; |
| |
| if (_playDeviceName) |
| { |
| // Copy the sink name |
| strncpy(_playDeviceName, i->name, kAdmMaxDeviceNameSize); |
| _playDeviceName[kAdmMaxDeviceNameSize - 1] = '\0'; |
| } |
| if (_playDisplayDeviceName) |
| { |
| // Copy the sink display name |
| strncpy(_playDisplayDeviceName, i->description, |
| kAdmMaxDeviceNameSize); |
| _playDisplayDeviceName[kAdmMaxDeviceNameSize - 1] = '\0'; |
| } |
| } |
| |
| _numPlayDevices++; |
| } |
| |
| void AudioDeviceLinuxPulse::PaSourceInfoCallbackHandler( |
| const pa_source_info *i, |
| int eol) |
| { |
| if (eol) |
| { |
| // Signal that we are done |
| LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); |
| return; |
| } |
| |
| // We don't want to list output devices |
| if (i->monitor_of_sink == PA_INVALID_INDEX) |
| { |
| if (_numRecDevices == _deviceIndex) |
| { |
| // Convert the device index to the one of the source |
| _paDeviceIndex = i->index; |
| |
| if (_recDeviceName) |
| { |
| // copy the source name |
| strncpy(_recDeviceName, i->name, kAdmMaxDeviceNameSize); |
| _recDeviceName[kAdmMaxDeviceNameSize - 1] = '\0'; |
| } |
| if (_recDisplayDeviceName) |
| { |
| // Copy the source display name |
| strncpy(_recDisplayDeviceName, i->description, |
| kAdmMaxDeviceNameSize); |
| _recDisplayDeviceName[kAdmMaxDeviceNameSize - 1] = '\0'; |
| } |
| } |
| |
| _numRecDevices++; |
| } |
| } |
| |
| void AudioDeviceLinuxPulse::PaServerInfoCallbackHandler( |
| const pa_server_info *i) |
| { |
| // Use PA native sampling rate |
| sample_rate_hz_ = i->sample_spec.rate; |
| |
| // Copy the PA server version |
| strncpy(_paServerVersion, i->server_version, 31); |
| _paServerVersion[31] = '\0'; |
| |
| if (_recDisplayDeviceName) |
| { |
| // Copy the source name |
| strncpy(_recDisplayDeviceName, i->default_source_name, |
| kAdmMaxDeviceNameSize); |
| _recDisplayDeviceName[kAdmMaxDeviceNameSize - 1] = '\0'; |
| } |
| |
| if (_playDisplayDeviceName) |
| { |
| // Copy the sink name |
| strncpy(_playDisplayDeviceName, i->default_sink_name, |
| kAdmMaxDeviceNameSize); |
| _playDisplayDeviceName[kAdmMaxDeviceNameSize - 1] = '\0'; |
| } |
| |
| LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamStateCallbackHandler(pa_stream *p) |
| { |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " stream state cb"); |
| |
| pa_stream_state_t state = LATE(pa_stream_get_state)(p); |
| switch (state) |
| { |
| case PA_STREAM_UNCONNECTED: |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " unconnected"); |
| break; |
| case PA_STREAM_CREATING: |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " creating"); |
| break; |
| case PA_STREAM_FAILED: |
| case PA_STREAM_TERMINATED: |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " failed"); |
| break; |
| case PA_STREAM_READY: |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " ready"); |
| break; |
| } |
| |
| LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::CheckPulseAudioVersion() |
| { |
| PaLock(); |
| |
| pa_operation* paOperation = NULL; |
| |
| // get the server info and update deviceName |
| paOperation = LATE(pa_context_get_server_info)(_paContext, |
| PaServerInfoCallback, |
| this); |
| |
| WaitForOperationCompletion(paOperation); |
| |
| PaUnLock(); |
| |
| WEBRTC_TRACE(kTraceStateInfo, kTraceAudioDevice, -1, |
| " checking PulseAudio version: %s", _paServerVersion); |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::InitSamplingFrequency() |
| { |
| PaLock(); |
| |
| pa_operation* paOperation = NULL; |
| |
| // Get the server info and update sample_rate_hz_ |
| paOperation = LATE(pa_context_get_server_info)(_paContext, |
| PaServerInfoCallback, |
| this); |
| |
| WaitForOperationCompletion(paOperation); |
| |
| PaUnLock(); |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::GetDefaultDeviceInfo(bool recDevice, |
| char* name, |
| uint16_t& index) |
| { |
| char tmpName[kAdmMaxDeviceNameSize] = {0}; |
| // subtract length of "default: " |
| uint16_t nameLen = kAdmMaxDeviceNameSize - 9; |
| char* pName = NULL; |
| |
| if (name) |
| { |
| // Add "default: " |
| strcpy(name, "default: "); |
| pName = &name[9]; |
| } |
| |
| // Tell the callback that we want |
| // the name for this device |
| if (recDevice) |
| { |
| _recDisplayDeviceName = tmpName; |
| } else |
| { |
| _playDisplayDeviceName = tmpName; |
| } |
| |
| // Set members |
| _paDeviceIndex = -1; |
| _deviceIndex = 0; |
| _numPlayDevices = 0; |
| _numRecDevices = 0; |
| |
| PaLock(); |
| |
| pa_operation* paOperation = NULL; |
| |
| // Get the server info and update deviceName |
| paOperation = LATE(pa_context_get_server_info)(_paContext, |
| PaServerInfoCallback, |
| this); |
| |
| WaitForOperationCompletion(paOperation); |
| |
| // Get the device index |
| if (recDevice) |
| { |
| paOperation |
| = LATE(pa_context_get_source_info_by_name)(_paContext, |
| (char *) tmpName, |
| PaSourceInfoCallback, |
| this); |
| } else |
| { |
| paOperation |
| = LATE(pa_context_get_sink_info_by_name)(_paContext, |
| (char *) tmpName, |
| PaSinkInfoCallback, |
| this); |
| } |
| |
| WaitForOperationCompletion(paOperation); |
| |
| PaUnLock(); |
| |
| // Set the index |
| index = _paDeviceIndex; |
| |
| if (name) |
| { |
| // Copy to name string |
| strncpy(pName, tmpName, nameLen); |
| } |
| |
| // Clear members |
| _playDisplayDeviceName = NULL; |
| _recDisplayDeviceName = NULL; |
| _paDeviceIndex = -1; |
| _deviceIndex = -1; |
| _numPlayDevices = 0; |
| _numRecDevices = 0; |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::InitPulseAudio() |
| { |
| int retVal = 0; |
| |
| // Load libpulse |
| if (!PaSymbolTable.Load()) |
| { |
| // Most likely the Pulse library and sound server are not installed on |
| // this system |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to load symbol table"); |
| return -1; |
| } |
| |
| // Create a mainloop API and connection to the default server |
| // the mainloop is the internal asynchronous API event loop |
| if (_paMainloop) { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " PA mainloop has already existed"); |
| return -1; |
| } |
| _paMainloop = LATE(pa_threaded_mainloop_new)(); |
| if (!_paMainloop) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " could not create mainloop"); |
| return -1; |
| } |
| |
| // Start the threaded main loop |
| retVal = LATE(pa_threaded_mainloop_start)(_paMainloop); |
| if (retVal != PA_OK) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to start main loop, error=%d", retVal); |
| return -1; |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " mainloop running!"); |
| |
| PaLock(); |
| |
| _paMainloopApi = LATE(pa_threaded_mainloop_get_api)(_paMainloop); |
| if (!_paMainloopApi) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " could not create mainloop API"); |
| PaUnLock(); |
| return -1; |
| } |
| |
| // Create a new PulseAudio context |
| if (_paContext){ |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " PA context has already existed"); |
| PaUnLock(); |
| return -1; |
| } |
| _paContext = LATE(pa_context_new)(_paMainloopApi, "WEBRTC VoiceEngine"); |
| |
| if (!_paContext) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " could not create context"); |
| PaUnLock(); |
| return -1; |
| } |
| |
| // Set state callback function |
| LATE(pa_context_set_state_callback)(_paContext, PaContextStateCallback, |
| this); |
| |
| // Connect the context to a server (default) |
| _paStateChanged = false; |
| retVal = LATE(pa_context_connect)(_paContext, |
| NULL, |
| PA_CONTEXT_NOAUTOSPAWN, |
| NULL); |
| |
| if (retVal != PA_OK) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to connect context, error=%d", retVal); |
| PaUnLock(); |
| return -1; |
| } |
| |
| // Wait for state change |
| while (!_paStateChanged) |
| { |
| LATE(pa_threaded_mainloop_wait)(_paMainloop); |
| } |
| |
| // Now check to see what final state we reached. |
| pa_context_state_t state = LATE(pa_context_get_state)(_paContext); |
| |
| if (state != PA_CONTEXT_READY) |
| { |
| if (state == PA_CONTEXT_FAILED) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to connect to PulseAudio sound server"); |
| } else if (state == PA_CONTEXT_TERMINATED) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " PulseAudio connection terminated early"); |
| } else |
| { |
| // Shouldn't happen, because we only signal on one of those three |
| // states |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " unknown problem connecting to PulseAudio"); |
| } |
| PaUnLock(); |
| return -1; |
| } |
| |
| PaUnLock(); |
| |
| // Give the objects to the mixer manager |
| _mixerManager.SetPulseAudioObjects(_paMainloop, _paContext); |
| |
| // Check the version |
| if (CheckPulseAudioVersion() < 0) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " PulseAudio version %s not supported", |
| _paServerVersion); |
| return -1; |
| } |
| |
| // Initialize sampling frequency |
| if (InitSamplingFrequency() < 0 || sample_rate_hz_ == 0) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to initialize sampling frequency," |
| " set to %d Hz", |
| sample_rate_hz_); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::TerminatePulseAudio() |
| { |
| // Do nothing if the instance doesn't exist |
| // likely PaSymbolTable.Load() fails |
| if (!_paMainloop) { |
| return 0; |
| } |
| |
| PaLock(); |
| |
| // Disconnect the context |
| if (_paContext) |
| { |
| LATE(pa_context_disconnect)(_paContext); |
| } |
| |
| // Unreference the context |
| if (_paContext) |
| { |
| LATE(pa_context_unref)(_paContext); |
| } |
| |
| PaUnLock(); |
| _paContext = NULL; |
| |
| // Stop the threaded main loop |
| if (_paMainloop) |
| { |
| LATE(pa_threaded_mainloop_stop)(_paMainloop); |
| } |
| |
| // Free the mainloop |
| if (_paMainloop) |
| { |
| LATE(pa_threaded_mainloop_free)(_paMainloop); |
| } |
| |
| _paMainloop = NULL; |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " PulseAudio terminated"); |
| |
| return 0; |
| } |
| |
| void AudioDeviceLinuxPulse::PaLock() |
| { |
| LATE(pa_threaded_mainloop_lock)(_paMainloop); |
| } |
| |
| void AudioDeviceLinuxPulse::PaUnLock() |
| { |
| LATE(pa_threaded_mainloop_unlock)(_paMainloop); |
| } |
| |
| void AudioDeviceLinuxPulse::WaitForOperationCompletion( |
| pa_operation* paOperation) const |
| { |
| if (!paOperation) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| "paOperation NULL in WaitForOperationCompletion"); |
| return; |
| } |
| |
| while (LATE(pa_operation_get_state)(paOperation) == PA_OPERATION_RUNNING) |
| { |
| LATE(pa_threaded_mainloop_wait)(_paMainloop); |
| } |
| |
| LATE(pa_operation_unref)(paOperation); |
| } |
| |
| // ============================================================================ |
| // Thread Methods |
| // ============================================================================ |
| |
| void AudioDeviceLinuxPulse::EnableWriteCallback() |
| { |
| if (LATE(pa_stream_get_state)(_playStream) == PA_STREAM_READY) |
| { |
| // May already have available space. Must check. |
| _tempBufferSpace = LATE(pa_stream_writable_size)(_playStream); |
| if (_tempBufferSpace > 0) |
| { |
| // Yup, there is already space available, so if we register a |
| // write callback then it will not receive any event. So dispatch |
| // one ourself instead. |
| _timeEventPlay.Set(); |
| return; |
| } |
| } |
| |
| LATE(pa_stream_set_write_callback)(_playStream, &PaStreamWriteCallback, |
| this); |
| } |
| |
| void AudioDeviceLinuxPulse::DisableWriteCallback() |
| { |
| LATE(pa_stream_set_write_callback)(_playStream, NULL, NULL); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamWriteCallback(pa_stream */*unused*/, |
| size_t buffer_space, |
| void *pThis) |
| { |
| static_cast<AudioDeviceLinuxPulse*> (pThis)->PaStreamWriteCallbackHandler( |
| buffer_space); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamWriteCallbackHandler(size_t bufferSpace) |
| { |
| _tempBufferSpace = bufferSpace; |
| |
| // Since we write the data asynchronously on a different thread, we have |
| // to temporarily disable the write callback or else Pulse will call it |
| // continuously until we write the data. We re-enable it below. |
| DisableWriteCallback(); |
| _timeEventPlay.Set(); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamUnderflowCallback(pa_stream */*unused*/, |
| void *pThis) |
| { |
| static_cast<AudioDeviceLinuxPulse*> (pThis)-> |
| PaStreamUnderflowCallbackHandler(); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamUnderflowCallbackHandler() |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " Playout underflow"); |
| |
| if (_configuredLatencyPlay == WEBRTC_PA_NO_LATENCY_REQUIREMENTS) |
| { |
| // We didn't configure a pa_buffer_attr before, so switching to |
| // one now would be questionable. |
| return; |
| } |
| |
| // Otherwise reconfigure the stream with a higher target latency. |
| |
| const pa_sample_spec *spec = LATE(pa_stream_get_sample_spec)(_playStream); |
| if (!spec) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " pa_stream_get_sample_spec()"); |
| return; |
| } |
| |
| size_t bytesPerSec = LATE(pa_bytes_per_second)(spec); |
| uint32_t newLatency = _configuredLatencyPlay + bytesPerSec * |
| WEBRTC_PA_PLAYBACK_LATENCY_INCREMENT_MSECS / |
| WEBRTC_PA_MSECS_PER_SEC; |
| |
| // Set the play buffer attributes |
| _playBufferAttr.maxlength = newLatency; |
| _playBufferAttr.tlength = newLatency; |
| _playBufferAttr.minreq = newLatency / WEBRTC_PA_PLAYBACK_REQUEST_FACTOR; |
| _playBufferAttr.prebuf = _playBufferAttr.tlength - _playBufferAttr.minreq; |
| |
| pa_operation *op = LATE(pa_stream_set_buffer_attr)(_playStream, |
| &_playBufferAttr, NULL, |
| NULL); |
| if (!op) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " pa_stream_set_buffer_attr()"); |
| return; |
| } |
| |
| // Don't need to wait for this to complete. |
| LATE(pa_operation_unref)(op); |
| |
| // Save the new latency in case we underflow again. |
| _configuredLatencyPlay = newLatency; |
| } |
| |
| void AudioDeviceLinuxPulse::EnableReadCallback() |
| { |
| LATE(pa_stream_set_read_callback)(_recStream, |
| &PaStreamReadCallback, |
| this); |
| } |
| |
| void AudioDeviceLinuxPulse::DisableReadCallback() |
| { |
| LATE(pa_stream_set_read_callback)(_recStream, NULL, NULL); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamReadCallback(pa_stream */*unused1*/, |
| size_t /*unused2*/, |
| void *pThis) |
| { |
| static_cast<AudioDeviceLinuxPulse*> (pThis)-> |
| PaStreamReadCallbackHandler(); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamReadCallbackHandler() |
| { |
| // We get the data pointer and size now in order to save one Lock/Unlock |
| // in the worker thread. |
| if (LATE(pa_stream_peek)(_recStream, |
| &_tempSampleData, |
| &_tempSampleDataSize) != 0) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Can't read data!"); |
| return; |
| } |
| |
| // Since we consume the data asynchronously on a different thread, we have |
| // to temporarily disable the read callback or else Pulse will call it |
| // continuously until we consume the data. We re-enable it below. |
| DisableReadCallback(); |
| _timeEventRec.Set(); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamOverflowCallback(pa_stream */*unused*/, |
| void *pThis) |
| { |
| static_cast<AudioDeviceLinuxPulse*> (pThis)-> |
| PaStreamOverflowCallbackHandler(); |
| } |
| |
| void AudioDeviceLinuxPulse::PaStreamOverflowCallbackHandler() |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| " Recording overflow"); |
| } |
| |
| int32_t AudioDeviceLinuxPulse::LatencyUsecs(pa_stream *stream) |
| { |
| if (!WEBRTC_PA_REPORT_LATENCY) |
| { |
| return 0; |
| } |
| |
| if (!stream) |
| { |
| return 0; |
| } |
| |
| pa_usec_t latency; |
| int negative; |
| if (LATE(pa_stream_get_latency)(stream, &latency, &negative) != 0) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " Can't query latency"); |
| // We'd rather continue playout/capture with an incorrect delay than |
| // stop it altogether, so return a valid value. |
| return 0; |
| } |
| |
| if (negative) |
| { |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " warning: pa_stream_get_latency reported negative " |
| "delay"); |
| |
| // The delay can be negative for monitoring streams if the captured |
| // samples haven't been played yet. In such a case, "latency" |
| // contains the magnitude, so we must negate it to get the real value. |
| int32_t tmpLatency = (int32_t) -latency; |
| if (tmpLatency < 0) |
| { |
| // Make sure that we don't use a negative delay. |
| tmpLatency = 0; |
| } |
| |
| return tmpLatency; |
| } else |
| { |
| return (int32_t) latency; |
| } |
| } |
| |
| int32_t AudioDeviceLinuxPulse::ReadRecordedData( |
| const void* bufferData, |
| size_t bufferSize) EXCLUSIVE_LOCKS_REQUIRED(_critSect) |
| { |
| size_t size = bufferSize; |
| uint32_t numRecSamples = _recordBufferSize / (2 * _recChannels); |
| |
| // Account for the peeked data and the used data. |
| uint32_t recDelay = (uint32_t) ((LatencyUsecs(_recStream) |
| / 1000) + 10 * ((size + _recordBufferUsed) / _recordBufferSize)); |
| |
| _sndCardRecDelay = recDelay; |
| |
| if (_playStream) |
| { |
| // Get the playout delay. |
| _sndCardPlayDelay = (uint32_t) (LatencyUsecs(_playStream) / 1000); |
| } |
| |
| if (_recordBufferUsed > 0) |
| { |
| // Have to copy to the buffer until it is full. |
| size_t copy = _recordBufferSize - _recordBufferUsed; |
| if (size < copy) |
| { |
| copy = size; |
| } |
| |
| memcpy(&_recBuffer[_recordBufferUsed], bufferData, copy); |
| _recordBufferUsed += copy; |
| bufferData = static_cast<const char *> (bufferData) + copy; |
| size -= copy; |
| |
| if (_recordBufferUsed != _recordBufferSize) |
| { |
| // Not enough data yet to pass to VoE. |
| return 0; |
| } |
| |
| // Provide data to VoiceEngine. |
| if (ProcessRecordedData(_recBuffer, numRecSamples, recDelay) == -1) |
| { |
| // We have stopped recording. |
| return -1; |
| } |
| |
| _recordBufferUsed = 0; |
| } |
| |
| // Now process full 10ms sample sets directly from the input. |
| while (size >= _recordBufferSize) |
| { |
| // Provide data to VoiceEngine. |
| if (ProcessRecordedData( |
| static_cast<int8_t *> (const_cast<void *> (bufferData)), |
| numRecSamples, recDelay) == -1) |
| { |
| // We have stopped recording. |
| return -1; |
| } |
| |
| bufferData = static_cast<const char *> (bufferData) + |
| _recordBufferSize; |
| size -= _recordBufferSize; |
| |
| // We have consumed 10ms of data. |
| recDelay -= 10; |
| } |
| |
| // Now save any leftovers for later. |
| if (size > 0) |
| { |
| memcpy(_recBuffer, bufferData, size); |
| _recordBufferUsed = size; |
| } |
| |
| return 0; |
| } |
| |
| int32_t AudioDeviceLinuxPulse::ProcessRecordedData( |
| int8_t *bufferData, |
| uint32_t bufferSizeInSamples, |
| uint32_t recDelay) EXCLUSIVE_LOCKS_REQUIRED(_critSect) |
| { |
| uint32_t currentMicLevel(0); |
| uint32_t newMicLevel(0); |
| |
| _ptrAudioBuffer->SetRecordedBuffer(bufferData, bufferSizeInSamples); |
| |
| if (AGC()) |
| { |
| // Store current mic level in the audio buffer if AGC is enabled |
| if (MicrophoneVolume(currentMicLevel) == 0) |
| { |
| // This call does not affect the actual microphone volume |
| _ptrAudioBuffer->SetCurrentMicLevel(currentMicLevel); |
| } |
| } |
| |
| const uint32_t clockDrift(0); |
| // TODO(andrew): this is a temporary hack, to avoid non-causal far- and |
| // near-end signals at the AEC for PulseAudio. I think the system delay is |
| // being correctly calculated here, but for legacy reasons we add +10 ms |
| // to the value in the AEC. The real fix will be part of a larger |
| // investigation into managing system delay in the AEC. |
| if (recDelay > 10) |
| recDelay -= 10; |
| else |
| recDelay = 0; |
| _ptrAudioBuffer->SetVQEData(_sndCardPlayDelay, recDelay, clockDrift); |
| _ptrAudioBuffer->SetTypingStatus(KeyPressed()); |
| // Deliver recorded samples at specified sample rate, |
| // mic level etc. to the observer using callback. |
| UnLock(); |
| _ptrAudioBuffer->DeliverRecordedData(); |
| Lock(); |
| |
| // We have been unlocked - check the flag again. |
| if (!_recording) |
| { |
| return -1; |
| } |
| |
| if (AGC()) |
| { |
| newMicLevel = _ptrAudioBuffer->NewMicLevel(); |
| if (newMicLevel != 0) |
| { |
| // The VQE will only deliver non-zero microphone levels when a |
| // change is needed. |
| // Set this new mic level (received from the observer as return |
| // value in the callback). |
| WEBRTC_TRACE(kTraceStream, kTraceAudioDevice, _id, |
| " AGC change of volume: old=%u => new=%u", |
| currentMicLevel, newMicLevel); |
| if (SetMicrophoneVolume(newMicLevel) == -1) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, |
| _id, |
| " the required modification of the microphone " |
| "volume failed"); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| bool AudioDeviceLinuxPulse::PlayThreadFunc(void* pThis) |
| { |
| return (static_cast<AudioDeviceLinuxPulse*> (pThis)->PlayThreadProcess()); |
| } |
| |
| bool AudioDeviceLinuxPulse::RecThreadFunc(void* pThis) |
| { |
| return (static_cast<AudioDeviceLinuxPulse*> (pThis)->RecThreadProcess()); |
| } |
| |
| bool AudioDeviceLinuxPulse::PlayThreadProcess() |
| { |
| switch (_timeEventPlay.Wait(1000)) |
| { |
| case kEventSignaled: |
| break; |
| case kEventError: |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| "EventWrapper::Wait() failed"); |
| return true; |
| case kEventTimeout: |
| return true; |
| } |
| |
| CriticalSectionScoped lock(&_critSect); |
| |
| if (_startPlay) |
| { |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| "_startPlay true, performing initial actions"); |
| |
| _startPlay = false; |
| _playDeviceName = NULL; |
| |
| // Set if not default device |
| if (_outputDeviceIndex > 0) |
| { |
| // Get the playout device name |
| _playDeviceName = new char[kAdmMaxDeviceNameSize]; |
| _deviceIndex = _outputDeviceIndex; |
| PlayoutDevices(); |
| } |
| |
| // Start muted only supported on 0.9.11 and up |
| if (LATE(pa_context_get_protocol_version)(_paContext) |
| >= WEBRTC_PA_ADJUST_LATENCY_PROTOCOL_VERSION) |
| { |
| // Get the currently saved speaker mute status |
| // and set the initial mute status accordingly |
| bool enabled(false); |
| _mixerManager.SpeakerMute(enabled); |
| if (enabled) |
| { |
| _playStreamFlags |= PA_STREAM_START_MUTED; |
| } |
| } |
| |
| // Get the currently saved speaker volume |
| uint32_t volume = 0; |
| if (update_speaker_volume_at_startup_) |
| _mixerManager.SpeakerVolume(volume); |
| |
| PaLock(); |
| |
| // NULL gives PA the choice of startup volume. |
| pa_cvolume* ptr_cvolume = NULL; |
| if (update_speaker_volume_at_startup_) { |
| pa_cvolume cVolumes; |
| ptr_cvolume = &cVolumes; |
| |
| // Set the same volume for all channels |
| const pa_sample_spec *spec = |
| LATE(pa_stream_get_sample_spec)(_playStream); |
| LATE(pa_cvolume_set)(&cVolumes, spec->channels, volume); |
| update_speaker_volume_at_startup_ = false; |
| } |
| |
| // Connect the stream to a sink |
| if (LATE(pa_stream_connect_playback)( |
| _playStream, |
| _playDeviceName, |
| &_playBufferAttr, |
| (pa_stream_flags_t) _playStreamFlags, |
| ptr_cvolume, NULL) != PA_OK) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to connect play stream, err=%d", |
| LATE(pa_context_errno)(_paContext)); |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " play stream connected"); |
| |
| // Wait for state change |
| while (LATE(pa_stream_get_state)(_playStream) != PA_STREAM_READY) |
| { |
| LATE(pa_threaded_mainloop_wait)(_paMainloop); |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " play stream ready"); |
| |
| // We can now handle write callbacks |
| EnableWriteCallback(); |
| |
| PaUnLock(); |
| |
| // Clear device name |
| if (_playDeviceName) |
| { |
| delete [] _playDeviceName; |
| _playDeviceName = NULL; |
| } |
| |
| _playing = true; |
| _playStartEvent.Set(); |
| |
| return true; |
| } |
| |
| if (_playing) |
| { |
| if (!_recording) |
| { |
| // Update the playout delay |
| _sndCardPlayDelay = (uint32_t) (LatencyUsecs(_playStream) |
| / 1000); |
| } |
| |
| if (_playbackBufferUnused < _playbackBufferSize) |
| { |
| |
| size_t write = _playbackBufferSize - _playbackBufferUnused; |
| if (_tempBufferSpace < write) |
| { |
| write = _tempBufferSpace; |
| } |
| |
| PaLock(); |
| if (LATE(pa_stream_write)( |
| _playStream, |
| (void *) &_playBuffer[_playbackBufferUnused], |
| write, NULL, (int64_t) 0, |
| PA_SEEK_RELATIVE) != PA_OK) |
| { |
| _writeErrors++; |
| if (_writeErrors > 10) |
| { |
| if (_playError == 1) |
| { |
| WEBRTC_TRACE(kTraceWarning, |
| kTraceUtility, _id, |
| " pending playout error exists"); |
| } |
| // Triggers callback from module process thread. |
| _playError = 1; |
| WEBRTC_TRACE( |
| kTraceError, |
| kTraceUtility, |
| _id, |
| " kPlayoutError message posted: " |
| "_writeErrors=%u, error=%d", |
| _writeErrors, |
| LATE(pa_context_errno)(_paContext)); |
| _writeErrors = 0; |
| } |
| } |
| PaUnLock(); |
| |
| _playbackBufferUnused += write; |
| _tempBufferSpace -= write; |
| } |
| |
| uint32_t numPlaySamples = _playbackBufferSize / (2 * _playChannels); |
| // Might have been reduced to zero by the above. |
| if (_tempBufferSpace > 0) |
| { |
| // Ask for new PCM data to be played out using the |
| // AudioDeviceBuffer ensure that this callback is executed |
| // without taking the audio-thread lock. |
| UnLock(); |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " requesting data"); |
| uint32_t nSamples = |
| _ptrAudioBuffer->RequestPlayoutData(numPlaySamples); |
| Lock(); |
| |
| // We have been unlocked - check the flag again. |
| if (!_playing) |
| { |
| return true; |
| } |
| |
| nSamples = _ptrAudioBuffer->GetPlayoutData(_playBuffer); |
| if (nSamples != numPlaySamples) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, |
| _id, " invalid number of output samples(%d)", |
| nSamples); |
| } |
| |
| size_t write = _playbackBufferSize; |
| if (_tempBufferSpace < write) |
| { |
| write = _tempBufferSpace; |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " will write"); |
| PaLock(); |
| if (LATE(pa_stream_write)(_playStream, (void *) &_playBuffer[0], |
| write, NULL, (int64_t) 0, |
| PA_SEEK_RELATIVE) != PA_OK) |
| { |
| _writeErrors++; |
| if (_writeErrors > 10) |
| { |
| if (_playError == 1) |
| { |
| WEBRTC_TRACE(kTraceWarning, |
| kTraceUtility, _id, |
| " pending playout error exists"); |
| } |
| // Triggers callback from module process thread. |
| _playError = 1; |
| WEBRTC_TRACE( |
| kTraceError, |
| kTraceUtility, |
| _id, |
| " kPlayoutError message posted: " |
| "_writeErrors=%u, error=%d", |
| _writeErrors, |
| LATE(pa_context_errno)(_paContext)); |
| _writeErrors = 0; |
| } |
| } |
| PaUnLock(); |
| |
| _playbackBufferUnused = write; |
| } |
| |
| _tempBufferSpace = 0; |
| PaLock(); |
| EnableWriteCallback(); |
| PaUnLock(); |
| |
| } // _playing |
| |
| return true; |
| } |
| |
| bool AudioDeviceLinuxPulse::RecThreadProcess() |
| { |
| switch (_timeEventRec.Wait(1000)) |
| { |
| case kEventSignaled: |
| break; |
| case kEventError: |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, |
| "EventWrapper::Wait() failed"); |
| return true; |
| case kEventTimeout: |
| return true; |
| } |
| |
| CriticalSectionScoped lock(&_critSect); |
| |
| if (_startRec) |
| { |
| WEBRTC_TRACE(kTraceInfo, kTraceAudioDevice, _id, |
| "_startRec true, performing initial actions"); |
| |
| _recDeviceName = NULL; |
| |
| // Set if not default device |
| if (_inputDeviceIndex > 0) |
| { |
| // Get the recording device name |
| _recDeviceName = new char[kAdmMaxDeviceNameSize]; |
| _deviceIndex = _inputDeviceIndex; |
| RecordingDevices(); |
| } |
| |
| PaLock(); |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " connecting stream"); |
| |
| // Connect the stream to a source |
| if (LATE(pa_stream_connect_record)(_recStream, |
| _recDeviceName, |
| &_recBufferAttr, |
| (pa_stream_flags_t) _recStreamFlags) != PA_OK) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, |
| " failed to connect rec stream, err=%d", |
| LATE(pa_context_errno)(_paContext)); |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " connected"); |
| |
| // Wait for state change |
| while (LATE(pa_stream_get_state)(_recStream) != PA_STREAM_READY) |
| { |
| LATE(pa_threaded_mainloop_wait)(_paMainloop); |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceAudioDevice, _id, |
| " done"); |
| |
| // We can now handle read callbacks |
| EnableReadCallback(); |
| |
| PaUnLock(); |
| |
| // Clear device name |
| if (_recDeviceName) |
| { |
| delete [] _recDeviceName; |
| _recDeviceName = NULL; |
| } |
| |
| _startRec = false; |
| _recording = true; |
| _recStartEvent.Set(); |
| |
| return true; |
| } |
| |
| if (_recording) |
| { |
| // Read data and provide it to VoiceEngine |
| if (ReadRecordedData(_tempSampleData, _tempSampleDataSize) == -1) |
| { |
| return true; |
| } |
| |
| _tempSampleData = NULL; |
| _tempSampleDataSize = 0; |
| |
| PaLock(); |
| while (true) |
| { |
| // Ack the last thing we read |
| if (LATE(pa_stream_drop)(_recStream) != 0) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, |
| _id, " failed to drop, err=%d\n", |
| LATE(pa_context_errno)(_paContext)); |
| } |
| |
| if (LATE(pa_stream_readable_size)(_recStream) <= 0) |
| { |
| // Then that was all the data |
| break; |
| } |
| |
| // Else more data. |
| const void *sampleData; |
| size_t sampleDataSize; |
| |
| if (LATE(pa_stream_peek)(_recStream, &sampleData, &sampleDataSize) |
| != 0) |
| { |
| _recError = 1; // triggers callback from module process thread |
| WEBRTC_TRACE(kTraceError, kTraceAudioDevice, |
| _id, " RECORD_ERROR message posted, error = %d", |
| LATE(pa_context_errno)(_paContext)); |
| break; |
| } |
| |
| _sndCardRecDelay = (uint32_t) (LatencyUsecs(_recStream) |
| / 1000); |
| |
| // Drop lock for sigslot dispatch, which could take a while. |
| PaUnLock(); |
| // Read data and provide it to VoiceEngine |
| if (ReadRecordedData(sampleData, sampleDataSize) == -1) |
| { |
| return true; |
| } |
| PaLock(); |
| |
| // Return to top of loop for the ack and the check for more data. |
| } |
| |
| EnableReadCallback(); |
| PaUnLock(); |
| |
| } // _recording |
| |
| return true; |
| } |
| |
| bool AudioDeviceLinuxPulse::KeyPressed() const{ |
| |
| char szKey[32]; |
| unsigned int i = 0; |
| char state = 0; |
| |
| if (!_XDisplay) |
| return false; |
| |
| // Check key map status |
| XQueryKeymap(_XDisplay, szKey); |
| |
| // A bit change in keymap means a key is pressed |
| for (i = 0; i < sizeof(szKey); i++) |
| state |= (szKey[i] ^ _oldKeyState[i]) & szKey[i]; |
| |
| // Save old state |
| memcpy((char*)_oldKeyState, (char*)szKey, sizeof(_oldKeyState)); |
| return (state != 0); |
| } |
| } |