diff options
Diffstat (limited to 'webrtc/modules/audio_device/linux/audio_device_pulse_linux.cc')
-rw-r--r-- | webrtc/modules/audio_device/linux/audio_device_pulse_linux.cc | 3022 |
1 files changed, 3022 insertions, 0 deletions
diff --git a/webrtc/modules/audio_device/linux/audio_device_pulse_linux.cc b/webrtc/modules/audio_device/linux/audio_device_pulse_linux.cc new file mode 100644 index 0000000000..929a758e40 --- /dev/null +++ b/webrtc/modules/audio_device/linux/audio_device_pulse_linux.cc @@ -0,0 +1,3022 @@ +/* + * 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/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; +} + +int32_t AudioDeviceLinuxPulse::Init() +{ + RTC_DCHECK(thread_checker_.CalledOnValidThread()); + if (_initialized) + { + return 0; + } + + // Initialize PulseAudio + if (InitPulseAudio() < 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, + " failed to initialize PulseAudio"); + + if (TerminatePulseAudio() < 0) + { + WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id, + " failed to terminate PulseAudio"); + } + + return -1; + } + + _playWarning = 0; + _playError = 0; + _recWarning = 0; + _recError = 0; + + //Get X display handle for typing detection + _XDisplay = XOpenDisplay(NULL); + if (!_XDisplay) + { + WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, _id, + " failed to open X display, typing detection will not work"); + } + + // RECORDING + const char* threadName = "webrtc_audio_module_rec_thread"; + _ptrThreadRec = ThreadWrapper::CreateThread(RecThreadFunc, this, + threadName); + if (!_ptrThreadRec->Start()) + { + WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, + " failed to start the rec audio thread"); + + _ptrThreadRec.reset(); + return -1; + } + + _ptrThreadRec->SetPriority(kRealtimePriority); + + // PLAYOUT + threadName = "webrtc_audio_module_play_thread"; + _ptrThreadPlay = ThreadWrapper::CreateThread(PlayThreadFunc, this, + threadName); + if (!_ptrThreadPlay->Start()) + { + WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id, + " failed to start the play audio thread"); + + _ptrThreadPlay.reset(); + return -1; + } + _ptrThreadPlay->SetPriority(kRealtimePriority); + + _initialized = true; + + return 0; +} + +int32_t AudioDeviceLinuxPulse::Terminate() +{ + RTC_DCHECK(thread_checker_.CalledOnValidThread()); + if (!_initialized) + { + return 0; + } + + _mixerManager.Close(); + + // RECORDING + if (_ptrThreadRec) + { + ThreadWrapper* tmpThread = _ptrThreadRec.release(); + + _timeEventRec.Set(); + tmpThread->Stop(); + delete tmpThread; + } + + // PLAYOUT + if (_ptrThreadPlay) + { + ThreadWrapper* 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); +} +} |