summaryrefslogtreecommitdiff
path: root/indra/llwebrtc/llwebrtc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llwebrtc/llwebrtc.cpp')
-rw-r--r--indra/llwebrtc/llwebrtc.cpp626
1 files changed, 457 insertions, 169 deletions
diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp
index 589934fcce..8b77a39541 100644
--- a/indra/llwebrtc/llwebrtc.cpp
+++ b/indra/llwebrtc/llwebrtc.cpp
@@ -26,8 +26,11 @@
#include "llwebrtc_impl.h"
#include <algorithm>
+#include <chrono>
+#include <future>
+#include <thread>
#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"
@@ -56,8 +59,8 @@ webrtc::LossBasedBweV2::Config::Config()
namespace llwebrtc
{
#if WEBRTC_WIN
-static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice;
-static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice;
+static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice;
+static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice;
#else
static int16_t PLAYOUT_DEVICE_DEFAULT = 0;
static int16_t RECORD_DEVICE_DEFAULT = 0;
@@ -148,7 +151,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;
@@ -264,17 +269,47 @@ 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)
+ {
+ // Ensure capture is running (it's normally already running -- capture is
+ // session-long) so the mic-level meter works, and stop rendering the
+ // call while tuning. The recording calls are no-ops if capture is
+ // already active, so this won't cold-start it.
+ inner_->InitMicrophone();
+ inner_->InitRecording();
+ inner_->StartRecording();
+ inner_->StopPlayout();
+ }
+ // On exit, capture is deliberately left running (mute is handled by gain,
+ // not by stopping the device, so there's no AEC cold-start hiss). Playout
+ // is restored by the caller via workerOpenPlayout(), keeping it gated on
+ // there being a connection to render.
+}
+
//
// LLWebRTCImpl implementation
//
LLWebRTCImpl::LLWebRTCImpl(LLWebRTCLogCallback* logCallback) :
+ mEnv(webrtc::CreateEnvironment(webrtc::CreateDefaultTaskQueueFactory())),
mLogSink(new LLWebRTCLogSink(logCallback)),
mPeerCustomProcessor(nullptr),
mMute(true),
+ mVoiceEnabled(false),
mTuningMode(false),
mDevicesDeploying(0),
- mGain(0.0f)
+ mGain(0.0f),
+ mBuiltinNS(false),
+ mBuiltinAGC(false),
+ mBuiltinAEC(false)
{
}
@@ -287,8 +322,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);
@@ -304,11 +337,19 @@ 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);
#if !CM_WEBRTC
mDeviceModule->SetObserver(this);
#endif
+ 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)
@@ -318,17 +359,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);
@@ -360,7 +406,6 @@ void LLWebRTCImpl::init()
{
if (mDeviceModule)
{
- mDeviceModule->EnableBuiltInAEC(false);
updateDevices();
}
});
@@ -369,8 +414,23 @@ void LLWebRTCImpl::init()
void LLWebRTCImpl::terminate()
{
- mWorkerThread->BlockingCall(
- [this]()
+ // Run all blocking WebRTC shutdown calls on a separate thread so that a
+ // hung BlockingCall cannot block the viewer shutdown indefinitely.
+ // Webrtc is not mission critical, we need to save personal data.
+ auto done_promise = std::make_shared<std::promise<void> >();
+ std::future<void> done_future = done_promise->get_future();
+
+ // Hand ownership of the connections to the shutdown thread. Nothing on
+ // this thread may touch them afterwards -- in the timeout case below the
+ // shutdown thread is detached and may still be working through them.
+ std::vector<webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>> connections;
+ connections.swap(mPeerConnections);
+
+ std::thread shutdown_thread(
+ [this, connections = std::move(connections), done_promise]() mutable
+ {
+ mWorkerThread->BlockingCall(
+ [this]()
{
if (mDeviceModule)
{
@@ -379,79 +439,155 @@ void LLWebRTCImpl::terminate()
}
});
- for (auto &connection : mPeerConnections)
- {
- connection->terminate();
- }
+ // Close the connections inline on the signaling thread. This can't be
+ // connection->terminate(), which only *posts* the close: that queues the
+ // real work behind everything below, so the connections would be closed
+ // after the factory and the device module are gone -- or not at all, if
+ // the thread is destroyed with the task still queued.
+ //
+ // It matters that the close completes here because closing a peer
+ // connection flushes any in-flight GetStats request and runs its
+ // callback inline, and that callback calls back into the viewer's
+ // signaling observers. Those observers are only valid until
+ // llwebrtc::terminate() returns.
+ mSignalingThread->BlockingCall(
+ [&connections]()
+ {
+ for (auto& connection : connections)
+ {
+ connection->closeOnSignalingThread();
+ }
+ // Destroy the connections here, on the signaling thread, while
+ // it's still running.
+ connections.clear();
+ });
- // connection->terminate() above spawns a number of Signaling thread calls to
- // shut down the connection. The following Blocking Call will wait
- // until they're done before it's executed, allowing time to clean up.
+ // Drain anything the closes posted before dropping the factory.
+ mSignalingThread->BlockingCall([]() {});
- mSignalingThread->BlockingCall([this]() { mPeerConnectionFactory = nullptr; });
+ mSignalingThread->BlockingCall([this]() {
+ mPeerConnectionFactory = nullptr;
+ });
- mWorkerThread->BlockingCall(
- [this]()
+ mWorkerThread->BlockingCall(
+ [this]()
{
if (mDeviceModule)
{
- mDeviceModule->Terminate();
+ mDeviceModule->ForceTerminate();
}
- mDeviceModule = nullptr;
- mTaskQueueFactory = nullptr;
+ mDeviceModule = nullptr;
});
- // In case peer connections still somehow have jobs in workers,
- // only clear connections up after clearing workers.
+ done_promise->set_value();
+ });
+
+ constexpr auto WEBRTC_TERMINATE_TIMEOUT = std::chrono::seconds(10);
+ if (done_future.wait_for(WEBRTC_TERMINATE_TIMEOUT) == std::future_status::timeout)
+ {
+ RTC_LOG(LS_WARNING) << __FUNCTION__
+ << ": timed out waiting for WebRTC thread shutdown."
+ " Detaching — some WebRTC resources will be leaked.";
+ shutdown_thread.detach();
+
+ // Release the unique_ptrs WITHOUT joining/deleting: the detached thread
+ // may still be using these thread objects.
+ // The raw pointers are intentionally leaked — the process is exiting anyway
+ // and our priority is saving cache and personal data.
+ (void)mNetworkThread.release();
+ (void)mWorkerThread.release();
+ (void)mSignalingThread.release();
+
+ // mPeerConnections is already empty -- the detached thread owns the
+ // connections now and must be left to finish with them.
+ webrtc::LogMessage::RemoveLogToStream(mLogSink);
+ return;
+ }
+
+ shutdown_thread.join();
+
+ // The connections were closed and destroyed on the signaling thread before
+ // the shutdown thread finished, so it's safe to drop the threads now.
mNetworkThread = nullptr;
mWorkerThread = nullptr;
mSignalingThread = nullptr;
- mPeerConnections.clear();
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()
@@ -471,20 +607,25 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer)
}
}
-// must be run in the worker thread.
-void LLWebRTCImpl::workerDeployDevices()
+// must be run in the worker thread. Selects the configured capture device and
+// starts recording. Capture runs the whole time voice is enabled (it's never
+// stopped for mute or between calls, so the AEC never cold-starts -- there's no
+// hiss on unmute), so this is a no-op when already recording. Device changes
+// go through workerDeployDevices(), which stops recording first to force a
+// clean re-select; voice off goes through setVoiceEnabled(false).
+void LLWebRTCImpl::workerStartRecording()
{
- if (!mDeviceModule)
+ // Only run capture while voice is enabled, and never cold-start it when
+ // it's already running (that would cause the unmute hiss).
+ if (!mDeviceModule || !mVoiceEnabled || mDeviceModule->Recording())
{
return;
}
int16_t recordingDevice = RECORD_DEVICE_DEFAULT;
- int16_t recording_device_start = 0;
-
if (mRecordingDevice != "Default")
{
- for (int16_t i = recording_device_start; i < mRecordingDeviceList.size(); i++)
+ for (int16_t i = 0; i < mRecordingDeviceList.size(); i++)
{
if (mRecordingDeviceList[i].mID == mRecordingDevice)
{
@@ -500,8 +641,6 @@ void LLWebRTCImpl::workerDeployDevices()
}
}
- mDeviceModule->StopPlayout();
- mDeviceModule->ForceStopRecording();
#if WEBRTC_WIN
if (recordingDevice < 0)
{
@@ -516,13 +655,32 @@ void LLWebRTCImpl::workerDeployDevices()
#endif
mDeviceModule->InitMicrophone();
mDeviceModule->SetStereoRecording(false);
+ // 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();
+ mDeviceModule->ForceStartRecording();
+}
+
+// must be run in the worker thread. Selects the configured playout device and
+// starts playout. Playout only runs while there's a connection to render
+// (running the output device with no engine data is heard as a buzz), so this
+// is a no-op when there are no connections or when already playing. Device
+// changes go through workerDeployDevices(), which stops playout first.
+void LLWebRTCImpl::workerStartPlayout()
+{
+ // Only run playout while voice is enabled and there's a connection to
+ // render (running the output device otherwise is heard as a buzz).
+ if (!mDeviceModule || !mVoiceEnabled || mTuningMode || mDeviceModule->Playing() || mPeerConnections.empty())
+ {
+ return;
+ }
int16_t playoutDevice = PLAYOUT_DEVICE_DEFAULT;
- int16_t playout_device_start = 0;
if (mPlayoutDevice != "Default")
{
- for (int16_t i = playout_device_start; i < mPlayoutDeviceList.size(); i++)
+ for (int16_t i = 0; i < mPlayoutDeviceList.size(); i++)
{
if (mPlayoutDeviceList[i].mID == mPlayoutDevice)
{
@@ -553,16 +711,29 @@ void LLWebRTCImpl::workerDeployDevices()
mDeviceModule->InitSpeaker();
mDeviceModule->SetStereoPlayout(true);
mDeviceModule->InitPlayout();
+ mDeviceModule->StartPlayout();
+}
- if ((!mMute && mPeerConnections.size()) || mTuningMode)
+// must be run in the worker thread. Used for device changes and tuning: forces
+// a clean re-select of both devices, then re-applies per-connection mute/track
+// state. To merely bring playout up when a connection is established (without
+// disturbing the connection's own mute/track management) call
+// workerOpenPlayout() directly -- see startPlayout().
+void LLWebRTCImpl::workerDeployDevices()
+{
+ if (!mDeviceModule)
{
- mDeviceModule->ForceStartRecording();
+ return;
}
- if (!mTuningMode)
- {
- mDeviceModule->StartPlayout();
- }
+ // Stop first so the start helpers (which no-op when already running) will
+ // re-select the now-current device.
+ mDeviceModule->StopPlayout();
+ mDeviceModule->ForceStopRecording();
+
+ workerStartRecording();
+ workerStartPlayout();
+
mSignalingThread->PostTask(
[this]
{
@@ -604,6 +775,35 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id)
}
}
+void LLWebRTCImpl::setVoiceEnabled(bool enable)
+{
+ mVoiceEnabled = enable;
+ mWorkerThread->PostTask(
+ [this, enable]()
+ {
+ if (!mDeviceModule)
+ {
+ return;
+ }
+ if (enable)
+ {
+ // Voice on: start the capture device (it then stays running
+ // across calls and mute/unmute), and start playout if there's
+ // already a connection to render.
+ mDeviceModule->Init();
+ workerDeployDevices();
+ }
+ else
+ {
+ // Voice off: release both devices so the OS mic/speaker aren't
+ // held open.
+ mDeviceModule->ForceStopRecording();
+ mDeviceModule->StopPlayout();
+ mDeviceModule->ForceTerminate();
+ }
+ });
+}
+
// updateDevices needs to happen on the worker thread.
void LLWebRTCImpl::updateDevices()
{
@@ -627,6 +827,7 @@ void LLWebRTCImpl::updateDevices()
char name[webrtc::kAdmMaxDeviceNameSize];
char guid[webrtc::kAdmMaxGuidSize];
mDeviceModule->PlayoutDeviceName(index, name, guid);
+ RTC_LOG(LS_VERBOSE) << "updateDevices: playout device [" << index << "] name='" << name << "' guid='" << guid << "'";
mPlayoutDeviceList.emplace_back(name, guid);
}
@@ -645,13 +846,18 @@ void LLWebRTCImpl::updateDevices()
char name[webrtc::kAdmMaxDeviceNameSize];
char guid[webrtc::kAdmMaxGuidSize];
mDeviceModule->RecordingDeviceName(index, name, guid);
+ RTC_LOG(LS_VERBOSE) << "updateDevices: recording device [" << index << "] name='" << name << "' guid='" << guid << "'";
mRecordingDeviceList.emplace_back(name, guid);
}
+ RTC_LOG(LS_INFO) << "updateDevices, playout count: " << renderDeviceCount << "; capture count: " << captureDeviceCount;
+
for (auto &observer : mVoiceDevicesObserverList)
{
observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList);
}
+
+ deployDevices();
}
void LLWebRTCImpl::OnDevicesUpdated()
@@ -674,6 +880,13 @@ void LLWebRTCImpl::setTuningMode(bool enable)
[this]
{
mDeviceModule->SetTuning(mTuningMode, mMute);
+ if (!mTuningMode)
+ {
+ // Restore playout after tuning, gated on there being a
+ // connection to render (so the output device isn't left
+ // spinning with no engine data).
+ workerStartPlayout();
+ }
mSignalingThread->PostTask(
[this]
{
@@ -745,39 +958,16 @@ void LLWebRTCImpl::setMute(bool mute, int delay_ms)
void LLWebRTCImpl::intSetMute(bool mute, int delay_ms)
{
+ // Mute by zeroing the captured (post-APM) gain; the sender track is also
+ // disabled per connection (see LLWebRTCPeerConnectionImpl::setMute). The
+ // capture device deliberately stays running for the whole session, so
+ // muting/unmuting never stops or starts it -- that's what avoids the AEC
+ // cold-start hiss on unmute. Capture start/stop is tied to device
+ // selection (workerStartRecording) and shutdown, not to mute.
if (mPeerCustomProcessor)
{
mPeerCustomProcessor->setGain(mMute ? 0.0f : mGain);
}
-
- // Sequence counter to prevent race conditions from rapid requests to mute/unmute
- static std::atomic<uint32_t> mute_sequence(0);
- uint32_t current_sequence = ++mute_sequence;
-
- if (mMute)
- {
- mWorkerThread->PostDelayedTask(
- [this, current_sequence]
- {
- if (mDeviceModule && (current_sequence == mute_sequence.load()))
- {
- mDeviceModule->ForceStopRecording();
- }
- },
- webrtc::TimeDelta::Millis(delay_ms));
- }
- else
- {
- mWorkerThread->PostTask(
- [this, current_sequence]
- {
- if (mDeviceModule && (current_sequence == mute_sequence.load()))
- {
- mDeviceModule->InitRecording();
- mDeviceModule->ForceStartRecording();
- }
- });
- }
}
//
@@ -786,8 +976,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())
{
@@ -795,6 +984,13 @@ LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection()
}
mPeerConnections.emplace_back(peerConnection);
+ // Playout is 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.
+ // Playout is started from OnConnectionChange(kConnected) instead, once audio
+ // is actually established (see startPlayout()). Capture follows
+ // voice-enabled state, so it's not touched here either.
+
peerConnection->enableSenderTracks(false);
peerConnection->resetMute();
return peerConnection.get();
@@ -811,10 +1007,45 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn
if (mPeerConnections.empty())
{
intSetMute(true);
+ // Last connection gone: stop playout (there's nothing to render).
+ // Capture stays running while voice is enabled so it's ready -- with
+ // no cold-start hiss -- when the next call comes up. But if voice
+ // has been disabled, stop capture now: setVoiceEnabled(false) tried
+ // to, but the engine's send stream was still active then (and the
+ // engine's own StopRecording is intentionally a no-op), so the stop
+ // only sticks once the connection -- and its stream -- is gone.
+ mWorkerThread->PostTask(
+ [this]()
+ {
+ if (mDeviceModule)
+ {
+ mDeviceModule->StopPlayout();
+ if (!mVoiceEnabled)
+ {
+ mDeviceModule->ForceStopRecording();
+ }
+ }
+ });
}
}
}
+void LLWebRTCImpl::startPlayout()
+{
+ // Called when a connection's audio is established. Only playout is started
+ // here: it's gated on there being a connection to render, because running
+ // the output device with no engine data is heard as a buzz. Capture is
+ // NOT touched here -- it follows voice-enabled state (setVoiceEnabled), so
+ // it's already running if voice is on and must stay off if voice is off.
+ // Starting it here would also let a stray kConnected during voice-disable
+ // teardown re-open the mic.
+ mWorkerThread->PostTask(
+ [this]()
+ {
+ workerStartPlayout();
+ });
+}
+
//
// LLWebRTCPeerConnectionImpl implementation.
@@ -822,13 +1053,16 @@ 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),
mAnswerReceived(false),
mPeerConnectionState(webrtc::PeerConnectionInterface::PeerConnectionState::kNew),
mDisconnectCount(0),
+ mStatsRequestPending(false),
+ mShuttingDown(false),
mPendingJobs(0)
{
}
@@ -857,50 +1091,68 @@ void LLWebRTCPeerConnectionImpl::init(LLWebRTCImpl * webrtc_impl)
void LLWebRTCPeerConnectionImpl::terminate()
{
mPendingJobs++;
+ webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this);
mWebRTCImpl->PostSignalingTask(
- [this]()
+ [self]()
{
- if (mPeerConnection)
- {
- if (mDataChannel)
- {
- {
- mDataChannel->Close();
- mDataChannel = nullptr;
- }
- }
+ self->closeOnSignalingThread();
+ self->mPendingJobs--;
+ });
+}
- // to remove 'Secondlife is recording' icon from taskbar
- // if user was speaking
- auto senders = mPeerConnection->GetSenders();
- for (auto& sender : senders)
- {
- auto track = sender->track();
- if (track)
- {
- track->set_enabled(false);
- }
- }
+// Signaling thread only.
+void LLWebRTCPeerConnectionImpl::closeOnSignalingThread()
+{
+ // Stop issuing stats requests; one may already be in flight, and
+ // Close() below will flush it.
+ mShuttingDown = true;
- mPeerConnection->Close();
- if (mLocalStream)
- {
- auto tracks = mLocalStream->GetAudioTracks();
- for (auto& track : tracks)
- {
- mLocalStream->RemoveTrack(track);
- }
- mLocalStream = nullptr;
- }
- mPeerConnection = nullptr;
+ if (mPeerConnection)
+ {
+ if (mDataChannel)
+ {
+ mDataChannel->Close();
+ mDataChannel = nullptr;
+ }
- for (auto &observer : mSignalingObserverList)
- {
- observer->OnPeerConnectionClosed();
- }
+ // to remove 'Secondlife is recording' icon from taskbar
+ // if user was speaking
+ auto senders = mPeerConnection->GetSenders();
+ for (auto& sender : senders)
+ {
+ auto track = sender->track();
+ if (track)
+ {
+ track->set_enabled(false);
}
- mPendingJobs--;
- });
+ }
+
+ // NOTE: Close() delivers any pending GetStats report inline, before it
+ // returns, so the observer list below must still be valid here.
+ mPeerConnection->Close();
+ if (mLocalStream)
+ {
+ auto tracks = mLocalStream->GetAudioTracks();
+ for (auto& track : tracks)
+ {
+ mLocalStream->RemoveTrack(track);
+ }
+ mLocalStream = nullptr;
+ }
+ mPeerConnection = nullptr;
+
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnPeerConnectionClosed();
+ }
+ }
+
+ // Nothing may call back into the viewer past this point. On shutdown the
+ // viewer's connection objects are torn down as soon as llwebrtc::terminate()
+ // returns and they deliberately don't unset themselves as observers, so any
+ // late callback would be reaching into freed memory.
+ mSignalingObserverList.clear();
+ mDataObserverList.clear();
}
void LLWebRTCPeerConnectionImpl::setSignalingObserver(LLWebRTCSignalingObserver *observer) { mSignalingObserverList.emplace_back(observer); }
@@ -922,8 +1174,9 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti
mAnswerReceived = false;
mPendingJobs++;
+ webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this);
mWebRTCImpl->PostSignalingTask(
- [this,options]()
+ [self,options]()
{
webrtc::PeerConnectionInterface::RTCConfiguration config;
for (auto server : options.mServers)
@@ -942,42 +1195,42 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti
config.set_min_port(60000);
config.set_max_port(60100);
- webrtc::PeerConnectionDependencies pc_dependencies(this);
+ webrtc::PeerConnectionDependencies pc_dependencies(self.get());
// Other thread manages mPeerConnectionFactory's lifetime and it can be reset
// at any momment, create own scoped_refptr (atomic).
- webrtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> peer_connection_factory = mPeerConnectionFactory;
+ webrtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> peer_connection_factory = self->mPeerConnectionFactory;
if (peer_connection_factory == nullptr)
{
RTC_LOG(LS_ERROR) << __FUNCTION__ << "Error creating peer connection, factory doesn't exist";
// Too early?
- mPendingJobs--;
+ self->mPendingJobs--;
return;
}
auto error_or_peer_connection = peer_connection_factory->CreatePeerConnectionOrError(config, std::move(pc_dependencies));
if (error_or_peer_connection.ok())
{
- mPeerConnection = std::move(error_or_peer_connection.value());
+ self->mPeerConnection = std::move(error_or_peer_connection.value());
}
else
{
RTC_LOG(LS_ERROR) << __FUNCTION__ << "Error creating peer connection: " << error_or_peer_connection.error().message();
- for (auto &observer : mSignalingObserverList)
+ for (auto &observer : self->mSignalingObserverList)
{
observer->OnRenegotiationNeeded();
}
- mPendingJobs--;
+ self->mPendingJobs--;
return;
}
webrtc::DataChannelInit init;
init.ordered = true;
- auto data_channel_or_error = mPeerConnection->CreateDataChannelOrError("SLData", &init);
+ auto data_channel_or_error = self->mPeerConnection->CreateDataChannelOrError("SLData", &init);
if (data_channel_or_error.ok())
{
- mDataChannel = std::move(data_channel_or_error.value());
+ self->mDataChannel = std::move(data_channel_or_error.value());
- mDataChannel->RegisterObserver(this);
+ self->mDataChannel->RegisterObserver(self.get());
}
webrtc::AudioOptions audioOptions;
@@ -986,16 +1239,16 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti
audioOptions.noise_suppression = true;
audioOptions.init_recording_on_send = false;
- mLocalStream = peer_connection_factory->CreateLocalMediaStream("SLStream");
+ self->mLocalStream = peer_connection_factory->CreateLocalMediaStream("SLStream");
webrtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
peer_connection_factory->CreateAudioTrack("SLAudio", peer_connection_factory->CreateAudioSource(audioOptions).get()));
audio_track->set_enabled(false);
- mLocalStream->AddTrack(audio_track);
+ self->mLocalStream->AddTrack(audio_track);
- mPeerConnection->AddTrack(audio_track, {"SLStream"});
+ self->mPeerConnection->AddTrack(audio_track, {"SLStream"});
- auto senders = mPeerConnection->GetSenders();
+ auto senders = self->mPeerConnection->GetSenders();
for (auto &sender : senders)
{
@@ -1011,7 +1264,7 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti
sender->SetParameters(params);
}
- auto receivers = mPeerConnection->GetReceivers();
+ auto receivers = self->mPeerConnection->GetReceivers();
for (auto &receiver : receivers)
{
webrtc::RtpParameters params;
@@ -1027,9 +1280,9 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti
}
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions offerOptions;
- this->AddRef(); // CreateOffer will deref this when it's done. Without this, the callbacks never get called.
- mPeerConnection->CreateOffer(this, offerOptions);
- mPendingJobs--;
+ self->AddRef(); // CreateOffer will deref this when it's done. Without this, the callbacks never get called.
+ self->mPeerConnection->CreateOffer(self.get(), offerOptions);
+ self->mPendingJobs--;
});
return true;
@@ -1106,14 +1359,15 @@ void LLWebRTCPeerConnectionImpl::setMute(bool mute)
mPendingJobs++;
+ webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this);
mWebRTCImpl->PostSignalingTask(
- [this, force_reset, enable]()
+ [self, force_reset, enable]()
{
- if (mPeerConnection)
+ if (self->mPeerConnection)
{
- auto senders = mPeerConnection->GetSenders();
+ auto senders = self->mPeerConnection->GetSenders();
- RTC_LOG(LS_INFO) << __FUNCTION__ << (mMute ? "disabling" : "enabling") << " streams count " << senders.size();
+ RTC_LOG(LS_INFO) << __FUNCTION__ << (self->mMute ? "disabling" : "enabling") << " streams count " << senders.size();
for (auto &sender : senders)
{
auto track = sender->track();
@@ -1129,7 +1383,7 @@ void LLWebRTCPeerConnectionImpl::setMute(bool mute)
track->set_enabled(enable);
}
}
- mPendingJobs--;
+ self->mPendingJobs--;
}
});
}
@@ -1268,16 +1522,25 @@ void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterf
{
case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
{
+ // Audio is established now -- start playout for this connection.
+ // (Capture follows voice-enabled state, so it's already running and
+ // isn't touched here.) Doing playout here rather than at connection
+ // creation avoids running the output device with no decoded audio
+ // during the handshake (heard as a buzz).
+ mWebRTCImpl->startPlayout();
mPendingJobs++;
- mWebRTCImpl->PostWorkerTask([this]() {
- for (auto &observer : mSignalingObserverList)
+ webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this);
+ mWebRTCImpl->PostWorkerTask([self]()
+ {
+ for (auto &observer : self->mSignalingObserverList)
{
- observer->OnAudioEstablished(this);
+ observer->OnAudioEstablished(self.get());
}
- mPendingJobs--;
+ self->mPendingJobs--;
});
break;
}
+
case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed:
{
for (auto &observer : mSignalingObserverList)
@@ -1616,16 +1879,41 @@ void LLWebRTCPeerConnectionImpl::gatherConnectionStats()
return;
}
- auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>(
- [this](const LLWebRTCStatsMap& generic_stats)
+ webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this);
+ mWebRTCImpl->PostSignalingTask(
+ [self]()
+ {
+ if (!self->mPeerConnection
+ || self->mShuttingDown
+ || self->mPeerConnectionState != webrtc::PeerConnectionInterface::PeerConnectionState::kConnected
+ || self->mStatsRequestPending) // signaling thread only
{
- for (auto& observer : mSignalingObserverList)
+ return;
+ }
+
+ self->mStatsRequestPending = true;
+
+ auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>(
+ [self](const LLWebRTCStatsMap& generic_stats)
+ {
+ self->mStatsRequestPending = false;
+
+ // This can be delivered inline from PeerConnection::Close(), which
+ // flushes pending stats requests as it tears down. Don't call out
+ // to the observers in that case -- we're on our way out.
+ if (!self->mPeerConnection || self->mShuttingDown)
+ {
+ return;
+ }
+
+ for (auto& observer : self->mSignalingObserverList)
{
observer->OnStatsDelivered(generic_stats);
}
});
- mPeerConnection->GetStats(stats_callback.get());
+ self->mPeerConnection->GetStats(stats_callback.get());
+ });
}
LLWebRTCImpl * gWebRTCImpl = nullptr;