mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-15 06:40:43 +01:00

(Re-upload of https://codereview.webrtc.org/3020493002/) Bug: webrtc:4690, webrtc:7306 Change-Id: I67fb9ebca1296aabc08eae8a292a5c69832dc35e Reviewed-on: https://webrtc-review.googlesource.com/5360 Commit-Queue: Fredrik Solenberg <solenberg@webrtc.org> Reviewed-by: Henrik Andreassson <henrika@webrtc.org> Cr-Commit-Position: refs/heads/master@{#20083}
1960 lines
50 KiB
C++
1960 lines
50 KiB
C++
/*
|
|
* 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 "modules/audio_device/audio_device_config.h"
|
|
#include "modules/audio_device/linux/audio_device_alsa_linux.h"
|
|
#include "rtc_base/logging.h"
|
|
|
|
#include "system_wrappers/include/event_wrapper.h"
|
|
#include "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)
|
|
{
|
|
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
|
|
|
|
_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
|
|
|
|
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);
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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
|