blob: 9e2c5b1835b9885220dc63a9c8d5bb9a3ba7231e [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 <assert.h>
#include "webrtc/modules/audio_device/audio_device_config.h"
#include "webrtc/modules/audio_device/linux/audio_device_alsa_linux.h"
#include "webrtc/rtc_base/logging.h"
#include "webrtc/system_wrappers/include/event_wrapper.h"
#include "webrtc/system_wrappers/include/sleep.h"
webrtc::adm_linux_alsa::AlsaSymbolTable AlsaSymbolTable;
// 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, &AlsaSymbolTable, 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),
_AGC(false),
_recordingDelay(0),
_playoutDelay(0),
_playWarning(0),
_playError(0),
_recWarning(0),
_recError(0)
{
memset(_oldKeyState, 0, sizeof(_oldKeyState));
LOG(LS_INFO) << __FUNCTION__ << " created";
}
// ----------------------------------------------------------------------------
// AudioDeviceLinuxALSA - dtor
// ----------------------------------------------------------------------------
AudioDeviceLinuxALSA::~AudioDeviceLinuxALSA()
{
LOG(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)
{
rtc::CritScope lock(&_critSect);
_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() {
rtc::CritScope lock(&_critSect);
// Load libasound
if (!AlsaSymbolTable.Load()) {
// Alsa is not installed on this system
LOG(LS_ERROR) << "failed to load symbol table";
return InitStatus::OTHER_ERROR;
}
if (_initialized) {
return InitStatus::OK;
}
#if defined(USE_X11)
//Get X display handle for typing detection
_XDisplay = XOpenDisplay(NULL);
if (!_XDisplay) {
LOG(LS_WARNING)
<< "failed to open X display, typing detection will not work";
}
#endif
_playWarning = 0;
_playError = 0;
_recWarning = 0;
_recError = 0;
_initialized = true;
return InitStatus::OK;
}
int32_t AudioDeviceLinuxALSA::Terminate()
{
if (!_initialized)
{
return 0;
}
rtc::CritScope lock(&_critSect);
_mixerManager.Close();
// RECORDING
if (_ptrThreadRec)
{
rtc::PlatformThread* tmpThread = _ptrThreadRec.release();
_critSect.Leave();
tmpThread->Stop();
delete tmpThread;
_critSect.Enter();
}
// PLAYOUT
if (_ptrThreadPlay)
{
rtc::PlatformThread* tmpThread = _ptrThreadPlay.release();
_critSect.Leave();
tmpThread->Stop();
delete tmpThread;
_critSect.Enter();
}
#if defined(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()
{
rtc::CritScope lock(&_critSect);
if (_playing)
{
return -1;
}
char devName[kAdmMaxDeviceNameSize] = {0};
GetDevicesInfo(2, true, _outputDeviceIndex, devName, kAdmMaxDeviceNameSize);
return _mixerManager.OpenSpeaker(devName);
}
int32_t AudioDeviceLinuxALSA::InitMicrophone()
{
rtc::CritScope lock(&_critSect);
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)
{
rtc::CritScope lock(&_critSect);
// 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)
{
StopRecording();
}
// Try init in stereo;
_recChannels = 2;
if (InitRecording() == 0)
{
available = true;
}
// Stop/uninitialize recording
StopRecording();
// Recover previous states
_recChannels = recChannels;
if (recIsInitialized)
{
InitRecording();
}
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)
{
rtc::CritScope lock(&_critSect);
// 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)
{
StopPlayout();
}
// Try init in stereo;
_playChannels = 2;
if (InitPlayout() == 0)
{
available = true;
}
// Stop/uninitialize recording
StopPlayout();
// Recover previous states
_playChannels = playChannels;
if (playIsInitialized)
{
InitPlayout();
}
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::SetAGC(bool enable)
{
_AGC = enable;
return 0;
}
bool AudioDeviceLinuxALSA::AGC() const
{
return _AGC;
}
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));
return 0;
}
int32_t AudioDeviceLinuxALSA::MicrophoneVolume(uint32_t& volume) const
{
uint32_t level(0);
if (_mixerManager.MicrophoneVolume(level) == -1)
{
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);
LOG(LS_VERBOSE) << "number of available audio output devices is "
<< nDevices;
if (index > (nDevices-1))
{
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*/)
{
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);
LOG(LS_VERBOSE) << "number of availiable audio input devices is "
<< nDevices;
if (index > (nDevices-1))
{
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*/)
{
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()
{
int errVal = 0;
rtc::CritScope lock(&_critSect);
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)
{
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)
{
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);
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)
{
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;
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)
{
LOG(LS_ERROR) << "snd_pcm_get_params: " << LATE(snd_strerror)(errVal)
<< " (" << errVal << ")";
_playoutBufferSizeInFrame = 0;
_playoutPeriodSizeInFrame = 0;
}
else {
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
_playWarning = 0;
_playError = 0;
if (_handlePlayout != NULL)
{
_playIsInitialized = true;
return 0;
}
else
{
return -1;
}
return 0;
}
int32_t AudioDeviceLinuxALSA::InitRecording()
{
int errVal = 0;
rtc::CritScope lock(&_critSect);
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)
{
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)
{
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);
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)
{
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;
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)
{
LOG(LS_ERROR) << "snd_pcm_get_params " << LATE(snd_strerror)(errVal)
<< " (" << errVal << ")";
_recordingBuffersizeInFrame = 0;
_recordingPeriodSizeInFrame = 0;
}
else {
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;
}
return 0;
}
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)
{
LOG(LS_ERROR) << "failed to alloc recording buffer";
_recording = false;
return -1;
}
// RECORDING
_ptrThreadRec.reset(new rtc::PlatformThread(
RecThreadFunc, this, "webrtc_audio_module_capture_thread"));
_ptrThreadRec->Start();
_ptrThreadRec->SetPriority(rtc::kRealtimePriority);
errVal = LATE(snd_pcm_prepare)(_handleRecord);
if (errVal < 0)
{
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)
{
LOG(LS_ERROR) << "capture snd_pcm_start err: "
<< LATE(snd_strerror)(errVal);
errVal = LATE(snd_pcm_start)(_handleRecord);
if (errVal < 0)
{
LOG(LS_ERROR) << "capture snd_pcm_start 2nd try err: "
<< LATE(snd_strerror)(errVal);
StopRecording();
return -1;
}
}
return 0;
}
int32_t AudioDeviceLinuxALSA::StopRecording()
{
{
rtc::CritScope lock(&_critSect);
if (!_recIsInitialized)
{
return 0;
}
if (_handleRecord == NULL)
{
return -1;
}
// Make sure we don't start recording (it's asynchronous).
_recIsInitialized = false;
_recording = false;
}
if (_ptrThreadRec)
{
_ptrThreadRec->Stop();
_ptrThreadRec.reset();
}
rtc::CritScope lock(&_critSect);
_recordingFramesLeft = 0;
if (_recordingBuffer)
{
delete [] _recordingBuffer;
_recordingBuffer = NULL;
}
// Stop and close pcm recording device.
int errVal = LATE(snd_pcm_drop)(_handleRecord);
if (errVal < 0)
{
LOG(LS_ERROR) << "Error stop recording: " << LATE(snd_strerror)(errVal);
return -1;
}
errVal = LATE(snd_pcm_close)(_handleRecord);
if (errVal < 0)
{
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)
{
LOG(LS_ERROR) << "failed to alloc playout buf";
_playing = false;
return -1;
}
// PLAYOUT
_ptrThreadPlay.reset(new rtc::PlatformThread(
PlayThreadFunc, this, "webrtc_audio_module_play_thread"));
_ptrThreadPlay->Start();
_ptrThreadPlay->SetPriority(rtc::kRealtimePriority);
int errVal = LATE(snd_pcm_prepare)(_handlePlayout);
if (errVal < 0)
{
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()
{
{
rtc::CritScope lock(&_critSect);
if (!_playIsInitialized)
{
return 0;
}
if (_handlePlayout == NULL)
{
return -1;
}
_playing = false;
}
// stop playout thread first
if (_ptrThreadPlay)
{
_ptrThreadPlay->Stop();
_ptrThreadPlay.reset();
}
rtc::CritScope lock(&_critSect);
_playoutFramesLeft = 0;
delete [] _playoutBuffer;
_playoutBuffer = NULL;
// stop and close pcm playout device
int errVal = LATE(snd_pcm_drop)(_handlePlayout);
if (errVal < 0)
{
LOG(LS_ERROR) << "Error stop playing: " << LATE(snd_strerror)(errVal);
}
errVal = LATE(snd_pcm_close)(_handlePlayout);
if (errVal < 0)
LOG(LS_ERROR) << "Error closing playout sound device, error: "
<< LATE(snd_strerror)(errVal);
// set the pcm input handle to NULL
_playIsInitialized = false;
_handlePlayout = NULL;
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;
}
int32_t AudioDeviceLinuxALSA::RecordingDelay(uint16_t& delayMS) const
{
// Adding 10ms adjusted value to the record delay due to 10ms buffering.
delayMS = (uint16_t)(10 + _recordingDelay * 1000 / _recordingFreq);
return 0;
}
bool AudioDeviceLinuxALSA::Playing() const
{
return (_playing);
}
bool AudioDeviceLinuxALSA::PlayoutWarning() const
{
rtc::CritScope lock(&_critSect);
return (_playWarning > 0);
}
bool AudioDeviceLinuxALSA::PlayoutError() const
{
rtc::CritScope lock(&_critSect);
return (_playError > 0);
}
bool AudioDeviceLinuxALSA::RecordingWarning() const
{
rtc::CritScope lock(&_critSect);
return (_recWarning > 0);
}
bool AudioDeviceLinuxALSA::RecordingError() const
{
rtc::CritScope lock(&_critSect);
return (_recError > 0);
}
void AudioDeviceLinuxALSA::ClearPlayoutWarning()
{
rtc::CritScope lock(&_critSect);
_playWarning = 0;
}
void AudioDeviceLinuxALSA::ClearPlayoutError()
{
rtc::CritScope lock(&_critSect);
_playError = 0;
}
void AudioDeviceLinuxALSA::ClearRecordingWarning()
{
rtc::CritScope lock(&_critSect);
_recWarning = 0;
}
void AudioDeviceLinuxALSA::ClearRecordingError()
{
rtc::CritScope lock(&_critSect);
_recError = 0;
}
// ============================================================================
// 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)
{
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)
{
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)
{
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)
{
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)
{
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.
LOG(LS_ERROR)
<< "GetDevicesInfo - Could not find device name or numbers";
return -1;
}
return 0;
}
int32_t AudioDeviceLinuxALSA::InputSanityCheckAfterUnlockedPeriod() const
{
if (_handleRecord == NULL)
{
LOG(LS_ERROR) << "input state has been modified during unlocked period";
return -1;
}
return 0;
}
int32_t AudioDeviceLinuxALSA::OutputSanityCheckAfterUnlockedPeriod() const
{
if (_handlePlayout == NULL)
{
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);
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)
{
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)
{
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)
{
LOG(LS_ERROR) << "Recovery - snd_pcm_start error: "
<< LATE(snd_strerror)(err);
return -1;
}
}
return -EPIPE == error ? 1 : 0;
}
else {
LOG(LS_ERROR) << "Unrecoverable alsa stream error: " << res;
}
return res;
}
// ============================================================================
// Thread Methods
// ============================================================================
bool AudioDeviceLinuxALSA::PlayThreadFunc(void* pThis)
{
return (static_cast<AudioDeviceLinuxALSA*>(pThis)->PlayThreadProcess());
}
bool AudioDeviceLinuxALSA::RecThreadFunc(void* pThis)
{
return (static_cast<AudioDeviceLinuxALSA*>(pThis)->RecThreadProcess());
}
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)
{
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
LOG(LS_VERBOSE) << "playout snd_pcm_wait timeout";
}
return true;
}
if (_playoutFramesLeft <= 0)
{
UnLock();
_ptrAudioBuffer->RequestPlayoutData(_playoutFramesIn10MS);
Lock();
_playoutFramesLeft = _ptrAudioBuffer->GetPlayoutData(_playoutBuffer);
assert(_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)
{
LOG(LS_VERBOSE) << "playout snd_pcm_writei error: "
<< LATE(snd_strerror)(frames);
_playoutFramesLeft = 0;
ErrorRecovery(frames, _handlePlayout);
UnLock();
return true;
}
else {
assert(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)
{
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
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)
{
LOG(LS_ERROR) << "capture snd_pcm_readi error: "
<< LATE(snd_strerror)(frames);
ErrorRecovery(frames, _handleRecord);
UnLock();
return true;
}
else if (frames > 0)
{
assert(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);
uint32_t currentMicLevel = 0;
uint32_t newMicLevel = 0;
if (AGC())
{
// store current mic level in the audio buffer if AGC is enabled
if (MicrophoneVolume(currentMicLevel) == 0)
{
if (currentMicLevel == 0xffffffff)
currentMicLevel = 100;
// this call does not affect the actual microphone volume
_ptrAudioBuffer->SetCurrentMicLevel(currentMicLevel);
}
}
// 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;
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;
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, 0);
_ptrAudioBuffer->SetTypingStatus(KeyPressed());
// Deliver recorded samples at specified sample rate, mic level etc.
// to the observer using callback.
UnLock();
_ptrAudioBuffer->DeliverRecordedData();
Lock();
if (AGC())
{
newMicLevel = _ptrAudioBuffer->NewMicLevel();
if (newMicLevel != 0)
{
// The VQE will only deliver non-zero microphone levels when a
// change is needed. Set this new mic level (received from the
// observer as return value in the callback).
if (SetMicrophoneVolume(newMicLevel) == -1)
LOG(LS_WARNING)
<< "the required modification of the microphone volume failed";
}
}
}
}
UnLock();
return true;
}
bool AudioDeviceLinuxALSA::KeyPressed() const{
#if defined(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