blob: 4876c0fb91c33428435f2670fb56bbfc74a1dc9b [file] [log] [blame]
/*
* 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 <string.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"
WebRTCPulseSymbolTable* GetPulseSymbolTable() {
static WebRTCPulseSymbolTable* pulse_symbol_table =
new WebRTCPulseSymbolTable();
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(NULL),
_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(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) {
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 = NULL;
}
if (_playBuffer) {
delete[] _playBuffer;
_playBuffer = NULL;
}
if (_playDeviceName) {
delete[] _playDeviceName;
_playDeviceName = NULL;
}
if (_recDeviceName) {
delete[] _recDeviceName;
_recDeviceName = NULL;
}
}
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(NULL);
if (!_XDisplay) {
RTC_LOG(LS_WARNING)
<< "failed to open X display, typing detection will not work";
}
#endif
// RECORDING
const auto attributes =
rtc::ThreadAttributes().SetPriority(rtc::ThreadPriority::kRealtime);
_ptrThreadRec = rtc::PlatformThread::SpawnJoinable(
[this] {
while (RecThreadProcess()) {
}
},
"webrtc_audio_module_rec_thread", attributes);
// PLAYOUT
_ptrThreadPlay = rtc::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 = NULL;
}
#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, 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_.IsCurrent());
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_.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 = 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_.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 == 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_.IsCurrent());
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_.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, NULL);
}
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, NULL);
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(10000)) {
{
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 == NULL) {
return -1;
}
_recIsInitialized = false;
_recording = false;
RTC_LOG(LS_VERBOSE) << "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) {
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 = 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_.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(10000)) {
{
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 == NULL) {
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, 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) {
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 = 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 {
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 = NULL;
// 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 = 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 (!GetPulseSymbolTable()->Load()) {
// 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, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
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 = 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;
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, NULL, NULL);
}
void AudioDeviceLinuxPulse::PaStreamWriteCallback(pa_stream* /*unused*/,
size_t buffer_space,
void* pThis) {
static_cast<AudioDeviceLinuxPulse*>(pThis)->PaStreamWriteCallbackHandler(
buffer_space);
}
void AudioDeviceLinuxPulse::PaStreamWriteCallbackHandler(size_t bufferSpace) {
_tempBufferSpace = bufferSpace;
// Since we write the data asynchronously on a different thread, we have
// to temporarily disable the write callback or else Pulse will call it
// continuously until we write the data. We re-enable it below.
DisableWriteCallback();
_timeEventPlay.Set();
}
void AudioDeviceLinuxPulse::PaStreamUnderflowCallback(pa_stream* /*unused*/,
void* pThis) {
static_cast<AudioDeviceLinuxPulse*>(pThis)
->PaStreamUnderflowCallbackHandler();
}
void AudioDeviceLinuxPulse::PaStreamUnderflowCallbackHandler() {
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, NULL, NULL);
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, NULL, NULL);
}
void AudioDeviceLinuxPulse::PaStreamReadCallback(pa_stream* /*unused1*/,
size_t /*unused2*/,
void* pThis) {
static_cast<AudioDeviceLinuxPulse*>(pThis)->PaStreamReadCallbackHandler();
}
void AudioDeviceLinuxPulse::PaStreamReadCallbackHandler() {
// We get the data pointer and size now in order to save one Lock/Unlock
// in the worker thread.
if (LATE(pa_stream_peek)(_recStream, &_tempSampleData,
&_tempSampleDataSize) != 0) {
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(1000)) {
return true;
}
MutexLock lock(&mutex_);
if (quit_) {
return false;
}
if (_startPlay) {
RTC_LOG(LS_VERBOSE) << "_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) {
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 = 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) {
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,
NULL, (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(1000)) {
return true;
}
MutexLock lock(&mutex_);
if (quit_) {
return false;
}
if (_startRec) {
RTC_LOG(LS_VERBOSE) << "_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();
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 = 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) {
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 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 {
#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