summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoxie Linden <roxie@lindenlab.com>2026-06-26 00:07:52 -0700
committerRoxie Linden <roxie@lindenlab.com>2026-07-07 10:14:47 -0700
commit95120bc676fc68793da2d4c06542cf7cb3272d00 (patch)
tree04a992e23898438622589335339dd305218f09aa
parent1c8144b1708420b30c54472a5139d451034e89de (diff)
Update libwebrtc to m144 and fix audio device lifecycle/processing
- Update libwebrtc to version m144 (autobuild.xml). - Use WebRTC's software APM exclusively; disable built-in (hardware/OS) AEC/AGC/NS, including after each device (re)deploy. - Only run the output device once a peer connection's audio is established (and bring devices up with the user's selected device at that point), fixing the buzz heard before/without an active connection. - Keep capture warm across mute/unmute to avoid the AEC cold-start hiss; stop recording 30s after a sustained mute so the OS mic indicator clears. - Reliably (re)select and (re)start capture/playout after teleport or voice restart so audio sends/records again. - Don't suspend the voice channel when entering tuning mode (Vivox-era behavior that dropped the peer connection). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-rw-r--r--autobuild.xml14
-rw-r--r--indra/llwebrtc/llwebrtc.cpp294
-rw-r--r--indra/llwebrtc/llwebrtc_impl.h52
-rw-r--r--indra/newview/llpanelvoicedevicesettings.cpp7
4 files changed, 284 insertions, 83 deletions
diff --git a/autobuild.xml b/autobuild.xml
index 1456dca104..5a08e4eeba 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -2607,11 +2607,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
- <string>72ed1f6d469a8ffaffd69be39b7af186d7c3b1d7</string>
+ <string>c70247d7683312ee81149dbae603574c0851e04c</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
- <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.22/webrtc-m137.7151.04.22.21966754211-darwin64-21966754211.tar.zst</string>
+ <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.16/webrtc-m144.7559.06.16.28218655958-darwin64-28218655958.tar.zst</string>
</map>
<key>name</key>
<string>darwin64</string>
@@ -2621,11 +2621,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
- <string>b4d0c836d99491841c3816ff93bb2655a2817bd3</string>
+ <string>d187fd666eec8c14dbef959cdc9a6600a13736c7</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
- <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.22/webrtc-m137.7151.04.22.21966754211-linux64-21966754211.tar.zst</string>
+ <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.16/webrtc-m144.7559.06.16.28218655958-linux64-28218655958.tar.zst</string>
</map>
<key>name</key>
<string>linux64</string>
@@ -2635,11 +2635,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
- <string>ab2bddd77b1568b22b50ead13c1c33da94f4d59a</string>
+ <string>47ecfec6deaa775c958fc532d7a43d186ba191f1</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
- <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.22/webrtc-m137.7151.04.22.21966754211-windows64-21966754211.tar.zst</string>
+ <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.16/webrtc-m144.7559.06.16.28218655958-windows64-28218655958.tar.zst</string>
</map>
<key>name</key>
<string>windows64</string>
@@ -2652,7 +2652,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>copyright</key>
<string>Copyright (c) 2011, The WebRTC project authors. All rights reserved.</string>
<key>version</key>
- <string>m137.7151.04.22.21966754211</string>
+ <string>m144.7559.06.16.28218655958</string>
<key>name</key>
<string>webrtc</string>
<key>vcs_branch</key>
diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp
index f4ecce63a6..ab455c9645 100644
--- a/indra/llwebrtc/llwebrtc.cpp
+++ b/indra/llwebrtc/llwebrtc.cpp
@@ -27,7 +27,7 @@
#include "llwebrtc_impl.h"
#include <algorithm>
#include <string.h>
-
+#include "api/audio/create_audio_device_module.h"
#include "api/audio_codecs/audio_decoder_factory.h"
#include "api/audio_codecs/audio_encoder_factory.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
@@ -49,6 +49,12 @@ static int16_t PLAYOUT_DEVICE_DEFAULT = 0;
static int16_t RECORD_DEVICE_DEFAULT = 0;
#endif
+// How long to keep the capture device running after a mute before stopping it.
+// Keeping capture alive across brief mute/unmute cycles avoids cold-starting
+// the AEC (heard as a short hiss on unmute); once the mute has been held this
+// long we stop recording so the OS "mic in use" indicator clears.
+static const int MUTE_STOP_RECORDING_DELAY_MS = 30000;
+
//
// LLWebRTCAudioTransport implementation
@@ -134,7 +140,9 @@ int32_t LLWebRTCAudioTransport::NeedMorePlayData(size_t number_of_frames,
if (!engine)
{
// No engine sink; output silence to be safe.
- const size_t bytes = number_of_frames * bytes_per_frame * number_of_channels;
+ // bytes_per_frame already accounts for all channels, so do not multiply
+ // by number_of_channels again (that would overrun the playout buffer).
+ const size_t bytes = number_of_frames * bytes_per_frame;
memset(audio_data, 0, bytes);
number_of_samples_out = bytes_per_frame;
return 0;
@@ -250,17 +258,51 @@ void LLCustomProcessor::Process(webrtc::AudioBuffer *audio)
mState->setMicrophoneEnergy(std::sqrt(totalSum / (audio->num_channels() * audio->num_frames() * buffer_size)));
}
+
+//
+// LLWebRTCImpl implementation
+//
+
+void LLWebRTCAudioDeviceModule::SetTuning(bool tuning, bool mute)
+{
+ tuning_ = tuning;
+ if (tuning)
+ {
+ int32_t hr = inner_->InitMicrophone();
+ hr = inner_->InitRecording();
+ hr = inner_->StartRecording();
+ hr = inner_->StopPlayout();
+ }
+ else
+ {
+ if (mute)
+ {
+ inner_->StopRecording();
+ }
+ else
+ {
+ inner_->InitRecording();
+ inner_->StartRecording();
+ }
+ inner_->StartPlayout();
+ }
+}
+
//
// LLWebRTCImpl implementation
//
LLWebRTCImpl::LLWebRTCImpl(LLWebRTCLogCallback* logCallback) :
+ mEnv(webrtc::CreateEnvironment(webrtc::CreateDefaultTaskQueueFactory())),
mLogSink(new LLWebRTCLogSink(logCallback)),
mPeerCustomProcessor(nullptr),
mMute(true),
mTuningMode(false),
mDevicesDeploying(0),
- mGain(0.0f)
+ mGain(0.0f),
+ mBuiltinNS(false),
+ mBuiltinAGC(false),
+ mBuiltinAEC(false)
{
}
@@ -273,8 +315,6 @@ void LLWebRTCImpl::init()
webrtc::LogMessage::SetLogToStderr(true);
webrtc::LogMessage::AddLogToStream(mLogSink, webrtc::LS_VERBOSE);
- mTaskQueueFactory = webrtc::CreateDefaultTaskQueueFactory();
-
// Create the native threads.
mNetworkThread = webrtc::Thread::CreateWithSocketServer();
mNetworkThread->SetName("WebRTCNetworkThread", nullptr);
@@ -290,9 +330,17 @@ void LLWebRTCImpl::init()
[this]()
{
webrtc::scoped_refptr<webrtc::AudioDeviceModule> realADM =
- webrtc::AudioDeviceModule::Create(webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio, mTaskQueueFactory.get());
+ webrtc::CreateAudioDeviceModule(mEnv, webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio);
mDeviceModule = webrtc::make_ref_counted<LLWebRTCAudioDeviceModule>(realADM);
mDeviceModule->SetObserver(this);
+ mDeviceModule->Init();
+
+ mBuiltinNS = mDeviceModule->BuiltInNSIsAvailable();
+ mBuiltinAEC = mDeviceModule->BuiltInAECIsAvailable();
+ mBuiltinAGC = mDeviceModule->BuiltInAGCIsAvailable();
+ // All audio processing is done by WebRTC's software APM (configured
+ // below); make sure the hardware processors stay off.
+ workerDisableBuiltInAudioProcessing();
});
// The custom processor allows us to retrieve audio data (and levels)
@@ -302,17 +350,22 @@ void LLWebRTCImpl::init()
apb.SetCapturePostProcessing(std::make_unique<LLCustomProcessor>(mPeerCustomProcessor));
mAudioProcessingModule = apb.Build(webrtc::CreateEnvironment());
+ // Initial software-APM state, matching setAudioConfig() so there's no
+ // window where processing differs before the viewer's first config call.
+ // All processing is done here in software (the hardware AEC/AGC/NS is kept
+ // disabled), so enable echo cancellation from the very first frame.
webrtc::AudioProcessing::Config apm_config;
- apm_config.echo_canceller.enabled = false;
- apm_config.echo_canceller.mobile_mode = false;
- apm_config.gain_controller1.enabled = false;
- apm_config.gain_controller2.enabled = true;
- apm_config.high_pass_filter.enabled = true;
- apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
- apm_config.transient_suppression.enabled = true;
- apm_config.pipeline.multi_channel_render = true;
- apm_config.pipeline.multi_channel_capture = false;
+ apm_config.echo_canceller.enabled = true;
+ apm_config.echo_canceller.mobile_mode = false;
+ apm_config.gain_controller1.enabled = false;
+ apm_config.gain_controller2.enabled = true;
+ apm_config.gain_controller2.adaptive_digital.enabled = true; // auto-level speech
+ apm_config.high_pass_filter.enabled = true;
+ apm_config.noise_suppression.enabled = true;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
+ apm_config.transient_suppression.enabled = true;
+ apm_config.pipeline.multi_channel_render = true;
+ apm_config.pipeline.multi_channel_capture = true;
mAudioProcessingModule->ApplyConfig(apm_config);
@@ -344,7 +397,6 @@ void LLWebRTCImpl::init()
{
if (mDeviceModule)
{
- mDeviceModule->EnableBuiltInAEC(false);
updateDevices();
}
});
@@ -382,7 +434,6 @@ void LLWebRTCImpl::terminate()
mDeviceModule->Terminate();
}
mDeviceModule = nullptr;
- mTaskQueueFactory = nullptr;
});
// In case peer connections still somehow have jobs in workers,
@@ -395,47 +446,79 @@ void LLWebRTCImpl::terminate()
webrtc::LogMessage::RemoveLogToStream(mLogSink);
}
+
void LLWebRTCImpl::setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config)
{
+ // All audio processing is handled by WebRTC's software APM here. The
+ // platform/hardware AEC/AGC/NS is always disabled (see
+ // workerDisableBuiltInAudioProcessing), so these are enabled purely on the
+ // requested config without deferring to any built-in processor.
webrtc::AudioProcessing::Config apm_config;
- apm_config.echo_canceller.enabled = config.mEchoCancellation;
- apm_config.echo_canceller.mobile_mode = false;
- apm_config.gain_controller1.enabled = false;
- apm_config.gain_controller2.enabled = config.mAGC;
+ apm_config.echo_canceller.enabled = config.mEchoCancellation;
+ apm_config.echo_canceller.mobile_mode = false;
+ apm_config.gain_controller1.enabled = false;
+ apm_config.gain_controller2.enabled = config.mAGC;
apm_config.gain_controller2.adaptive_digital.enabled = true; // auto-level speech
- apm_config.high_pass_filter.enabled = true;
- apm_config.transient_suppression.enabled = true;
- apm_config.pipeline.multi_channel_render = true;
- apm_config.pipeline.multi_channel_capture = true;
- apm_config.pipeline.multi_channel_capture = true;
+ apm_config.high_pass_filter.enabled = true;
+ apm_config.transient_suppression.enabled = true;
+ apm_config.pipeline.multi_channel_render = true;
+ apm_config.pipeline.multi_channel_capture = true;
switch (config.mNoiseSuppressionLevel)
{
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_NONE:
apm_config.noise_suppression.enabled = false;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
break;
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_LOW:
apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
break;
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_MODERATE:
apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;
break;
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_HIGH:
apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh;
break;
case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_VERY_HIGH:
apm_config.noise_suppression.enabled = true;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
break;
default:
apm_config.noise_suppression.enabled = false;
- apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
}
mAudioProcessingModule->ApplyConfig(apm_config);
+
+ // Keep the hardware processors off; the APM above is the only processing.
+ PostWorkerTask([this]() { workerDisableBuiltInAudioProcessing(); });
+}
+
+void LLWebRTCImpl::workerDisableBuiltInAudioProcessing()
+{
+ if (!mDeviceModule)
+ {
+ return;
+ }
+
+ // We always use WebRTC's internal (software APM) audio processing. Running
+ // the platform/hardware AEC, AGC, or NS alongside it causes the two to
+ // fight -- pumping levels, double noise suppression, and mismatched AEC
+ // references -- so disable any that the device exposes.
+ if (mBuiltinNS)
+ {
+ mDeviceModule->EnableBuiltInNS(false);
+ }
+ if (mBuiltinAGC)
+ {
+ mDeviceModule->EnableBuiltInAGC(false);
+ }
+ if (mBuiltinAEC)
+ {
+ mDeviceModule->EnableBuiltInAEC(false);
+ }
}
void LLWebRTCImpl::refreshDevices()
@@ -455,8 +538,11 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer)
}
}
-// must be run in the worker thread.
-void LLWebRTCImpl::workerDeployDevices()
+// must be run in the worker thread. Selects the user's chosen capture/playout
+// devices and (re)initializes and starts them. Does NOT touch per-connection
+// tracks -- callers that also need mute/track state re-applied use
+// workerDeployDevices().
+void LLWebRTCImpl::workerStartDevices()
{
if (!mDeviceModule)
{
@@ -500,8 +586,20 @@ void LLWebRTCImpl::workerDeployDevices()
#endif
mDeviceModule->InitMicrophone();
mDeviceModule->SetStereoRecording(false);
+ mBuiltinNS = mDeviceModule->BuiltInNSIsAvailable();
+ mBuiltinAEC = mDeviceModule->BuiltInAECIsAvailable();
+ mBuiltinAGC = mDeviceModule->BuiltInAGCIsAvailable();
+ // A newly-selected capture device may default its hardware AEC/AGC/NS on;
+ // disable before InitRecording so the recording stream is configured to
+ // use only WebRTC's software APM.
+ workerDisableBuiltInAudioProcessing();
mDeviceModule->InitRecording();
+ if ((!mMute && mPeerConnections.size()) || mTuningMode)
+ {
+ mDeviceModule->ForceStartRecording();
+ }
+
int16_t playoutDevice = PLAYOUT_DEVICE_DEFAULT;
int16_t playout_device_start = 0;
if (mPlayoutDevice != "Default")
@@ -538,15 +636,30 @@ void LLWebRTCImpl::workerDeployDevices()
mDeviceModule->SetStereoPlayout(true);
mDeviceModule->InitPlayout();
- if ((!mMute && mPeerConnections.size()) || mTuningMode)
+ // Only run playout when there's actually something to render. Starting
+ // playout with no peer connection leaves the output device spinning with
+ // no engine data, which is heard as a buzz until a connection is made.
+ // (Recording is gated on the same condition above.)
+ if (!mTuningMode && !mPeerConnections.empty())
{
- mDeviceModule->ForceStartRecording();
+ mDeviceModule->StartPlayout();
}
+}
- if (!mTuningMode)
+// must be run in the worker thread. Selects/starts the devices (via
+// workerStartDevices) and then re-applies per-connection mute/track state.
+// Use this for device changes and tuning; for simply bringing devices up when
+// a connection is established (without disturbing the connection's own
+// mute/track management) call workerStartDevices() directly.
+void LLWebRTCImpl::workerDeployDevices()
+{
+ if (!mDeviceModule)
{
- mDeviceModule->StartPlayout();
+ return;
}
+
+ workerStartDevices();
+
mSignalingThread->PostTask(
[this]
{
@@ -740,6 +853,12 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
if (mMute)
{
+ // Keep capturing for a while after muting so quick mute/unmute cycles
+ // don't cold-start the AEC (and any OS capture effect such as Windows
+ // Voice Clarity), which is heard as a short hiss on unmute. Once the
+ // mute has been held this long, stop recording so the OS "mic in use"
+ // indicator clears. If the user unmutes or toggles before this fires,
+ // the sequence check turns it into a no-op and capture keeps running.
mWorkerThread->PostDelayedTask(
[this, current_sequence]
{
@@ -748,7 +867,7 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
mDeviceModule->ForceStopRecording();
}
},
- webrtc::TimeDelta::Millis(delay_ms));
+ webrtc::TimeDelta::Millis(MUTE_STOP_RECORDING_DELAY_MS));
}
else
{
@@ -757,6 +876,9 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
{
if (mDeviceModule && (current_sequence == mute_sequence.load()))
{
+ // No-op if capture is still running (the common case, when
+ // unmuting within the stop delay -> no AEC cold start);
+ // restarts capture if a sustained mute had stopped it.
mDeviceModule->InitRecording();
mDeviceModule->ForceStartRecording();
}
@@ -770,8 +892,7 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection()
{
- bool empty = mPeerConnections.empty();
- webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new webrtc::RefCountedObject<LLWebRTCPeerConnectionImpl>());
+ webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new webrtc::RefCountedObject<LLWebRTCPeerConnectionImpl>(mEnv));
peerConnection->init(this);
if (mPeerConnections.empty())
{
@@ -779,6 +900,13 @@ LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection()
}
mPeerConnections.emplace_back(peerConnection);
+ // The capture/playout devices are intentionally NOT started here. This
+ // runs when the connection is created/connecting; starting the output
+ // device now leaves it spinning with no decoded audio during the handshake,
+ // which is heard as a buzz. The devices are (re)started from
+ // OnConnectionChange(kConnected) instead, once audio is actually
+ // established (see startAudioDevices()).
+
peerConnection->enableSenderTracks(false);
peerConnection->resetMute();
return peerConnection.get();
@@ -795,10 +923,82 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn
if (mPeerConnections.empty())
{
intSetMute(true);
+ // Last connection gone: stop capture immediately rather than
+ // waiting out the mute stop-delay, so the mic isn't held open after
+ // the call, and stop playout so the output device isn't left
+ // spinning with no engine data.
+ mWorkerThread->PostTask(
+ [this]()
+ {
+ if (mDeviceModule)
+ {
+ mDeviceModule->ForceStopRecording();
+ mDeviceModule->StopPlayout();
+ }
+ });
}
}
}
+void LLWebRTCImpl::startAudioDevices()
+{
+ // Called when a connection's audio is established. This is the
+ // authoritative point that brings the devices back (with the user's
+ // selected devices applied) after all connections dropped (teleport, voice
+ // restart) or for the first call of a session. It matters because the
+ // WebRTC engine no-ops Start/StopRecording on our ADM wrapper -- only our
+ // explicit Force* calls actually drive capture -- so when the devices were
+ // stopped, nothing else will restart them.
+ //
+ // It's guarded on Playing()/Recording() so a second connection establishing
+ // won't glitch an already-running stream, and doing this at "connected"
+ // rather than at connection creation avoids running the output device with
+ // no decoded audio during the handshake.
+ mWorkerThread->PostTask(
+ [this]()
+ {
+ if (!mDeviceModule || mTuningMode)
+ {
+ return;
+ }
+
+ if (!mDeviceModule->Playing())
+ {
+ // First established connection for this call: select and start
+ // the user's *chosen* capture/playout devices
+ // (SetRecordingDevice/SetPlayoutDevice). Just calling
+ // InitPlayout/InitRecording here would bring the devices up on
+ // whatever the ADM currently has selected -- the system default
+ // after a cold start -- which is why a p2p call (or a call after
+ // teleport/voice-restart) could come up on the wrong device.
+ //
+ // We call workerStartDevices() rather than the full
+ // deployDevices() on purpose: deployDevices() also re-applies
+ // per-connection mute/track state, which races with the
+ // viewer's own mute setup for the freshly-establishing
+ // connection and can leave the sender track disabled (recording
+ // runs but nothing transmits after teleport).
+ workerStartDevices();
+ }
+
+ // Authoritatively (re)start capture whenever we're connected and not
+ // device-muted. This runs unconditionally -- NOT just in an else
+ // branch -- because workerStartDevices() above stops recording while
+ // re-selecting the device and only restarts it behind a gate; if
+ // that gate doesn't line up (or capture was stopped on a prior
+ // disconnect, e.g. teleport), this is what reliably brings the mic
+ // back. No-op if capture is already running.
+ if (!mMute && !mPeerConnections.empty() && !mDeviceModule->Recording())
+ {
+ if (!mDeviceModule->RecordingIsInitialized())
+ {
+ mDeviceModule->InitRecording();
+ }
+ mDeviceModule->ForceStartRecording();
+ }
+ });
+}
+
//
// LLWebRTCPeerConnectionImpl implementation.
@@ -806,7 +1006,8 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn
// Most peer connection (signaling) happens on
// the signaling thread.
-LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() :
+LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl(const webrtc::Environment& env) :
+ mEnv(env),
mWebRTCImpl(nullptr),
mPeerConnection(nullptr),
mMute(MUTE_INITIAL),
@@ -1255,6 +1456,12 @@ void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterf
{
case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
{
+ // Audio is established now -- (re)start the capture and playout
+ // devices. Doing this here rather than at connection creation
+ // avoids running the output device during the handshake (heard as a
+ // buzz), and reliably restores the devices after a full teardown
+ // (teleport / voice restart).
+ mWebRTCImpl->startAudioDevices();
mPendingJobs++;
webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this);
mWebRTCImpl->PostWorkerTask([self]()
@@ -1267,6 +1474,7 @@ void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterf
});
break;
}
+
case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed:
{
for (auto &observer : mSignalingObserverList)
diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h
index bd7a2e0bcf..cfb0d10c29 100644
--- a/indra/llwebrtc/llwebrtc_impl.h
+++ b/indra/llwebrtc/llwebrtc_impl.h
@@ -323,30 +323,8 @@ public:
// tuning microphone energy calculations
float GetMicrophoneEnergy() { return audio_transport_.GetMicrophoneEnergy(); }
void SetTuningMicGain(float gain) { audio_transport_.SetGain(gain); }
- void SetTuning(bool tuning, bool mute)
- {
- tuning_ = tuning;
- if (tuning)
- {
- inner_->InitRecording();
- inner_->StartRecording();
- inner_->StopPlayout();
- }
- else
- {
- if (mute)
- {
- inner_->StopRecording();
- }
- else
- {
- inner_->InitRecording();
- inner_->StartRecording();
- }
- inner_->InitPlayout();
- inner_->StartPlayout();
- }
- }
+
+ void SetTuning(bool tuning, bool mute);
protected:
~LLWebRTCAudioDeviceModule() override = default;
@@ -436,7 +414,6 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
//
void setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config = LLWebRTCDeviceInterface::AudioConfig()) override;
-
void refreshDevices() override;
void setDevicesObserver(LLWebRTCDevicesObserver *observer) override;
@@ -522,9 +499,22 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
LLWebRTCPeerConnectionInterface* newPeerConnection();
void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection);
+ // (Re)start the capture and playout devices once a connection's audio is
+ // established. This is the authoritative point for bringing the devices
+ // back after all connections dropped (teleport, voice restart). Idempotent
+ // and safe to call from any thread (work is posted to the worker thread).
+ void startAudioDevices();
+
protected:
+ const webrtc::Environment mEnv;
+ void workerStartDevices();
void workerDeployDevices();
+ // We always rely on WebRTC's internal (software APM) audio processing, so
+ // any platform/hardware AEC/AGC/NS must be kept disabled.
+ void workerDisableBuiltInAudioProcessing();
+
+
LLWebRTCLogSink* mLogSink;
// The native webrtc threads
@@ -537,10 +527,6 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
webrtc::scoped_refptr<webrtc::AudioProcessing> mAudioProcessingModule;
- // more native webrtc stuff
- std::unique_ptr<webrtc::TaskQueueFactory> mTaskQueueFactory;
-
-
// Devices
void updateDevices();
void deployDevices();
@@ -548,6 +534,10 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
webrtc::scoped_refptr<LLWebRTCAudioDeviceModule> mDeviceModule;
std::vector<LLWebRTCDevicesObserver *> mVoiceDevicesObserverList;
+ bool mBuiltinNS;
+ bool mBuiltinAGC;
+ bool mBuiltinAEC;
+
// accessors in native webrtc for devices aren't apparently implemented yet.
bool mTuningMode;
std::string mRecordingDevice;
@@ -580,7 +570,7 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
{
public:
- LLWebRTCPeerConnectionImpl();
+ LLWebRTCPeerConnectionImpl(const webrtc::Environment& env);
~LLWebRTCPeerConnectionImpl();
void init(LLWebRTCImpl * webrtc_impl);
@@ -659,7 +649,7 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
void gatherConnectionStats() override;
protected:
-
+ const webrtc::Environment mEnv;
LLWebRTCImpl * mWebRTCImpl;
webrtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory;
diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp
index d8d6bcf5fd..5aaa53b732 100644
--- a/indra/newview/llpanelvoicedevicesettings.cpp
+++ b/indra/newview/llpanelvoicedevicesettings.cpp
@@ -338,8 +338,12 @@ void LLPanelVoiceDeviceSettings::initialize()
// put voice client in "tuning" mode
if (mUseTuningMode)
{
+ // WebRTC tuning only affects the local audio device (mic-level
+ // monitoring and device selection); the peer connection stays up and
+ // its send/receive tracks are disabled for the duration. Unlike Vivox,
+ // there's no need to suspend (and tear down) the voice channel, which
+ // previously dropped the call and failed to reconnect on resume.
LLVoiceClient::getInstance()->tuningStart();
- LLVoiceChannel::suspend();
}
}
@@ -348,7 +352,6 @@ void LLPanelVoiceDeviceSettings::cleanup()
if (mUseTuningMode)
{
LLVoiceClient::getInstance()->tuningStop();
- LLVoiceChannel::resume();
}
}