| /* |
| * 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 <assert.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_LOG(LS_INFO) << __FUNCTION__ << " created"; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // AudioDeviceLinuxALSA - dtor |
| // ---------------------------------------------------------------------------- |
| |
| AudioDeviceLinuxALSA::~AudioDeviceLinuxALSA() { |
| RTC_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 (!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; |
| } |
| |
| 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(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() { |
| 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::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) { |
| 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() { |
| 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) { |
| 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; |
| } |
| |
| 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) { |
| 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; |
| } |
| |
| 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) { |
| RTC_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", |
| rtc::kRealtimePriority)); |
| |
| _ptrThreadRec->Start(); |
| |
| 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() { |
| { |
| 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) { |
| 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.reset(new rtc::PlatformThread( |
| PlayThreadFunc, this, "webrtc_audio_module_play_thread", |
| rtc::kRealtimePriority)); |
| _ptrThreadPlay->Start(); |
| |
| 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() { |
| { |
| 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) { |
| 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 |
| // ============================================================================ |
| |
| void AudioDeviceLinuxALSA::PlayThreadFunc(void* pThis) { |
| AudioDeviceLinuxALSA* device = static_cast<AudioDeviceLinuxALSA*>(pThis); |
| while (device->PlayThreadProcess()) { |
| } |
| } |
| |
| void AudioDeviceLinuxALSA::RecThreadFunc(void* pThis) { |
| AudioDeviceLinuxALSA* device = static_cast<AudioDeviceLinuxALSA*>(pThis); |
| while (device->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) { |
| 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); |
| 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) { |
| RTC_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) { |
| 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) { |
| 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); |
| |
| // 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 |