blob: 50cf3beb6c972c9838ec3c48a88b78d6cea5fd13 [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_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;
}
uint32_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;
}
uint32_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