FIRE-36022 - Removing my USB headset crashes entire viewer - audio device mutex

As with the previous change, this is to address the issue FIRE-36022 where users who remove their USB headsets with microphones are having the viewer lock up.

This introduces a new inline mutex called iAudioDeviceMutex. This is stored in a shared header file located on llcommon. iAudioDeviceMutex is a timed_mutex which allows for a time limit to be placed on a wait for a lock. Once the time runs out or the lock is achiveved the code moves on. With a check after the lock, if the owner is the current thread, then the lock was successful, otherwise it's still being used by another thread and the function should exit to try again later on.

This logic is wrapping WebRTC, FMOD and Vinvox calls to the audio hardware by the Viewer. OpenAL does not currently support changing of audio hardware and always defaults to the default audio hardware.

Added exceptions handling for the new unique_lock with the timed_mutex as they can throw 2 exceptions if the thread is already locked by the current thread and if the mutex is invalid.

Further testing may reveal other areas which would need the timed_mutex added to protect from threads locking up.
master
minerjr 2025-10-28 23:22:13 -03:00
parent 0cdae450d8
commit 6ad9a57c39
8 changed files with 411 additions and 0 deletions

View File

@ -39,6 +39,10 @@
#include "llframetimer.h"
#include "llassettype.h"
#include "llextendedstatus.h"
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
// Need to include for audio device mutex shared with other audio/voice systems.
#include "inlinemutexs.h"
// </FS:minerjr> [FIRE-36022]
#include "lllistener.h"

View File

@ -44,6 +44,9 @@
#include "sound_ids.h"
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
using namespace std::chrono_literals; // Needed for shared timed mutex to use time
// </FS:minerjr> [FIRE-36022]
constexpr U32 EXTRA_SOUND_CHANNELS = 10;
FMOD_RESULT F_CALL windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels);
@ -114,7 +117,11 @@ static void set_device(FMOD::System* system, const LLUUID& device_uuid)
}
FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE type, void *commanddata1, void *commanddata2, void* userdata)
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
{
try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid
{
// </FS:minerjr> [FIRE-36022]
FMOD::System* sys = (FMOD::System*)system;
LLAudioEngine_FMODSTUDIO* audio_engine = (LLAudioEngine_FMODSTUDIO*)userdata;
@ -124,6 +131,17 @@ FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE
LL_DEBUGS("FMOD") << "FMOD system callback FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED" << LL_ENDL;
if (sys && audio_engine)
{
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
// Attempt to lock the access to the audio device, wait up to 3 seconds for other threads to unlock.
std::unique_lock lock(iAudioDeviceMutex, 3s);
// If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass.
// Prevents threads from interacting with the hardware at the same time as other audio/voice threads.
if (not lock.owns_lock())
{
LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL;
return FMOD_OK; // Could be a FMOD_ERR_ALREADY_LOCKED;
}
// </FS:minerjr> [FIRE-36022]
set_device(sys, audio_engine->getSelectedDeviceUUID());
audio_engine->OnOutputDeviceListChanged(audio_engine->getDevices());
}
@ -132,6 +150,34 @@ FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE
break;
}
return FMOD_OK;
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
}
// There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur
catch (const std::system_error& e)
{
if (e.code() == std::errc::resource_deadlock_would_occur)
{
LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL;
return FMOD_ERR_ALREADY_LOCKED;
}
else if (e.code() == std::errc::operation_not_permitted)
{
LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL;
return FMOD_ERR_ALREADY_LOCKED;
}
else
{
LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL;
}
return FMOD_ERR_ALREADY_LOCKED;
}
catch (const std::exception& e)
{
LL_WARNS() << "Exception FMOD: " << " " << e.what() << LL_ENDL;
return FMOD_ERR_ALREADY_LOCKED;
}
// </FS:minerjr> [FIRE-36022]
}
LLAudioEngine_FMODSTUDIO::LLAudioEngine_FMODSTUDIO(bool enable_profiler, U32 resample_method)
@ -373,7 +419,47 @@ LLAudioEngine_FMODSTUDIO::output_device_map_t LLAudioEngine_FMODSTUDIO::getDevic
void LLAudioEngine_FMODSTUDIO::setDevice(const LLUUID& device_uuid)
{
mSelectedDeviceUUID = device_uuid;
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid
{
// Attempt to lock the access to the audio device, wait up to 3 seconds for other threads to unlock.
std::unique_lock lock(iAudioDeviceMutex, 3s);
// If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass.
// Prevents threads from interacting with the hardware at the same time as other audio/voice threads.
if (not lock.owns_lock())
{
LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL;
return;
}
// </FS:minerjr> [FIRE-36022]
set_device(mSystem, device_uuid);
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
}
// There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur
catch (const std::system_error& e)
{
if (e.code() == std::errc::resource_deadlock_would_occur)
{
LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL;
}
else if (e.code() == std::errc::operation_not_permitted)
{
LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL;
}
else
{
LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL;
}
return;
}
catch (const std::exception& e)
{
LL_WARNS() << "Exception FMOD: " << " " << e.what() << LL_ENDL;
return;
}
// </FS:minerjr> [FIRE-36022]
}
std::string LLAudioEngine_FMODSTUDIO::getDriverName(bool verbose)

