Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions autobuild.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2454,11 +2454,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>9622dbeb12d1c9592de3ea3f83c2c1a95b3c0822</string>
<string>516b69b841109d38bd14286d28a8d77ee67e1c52</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.19.hot-mic/webrtc-m144.7559.06.19.hot-mic.29761579711-darwin64-29761579711.tar.zst</string>
<string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.22/webrtc-m144.7559.06.22.38629291819-darwin64-38629291819.tar.zst</string>
</map>
<key>name</key>
<string>darwin64</string>
Expand All @@ -2468,11 +2468,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>9113fdfafca5ca750e415abafaf080540346e068</string>
<string>69429645f9f5eb0080546af0f4e32082f285a852</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.19.hot-mic/webrtc-m144.7559.06.19.hot-mic.29761579711-linux64-29761579711.tar.zst</string>
<string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.22/webrtc-m144.7559.06.22.38629291819-linux64-38629291819.tar.zst</string>
</map>
<key>name</key>
<string>linux64</string>
Expand All @@ -2482,11 +2482,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>1d8a82b72887abfb1c5014fcca4337703dfdcd25</string>
<string>a9ef8bafede7a26d561faca7d770a31aebeda3a5</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.19.hot-mic/webrtc-m144.7559.06.19.hot-mic.29761579711-windows64-29761579711.tar.zst</string>
<string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.22/webrtc-m144.7559.06.22.38629291819-windows64-38629291819.tar.zst</string>
</map>
<key>name</key>
<string>windows64</string>
Expand All @@ -2499,7 +2499,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>m144.7559.06.19.hot-mic.29761579711</string>
<string>m144.7559.06.22.38629291819</string>
<key>name</key>
<string>webrtc</string>
<key>vcs_branch</key>
Expand Down
4 changes: 3 additions & 1 deletion indra/llwebrtc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ if (WINDOWS)
msdmo
strmiids
iphlpapi
libcmt)
libcmt
crypt32)
# as the webrtc libraries are release, build this binary as release as well.
target_compile_options(llwebrtc PRIVATE "/MT" "/Zc:wchar_t")
if (USE_BUGSPLAT)
Expand All @@ -54,6 +55,7 @@ elseif (DARWIN)
"-framework CoreMedia"
"-framework CoreVideo"
"-framework Foundation"
"-framework Security"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixed tab and space indenting making it not line up

)
if (USE_BUGSPLAT)
set_target_properties(llwebrtc PROPERTIES XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym"
Expand Down
176 changes: 135 additions & 41 deletions indra/llwebrtc/llwebrtc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@
namespace llwebrtc
{
#if WEBRTC_WIN
static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice;
static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice;
static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice;
static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice;
#else
static int16_t PLAYOUT_DEVICE_DEFAULT = 0;
static int16_t RECORD_DEVICE_DEFAULT = 0;
#endif

constexpr int32_t MIC_CAPTURE_STOP_DELAY_MS = 40;


//
// LLWebRTCAudioTransport implementation
Expand Down Expand Up @@ -265,19 +267,18 @@ 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.
// Ensure capture is running so the mic-level meter works, and stop rendering
// the call while tuning. The recording calls are no-ops if capture is already
// active (it may not be, with "stop capture while muted" enabled).
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.
// On exit, capture is left running here; with "stop capture while muted" the
// caller (setTuningMode) stops it again if we're muted or have no connection.
// Playout is restored by the caller via workerOpenPlayout(), keeping it gated
// on there being a connection to render.
}

//
Expand Down Expand Up @@ -638,16 +639,21 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer)
}

// 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).
// starts recording. By default capture runs the whole time voice is enabled
// (never stopped for mute or between calls, so the AEC never cold-starts -- no
// hiss on unmute), so this is a no-op when already recording. With "stop capture
// while muted" it's only started while unmuted and once a connection's audio is
// established. Device changes go through workerDeployDevices(), which stops
// recording first to force a clean re-select; voice off goes through setVoiceEnabled(false).
void LLWebRTCImpl::workerStartRecording()
{
// 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())
// it's already running (that would cause the unmute hiss). With "stop
// capture while muted", also hold it off while muted or with no connection's
// audio is established.
if (!mDeviceModule || !mVoiceEnabled || mDeviceModule->Recording() ||
(mStopCaptureOnMute.load() && !mTuningMode &&
(mMute || !mAudioEstablished)))
{
return;
}
Expand Down Expand Up @@ -689,7 +695,9 @@ void LLWebRTCImpl::workerStartRecording()
// disable before InitRecording so the recording stream is configured to
// use only WebRTC's software APM.
workerDisableBuiltInAudioProcessing();
mDeviceModule->InitRecording();
// With "stop capture while muted", capture as media; otherwise as communications.
mDeviceModule->ForceInitRecording(mStopCaptureOnMute.load() ? webrtc::AudioDeviceModule::AudioCategory::kMedia
: webrtc::AudioDeviceModule::AudioCategory::kCommunications);
mDeviceModule->ForceStartRecording();
}

Expand Down Expand Up @@ -833,9 +841,9 @@ void LLWebRTCImpl::setVoiceEnabled(bool enable)
}
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.
// Voice on: start the capture device (by default it then stays
// running across calls and mute/unmute), and start playout if
// there's already a connection to render.
mDeviceModule->Init();
workerDeployDevices();
}
Expand All @@ -850,6 +858,33 @@ void LLWebRTCImpl::setVoiceEnabled(bool enable)
});
}

