| /* | |
| * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "modules/audio_device/linux/audio_device_pulse_linux.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include "api/audio/audio_device.h" | |
| #include "api/audio/audio_device_defines.h" | |
| #include "api/units/time_delta.h" | |
| #include "modules/audio_device/audio_device_buffer.h" | |
| #include "modules/audio_device/audio_device_generic.h" | |
| #include "modules/audio_device/linux/latebindingsymboltable_linux.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/logging.h" | |
| #include "rtc_base/platform_thread.h" | |
| #include "rtc_base/synchronization/mutex.h" | |
| #include "rtc_base/thread_annotations.h" | |
| #if defined(WEBRTC_USE_X11) | |
| #include <X11/Xlib.h> | |
| #endif | |
| WebRTCPulseSymbolTable* GetPulseSymbolTable() { | |
| static WebRTCPulseSymbolTable* pulse_symbol_table = []() { | |
| auto* table = new WebRTCPulseSymbolTable(); | |
| table->Load(); | |
| return table; | |
| }(); | |
| return pulse_symbol_table; | |
| } | |
| // 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, \ | |
| GetPulseSymbolTable(), sym) | |
| namespace webrtc { | |
| AudioDeviceLinuxPulse::AudioDeviceLinuxPulse() | |
| : _ptrAudioBuffer(nullptr), | |
| _inputDeviceIndex(0), | |
| _outputDeviceIndex(0), | |
| _inputDeviceIsSpecified(false), | |
| _outputDeviceIsSpecified(false), | |
| sample_rate_hz_(0), | |
| _recChannels(1), | |
| _playChannels(1), | |
| _initialized(false), | |
| _recording(false), | |
| _playing(false), | |
| _recIsInitialized(false), | |
| _playIsInitialized(false), | |
| _startRec(false), | |
| _startPlay(false), | |
| update_speaker_volume_at_startup_(false), | |
| quit_(false), | |
| _sndCardPlayDelay(0), | |
| _writeErrors(0), | |
| _deviceIndex(-1), | |
| _numPlayDevices(0), | |
| _numRecDevices(0), | |
| _playDeviceName(nullptr), | |
| _recDeviceName(nullptr), | |
| _playDisplayDeviceName(nullptr), | |
| _recDisplayDeviceName(nullptr), | |
| _playBuffer(nullptr), | |
| _playbackBufferSize(0), | |
| _playbackBufferUnused(0), | |
| _tempBufferSpace(0), | |
| _recBuffer(nullptr), | |
| _recordBufferSize(0), | |
| _recordBufferUsed(0), | |
| _tempSampleData(nullptr), | |
| _tempSampleDataSize(0), | |
| _configuredLatencyPlay(0), | |
| _configuredLatencyRec(0), | |
| _paDeviceIndex(-1), | |
| _paStateChanged(false), | |
| _paMainloop(nullptr), | |
| _paMainloopApi(nullptr), | |
| _paContext(nullptr), | |
| _recStream(nullptr), | |
| _playStream(nullptr), | |
| _recStreamFlags(0), | |
| _playStreamFlags(0) { | |
| RTC_DLOG(LS_INFO) << __FUNCTION__ << " created"; | |
| memset(_paServerVersion, 0, sizeof(_paServerVersion)); | |
| memset(&_playBufferAttr, 0, sizeof(_playBufferAttr)); | |
| memset(&_recBufferAttr, 0, sizeof(_recBufferAttr)); | |
| memset(_oldKeyState, 0, sizeof(_oldKeyState)); | |
| } | |
| AudioDeviceLinuxPulse::~AudioDeviceLinuxPulse() { | |
| RTC_DLOG(LS_INFO) << __FUNCTION__ << " destroyed"; | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| Terminate(); | |
| if (_recBuffer) { | |
| delete[] _recBuffer; | |
| _recBuffer = nullptr; | |
| } | |
| if (_playBuffer) { | |
| delete[] _playBuffer; | |
| _playBuffer = nullptr; | |
| } | |
| if (_playDeviceName) { | |
| delete[] _playDeviceName; | |
| _playDeviceName = nullptr; | |
| } | |
| if (_recDeviceName) { | |
| delete[] _recDeviceName; | |
| _recDeviceName = nullptr; | |
| } | |
| } | |
| void AudioDeviceLinuxPulse::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| _ptrAudioBuffer = audioBuffer; | |
| // Inform the AudioBuffer about default settings for this implementation. | |
| // Set all values to zero here since the actual settings will be done by | |
| // InitPlayout and InitRecording later. | |
| _ptrAudioBuffer->SetRecordingSampleRate(0); | |
| _ptrAudioBuffer->SetPlayoutSampleRate(0); | |
| _ptrAudioBuffer->SetRecordingChannels(0); | |
| _ptrAudioBuffer->SetPlayoutChannels(0); | |
| } | |
| // ---------------------------------------------------------------------------- | |
| // ActiveAudioLayer | |
| // ---------------------------------------------------------------------------- | |
| int32_t AudioDeviceLinuxPulse::ActiveAudioLayer( | |
| AudioDeviceModule::AudioLayer& audioLayer) const { | |
| audioLayer = AudioDeviceModule::kLinuxPulseAudio; | |
| return 0; | |
| } | |
| AudioDeviceGeneric::InitStatus AudioDeviceLinuxPulse::Init() { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| if (_initialized) { | |
| return InitStatus::OK; | |
| } | |
| // Initialize PulseAudio | |
| if (InitPulseAudio() < 0) { | |
| RTC_LOG(LS_ERROR) << "failed to initialize PulseAudio"; | |
| if (TerminatePulseAudio() < 0) { | |
| RTC_LOG(LS_ERROR) << "failed to terminate PulseAudio"; | |
| } | |
| return InitStatus::OTHER_ERROR; | |
| } | |
| #if defined(WEBRTC_USE_X11) | |
| // Get X display handle for typing detection | |
| _XDisplay = XOpenDisplay(nullptr); | |
| if (!_XDisplay) { | |
| RTC_LOG(LS_WARNING) | |
| << "failed to open X display, typing detection will not work"; | |
| } | |
| #endif | |
| // RECORDING | |
| const auto attributes = | |
| ThreadAttributes().SetPriority(ThreadPriority::kRealtime); | |
| _ptrThreadRec = PlatformThread::SpawnJoinable( | |
| [this] { | |
| while (RecThreadProcess()) { | |
| } | |
| }, | |
| "webrtc_audio_module_rec_thread", attributes); | |
| // PLAYOUT | |
| _ptrThreadPlay = PlatformThread::SpawnJoinable( | |
| [this] { | |
| while (PlayThreadProcess()) { | |
| } | |
| }, | |
| "webrtc_audio_module_play_thread", attributes); | |
| _initialized = true; | |
| return InitStatus::OK; | |
| } | |
| int32_t AudioDeviceLinuxPulse::Terminate() { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| if (!_initialized) { | |
| return 0; | |
| } | |
| { | |
| MutexLock lock(&mutex_); | |
| quit_ = true; | |
| } | |
| _mixerManager.Close(); | |
| // RECORDING | |
| _timeEventRec.Set(); | |
| _ptrThreadRec.Finalize(); | |
| // PLAYOUT | |
| _timeEventPlay.Set(); | |
| _ptrThreadPlay.Finalize(); | |
| // Terminate PulseAudio | |
| if (TerminatePulseAudio() < 0) { | |
| RTC_LOG(LS_ERROR) << "failed to terminate PulseAudio"; | |
| return -1; | |
| } | |
| #if defined(WEBRTC_USE_X11) | |
| if (_XDisplay) { | |
| XCloseDisplay(_XDisplay); | |
| _XDisplay = nullptr; | |
| } | |
| #endif | |
| _initialized = false; | |
| _outputDeviceIsSpecified = false; | |
| _inputDeviceIsSpecified = false; | |
| return 0; | |
| } | |
| bool AudioDeviceLinuxPulse::Initialized() const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| return (_initialized); | |
| } | |
| int32_t AudioDeviceLinuxPulse::InitSpeaker() { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| if (_playing) { | |
| return -1; | |
| } | |
| if (!_outputDeviceIsSpecified) { | |
| return -1; | |
| } | |
| // check if default device | |
| if (_outputDeviceIndex == 0) { | |
| uint16_t deviceIndex = 0; | |
| GetDefaultDeviceInfo(false, nullptr, 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_.IsCurrent()); | |
| if (_recording) { | |
| return -1; | |
| } | |
| if (!_inputDeviceIsSpecified) { | |
| return -1; | |
| } | |
| // Check if default device | |
| if (_inputDeviceIndex == 0) { | |
| uint16_t deviceIndex = 0; | |
| GetDefaultDeviceInfo(true, nullptr, 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_.IsCurrent()); | |
| return (_mixerManager.SpeakerIsInitialized()); | |
| } | |
| bool AudioDeviceLinuxPulse::MicrophoneIsInitialized() const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| return (_mixerManager.MicrophoneIsInitialized()); | |
| } | |
| int32_t AudioDeviceLinuxPulse::SpeakerVolumeIsAvailable(bool& available) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| 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_.IsCurrent()); | |
| 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_.IsCurrent()); | |
| uint32_t level(0); | |
| if (_mixerManager.SpeakerVolume(level) == -1) { | |
| return -1; | |
| } | |
| volume = level; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::MaxSpeakerVolume(uint32_t& maxVolume) const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| 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_.IsCurrent()); | |
| uint32_t minVol(0); | |
| if (_mixerManager.MinSpeakerVolume(minVol) == -1) { | |
| return -1; | |
| } | |
| minVolume = minVol; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::SpeakerMuteIsAvailable(bool& available) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| 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_.IsCurrent()); | |
| return (_mixerManager.SetSpeakerMute(enable)); | |
| } | |
| int32_t AudioDeviceLinuxPulse::SpeakerMute(bool& enabled) const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| bool muted(0); | |
| if (_mixerManager.SpeakerMute(muted) == -1) { | |
| return -1; | |
| } | |
| enabled = muted; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::MicrophoneMuteIsAvailable(bool& available) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| 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_.IsCurrent()); | |
| return (_mixerManager.SetMicrophoneMute(enable)); | |
| } | |
| int32_t AudioDeviceLinuxPulse::MicrophoneMute(bool& enabled) const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| bool muted(0); | |
| if (_mixerManager.MicrophoneMute(muted) == -1) { | |
| return -1; | |
| } | |
| enabled = muted; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::StereoRecordingIsAvailable(bool& available) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| 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_.IsCurrent()); | |
| if (enable) | |
| _recChannels = 2; | |
| else | |
| _recChannels = 1; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::StereoRecording(bool& enabled) const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| if (_recChannels == 2) | |
| enabled = true; | |
| else | |
| enabled = false; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::StereoPlayoutIsAvailable(bool& available) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| 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_.IsCurrent()); | |
| if (enable) | |
| _playChannels = 2; | |
| else | |
| _playChannels = 1; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::StereoPlayout(bool& enabled) const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| if (_playChannels == 2) | |
| enabled = true; | |
| else | |
| enabled = false; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::MicrophoneVolumeIsAvailable(bool& available) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| 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) { | |
| RTC_LOG(LS_WARNING) << "failed to retrieve 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; | |
| } | |
| int16_t AudioDeviceLinuxPulse::PlayoutDevices() { | |
| PaLock(); | |
| pa_operation* paOperation = nullptr; | |
| _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_.IsCurrent()); | |
| if (_playIsInitialized) { | |
| return -1; | |
| } | |
| const uint16_t nDevices = PlayoutDevices(); | |
| RTC_LOG(LS_VERBOSE) << "number of availiable output devices is " << nDevices; | |
| if (index > (nDevices - 1)) { | |
| RTC_LOG(LS_ERROR) << "device index is out of range [0," << (nDevices - 1) | |
| << "]"; | |
| return -1; | |
| } | |
| _outputDeviceIndex = index; | |
| _outputDeviceIsSpecified = true; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::SetPlayoutDevice( | |
| AudioDeviceModule::WindowsDeviceType /*device*/) { | |
| RTC_LOG(LS_ERROR) << "WindowsDeviceType not supported"; | |
| return -1; | |
| } | |
| int32_t AudioDeviceLinuxPulse::PlayoutDeviceName( | |
| uint16_t index, | |
| char name[kAdmMaxDeviceNameSize], | |
| char guid[kAdmMaxGuidSize]) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| const uint16_t nDevices = PlayoutDevices(); | |
| if ((index > (nDevices - 1)) || (name == nullptr)) { | |
| return -1; | |
| } | |
| memset(name, 0, kAdmMaxDeviceNameSize); | |
| if (guid != nullptr) { | |
| 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 = nullptr; | |
| _deviceIndex = -1; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::RecordingDeviceName( | |
| uint16_t index, | |
| char name[kAdmMaxDeviceNameSize], | |
| char guid[kAdmMaxGuidSize]) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| const uint16_t nDevices(RecordingDevices()); | |
| if ((index > (nDevices - 1)) || (name == nullptr)) { | |
| return -1; | |
| } | |
| memset(name, 0, kAdmMaxDeviceNameSize); | |
| if (guid != nullptr) { | |
| 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 = nullptr; | |
| _deviceIndex = -1; | |
| return 0; | |
| } | |
| int16_t AudioDeviceLinuxPulse::RecordingDevices() { | |
| PaLock(); | |
| pa_operation* paOperation = nullptr; | |
| _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_.IsCurrent()); | |
| if (_recIsInitialized) { | |
| return -1; | |
| } | |
| const uint16_t nDevices(RecordingDevices()); | |
| RTC_LOG(LS_VERBOSE) << "number of availiable input devices is " << nDevices; | |
| if (index > (nDevices - 1)) { | |
| RTC_LOG(LS_ERROR) << "device index is out of range [0," << (nDevices - 1) | |
| << "]"; | |
| return -1; | |
| } | |
| _inputDeviceIndex = index; | |
| _inputDeviceIsSpecified = true; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::SetRecordingDevice( | |
| AudioDeviceModule::WindowsDeviceType /*device*/) { | |
| RTC_LOG(LS_ERROR) << "WindowsDeviceType not supported"; | |
| return -1; | |
| } | |
| int32_t AudioDeviceLinuxPulse::PlayoutIsAvailable(bool& available) { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| 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_.IsCurrent()); | |
| 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_.IsCurrent()); | |
| if (_playing) { | |
| return -1; | |
| } | |
| if (!_outputDeviceIsSpecified) { | |
| return -1; | |
| } | |
| if (_playIsInitialized) { | |
| return 0; | |
| } | |
| // Initialize the speaker (devices might have been added or removed) | |
| if (InitSpeaker() == -1) { | |
| RTC_LOG(LS_WARNING) << "InitSpeaker() failed"; | |
| } | |
| // 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 | |
| { | |
| MutexLock lock(&mutex_); | |
| _playStream = | |
| LATE(pa_stream_new)(_paContext, "playStream", &playSampleSpec, nullptr); | |
| } | |
| if (!_playStream) { | |
| RTC_LOG(LS_ERROR) << "failed to create play stream, err=" | |
| << 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); | |
| } | |
| RTC_LOG(LS_VERBOSE) << "stream state " | |
| << 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) { | |
| RTC_LOG(LS_ERROR) << "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 | |
| { | |
| MutexLock lock(&mutex_); | |
| _playIsInitialized = true; | |
| _sndCardPlayDelay = 0; | |
| } | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::InitRecording() { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| if (_recording) { | |
| return -1; | |
| } | |
| if (!_inputDeviceIsSpecified) { | |
| return -1; | |
| } | |
| if (_recIsInitialized) { | |
| return 0; | |
| } | |
| // Initialize the microphone (devices might have been added or removed) | |
| if (InitMicrophone() == -1) { | |
| RTC_LOG(LS_WARNING) << "InitMicrophone() failed"; | |
| } | |
| // 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, nullptr); | |
| if (!_recStream) { | |
| RTC_LOG(LS_ERROR) << "failed to create rec stream, err=" | |
| << 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) { | |
| RTC_LOG(LS_ERROR) << "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_.IsCurrent()); | |
| 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 (!_recStartEvent.Wait(TimeDelta::Seconds(10))) { | |
| { | |
| MutexLock lock(&mutex_); | |
| _startRec = false; | |
| } | |
| StopRecording(); | |
| RTC_LOG(LS_ERROR) << "failed to activate recording"; | |
| return -1; | |
| } | |
| { | |
| MutexLock lock(&mutex_); | |
| if (_recording) { | |
| // The recording state is set by the audio thread after recording | |
| // has started. | |
| } else { | |
| RTC_LOG(LS_ERROR) << "failed to activate recording"; | |
| return -1; | |
| } | |
| } | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::StopRecording() { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| MutexLock lock(&mutex_); | |
| if (!_recIsInitialized) { | |
| return 0; | |
| } | |
| if (_recStream == nullptr) { | |
| return -1; | |
| } | |
| _recIsInitialized = false; | |
| _recording = false; | |
| RTC_LOG(LS_VERBOSE) << "stopping recording"; | |
| // Stop Recording | |
| PaLock(); | |
| DisableReadCallback(); | |
| LATE(pa_stream_set_overflow_callback)(_recStream, nullptr, nullptr); | |
| // Unset this here so that we don't get a TERMINATED callback | |
| LATE(pa_stream_set_state_callback)(_recStream, nullptr, nullptr); | |
| if (LATE(pa_stream_get_state)(_recStream) != PA_STREAM_UNCONNECTED) { | |
| // Disconnect the stream | |
| if (LATE(pa_stream_disconnect)(_recStream) != PA_OK) { | |
| RTC_LOG(LS_ERROR) << "failed to disconnect rec stream, err=" | |
| << LATE(pa_context_errno)(_paContext); | |
| PaUnLock(); | |
| return -1; | |
| } | |
| RTC_LOG(LS_VERBOSE) << "disconnected recording"; | |
| } | |
| LATE(pa_stream_unref)(_recStream); | |
| _recStream = nullptr; | |
| PaUnLock(); | |
| // Provide the recStream to the mixer | |
| _mixerManager.SetRecStream(_recStream); | |
| if (_recBuffer) { | |
| delete[] _recBuffer; | |
| _recBuffer = nullptr; | |
| } | |
| return 0; | |
| } | |
| bool AudioDeviceLinuxPulse::RecordingIsInitialized() const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| return (_recIsInitialized); | |
| } | |
| bool AudioDeviceLinuxPulse::Recording() const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| return (_recording); | |
| } | |
| bool AudioDeviceLinuxPulse::PlayoutIsInitialized() const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| return (_playIsInitialized); | |
| } | |
| int32_t AudioDeviceLinuxPulse::StartPlayout() { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| if (!_playIsInitialized) { | |
| return -1; | |
| } | |
| if (_playing) { | |
| return 0; | |
| } | |
| // Set state to ensure that playout starts from the audio thread. | |
| { | |
| MutexLock lock(&mutex_); | |
| _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 (!_playStartEvent.Wait(TimeDelta::Seconds(10))) { | |
| { | |
| MutexLock lock(&mutex_); | |
| _startPlay = false; | |
| } | |
| StopPlayout(); | |
| RTC_LOG(LS_ERROR) << "failed to activate playout"; | |
| return -1; | |
| } | |
| { | |
| MutexLock lock(&mutex_); | |
| if (_playing) { | |
| // The playing state is set by the audio thread after playout | |
| // has started. | |
| } else { | |
| RTC_LOG(LS_ERROR) << "failed to activate playing"; | |
| return -1; | |
| } | |
| } | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::StopPlayout() { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| MutexLock lock(&mutex_); | |
| if (!_playIsInitialized) { | |
| return 0; | |
| } | |
| if (_playStream == nullptr) { | |
| return -1; | |
| } | |
| _playIsInitialized = false; | |
| _playing = false; | |
| _sndCardPlayDelay = 0; | |
| RTC_LOG(LS_VERBOSE) << "stopping playback"; | |
| // Stop Playout | |
| PaLock(); | |
| DisableWriteCallback(); | |
| LATE(pa_stream_set_underflow_callback)(_playStream, nullptr, nullptr); | |
| // Unset this here so that we don't get a TERMINATED callback | |
| LATE(pa_stream_set_state_callback)(_playStream, nullptr, nullptr); | |
| if (LATE(pa_stream_get_state)(_playStream) != PA_STREAM_UNCONNECTED) { | |
| // Disconnect the stream | |
| if (LATE(pa_stream_disconnect)(_playStream) != PA_OK) { | |
| RTC_LOG(LS_ERROR) << "failed to disconnect play stream, err=" | |
| << LATE(pa_context_errno)(_paContext); | |
| PaUnLock(); | |
| return -1; | |
| } | |
| RTC_LOG(LS_VERBOSE) << "disconnected playback"; | |
| } | |
| LATE(pa_stream_unref)(_playStream); | |
| _playStream = nullptr; | |
| PaUnLock(); | |
| // Provide the playStream to the mixer | |
| _mixerManager.SetPlayStream(_playStream); | |
| if (_playBuffer) { | |
| delete[] _playBuffer; | |
| _playBuffer = nullptr; | |
| } | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::PlayoutDelay(uint16_t& delayMS) const { | |
| MutexLock lock(&mutex_); | |
| delayMS = (uint16_t)_sndCardPlayDelay; | |
| return 0; | |
| } | |
| bool AudioDeviceLinuxPulse::Playing() const { | |
| RTC_DCHECK(thread_checker_.IsCurrent()); | |
| return (_playing); | |
| } | |
| // ============================================================================ | |
| // 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) { | |
| RTC_LOG(LS_VERBOSE) << "context state cb"; | |
| pa_context_state_t state = LATE(pa_context_get_state)(c); | |
| switch (state) { | |
| case PA_CONTEXT_UNCONNECTED: | |
| RTC_LOG(LS_VERBOSE) << "unconnected"; | |
| break; | |
| case PA_CONTEXT_CONNECTING: | |
| case PA_CONTEXT_AUTHORIZING: | |
| case PA_CONTEXT_SETTING_NAME: | |
| RTC_LOG(LS_VERBOSE) << "no state"; | |
| break; | |
| case PA_CONTEXT_FAILED: | |
| case PA_CONTEXT_TERMINATED: | |
| RTC_LOG(LS_VERBOSE) << "failed"; | |
| _paStateChanged = true; | |
| LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); | |
| break; | |
| case PA_CONTEXT_READY: | |
| RTC_LOG(LS_VERBOSE) << "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) { | |
| RTC_LOG(LS_VERBOSE) << "stream state cb"; | |
| pa_stream_state_t state = LATE(pa_stream_get_state)(p); | |
| switch (state) { | |
| case PA_STREAM_UNCONNECTED: | |
| RTC_LOG(LS_VERBOSE) << "unconnected"; | |
| break; | |
| case PA_STREAM_CREATING: | |
| RTC_LOG(LS_VERBOSE) << "creating"; | |
| break; | |
| case PA_STREAM_FAILED: | |
| case PA_STREAM_TERMINATED: | |
| RTC_LOG(LS_VERBOSE) << "failed"; | |
| break; | |
| case PA_STREAM_READY: | |
| RTC_LOG(LS_VERBOSE) << "ready"; | |
| break; | |
| } | |
| LATE(pa_threaded_mainloop_signal)(_paMainloop, 0); | |
| } | |
| int32_t AudioDeviceLinuxPulse::CheckPulseAudioVersion() { | |
| PaLock(); | |
| pa_operation* paOperation = nullptr; | |
| // get the server info and update deviceName | |
| paOperation = | |
| LATE(pa_context_get_server_info)(_paContext, PaServerInfoCallback, this); | |
| WaitForOperationCompletion(paOperation); | |
| PaUnLock(); | |
| RTC_LOG(LS_VERBOSE) << "checking PulseAudio version: " << _paServerVersion; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::InitSamplingFrequency() { | |
| PaLock(); | |
| pa_operation* paOperation = nullptr; | |
| // 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 = nullptr; | |
| 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 = nullptr; | |
| // 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 = nullptr; | |
| _recDisplayDeviceName = nullptr; | |
| _paDeviceIndex = -1; | |
| _deviceIndex = -1; | |
| _numPlayDevices = 0; | |
| _numRecDevices = 0; | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::InitPulseAudio() { | |
| int retVal = 0; | |
| // Load libpulse | |
| if (!GetPulseSymbolTable()->IsLoaded()) { | |
| // Most likely the Pulse library and sound server are not installed on | |
| // this system | |
| RTC_LOG(LS_ERROR) << "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) { | |
| RTC_LOG(LS_ERROR) << "PA mainloop has already existed"; | |
| return -1; | |
| } | |
| _paMainloop = LATE(pa_threaded_mainloop_new)(); | |
| if (!_paMainloop) { | |
| RTC_LOG(LS_ERROR) << "could not create mainloop"; | |
| return -1; | |
| } | |
| // Start the threaded main loop | |
| retVal = LATE(pa_threaded_mainloop_start)(_paMainloop); | |
| if (retVal != PA_OK) { | |
| RTC_LOG(LS_ERROR) << "failed to start main loop, error=" << retVal; | |
| return -1; | |
| } | |
| RTC_LOG(LS_VERBOSE) << "mainloop running!"; | |
| PaLock(); | |
| _paMainloopApi = LATE(pa_threaded_mainloop_get_api)(_paMainloop); | |
| if (!_paMainloopApi) { | |
| RTC_LOG(LS_ERROR) << "could not create mainloop API"; | |
| PaUnLock(); | |
| return -1; | |
| } | |
| // Create a new PulseAudio context | |
| if (_paContext) { | |
| RTC_LOG(LS_ERROR) << "PA context has already existed"; | |
| PaUnLock(); | |
| return -1; | |
| } | |
| _paContext = LATE(pa_context_new)(_paMainloopApi, "WEBRTC VoiceEngine"); | |
| if (!_paContext) { | |
| RTC_LOG(LS_ERROR) << "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, nullptr, PA_CONTEXT_NOAUTOSPAWN, | |
| nullptr); | |
| if (retVal != PA_OK) { | |
| RTC_LOG(LS_ERROR) << "failed to connect context, error=" << 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) { | |
| RTC_LOG(LS_ERROR) << "failed to connect to PulseAudio sound server"; | |
| } else if (state == PA_CONTEXT_TERMINATED) { | |
| RTC_LOG(LS_ERROR) << "PulseAudio connection terminated early"; | |
| } else { | |
| // Shouldn't happen, because we only signal on one of those three | |
| // states | |
| RTC_LOG(LS_ERROR) << "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) { | |
| RTC_LOG(LS_ERROR) << "PulseAudio version " << _paServerVersion | |
| << " not supported"; | |
| return -1; | |
| } | |
| // Initialize sampling frequency | |
| if (InitSamplingFrequency() < 0 || sample_rate_hz_ == 0) { | |
| RTC_LOG(LS_ERROR) << "failed to initialize sampling frequency, set to " | |
| << sample_rate_hz_ << " Hz"; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int32_t AudioDeviceLinuxPulse::TerminatePulseAudio() { | |
| // Do nothing if the instance doesn't exist | |
| // likely GetPulseSymbolTable.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 = nullptr; | |
| // 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 = nullptr; | |
| RTC_LOG(LS_VERBOSE) << "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) { | |
| RTC_LOG(LS_ERROR) << "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, nullptr, nullptr); | |
| } | |
| 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() { | |
| RTC_LOG(LS_WARNING) << "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) { | |
| RTC_LOG(LS_ERROR) << "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, nullptr, nullptr); | |
| if (!op) { | |
| RTC_LOG(LS_ERROR) << "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, nullptr, nullptr); | |
| } | |
| 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) { | |
| RTC_LOG(LS_ERROR) << "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() { | |
| RTC_LOG(LS_WARNING) << "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) { | |
| RTC_LOG(LS_ERROR) << "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) { | |
| RTC_LOG(LS_VERBOSE) | |
| << "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) | |
| RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { | |
| 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)); | |
| 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) | |
| RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { | |
| _ptrAudioBuffer->SetRecordedBuffer(bufferData, bufferSizeInSamples); | |
| // 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); | |
| _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; | |
| } | |
| return 0; | |
| } | |
| bool AudioDeviceLinuxPulse::PlayThreadProcess() { | |
| if (!_timeEventPlay.Wait(TimeDelta::Seconds(1))) { | |
| return true; | |
| } | |
| MutexLock lock(&mutex_); | |
| if (quit_) { | |
| return false; | |
| } | |
| if (_startPlay) { | |
| RTC_LOG(LS_VERBOSE) << "_startPlay true, performing initial actions"; | |
| _startPlay = false; | |
| _playDeviceName = nullptr; | |
| // 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 = nullptr; | |
| 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, nullptr) != PA_OK) { | |
| RTC_LOG(LS_ERROR) << "failed to connect play stream, err=" | |
| << LATE(pa_context_errno)(_paContext); | |
| } | |
| RTC_LOG(LS_VERBOSE) << "play stream connected"; | |
| // Wait for state change | |
| while (LATE(pa_stream_get_state)(_playStream) != PA_STREAM_READY) { | |
| LATE(pa_threaded_mainloop_wait)(_paMainloop); | |
| } | |
| RTC_LOG(LS_VERBOSE) << "play stream ready"; | |
| // We can now handle write callbacks | |
| EnableWriteCallback(); | |
| PaUnLock(); | |
| // Clear device name | |
| if (_playDeviceName) { | |
| delete[] _playDeviceName; | |
| _playDeviceName = nullptr; | |
| } | |
| _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, | |
| nullptr, (int64_t)0, PA_SEEK_RELATIVE) != PA_OK) { | |
| _writeErrors++; | |
| if (_writeErrors > 10) { | |
| RTC_LOG(LS_ERROR) << "Playout error: _writeErrors=" << _writeErrors | |
| << ", error=" << 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(); | |
| RTC_LOG(LS_VERBOSE) << "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) { | |
| RTC_LOG(LS_ERROR) << "invalid number of output samples(" << nSamples | |
| << ")"; | |
| } | |
| size_t write = _playbackBufferSize; | |
| if (_tempBufferSpace < write) { | |
| write = _tempBufferSpace; | |
| } | |
| RTC_LOG(LS_VERBOSE) << "will write"; | |
| PaLock(); | |
| if (LATE(pa_stream_write)(_playStream, (void*)&_playBuffer[0], write, | |
| nullptr, (int64_t)0, | |
| PA_SEEK_RELATIVE) != PA_OK) { | |
| _writeErrors++; | |
| if (_writeErrors > 10) { | |
| RTC_LOG(LS_ERROR) << "Playout error: _writeErrors=" << _writeErrors | |
| << ", error=" << LATE(pa_context_errno)(_paContext); | |
| _writeErrors = 0; | |
| } | |
| } | |
| PaUnLock(); | |
| _playbackBufferUnused = write; | |
| } | |
| _tempBufferSpace = 0; | |
| PaLock(); | |
| EnableWriteCallback(); | |
| PaUnLock(); | |
| } // _playing | |
| return true; | |
| } | |
| bool AudioDeviceLinuxPulse::RecThreadProcess() { | |
| if (!_timeEventRec.Wait(TimeDelta::Seconds(1))) { | |
| return true; | |
| } | |
| MutexLock lock(&mutex_); | |
| if (quit_) { | |
| return false; | |
| } | |
| if (_startRec) { | |
| RTC_LOG(LS_VERBOSE) << "_startRec true, performing initial actions"; | |
| _recDeviceName = nullptr; | |
| // Set if not default device | |
| if (_inputDeviceIndex > 0) { | |
| // Get the recording device name | |
| _recDeviceName = new char[kAdmMaxDeviceNameSize]; | |
| _deviceIndex = _inputDeviceIndex; | |
| RecordingDevices(); | |
| } | |
| PaLock(); | |
| RTC_LOG(LS_VERBOSE) << "connecting stream"; | |
| // Connect the stream to a source | |
| if (LATE(pa_stream_connect_record)( | |
| _recStream, _recDeviceName, &_recBufferAttr, | |
| (pa_stream_flags_t)_recStreamFlags) != PA_OK) { | |
| RTC_LOG(LS_ERROR) << "failed to connect rec stream, err=" | |
| << LATE(pa_context_errno)(_paContext); | |
| } | |
| RTC_LOG(LS_VERBOSE) << "connected"; | |
| // Wait for state change | |
| while (LATE(pa_stream_get_state)(_recStream) != PA_STREAM_READY) { | |
| LATE(pa_threaded_mainloop_wait)(_paMainloop); | |
| } | |
| RTC_LOG(LS_VERBOSE) << "done"; | |
| // We can now handle read callbacks | |
| EnableReadCallback(); | |
| PaUnLock(); | |
| // Clear device name | |
| if (_recDeviceName) { | |
| delete[] _recDeviceName; | |
| _recDeviceName = nullptr; | |
| } | |
| _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 = nullptr; | |
| _tempSampleDataSize = 0; | |
| PaLock(); | |
| while (true) { | |
| // Ack the last thing we read | |
| if (LATE(pa_stream_drop)(_recStream) != 0) { | |
| RTC_LOG(LS_WARNING) | |
| << "failed to drop, err=" << 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) { | |
| RTC_LOG(LS_ERROR) << "RECORD_ERROR, error = " | |
| << LATE(pa_context_errno)(_paContext); | |
| break; | |
| } | |
| // Drop lock for recorded audio handling, 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 { | |
| #if defined(WEBRTC_USE_X11) | |
| 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); | |
| #else | |
| return false; | |
| #endif | |
| } | |
| } // namespace webrtc |