View File

@ -122,6 +122,7 @@ set(llcommon_HEADER_FILES
fsyspath.h
function_types.h
indra_constants.h
inlinemutexs.h
lazyeventapi.h
linden_common.h
llalignedarray.h

View File

@ -0,0 +1,39 @@
/**
* @file inlinemutexs.h
* @brief Declaration of inline mutexs
* @author minerjr@firestorm
*
* $LicenseInfo:firstyear=2025&license=fsviewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2025, Minerjr
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
* $/LicenseInfo$
*/
#ifndef INLINE_MUTEXS_HEADER
#define INLINE_MUTEXS_HEADER
#include <mutex>
#include <chrono>
#include <atomic>
// Audio device mutex to be shared between audio engine and Voice systems to
// syncronize on when audio hardware accessed for disconnected/connecting hardware
// Uses Timed Mutex so as to not lockup the threads forever.
inline std::timed_mutex iAudioDeviceMutex;
#endif

View File

@ -27,6 +27,11 @@
#include "llwebrtc_impl.h"
#include <algorithm>
#include <string.h>
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
// Needed for accessing the inline timed mutex for accessing audio hardware.
#include <mutex>
#include <chrono>
// </FS:minerjr> [FIRE-36022]
#include "api/audio_codecs/audio_decoder_factory.h"
#include "api/audio_codecs/audio_encoder_factory.h"
@ -39,6 +44,14 @@
#include "modules/audio_mixer/audio_mixer_impl.h"
#include "api/environment/environment_factory.h"
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
// Audio device mutex to be shared between audio engine and Voice systems to
// syncronize on when audio hardware accessed for disconnected/connecting hardware
// Uses Timed Mutex so as to not lockup the threads forever.
inline std::timed_mutex iAudioDeviceMutex;
// Need to use to access the 3 second timeout for the lock.
using namespace std::chrono_literals;
// </FS:minerjr> [FIRE-36022]
namespace llwebrtc
{
#if WEBRTC_WIN
@ -269,7 +282,11 @@ void LLWebRTCImpl::init()
webrtc::InitializeSSL();
// Normal logging is rather spammy, so turn it off.
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
// Turn on more verbose logging as we are looking for crashes.
webrtc::LogMessage::LogToDebug(webrtc::LS_NONE);
//webrtc::LogMessage::LogToDebug(webrtc::LS_VERBOSE);
// </FS:minerjr> [FIRE-36022]
webrtc::LogMessage::SetLogToStderr(true);
webrtc::LogMessage::AddLogToStream(mLogSink, webrtc::LS_VERBOSE);
@ -457,6 +474,18 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer)
// must be run in the worker thread.
void LLWebRTCImpl::workerDeployDevices()
{
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid
{
// Attempt to lock the access to the audio device, wait up to 3 seconds for other threads to unlock.
std::unique_lock lock(iAudioDeviceMutex, 3s);
// If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass.
// Prevents threads from interacting with the hardware at the same time as other audio/voice threads.
if (not lock.owns_lock())
{
return;
}
// </FS:minerjr> [FIRE-36022]
if (!mDeviceModule)
{
// <FS:minerjr> [FIRE-36022]
@ -578,6 +607,38 @@ void LLWebRTCImpl::workerDeployDevices()
mWorkerThread->PostTask([this] { workerDeployDevices(); });
}
});
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
}
// There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur
catch (const std::system_error& e)
{
if (e.code() == std::errc::resource_deadlock_would_occur)
{
// Another thead may have alreayd called this method
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
else if (e.code() == std::errc::operation_not_permitted)
{
// This should not be reached
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
else
{
// Log any other message
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
// Device no longer being interacted with
iWebRTCUpdateDevices = false;
return;
}
catch (const std::exception& e)
{
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
// Device no longer being interacted with
iWebRTCUpdateDevices = false;
return;
}
// </FS:minerjr> [FIRE-36022]
}
void LLWebRTCImpl::setCaptureDevice(const std::string &id)
@ -596,6 +657,18 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id)
// updateDevices needs to happen on the worker thread.
void LLWebRTCImpl::updateDevices()
{
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid
{
// Attempt to lock the access to the audio device, wait up to 3 seconds for other threads to unlock.
std::unique_lock lock(iAudioDeviceMutex, 3s);
// If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass.
// Prevents threads from interacting with the hardware at the same time as other audio/voice threads.
if (not lock.owns_lock())
{
return;
}
// </FS:minerjr> [FIRE-36022]
if (!mDeviceModule)
{
return;
@ -649,6 +722,38 @@ void LLWebRTCImpl::updateDevices()
{
observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList);
}
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
}
// There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur
catch (const std::system_error& e)
{
if (e.code() == std::errc::resource_deadlock_would_occur)
{
// Another thead may have alreayd called this method
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
else if (e.code() == std::errc::operation_not_permitted)
{
// This should not be reached
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
else
{
// Log any other message
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
// Device no longer being interacted with
iWebRTCUpdateDevices = false;
return;
}
catch (const std::exception& e)
{
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
// Device no longer being interacted with
iWebRTCUpdateDevices = false;
return;
}
// </FS:minerjr> [FIRE-36022]
}
void LLWebRTCImpl::OnDevicesUpdated()
@ -746,6 +851,18 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
{
if (mDeviceModule)
{
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is
// invalid
{
// Attempt to lock the access to the audio device, wait up to 3 seconds for other threads to unlock.
std::unique_lock lock(iAudioDeviceMutex, 3s);
// If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass.
// Prevents threads from interacting with the hardware at the same time as other audio/voice threads.
if (not lock.owns_lock())
{
return;
}
// Flag the device is being interacted with for the Co-routine in case something goes wrong.
iWebRTCUpdateDevices = true;
// </FS:minerjr> [FIRE-36022]
@ -753,6 +870,36 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
// Finally signal to the co-routine everyting is OK.
iWebRTCUpdateDevices = false;
}
// There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur
catch (const std::system_error& e)
{
if (e.code() == std::errc::resource_deadlock_would_occur)
{
// Another thead may have alreayd called this method
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
else if (e.code() == std::errc::operation_not_permitted)
{
// This should not be reached
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
else
{
// Log any other message
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
// Device no longer being interacted with
iWebRTCUpdateDevices = false;
return;
}
catch (const std::exception& e)
{
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
// Device no longer being interacted with
iWebRTCUpdateDevices = false;
return;
}
// </FS:minerjr> [FIRE-36022]
}
},
@ -766,6 +913,17 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
if (mDeviceModule)
{
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is
// invalid
{
// Attempt to lock the access to the audio device, wait up to 3 seconds for other threads to unlock.
std::unique_lock lock(iAudioDeviceMutex, 3s);
// If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass.
// Prevents threads from interacting with the hardware at the same time as other audio/voice threads.
if (not lock.owns_lock())
{
return;
}
// Flag the device is being interacted with for the Co-routine in case something goes wrong.
iWebRTCUpdateDevices = true;
// </FS:minerjr> [FIRE-36022]
@ -774,6 +932,36 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
// Finally signal to the co-routine everyting is OK.
iWebRTCUpdateDevices = false;
}
// There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur
catch (const std::system_error& e)
{
if (e.code() == std::errc::resource_deadlock_would_occur)
{
// Another thead may have alreayd called this method
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
else if (e.code() == std::errc::operation_not_permitted)
{
// This should not be reached
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
else
{
// Log any other message
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
}
// Device no longer being interacted with
iWebRTCUpdateDevices = false;
return;
}
catch (const std::exception& e)
{
mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what());
// Device no longer being interacted with
iWebRTCUpdateDevices = false;
return;
}
// </FS:minerjr> [FIRE-36022]
}
});

View File

@ -38,6 +38,10 @@ class LLVOAvatar;
#include "llcallingcard.h" // for LLFriendObserver
#include "llsecapi.h"
#include "llcontrol.h"
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
// Need to include for audio device mutex shared with other audio/voice systems.
#include "inlinemutexs.h"
// </FS:minerjr> [FIRE-36022]
// devices

View File

@ -82,6 +82,9 @@ extern void handle_voice_morphing_subscribe();
const std::string VIVOX_VOICE_SERVER_TYPE = "vivox";
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
using namespace std::chrono_literals; // Needed for shared timed mutex to use time
// </FS:minerjr> [FIRE-36022]
namespace {
const F32 VOLUME_SCALE_VIVOX = 0.01f;
@ -2413,6 +2416,19 @@ void LLVivoxVoiceClient::sendCaptureAndRenderDevices()
{
if (mCaptureDeviceDirty || mRenderDeviceDirty)
{
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid
{
// Attempt to lock the access to the audio device, wait up to 3 seconds for other threads to unlock.
std::unique_lock lock(iAudioDeviceMutex, 3s);
// If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass.
// Prevents threads from interacting with the hardware at the same time as other audio/voice threads.
if (not lock.owns_lock())
{
LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL;
return;
}
// </FS:minerjr> [FIRE-36022]
std::ostringstream stream;
buildSetCaptureDevice(stream);
@ -2424,6 +2440,35 @@ void LLVivoxVoiceClient::sendCaptureAndRenderDevices()
}
llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
}
// There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur
catch (const std::system_error& e)
{
if (e.code() == std::errc::resource_deadlock_would_occur)
{
// When trying to lock the same lock a second time
LL_WARNS() << "Exception Vinvox: " << e.code() << " " << e.what() << LL_ENDL;
}
else if (e.code() == std::errc::operation_not_permitted)
{
// When the mutex is invalid
LL_WARNS() << "Exception Vinvox: " << e.code() << " " << e.what() << LL_ENDL;
}
else
{
// Everything else
LL_WARNS() << "Exception Vinvox: " << e.code() << " " << e.what() << LL_ENDL;
}
return;
}
catch (const std::exception& e)
{
LL_WARNS() << "Exception Vinvox: " << " " << e.what() << LL_ENDL;
return;
}
// </FS:minerjr> [FIRE-36022]
}
}

View File

@ -80,6 +80,9 @@
const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc";
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
using namespace std::chrono_literals; // Needed for shared timed mutex to use time
// </FS:minerjr> [FIRE-36022]
namespace {
const F32 MAX_AUDIO_DIST = 50.0f;
@ -757,6 +760,19 @@ void LLWebRTCVoiceClient::OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceLi
void LLWebRTCVoiceClient::OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices,
const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices)
{
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid
{
// Attempt to lock the access to the audio device, wait up to 3 seconds for other threads to unlock.
std::unique_lock lock(iAudioDeviceMutex, 3s);
// If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass.
// Prevents threads from interacting with the hardware at the same time as other audio/voice threads.
if (not lock.owns_lock())
{
LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL;
return;
}
// </FS:minerjr> [FIRE-36022]
if (sShuttingDown)
{
return;
@ -800,6 +816,34 @@ void LLWebRTCVoiceClient::OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDevi
}
setDevicesListUpdated(true);
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
}
catch (const std::system_error& e)
{
if (e.code() == std::errc::resource_deadlock_would_occur)
{
// When trying to lock the same lock a second time
LL_WARNS() << "Exception WebRTC: " << e.code() << " " << e.what() << LL_ENDL;
}
else if (e.code() == std::errc::operation_not_permitted)
{
// When the mutex is invalid
LL_WARNS() << "Exception WebRTC: " << e.code() << " " << e.what() << LL_ENDL;
}
else
{
// Everything else
LL_WARNS() << "Exception WebRTC: " << e.code() << " " << e.what() << LL_ENDL;
}
return;
}
catch (const std::exception& e)
{
LL_WARNS() << "Exception WebRTC: " << " " << e.what() << LL_ENDL;
return;
}
// </FS:minerjr> [FIRE-36022]
}
void LLWebRTCVoiceClient::clearRenderDevices()