void LLWebRTCImpl::setStopCaptureOnMute(bool stop)
{
if (mStopCaptureOnMute.exchange(stop) == stop)
{
return;
}

mWorkerThread->PostTask([this, stop]
{
if (!mVoiceEnabled || !mDeviceModule || mTuningMode)
{
return;
}
if (stop)
{
if (mMute || !mAudioEstablished)
{
intSetMute(true, MIC_CAPTURE_STOP_DELAY_MS);
}
}
else
{
intSetMute(false, 0);
}
});
}

// updateDevices needs to happen on the worker thread.
void LLWebRTCImpl::updateDevices()
{
Expand Down Expand Up @@ -930,6 +965,11 @@ void LLWebRTCImpl::setTuningMode(bool enable)
mDeviceModule->SetTuning(mTuningMode, mMute);
if (!mTuningMode)
{
if (mStopCaptureOnMute.load() && (mMute || !mAudioEstablished))
{
intSetMute(true, MIC_CAPTURE_STOP_DELAY_MS);
}

// Restore playout after tuning, gated on there being a
// connection to render (so the output device isn't left
// spinning with no engine data).
Expand Down Expand Up @@ -1007,15 +1047,54 @@ 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
// disabled per connection (see LLWebRTCPeerConnectionImpl::setMute). By
// default the capture device 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.
// cold-start hiss on unmute. With "stop capture while muted", it's stopped
// after the mute delay and restarted on unmute (below).
if (mPeerCustomProcessor)
{
mPeerCustomProcessor->setGain(mMute ? 0.0f : mGain);
}

if (mute && !mStopCaptureOnMute.load())
{
return;
}

// Prevent a delayed stop from an earlier mute from stopping a newly restarted recording after a rapid mute/unmute.
static std::atomic<uint32_t> mute_sequence(0);
uint32_t current_sequence = ++mute_sequence;

auto setRecording = [this, current_sequence](bool start)
{
if (!mDeviceModule || !mVoiceEnabled || mTuningMode ||
current_sequence != mute_sequence.load())
{
return;
}

if (start)
{
workerStartRecording();
}
else if (mStopCaptureOnMute.load())
{
mDeviceModule->ForceStopRecording();
}
};

if (mute)
{
mWorkerThread->PostDelayedTask([setRecording]
{
setRecording(false);
}, webrtc::TimeDelta::Millis(delay_ms));
}
else
{
mWorkerThread->PostTask([setRecording] { setRecording(true); });
}
}

//
Expand All @@ -1026,18 +1105,20 @@ LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection()
{
webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new webrtc::RefCountedObject<LLWebRTCPeerConnectionImpl>(mEnv));
peerConnection->init(this);
if (mPeerConnections.empty())
bool first_connection = mPeerConnections.empty();
mPeerConnections.emplace_back(peerConnection);
if (first_connection)
{
intSetMute(mMute);
}
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.
// is actually established (see startPlayout()). Capture isn't started here
// either: with "stop capture while muted" it waits for audio to be
// established, and the first connection's intSetMute() above may only stop it.

peerConnection->enableSenderTracks(false);
peerConnection->resetMute();
Expand All @@ -1054,10 +1135,12 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn
mPeerConnections.erase(it);
if (mPeerConnections.empty())
{
mAudioEstablished = false;
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
// Capture stays running while voice is enabled so it's ready -- with no
// cold-start hiss -- when the next call comes up, unless "stop capture while
// muted" is on (intSetMute(true) above stops it). 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
Expand All @@ -1083,17 +1166,27 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn

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.
// Called when a connection's audio is established. 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 normally follows voice-enabled state (setVoiceEnabled), so it's
// already running if voice is on. The exception is "stop capture while
// muted", where it's held off until unmuted and audio is established.
// workerStartRecording() is gated on mVoiceEnabled, so this can't re-open
// the mic during voice-disable teardown.
mWorkerThread->PostTask(
[this]()
{
if (mPeerConnections.empty())
{
return;
}
mAudioEstablished = true;
workerStartPlayout();
if (mStopCaptureOnMute.load())
{
workerStartRecording();
}
});
}

Expand Down Expand Up @@ -1583,9 +1676,10 @@ 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
// (Capture normally follows voice-enabled state, so it's already running;
// startPlayout() starts it only for "stop capture while muted".)
// 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++;
Expand Down
10 changes: 7 additions & 3 deletions indra/llwebrtc/llwebrtc.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,14 @@ class LLWebRTCDeviceInterface
// Enable/disable the audio devices, set when voice is enabled/disabled.
// The capture (microphone) and playout (speaker) devices only run while this
// is enabled, so neither is held open when the user has voice off. While
// enabled, capture stays running across calls and mute/unmute so the AEC
// never cold-starts (no unmute hiss); playout still only runs when there's a
// connection to render.
// enabled, capture normally stays running across calls and mute/unmute so the
// AEC never cold-starts (no unmute hiss), unless setStopCaptureOnMute() is on;
// playout still only runs when there's a connection to render.
virtual void setVoiceEnabled(bool enable) = 0;
// When true, capture is stopped while muted (or with no connection) and
// restarted on unmute, so Bluetooth headsets aren't held in call mode.
// May cause a brief hiss on unmute.
virtual void setStopCaptureOnMute(bool stop) = 0;

// Device observers for device change callbacks.
virtual void setDevicesObserver(LLWebRTCDevicesObserver *observer) = 0;
Expand Down
Loading
Loading