summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/audio_device/linux/audio_device_alsa_linux.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/modules/audio_device/linux/audio_device_alsa_linux.cc
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/audio_device/linux/audio_device_alsa_linux.cc')
-rw-r--r--third_party/libwebrtc/modules/audio_device/linux/audio_device_alsa_linux.cc1636
1 files changed, 1636 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/audio_device/linux/audio_device_alsa_linux.cc b/third_party/libwebrtc/modules/audio_device/linux/audio_device_alsa_linux.cc
new file mode 100644
index 0000000000..1e0ac8be28
--- /dev/null
+++ b/third_party/libwebrtc/modules/audio_device/linux/audio_device_alsa_linux.cc
@@ -0,0 +1,1636 @@
+/*
+ * 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_alsa_linux.h"
+
+#include "modules/audio_device/audio_device_config.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/system/arch.h"
+#include "system_wrappers/include/sleep.h"
+
+WebRTCAlsaSymbolTable* GetAlsaSymbolTable() {
+ static WebRTCAlsaSymbolTable* alsa_symbol_table = new WebRTCAlsaSymbolTable();
+ return alsa_symbol_table;
+}
+
+// Accesses ALSA functions through our late-binding symbol table instead of
+// directly. This way we don't have to link to libasound, which means our binary
+// will work on systems that don't have it.
+#define LATE(sym) \
+ LATESYM_GET(webrtc::adm_linux_alsa::AlsaSymbolTable, GetAlsaSymbolTable(), \
+ sym)
+
+// Redefine these here to be able to do late-binding
+#undef snd_ctl_card_info_alloca
+#define snd_ctl_card_info_alloca(ptr) \
+ do { \
+ *ptr = (snd_ctl_card_info_t*)__builtin_alloca( \
+ LATE(snd_ctl_card_info_sizeof)()); \
+ memset(*ptr, 0, LATE(snd_ctl_card_info_sizeof)()); \
+ } while (0)
+
+#undef snd_pcm_info_alloca
+#define snd_pcm_info_alloca(pInfo) \
+ do { \
+ *pInfo = (snd_pcm_info_t*)__builtin_alloca(LATE(snd_pcm_info_sizeof)()); \
+ memset(*pInfo, 0, LATE(snd_pcm_info_sizeof)()); \
+ } while (0)
+
+// snd_lib_error_handler_t
+void WebrtcAlsaErrorHandler(const char* file,
+ int line,
+ const char* function,
+ int err,
+ const char* fmt,
+ ...) {}
+
+namespace webrtc {
+static const unsigned int ALSA_PLAYOUT_FREQ = 48000;
+static const unsigned int ALSA_PLAYOUT_CH = 2;
+static const unsigned int ALSA_PLAYOUT_LATENCY = 40 * 1000; // in us
+static const unsigned int ALSA_CAPTURE_FREQ = 48000;
+static const unsigned int ALSA_CAPTURE_CH = 2;
+static const unsigned int ALSA_CAPTURE_LATENCY = 40 * 1000; // in us
+static const unsigned int ALSA_CAPTURE_WAIT_TIMEOUT = 5; // in ms
+
+#define FUNC_GET_NUM_OF_DEVICE 0
+#define FUNC_GET_DEVICE_NAME 1
+#define FUNC_GET_DEVICE_NAME_FOR_AN_ENUM 2
+
+AudioDeviceLinuxALSA::AudioDeviceLinuxALSA()
+ : _ptrAudioBuffer(NULL),
+ _inputDeviceIndex(0),
+ _outputDeviceIndex(0),
+ _inputDeviceIsSpecified(false),
+ _outputDeviceIsSpecified(false),
+ _handleRecord(NULL),
+ _handlePlayout(NULL),
+ _recordingBuffersizeInFrame(0),
+ _recordingPeriodSizeInFrame(0),
+ _playoutBufferSizeInFrame(0),
+ _playoutPeriodSizeInFrame(0),
+ _recordingBufferSizeIn10MS(0),
+ _playoutBufferSizeIn10MS(0),
+ _recordingFramesIn10MS(0),
+ _playoutFramesIn10MS(0),
+ _recordingFreq(ALSA_CAPTURE_FREQ),
+ _playoutFreq(ALSA_PLAYOUT_FREQ),
+ _recChannels(ALSA_CAPTURE_CH),
+ _playChannels(ALSA_PLAYOUT_CH),
+ _recordingBuffer(NULL),
+ _playoutBuffer(NULL),
+ _recordingFramesLeft(0),
+ _playoutFramesLeft(0),
+ _initialized(false),
+ _recording(false),
+ _playing(false),
+ _recIsInitialized(false),
+ _playIsInitialized(false),
+ _recordingDelay(0),
+ _playoutDelay(0) {
+ memset(_oldKeyState, 0, sizeof(_oldKeyState));
+ RTC_DLOG(LS_INFO) << __FUNCTION__ << " created";
+}
+
+// ----------------------------------------------------------------------------
+// AudioDeviceLinuxALSA - dtor
+// ----------------------------------------------------------------------------
+
+AudioDeviceLinuxALSA::~AudioDeviceLinuxALSA() {
+ RTC_DLOG(LS_INFO) << __FUNCTION__ << " destroyed";
+
+ Terminate();
+
+ // Clean up the recording buffer and playout buffer.
+ if (_recordingBuffer) {
+ delete[] _recordingBuffer;
+ _recordingBuffer = NULL;
+ }
+ if (_playoutBuffer) {
+ delete[] _playoutBuffer;
+ _playoutBuffer = NULL;
+ }
+}
+
+void AudioDeviceLinuxALSA::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
+ MutexLock lock(&mutex_);
+
+ _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);
+}
+
+int32_t AudioDeviceLinuxALSA::ActiveAudioLayer(
+ AudioDeviceModule::AudioLayer& audioLayer) const {
+ audioLayer = AudioDeviceModule::kLinuxAlsaAudio;
+ return 0;
+}
+
+AudioDeviceGeneric::InitStatus AudioDeviceLinuxALSA::Init() {
+ MutexLock lock(&mutex_);
+
+ // Load libasound
+ if (!GetAlsaSymbolTable()->Load()) {
+ // Alsa is not installed on this system
+ RTC_LOG(LS_ERROR) << "failed to load symbol table";
+ return InitStatus::OTHER_ERROR;
+ }
+
+ if (_initialized) {
+ return InitStatus::OK;
+ }
+#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
+
+ _initialized = true;
+
+ return InitStatus::OK;
+}
+
+int32_t AudioDeviceLinuxALSA::Terminate() {
+ if (!_initialized) {
+ return 0;
+ }
+
+ MutexLock lock(&mutex_);
+
+ _mixerManager.Close();
+
+ // RECORDING
+ mutex_.Unlock();
+ _ptrThreadRec.Finalize();
+
+ // PLAYOUT
+ _ptrThreadPlay.Finalize();
+ mutex_.Lock();
+
+#if defined(WEBRTC_USE_X11)
+ if (_XDisplay) {
+ XCloseDisplay(_XDisplay);
+ _XDisplay = NULL;
+ }
+#endif
+ _initialized = false;
+ _outputDeviceIsSpecified = false;
+ _inputDeviceIsSpecified = false;
+
+ return 0;
+}
+
+bool AudioDeviceLinuxALSA::Initialized() const {
+ return (_initialized);
+}
+
+int32_t AudioDeviceLinuxALSA::InitSpeaker() {
+ MutexLock lock(&mutex_);
+ return InitSpeakerLocked();
+}
+
+int32_t AudioDeviceLinuxALSA::InitSpeakerLocked() {
+ if (_playing) {
+ return -1;
+ }
+
+ char devName[kAdmMaxDeviceNameSize] = {0};
+ GetDevicesInfo(2, true, _outputDeviceIndex, devName, kAdmMaxDeviceNameSize);
+ return _mixerManager.OpenSpeaker(devName);
+}
+
+int32_t AudioDeviceLinuxALSA::InitMicrophone() {
+ MutexLock lock(&mutex_);
+ return InitMicrophoneLocked();
+}
+
+int32_t AudioDeviceLinuxALSA::InitMicrophoneLocked() {
+ if (_recording) {
+ return -1;
+ }
+
+ char devName[kAdmMaxDeviceNameSize] = {0};
+ GetDevicesInfo(2, false, _inputDeviceIndex, devName, kAdmMaxDeviceNameSize);
+ return _mixerManager.OpenMicrophone(devName);
+}
+
+bool AudioDeviceLinuxALSA::SpeakerIsInitialized() const {
+ return (_mixerManager.SpeakerIsInitialized());
+}
+
+bool AudioDeviceLinuxALSA::MicrophoneIsInitialized() const {
+ return (_mixerManager.MicrophoneIsInitialized());
+}
+
+int32_t AudioDeviceLinuxALSA::SpeakerVolumeIsAvailable(bool& available) {
+ 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 that a volume control
+ // exists
+ available = true;
+
+ // Close the initialized output mixer
+ if (!wasInitialized) {
+ _mixerManager.CloseSpeaker();
+ }
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::SetSpeakerVolume(uint32_t volume) {
+ return (_mixerManager.SetSpeakerVolume(volume));
+}
+
+int32_t AudioDeviceLinuxALSA::SpeakerVolume(uint32_t& volume) const {
+ uint32_t level(0);
+
+ if (_mixerManager.SpeakerVolume(level) == -1) {
+ return -1;
+ }
+
+ volume = level;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::MaxSpeakerVolume(uint32_t& maxVolume) const {
+ uint32_t maxVol(0);
+
+ if (_mixerManager.MaxSpeakerVolume(maxVol) == -1) {
+ return -1;
+ }
+
+ maxVolume = maxVol;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::MinSpeakerVolume(uint32_t& minVolume) const {
+ uint32_t minVol(0);
+
+ if (_mixerManager.MinSpeakerVolume(minVol) == -1) {
+ return -1;
+ }
+
+ minVolume = minVol;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::SpeakerMuteIsAvailable(bool& available) {
+ 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 AudioDeviceLinuxALSA::SetSpeakerMute(bool enable) {
+ return (_mixerManager.SetSpeakerMute(enable));
+}
+
+int32_t AudioDeviceLinuxALSA::SpeakerMute(bool& enabled) const {
+ bool muted(0);
+
+ if (_mixerManager.SpeakerMute(muted) == -1) {
+ return -1;
+ }
+
+ enabled = muted;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::MicrophoneMuteIsAvailable(bool& available) {
+ 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 mute 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 AudioDeviceLinuxALSA::SetMicrophoneMute(bool enable) {
+ return (_mixerManager.SetMicrophoneMute(enable));
+}
+
+// ----------------------------------------------------------------------------
+// MicrophoneMute
+// ----------------------------------------------------------------------------
+
+int32_t AudioDeviceLinuxALSA::MicrophoneMute(bool& enabled) const {
+ bool muted(0);
+
+ if (_mixerManager.MicrophoneMute(muted) == -1) {
+ return -1;
+ }
+
+ enabled = muted;
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::StereoRecordingIsAvailable(bool& available) {
+ MutexLock lock(&mutex_);
+
+ // If we already have initialized in stereo it's obviously available
+ if (_recIsInitialized && (2 == _recChannels)) {
+ available = true;
+ return 0;
+ }
+
+ // Save rec states and the number of rec channels
+ bool recIsInitialized = _recIsInitialized;
+ bool recording = _recording;
+ int recChannels = _recChannels;
+
+ available = false;
+
+ // Stop/uninitialize recording if initialized (and possibly started)
+ if (_recIsInitialized) {
+ StopRecordingLocked();
+ }
+
+ // Try init in stereo;
+ _recChannels = 2;
+ if (InitRecordingLocked() == 0) {
+ available = true;
+ }
+
+ // Stop/uninitialize recording
+ StopRecordingLocked();
+
+ // Recover previous states
+ _recChannels = recChannels;
+ if (recIsInitialized) {
+ InitRecordingLocked();
+ }
+ if (recording) {
+ StartRecording();
+ }
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::SetStereoRecording(bool enable) {
+ if (enable)
+ _recChannels = 2;
+ else
+ _recChannels = 1;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::StereoRecording(bool& enabled) const {
+ if (_recChannels == 2)
+ enabled = true;
+ else
+ enabled = false;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::StereoPlayoutIsAvailable(bool& available) {
+ MutexLock lock(&mutex_);
+
+ // If we already have initialized in stereo it's obviously available
+ if (_playIsInitialized && (2 == _playChannels)) {
+ available = true;
+ return 0;
+ }
+
+ // Save rec states and the number of rec channels
+ bool playIsInitialized = _playIsInitialized;
+ bool playing = _playing;
+ int playChannels = _playChannels;
+
+ available = false;
+
+ // Stop/uninitialize recording if initialized (and possibly started)
+ if (_playIsInitialized) {
+ StopPlayoutLocked();
+ }
+
+ // Try init in stereo;
+ _playChannels = 2;
+ if (InitPlayoutLocked() == 0) {
+ available = true;
+ }
+
+ // Stop/uninitialize recording
+ StopPlayoutLocked();
+
+ // Recover previous states
+ _playChannels = playChannels;
+ if (playIsInitialized) {
+ InitPlayoutLocked();
+ }
+ if (playing) {
+ StartPlayout();
+ }
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::SetStereoPlayout(bool enable) {
+ if (enable)
+ _playChannels = 2;
+ else
+ _playChannels = 1;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::StereoPlayout(bool& enabled) const {
+ if (_playChannels == 2)
+ enabled = true;
+ else
+ enabled = false;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::MicrophoneVolumeIsAvailable(bool& available) {
+ 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 AudioDeviceLinuxALSA::SetMicrophoneVolume(uint32_t volume) {
+ return (_mixerManager.SetMicrophoneVolume(volume));
+}
+
+int32_t AudioDeviceLinuxALSA::MicrophoneVolume(uint32_t& volume) const {
+ uint32_t level(0);
+
+ if (_mixerManager.MicrophoneVolume(level) == -1) {
+ RTC_LOG(LS_WARNING) << "failed to retrive current microphone level";
+ return -1;
+ }
+
+ volume = level;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::MaxMicrophoneVolume(uint32_t& maxVolume) const {
+ uint32_t maxVol(0);
+
+ if (_mixerManager.MaxMicrophoneVolume(maxVol) == -1) {
+ return -1;
+ }
+
+ maxVolume = maxVol;
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::MinMicrophoneVolume(uint32_t& minVolume) const {
+ uint32_t minVol(0);
+
+ if (_mixerManager.MinMicrophoneVolume(minVol) == -1) {
+ return -1;
+ }
+
+ minVolume = minVol;
+
+ return 0;
+}
+
+int16_t AudioDeviceLinuxALSA::PlayoutDevices() {
+ return (int16_t)GetDevicesInfo(0, true);
+}
+
+int32_t AudioDeviceLinuxALSA::SetPlayoutDevice(uint16_t index) {
+ if (_playIsInitialized) {
+ return -1;
+ }
+
+ int32_t nDevices = GetDevicesInfo(0, true);
+ RTC_LOG(LS_VERBOSE) << "number of available audio 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 AudioDeviceLinuxALSA::SetPlayoutDevice(
+ AudioDeviceModule::WindowsDeviceType /*device*/) {
+ RTC_LOG(LS_ERROR) << "WindowsDeviceType not supported";
+ return -1;
+}
+
+int32_t AudioDeviceLinuxALSA::PlayoutDeviceName(
+ uint16_t index,
+ char name[kAdmMaxDeviceNameSize],
+ char guid[kAdmMaxGuidSize]) {
+ const uint16_t nDevices(PlayoutDevices());
+
+ if ((index > (nDevices - 1)) || (name == NULL)) {
+ return -1;
+ }
+
+ memset(name, 0, kAdmMaxDeviceNameSize);
+
+ if (guid != NULL) {
+ memset(guid, 0, kAdmMaxGuidSize);
+ }
+
+ return GetDevicesInfo(1, true, index, name, kAdmMaxDeviceNameSize);
+}
+
+int32_t AudioDeviceLinuxALSA::RecordingDeviceName(
+ uint16_t index,
+ char name[kAdmMaxDeviceNameSize],
+ char guid[kAdmMaxGuidSize]) {
+ const uint16_t nDevices(RecordingDevices());
+
+ if ((index > (nDevices - 1)) || (name == NULL)) {
+ return -1;
+ }
+
+ memset(name, 0, kAdmMaxDeviceNameSize);
+
+ if (guid != NULL) {
+ memset(guid, 0, kAdmMaxGuidSize);
+ }
+
+ return GetDevicesInfo(1, false, index, name, kAdmMaxDeviceNameSize);
+}
+
+int16_t AudioDeviceLinuxALSA::RecordingDevices() {
+ return (int16_t)GetDevicesInfo(0, false);
+}
+
+int32_t AudioDeviceLinuxALSA::SetRecordingDevice(uint16_t index) {
+ if (_recIsInitialized) {
+ return -1;
+ }
+
+ int32_t nDevices = GetDevicesInfo(0, false);
+ RTC_LOG(LS_VERBOSE) << "number of availiable audio 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;
+}
+
+// ----------------------------------------------------------------------------
+// SetRecordingDevice II (II)
+// ----------------------------------------------------------------------------
+
+int32_t AudioDeviceLinuxALSA::SetRecordingDevice(
+ AudioDeviceModule::WindowsDeviceType /*device*/) {
+ RTC_LOG(LS_ERROR) << "WindowsDeviceType not supported";
+ return -1;
+}
+
+int32_t AudioDeviceLinuxALSA::PlayoutIsAvailable(bool& available) {
+ available = false;
+
+ // Try to initialize the playout side with mono
+ // Assumes that user set num channels after calling this function
+ _playChannels = 1;
+ int32_t res = InitPlayout();
+
+ // Cancel effect of initialization
+ StopPlayout();
+
+ if (res != -1) {
+ available = true;
+ } else {
+ // It may be possible to play out in stereo
+ res = StereoPlayoutIsAvailable(available);
+ if (available) {
+ // Then set channels to 2 so InitPlayout doesn't fail
+ _playChannels = 2;
+ }
+ }
+
+ return res;
+}
+
+int32_t AudioDeviceLinuxALSA::RecordingIsAvailable(bool& available) {
+ available = false;
+
+ // Try to initialize the recording side with mono
+ // Assumes that user set num channels after calling this function
+ _recChannels = 1;
+ int32_t res = InitRecording();
+
+ // Cancel effect of initialization
+ StopRecording();
+
+ if (res != -1) {
+ available = true;
+ } else {
+ // It may be possible to record in stereo
+ res = StereoRecordingIsAvailable(available);
+ if (available) {
+ // Then set channels to 2 so InitPlayout doesn't fail
+ _recChannels = 2;
+ }
+ }
+
+ return res;
+}
+
+int32_t AudioDeviceLinuxALSA::InitPlayout() {
+ MutexLock lock(&mutex_);
+ return InitPlayoutLocked();
+}
+
+int32_t AudioDeviceLinuxALSA::InitPlayoutLocked() {
+ int errVal = 0;
+
+ if (_playing) {
+ return -1;
+ }
+
+ if (!_outputDeviceIsSpecified) {
+ return -1;
+ }
+
+ if (_playIsInitialized) {
+ return 0;
+ }
+ // Initialize the speaker (devices might have been added or removed)
+ if (InitSpeakerLocked() == -1) {
+ RTC_LOG(LS_WARNING) << "InitSpeaker() failed";
+ }
+
+ // Start by closing any existing wave-output devices
+ //
+ if (_handlePlayout != NULL) {
+ LATE(snd_pcm_close)(_handlePlayout);
+ _handlePlayout = NULL;
+ _playIsInitialized = false;
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "Error closing current playout sound device, error: "
+ << LATE(snd_strerror)(errVal);
+ }
+ }
+
+ // Open PCM device for playout
+ char deviceName[kAdmMaxDeviceNameSize] = {0};
+ GetDevicesInfo(2, true, _outputDeviceIndex, deviceName,
+ kAdmMaxDeviceNameSize);
+
+ RTC_LOG(LS_VERBOSE) << "InitPlayout open (" << deviceName << ")";
+
+ errVal = LATE(snd_pcm_open)(&_handlePlayout, deviceName,
+ SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+
+ if (errVal == -EBUSY) // Device busy - try some more!
+ {
+ for (int i = 0; i < 5; i++) {
+ SleepMs(1000);
+ errVal = LATE(snd_pcm_open)(&_handlePlayout, deviceName,
+ SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (errVal == 0) {
+ break;
+ }
+ }
+ }
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "unable to open playback device: "
+ << LATE(snd_strerror)(errVal) << " (" << errVal << ")";
+ _handlePlayout = NULL;
+ return -1;
+ }
+
+ _playoutFramesIn10MS = _playoutFreq / 100;
+ if ((errVal = LATE(snd_pcm_set_params)(
+ _handlePlayout,
+#if defined(WEBRTC_ARCH_BIG_ENDIAN)
+ SND_PCM_FORMAT_S16_BE,
+#else
+ SND_PCM_FORMAT_S16_LE, // format
+#endif
+ SND_PCM_ACCESS_RW_INTERLEAVED, // access
+ _playChannels, // channels
+ _playoutFreq, // rate
+ 1, // soft_resample
+ ALSA_PLAYOUT_LATENCY // 40*1000 //latency required overall latency
+ // in us
+ )) < 0) { /* 0.5sec */
+ _playoutFramesIn10MS = 0;
+ RTC_LOG(LS_ERROR) << "unable to set playback device: "
+ << LATE(snd_strerror)(errVal) << " (" << errVal << ")";
+ ErrorRecovery(errVal, _handlePlayout);
+ errVal = LATE(snd_pcm_close)(_handlePlayout);
+ _handlePlayout = NULL;
+ return -1;
+ }
+
+ errVal = LATE(snd_pcm_get_params)(_handlePlayout, &_playoutBufferSizeInFrame,
+ &_playoutPeriodSizeInFrame);
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "snd_pcm_get_params: " << LATE(snd_strerror)(errVal)
+ << " (" << errVal << ")";
+ _playoutBufferSizeInFrame = 0;
+ _playoutPeriodSizeInFrame = 0;
+ } else {
+ RTC_LOG(LS_VERBOSE) << "playout snd_pcm_get_params buffer_size:"
+ << _playoutBufferSizeInFrame
+ << " period_size :" << _playoutPeriodSizeInFrame;
+ }
+
+ if (_ptrAudioBuffer) {
+ // Update webrtc audio buffer with the selected parameters
+ _ptrAudioBuffer->SetPlayoutSampleRate(_playoutFreq);
+ _ptrAudioBuffer->SetPlayoutChannels(_playChannels);
+ }
+
+ // Set play buffer size
+ _playoutBufferSizeIn10MS =
+ LATE(snd_pcm_frames_to_bytes)(_handlePlayout, _playoutFramesIn10MS);
+
+ // Init varaibles used for play
+
+ if (_handlePlayout != NULL) {
+ _playIsInitialized = true;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+int32_t AudioDeviceLinuxALSA::InitRecording() {
+ MutexLock lock(&mutex_);
+ return InitRecordingLocked();
+}
+
+int32_t AudioDeviceLinuxALSA::InitRecordingLocked() {
+ int errVal = 0;
+
+ if (_recording) {
+ return -1;
+ }
+
+ if (!_inputDeviceIsSpecified) {
+ return -1;
+ }
+
+ if (_recIsInitialized) {
+ return 0;
+ }
+
+ // Initialize the microphone (devices might have been added or removed)
+ if (InitMicrophoneLocked() == -1) {
+ RTC_LOG(LS_WARNING) << "InitMicrophone() failed";
+ }
+
+ // Start by closing any existing pcm-input devices
+ //
+ if (_handleRecord != NULL) {
+ int errVal = LATE(snd_pcm_close)(_handleRecord);
+ _handleRecord = NULL;
+ _recIsInitialized = false;
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR)
+ << "Error closing current recording sound device, error: "
+ << LATE(snd_strerror)(errVal);
+ }
+ }
+
+ // Open PCM device for recording
+ // The corresponding settings for playout are made after the record settings
+ char deviceName[kAdmMaxDeviceNameSize] = {0};
+ GetDevicesInfo(2, false, _inputDeviceIndex, deviceName,
+ kAdmMaxDeviceNameSize);
+
+ RTC_LOG(LS_VERBOSE) << "InitRecording open (" << deviceName << ")";
+ errVal = LATE(snd_pcm_open)(&_handleRecord, deviceName,
+ SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
+
+ // Available modes: 0 = blocking, SND_PCM_NONBLOCK, SND_PCM_ASYNC
+ if (errVal == -EBUSY) // Device busy - try some more!
+ {
+ for (int i = 0; i < 5; i++) {
+ SleepMs(1000);
+ errVal = LATE(snd_pcm_open)(&_handleRecord, deviceName,
+ SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
+ if (errVal == 0) {
+ break;
+ }
+ }
+ }
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "unable to open record device: "
+ << LATE(snd_strerror)(errVal);
+ _handleRecord = NULL;
+ return -1;
+ }
+
+ _recordingFramesIn10MS = _recordingFreq / 100;
+ if ((errVal =
+ LATE(snd_pcm_set_params)(_handleRecord,
+#if defined(WEBRTC_ARCH_BIG_ENDIAN)
+ SND_PCM_FORMAT_S16_BE, // format
+#else
+ SND_PCM_FORMAT_S16_LE, // format
+#endif
+ SND_PCM_ACCESS_RW_INTERLEAVED, // access
+ _recChannels, // channels
+ _recordingFreq, // rate
+ 1, // soft_resample
+ ALSA_CAPTURE_LATENCY // latency in us
+ )) < 0) {
+ // Fall back to another mode then.
+ if (_recChannels == 1)
+ _recChannels = 2;
+ else
+ _recChannels = 1;
+
+ if ((errVal =
+ LATE(snd_pcm_set_params)(_handleRecord,
+#if defined(WEBRTC_ARCH_BIG_ENDIAN)
+ SND_PCM_FORMAT_S16_BE, // format
+#else
+ SND_PCM_FORMAT_S16_LE, // format
+#endif
+ SND_PCM_ACCESS_RW_INTERLEAVED, // access
+ _recChannels, // channels
+ _recordingFreq, // rate
+ 1, // soft_resample
+ ALSA_CAPTURE_LATENCY // latency in us
+ )) < 0) {
+ _recordingFramesIn10MS = 0;
+ RTC_LOG(LS_ERROR) << "unable to set record settings: "
+ << LATE(snd_strerror)(errVal) << " (" << errVal << ")";
+ ErrorRecovery(errVal, _handleRecord);
+ errVal = LATE(snd_pcm_close)(_handleRecord);
+ _handleRecord = NULL;
+ return -1;
+ }
+ }
+
+ errVal = LATE(snd_pcm_get_params)(_handleRecord, &_recordingBuffersizeInFrame,
+ &_recordingPeriodSizeInFrame);
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "snd_pcm_get_params " << LATE(snd_strerror)(errVal)
+ << " (" << errVal << ")";
+ _recordingBuffersizeInFrame = 0;
+ _recordingPeriodSizeInFrame = 0;
+ } else {
+ RTC_LOG(LS_VERBOSE) << "capture snd_pcm_get_params, buffer_size:"
+ << _recordingBuffersizeInFrame
+ << ", period_size:" << _recordingPeriodSizeInFrame;
+ }
+
+ if (_ptrAudioBuffer) {
+ // Update webrtc audio buffer with the selected parameters
+ _ptrAudioBuffer->SetRecordingSampleRate(_recordingFreq);
+ _ptrAudioBuffer->SetRecordingChannels(_recChannels);
+ }
+
+ // Set rec buffer size and create buffer
+ _recordingBufferSizeIn10MS =
+ LATE(snd_pcm_frames_to_bytes)(_handleRecord, _recordingFramesIn10MS);
+
+ if (_handleRecord != NULL) {
+ // Mark recording side as initialized
+ _recIsInitialized = true;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+int32_t AudioDeviceLinuxALSA::StartRecording() {
+ if (!_recIsInitialized) {
+ return -1;
+ }
+
+ if (_recording) {
+ return 0;
+ }
+
+ _recording = true;
+
+ int errVal = 0;
+ _recordingFramesLeft = _recordingFramesIn10MS;
+
+ // Make sure we only create the buffer once.
+ if (!_recordingBuffer)
+ _recordingBuffer = new int8_t[_recordingBufferSizeIn10MS];
+ if (!_recordingBuffer) {
+ RTC_LOG(LS_ERROR) << "failed to alloc recording buffer";
+ _recording = false;
+ return -1;
+ }
+ // RECORDING
+ _ptrThreadRec = rtc::PlatformThread::SpawnJoinable(
+ [this] {
+ while (RecThreadProcess()) {
+ }
+ },
+ "webrtc_audio_module_capture_thread",
+ rtc::ThreadAttributes().SetPriority(rtc::ThreadPriority::kRealtime));
+
+ errVal = LATE(snd_pcm_prepare)(_handleRecord);
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "capture snd_pcm_prepare failed ("
+ << LATE(snd_strerror)(errVal) << ")\n";
+ // just log error
+ // if snd_pcm_open fails will return -1
+ }
+
+ errVal = LATE(snd_pcm_start)(_handleRecord);
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "capture snd_pcm_start err: "
+ << LATE(snd_strerror)(errVal);
+ errVal = LATE(snd_pcm_start)(_handleRecord);
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "capture snd_pcm_start 2nd try err: "
+ << LATE(snd_strerror)(errVal);
+ StopRecording();
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::StopRecording() {
+ MutexLock lock(&mutex_);
+ return StopRecordingLocked();
+}
+
+int32_t AudioDeviceLinuxALSA::StopRecordingLocked() {
+ if (!_recIsInitialized) {
+ return 0;
+ }
+
+ if (_handleRecord == NULL) {
+ return -1;
+ }
+
+ // Make sure we don't start recording (it's asynchronous).
+ _recIsInitialized = false;
+ _recording = false;
+
+ _ptrThreadRec.Finalize();
+
+ _recordingFramesLeft = 0;
+ if (_recordingBuffer) {
+ delete[] _recordingBuffer;
+ _recordingBuffer = NULL;
+ }
+
+ // Stop and close pcm recording device.
+ int errVal = LATE(snd_pcm_drop)(_handleRecord);
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "Error stop recording: " << LATE(snd_strerror)(errVal);
+ return -1;
+ }
+
+ errVal = LATE(snd_pcm_close)(_handleRecord);
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "Error closing record sound device, error: "
+ << LATE(snd_strerror)(errVal);
+ return -1;
+ }
+
+ // Check if we have muted and unmute if so.
+ bool muteEnabled = false;
+ MicrophoneMute(muteEnabled);
+ if (muteEnabled) {
+ SetMicrophoneMute(false);
+ }
+
+ // set the pcm input handle to NULL
+ _handleRecord = NULL;
+ return 0;
+}
+
+bool AudioDeviceLinuxALSA::RecordingIsInitialized() const {
+ return (_recIsInitialized);
+}
+
+bool AudioDeviceLinuxALSA::Recording() const {
+ return (_recording);
+}
+
+bool AudioDeviceLinuxALSA::PlayoutIsInitialized() const {
+ return (_playIsInitialized);
+}
+
+int32_t AudioDeviceLinuxALSA::StartPlayout() {
+ if (!_playIsInitialized) {
+ return -1;
+ }
+
+ if (_playing) {
+ return 0;
+ }
+
+ _playing = true;
+
+ _playoutFramesLeft = 0;
+ if (!_playoutBuffer)
+ _playoutBuffer = new int8_t[_playoutBufferSizeIn10MS];
+ if (!_playoutBuffer) {
+ RTC_LOG(LS_ERROR) << "failed to alloc playout buf";
+ _playing = false;
+ return -1;
+ }
+
+ // PLAYOUT
+ _ptrThreadPlay = rtc::PlatformThread::SpawnJoinable(
+ [this] {
+ while (PlayThreadProcess()) {
+ }
+ },
+ "webrtc_audio_module_play_thread",
+ rtc::ThreadAttributes().SetPriority(rtc::ThreadPriority::kRealtime));
+
+ int errVal = LATE(snd_pcm_prepare)(_handlePlayout);
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "playout snd_pcm_prepare failed ("
+ << LATE(snd_strerror)(errVal) << ")\n";
+ // just log error
+ // if snd_pcm_open fails will return -1
+ }
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::StopPlayout() {
+ MutexLock lock(&mutex_);
+ return StopPlayoutLocked();
+}
+
+int32_t AudioDeviceLinuxALSA::StopPlayoutLocked() {
+ if (!_playIsInitialized) {
+ return 0;
+ }
+
+ if (_handlePlayout == NULL) {
+ return -1;
+ }
+
+ _playing = false;
+
+ // stop playout thread first
+ _ptrThreadPlay.Finalize();
+
+ _playoutFramesLeft = 0;
+ delete[] _playoutBuffer;
+ _playoutBuffer = NULL;
+
+ // stop and close pcm playout device
+ int errVal = LATE(snd_pcm_drop)(_handlePlayout);
+ if (errVal < 0) {
+ RTC_LOG(LS_ERROR) << "Error stop playing: " << LATE(snd_strerror)(errVal);
+ }
+
+ errVal = LATE(snd_pcm_close)(_handlePlayout);
+ if (errVal < 0)
+ RTC_LOG(LS_ERROR) << "Error closing playout sound device, error: "
+ << LATE(snd_strerror)(errVal);
+
+ // set the pcm input handle to NULL
+ _playIsInitialized = false;
+ _handlePlayout = NULL;
+ RTC_LOG(LS_VERBOSE) << "handle_playout is now set to NULL";
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::PlayoutDelay(uint16_t& delayMS) const {
+ delayMS = (uint16_t)_playoutDelay * 1000 / _playoutFreq;
+ return 0;
+}
+
+bool AudioDeviceLinuxALSA::Playing() const {
+ return (_playing);
+}
+
+// ============================================================================
+// Private Methods
+// ============================================================================
+
+int32_t AudioDeviceLinuxALSA::GetDevicesInfo(const int32_t function,
+ const bool playback,
+ const int32_t enumDeviceNo,
+ char* enumDeviceName,
+ const int32_t ednLen) const {
+ // Device enumeration based on libjingle implementation
+ // by Tristan Schmelcher at Google Inc.
+
+ const char* type = playback ? "Output" : "Input";
+ // dmix and dsnoop are only for playback and capture, respectively, but ALSA
+ // stupidly includes them in both lists.
+ const char* ignorePrefix = playback ? "dsnoop:" : "dmix:";
+ // (ALSA lists many more "devices" of questionable interest, but we show them
+ // just in case the weird devices may actually be desirable for some
+ // users/systems.)
+
+ int err;
+ int enumCount(0);
+ bool keepSearching(true);
+
+ // From Chromium issue 95797
+ // Loop through the sound cards to get Alsa device hints.
+ // Don't use snd_device_name_hint(-1,..) since there is a access violation
+ // inside this ALSA API with libasound.so.2.0.0.
+ int card = -1;
+ while (!(LATE(snd_card_next)(&card)) && (card >= 0) && keepSearching) {
+ void** hints;
+ err = LATE(snd_device_name_hint)(card, "pcm", &hints);
+ if (err != 0) {
+ RTC_LOG(LS_ERROR) << "GetDevicesInfo - device name hint error: "
+ << LATE(snd_strerror)(err);
+ return -1;
+ }
+
+ enumCount++; // default is 0
+ if ((function == FUNC_GET_DEVICE_NAME ||
+ function == FUNC_GET_DEVICE_NAME_FOR_AN_ENUM) &&
+ enumDeviceNo == 0) {
+ strcpy(enumDeviceName, "default");
+
+ err = LATE(snd_device_name_free_hint)(hints);
+ if (err != 0) {
+ RTC_LOG(LS_ERROR) << "GetDevicesInfo - device name free hint error: "
+ << LATE(snd_strerror)(err);
+ }
+
+ return 0;
+ }
+
+ for (void** list = hints; *list != NULL; ++list) {
+ char* actualType = LATE(snd_device_name_get_hint)(*list, "IOID");
+ if (actualType) { // NULL means it's both.
+ bool wrongType = (strcmp(actualType, type) != 0);
+ free(actualType);
+ if (wrongType) {
+ // Wrong type of device (i.e., input vs. output).
+ continue;
+ }
+ }
+
+ char* name = LATE(snd_device_name_get_hint)(*list, "NAME");
+ if (!name) {
+ RTC_LOG(LS_ERROR) << "Device has no name";
+ // Skip it.
+ continue;
+ }
+
+ // Now check if we actually want to show this device.
+ if (strcmp(name, "default") != 0 && strcmp(name, "null") != 0 &&
+ strcmp(name, "pulse") != 0 &&
+ strncmp(name, ignorePrefix, strlen(ignorePrefix)) != 0) {
+ // Yes, we do.
+ char* desc = LATE(snd_device_name_get_hint)(*list, "DESC");
+ if (!desc) {
+ // Virtual devices don't necessarily have descriptions.
+ // Use their names instead.
+ desc = name;
+ }
+
+ if (FUNC_GET_NUM_OF_DEVICE == function) {
+ RTC_LOG(LS_VERBOSE) << "Enum device " << enumCount << " - " << name;
+ }
+ if ((FUNC_GET_DEVICE_NAME == function) && (enumDeviceNo == enumCount)) {
+ // We have found the enum device, copy the name to buffer.
+ strncpy(enumDeviceName, desc, ednLen);
+ enumDeviceName[ednLen - 1] = '\0';
+ keepSearching = false;
+ // Replace '\n' with '-'.
+ char* pret = strchr(enumDeviceName, '\n' /*0xa*/); // LF
+ if (pret)
+ *pret = '-';
+ }
+ if ((FUNC_GET_DEVICE_NAME_FOR_AN_ENUM == function) &&
+ (enumDeviceNo == enumCount)) {
+ // We have found the enum device, copy the name to buffer.
+ strncpy(enumDeviceName, name, ednLen);
+ enumDeviceName[ednLen - 1] = '\0';
+ keepSearching = false;
+ }
+
+ if (keepSearching)
+ ++enumCount;
+
+ if (desc != name)
+ free(desc);
+ }
+
+ free(name);
+
+ if (!keepSearching)
+ break;
+ }
+
+ err = LATE(snd_device_name_free_hint)(hints);
+ if (err != 0) {
+ RTC_LOG(LS_ERROR) << "GetDevicesInfo - device name free hint error: "
+ << LATE(snd_strerror)(err);
+ // Continue and return true anyway, since we did get the whole list.
+ }
+ }
+
+ if (FUNC_GET_NUM_OF_DEVICE == function) {
+ if (enumCount == 1) // only default?
+ enumCount = 0;
+ return enumCount; // Normal return point for function 0
+ }
+
+ if (keepSearching) {
+ // If we get here for function 1 and 2, we didn't find the specified
+ // enum device.
+ RTC_LOG(LS_ERROR)
+ << "GetDevicesInfo - Could not find device name or numbers";
+ return -1;
+ }
+
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::InputSanityCheckAfterUnlockedPeriod() const {
+ if (_handleRecord == NULL) {
+ RTC_LOG(LS_ERROR) << "input state has been modified during unlocked period";
+ return -1;
+ }
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::OutputSanityCheckAfterUnlockedPeriod() const {
+ if (_handlePlayout == NULL) {
+ RTC_LOG(LS_ERROR)
+ << "output state has been modified during unlocked period";
+ return -1;
+ }
+ return 0;
+}
+
+int32_t AudioDeviceLinuxALSA::ErrorRecovery(int32_t error,
+ snd_pcm_t* deviceHandle) {
+ int st = LATE(snd_pcm_state)(deviceHandle);
+ RTC_LOG(LS_VERBOSE) << "Trying to recover from "
+ << ((LATE(snd_pcm_stream)(deviceHandle) ==
+ SND_PCM_STREAM_CAPTURE)
+ ? "capture"
+ : "playout")
+ << " error: " << LATE(snd_strerror)(error) << " ("
+ << error << ") (state " << st << ")";
+
+ // It is recommended to use snd_pcm_recover for all errors. If that function
+ // cannot handle the error, the input error code will be returned, otherwise
+ // 0 is returned. From snd_pcm_recover API doc: "This functions handles
+ // -EINTR (4) (interrupted system call), -EPIPE (32) (playout overrun or
+ // capture underrun) and -ESTRPIPE (86) (stream is suspended) error codes
+ // trying to prepare given stream for next I/O."
+
+ /** Open */
+ // SND_PCM_STATE_OPEN = 0,
+ /** Setup installed */
+ // SND_PCM_STATE_SETUP,
+ /** Ready to start */
+ // SND_PCM_STATE_PREPARED,
+ /** Running */
+ // SND_PCM_STATE_RUNNING,
+ /** Stopped: underrun (playback) or overrun (capture) detected */
+ // SND_PCM_STATE_XRUN,= 4
+ /** Draining: running (playback) or stopped (capture) */
+ // SND_PCM_STATE_DRAINING,
+ /** Paused */
+ // SND_PCM_STATE_PAUSED,
+ /** Hardware is suspended */
+ // SND_PCM_STATE_SUSPENDED,
+ // ** Hardware is disconnected */
+ // SND_PCM_STATE_DISCONNECTED,
+ // SND_PCM_STATE_LAST = SND_PCM_STATE_DISCONNECTED
+
+ // snd_pcm_recover isn't available in older alsa, e.g. on the FC4 machine
+ // in Sthlm lab.
+
+ int res = LATE(snd_pcm_recover)(deviceHandle, error, 1);
+ if (0 == res) {
+ RTC_LOG(LS_VERBOSE) << "Recovery - snd_pcm_recover OK";
+
+ if ((error == -EPIPE || error == -ESTRPIPE) && // Buf underrun/overrun.
+ _recording &&
+ LATE(snd_pcm_stream)(deviceHandle) == SND_PCM_STREAM_CAPTURE) {
+ // For capture streams we also have to repeat the explicit start()
+ // to get data flowing again.
+ int err = LATE(snd_pcm_start)(deviceHandle);
+ if (err != 0) {
+ RTC_LOG(LS_ERROR) << "Recovery - snd_pcm_start error: " << err;
+ return -1;
+ }
+ }
+
+ if ((error == -EPIPE || error == -ESTRPIPE) && // Buf underrun/overrun.
+ _playing &&
+ LATE(snd_pcm_stream)(deviceHandle) == SND_PCM_STREAM_PLAYBACK) {
+ // For capture streams we also have to repeat the explicit start() to get
+ // data flowing again.
+ int err = LATE(snd_pcm_start)(deviceHandle);
+ if (err != 0) {
+ RTC_LOG(LS_ERROR) << "Recovery - snd_pcm_start error: "
+ << LATE(snd_strerror)(err);
+ return -1;
+ }
+ }
+
+ return -EPIPE == error ? 1 : 0;
+ } else {
+ RTC_LOG(LS_ERROR) << "Unrecoverable alsa stream error: " << res;
+ }
+
+ return res;
+}
+
+// ============================================================================
+// Thread Methods
+// ============================================================================
+
+bool AudioDeviceLinuxALSA::PlayThreadProcess() {
+ if (!_playing)
+ return false;
+
+ int err;
+ snd_pcm_sframes_t frames;
+ snd_pcm_sframes_t avail_frames;
+
+ Lock();
+ // return a positive number of frames ready otherwise a negative error code
+ avail_frames = LATE(snd_pcm_avail_update)(_handlePlayout);
+ if (avail_frames < 0) {
+ RTC_LOG(LS_ERROR) << "playout snd_pcm_avail_update error: "
+ << LATE(snd_strerror)(avail_frames);
+ ErrorRecovery(avail_frames, _handlePlayout);
+ UnLock();
+ return true;
+ } else if (avail_frames == 0) {
+ UnLock();
+
+ // maximum tixe in milliseconds to wait, a negative value means infinity
+ err = LATE(snd_pcm_wait)(_handlePlayout, 2);
+ if (err == 0) { // timeout occured
+ RTC_LOG(LS_VERBOSE) << "playout snd_pcm_wait timeout";
+ }
+
+ return true;
+ }
+
+ if (_playoutFramesLeft <= 0) {
+ UnLock();
+ _ptrAudioBuffer->RequestPlayoutData(_playoutFramesIn10MS);
+ Lock();
+
+ _playoutFramesLeft = _ptrAudioBuffer->GetPlayoutData(_playoutBuffer);
+ RTC_DCHECK_EQ(_playoutFramesLeft, _playoutFramesIn10MS);
+ }
+
+ if (static_cast<uint32_t>(avail_frames) > _playoutFramesLeft)
+ avail_frames = _playoutFramesLeft;
+
+ int size = LATE(snd_pcm_frames_to_bytes)(_handlePlayout, _playoutFramesLeft);
+ frames = LATE(snd_pcm_writei)(
+ _handlePlayout, &_playoutBuffer[_playoutBufferSizeIn10MS - size],
+ avail_frames);
+
+ if (frames < 0) {
+ RTC_LOG(LS_VERBOSE) << "playout snd_pcm_writei error: "
+ << LATE(snd_strerror)(frames);
+ _playoutFramesLeft = 0;
+ ErrorRecovery(frames, _handlePlayout);
+ UnLock();
+ return true;
+ } else {
+ RTC_DCHECK_EQ(frames, avail_frames);
+ _playoutFramesLeft -= frames;
+ }
+
+ UnLock();
+ return true;
+}
+
+bool AudioDeviceLinuxALSA::RecThreadProcess() {
+ if (!_recording)
+ return false;
+
+ int err;
+ snd_pcm_sframes_t frames;
+ snd_pcm_sframes_t avail_frames;
+ int8_t buffer[_recordingBufferSizeIn10MS];
+
+ Lock();
+
+ // return a positive number of frames ready otherwise a negative error code
+ avail_frames = LATE(snd_pcm_avail_update)(_handleRecord);
+ if (avail_frames < 0) {
+ RTC_LOG(LS_ERROR) << "capture snd_pcm_avail_update error: "
+ << LATE(snd_strerror)(avail_frames);
+ ErrorRecovery(avail_frames, _handleRecord);
+ UnLock();
+ return true;
+ } else if (avail_frames == 0) { // no frame is available now
+ UnLock();
+
+ // maximum time in milliseconds to wait, a negative value means infinity
+ err = LATE(snd_pcm_wait)(_handleRecord, ALSA_CAPTURE_WAIT_TIMEOUT);
+ if (err == 0) // timeout occured
+ RTC_LOG(LS_VERBOSE) << "capture snd_pcm_wait timeout";
+
+ return true;
+ }
+
+ if (static_cast<uint32_t>(avail_frames) > _recordingFramesLeft)
+ avail_frames = _recordingFramesLeft;
+
+ frames = LATE(snd_pcm_readi)(_handleRecord, buffer,
+ avail_frames); // frames to be written
+ if (frames < 0) {
+ RTC_LOG(LS_ERROR) << "capture snd_pcm_readi error: "
+ << LATE(snd_strerror)(frames);
+ ErrorRecovery(frames, _handleRecord);
+ UnLock();
+ return true;
+ } else if (frames > 0) {
+ RTC_DCHECK_EQ(frames, avail_frames);
+
+ int left_size =
+ LATE(snd_pcm_frames_to_bytes)(_handleRecord, _recordingFramesLeft);
+ int size = LATE(snd_pcm_frames_to_bytes)(_handleRecord, frames);
+
+ memcpy(&_recordingBuffer[_recordingBufferSizeIn10MS - left_size], buffer,
+ size);
+ _recordingFramesLeft -= frames;
+
+ if (!_recordingFramesLeft) { // buf is full
+ _recordingFramesLeft = _recordingFramesIn10MS;
+
+ // store the recorded buffer (no action will be taken if the
+ // #recorded samples is not a full buffer)
+ _ptrAudioBuffer->SetRecordedBuffer(_recordingBuffer,
+ _recordingFramesIn10MS);
+
+ // calculate delay
+ _playoutDelay = 0;
+ _recordingDelay = 0;
+ if (_handlePlayout) {
+ err = LATE(snd_pcm_delay)(_handlePlayout,
+ &_playoutDelay); // returned delay in frames
+ if (err < 0) {
+ // TODO(xians): Shall we call ErrorRecovery() here?
+ _playoutDelay = 0;
+ RTC_LOG(LS_ERROR)
+ << "playout snd_pcm_delay: " << LATE(snd_strerror)(err);
+ }
+ }
+
+ err = LATE(snd_pcm_delay)(_handleRecord,
+ &_recordingDelay); // returned delay in frames
+ if (err < 0) {
+ // TODO(xians): Shall we call ErrorRecovery() here?
+ _recordingDelay = 0;
+ RTC_LOG(LS_ERROR) << "capture snd_pcm_delay: "
+ << LATE(snd_strerror)(err);
+ }
+
+ // TODO(xians): Shall we add 10ms buffer delay to the record delay?
+ _ptrAudioBuffer->SetVQEData(_playoutDelay * 1000 / _playoutFreq,
+ _recordingDelay * 1000 / _recordingFreq);
+
+ _ptrAudioBuffer->SetTypingStatus(KeyPressed());
+
+ // Deliver recorded samples at specified sample rate, mic level etc.
+ // to the observer using callback.
+ UnLock();
+ _ptrAudioBuffer->DeliverRecordedData();
+ Lock();
+ }
+ }
+
+ UnLock();
+ return true;
+}
+
+bool AudioDeviceLinuxALSA::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