/* * 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 #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 (pThis)-> PaContextStateCallbackHandler(c); } // ---------------------------------------------------------------------------- // PaSinkInfoCallback // ---------------------------------------------------------------------------- void AudioDeviceLinuxPulse::PaSinkInfoCallback(pa_context */*c*/, const pa_sink_info *i, int eol, void *pThis) { static_cast (pThis)->PaSinkInfoCallbackHandler( i, eol); } void AudioDeviceLinuxPulse::PaSourceInfoCallback(pa_context */*c*/, const pa_source_info *i, int eol, void *pThis) { static_cast (pThis)->PaSourceInfoCallbackHandler( i, eol); } void AudioDeviceLinuxPulse::PaServerInfoCallback(pa_context */*c*/, const pa_server_info *i, void *pThis) { static_cast (pThis)-> PaServerInfoCallbackHandler(i); } void AudioDeviceLinuxPulse::PaStreamStateCallback(pa_stream *p, void *pThis) { static_cast (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 (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 (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 (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 (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 (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 (const_cast (bufferData)), numRecSamples, recDelay) == -1) { // We have stopped recording. return -1; } bufferData = static_cast (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 (pThis)->PlayThreadProcess()); } bool AudioDeviceLinuxPulse::RecThreadFunc(void* pThis) { return (static_cast (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); } }