summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/webrtc/libwebrtcglue/AudioConduit.cpp975
1 files changed, 975 insertions, 0 deletions
diff --git a/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp b/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp
new file mode 100644
index 0000000000..d8492f779a
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp
@@ -0,0 +1,975 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioConduit.h"
+
+#include "common/browser_logging/CSFLog.h"
+#include "MediaConduitControl.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/Telemetry.h"
+#include "transport/runnable_utils.h"
+#include "transport/SrtpFlow.h" // For SRTP_MAX_EXPANSION
+#include "WebrtcCallWrapper.h"
+
+// libwebrtc includes
+#include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "audio/audio_receive_stream.h"
+#include "media/base/media_constants.h"
+
+// for ntohs
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#elif defined XP_WIN
+# include <winsock2.h>
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "AndroidBridge.h"
+#endif
+
+namespace mozilla {
+
+namespace {
+
+static const char* acLogTag = "WebrtcAudioSessionConduit";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG acLogTag
+
+using namespace cricket;
+using LocalDirection = MediaSessionConduitLocalDirection;
+
+const char kCodecParamCbr[] = "cbr";
+
+} // namespace
+
+/**
+ * Factory Method for AudioConduit
+ */
+RefPtr<AudioSessionConduit> AudioSessionConduit::Create(
+ RefPtr<WebrtcCallWrapper> aCall,
+ nsCOMPtr<nsISerialEventTarget> aStsThread) {
+ CSFLogDebug(LOGTAG, "%s ", __FUNCTION__);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return MakeRefPtr<WebrtcAudioConduit>(std::move(aCall),
+ std::move(aStsThread));
+}
+
+#define INIT_MIRROR(name, val) \
+ name(aCallThread, val, "WebrtcAudioConduit::Control::" #name " (Mirror)")
+WebrtcAudioConduit::Control::Control(const RefPtr<AbstractThread>& aCallThread)
+ : INIT_MIRROR(mReceiving, false),
+ INIT_MIRROR(mTransmitting, false),
+ INIT_MIRROR(mLocalSsrcs, Ssrcs()),
+ INIT_MIRROR(mLocalCname, std::string()),
+ INIT_MIRROR(mMid, std::string()),
+ INIT_MIRROR(mRemoteSsrc, 0),
+ INIT_MIRROR(mSyncGroup, std::string()),
+ INIT_MIRROR(mLocalRecvRtpExtensions, RtpExtList()),
+ INIT_MIRROR(mLocalSendRtpExtensions, RtpExtList()),
+ INIT_MIRROR(mSendCodec, Nothing()),
+ INIT_MIRROR(mRecvCodecs, std::vector<AudioCodecConfig>()) {}
+#undef INIT_MIRROR
+
+RefPtr<GenericPromise> WebrtcAudioConduit::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return InvokeAsync(mCallThread, "WebrtcAudioConduit::Shutdown (main thread)",
+ [this, self = RefPtr<WebrtcAudioConduit>(this)] {
+ mControl.mReceiving.DisconnectIfConnected();
+ mControl.mTransmitting.DisconnectIfConnected();
+ mControl.mLocalSsrcs.DisconnectIfConnected();
+ mControl.mLocalCname.DisconnectIfConnected();
+ mControl.mMid.DisconnectIfConnected();
+ mControl.mRemoteSsrc.DisconnectIfConnected();
+ mControl.mSyncGroup.DisconnectIfConnected();
+ mControl.mLocalRecvRtpExtensions.DisconnectIfConnected();
+ mControl.mLocalSendRtpExtensions.DisconnectIfConnected();
+ mControl.mSendCodec.DisconnectIfConnected();
+ mControl.mRecvCodecs.DisconnectIfConnected();
+ mControl.mOnDtmfEventListener.DisconnectIfExists();
+ mWatchManager.Shutdown();
+
+ {
+ AutoWriteLock lock(mLock);
+ DeleteSendStream();
+ DeleteRecvStream();
+ }
+
+ return GenericPromise::CreateAndResolve(
+ true, "WebrtcAudioConduit::Shutdown (call thread)");
+ });
+}
+
+WebrtcAudioConduit::WebrtcAudioConduit(
+ RefPtr<WebrtcCallWrapper> aCall, nsCOMPtr<nsISerialEventTarget> aStsThread)
+ : mCall(std::move(aCall)),
+ mSendTransport(this),
+ mRecvTransport(this),
+ mRecvStreamConfig(),
+ mRecvStream(nullptr),
+ mSendStreamConfig(&mSendTransport),
+ mSendStream(nullptr),
+ mSendStreamRunning(false),
+ mRecvStreamRunning(false),
+ mDtmfEnabled(false),
+ mLock("WebrtcAudioConduit::mLock"),
+ mCallThread(std::move(mCall->mCallThread)),
+ mStsThread(std::move(aStsThread)),
+ mControl(mCall->mCallThread),
+ mWatchManager(this, mCall->mCallThread) {
+ mRecvStreamConfig.rtcp_send_transport = &mRecvTransport;
+ mRecvStreamConfig.rtp.rtcp_event_observer = this;
+}
+
+/**
+ * Destruction defines for our super-classes
+ */
+WebrtcAudioConduit::~WebrtcAudioConduit() {
+ CSFLogDebug(LOGTAG, "%s ", __FUNCTION__);
+ MOZ_ASSERT(!mSendStream && !mRecvStream,
+ "Call DeleteStreams prior to ~WebrtcAudioConduit.");
+}
+
+#define CONNECT(aCanonical, aMirror) \
+ do { \
+ (aMirror).Connect(aCanonical); \
+ mWatchManager.Watch(aMirror, &WebrtcAudioConduit::OnControlConfigChange); \
+ } while (0)
+
+void WebrtcAudioConduit::InitControl(AudioConduitControlInterface* aControl) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ CONNECT(aControl->CanonicalReceiving(), mControl.mReceiving);
+ CONNECT(aControl->CanonicalTransmitting(), mControl.mTransmitting);
+ CONNECT(aControl->CanonicalLocalSsrcs(), mControl.mLocalSsrcs);
+ CONNECT(aControl->CanonicalLocalCname(), mControl.mLocalCname);
+ CONNECT(aControl->CanonicalMid(), mControl.mMid);
+ CONNECT(aControl->CanonicalRemoteSsrc(), mControl.mRemoteSsrc);
+ CONNECT(aControl->CanonicalSyncGroup(), mControl.mSyncGroup);
+ CONNECT(aControl->CanonicalLocalRecvRtpExtensions(),
+ mControl.mLocalRecvRtpExtensions);
+ CONNECT(aControl->CanonicalLocalSendRtpExtensions(),
+ mControl.mLocalSendRtpExtensions);
+ CONNECT(aControl->CanonicalAudioSendCodec(), mControl.mSendCodec);
+ CONNECT(aControl->CanonicalAudioRecvCodecs(), mControl.mRecvCodecs);
+ mControl.mOnDtmfEventListener = aControl->OnDtmfEvent().Connect(
+ mCall->mCallThread, this, &WebrtcAudioConduit::OnDtmfEvent);
+}
+
+#undef CONNECT
+
+void WebrtcAudioConduit::OnDtmfEvent(const DtmfEvent& aEvent) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mSendStream);
+ MOZ_ASSERT(mDtmfEnabled);
+ mSendStream->SendTelephoneEvent(aEvent.mPayloadType, aEvent.mPayloadFrequency,
+ aEvent.mEventCode, aEvent.mLengthMs);
+}
+
+void WebrtcAudioConduit::OnControlConfigChange() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ bool recvStreamReconfigureNeeded = false;
+ bool sendStreamReconfigureNeeded = false;
+ bool recvStreamRecreationNeeded = false;
+ bool sendStreamRecreationNeeded = false;
+
+ if (!mControl.mLocalSsrcs.Ref().empty()) {
+ if (mControl.mLocalSsrcs.Ref()[0] != mSendStreamConfig.rtp.ssrc) {
+ sendStreamRecreationNeeded = true;
+
+ // For now...
+ recvStreamRecreationNeeded = true;
+ }
+ mRecvStreamConfig.rtp.local_ssrc = mControl.mLocalSsrcs.Ref()[0];
+ mSendStreamConfig.rtp.ssrc = mControl.mLocalSsrcs.Ref()[0];
+
+ // In the future we can do this instead of recreating the recv stream:
+ // if (mRecvStream) {
+ // mCall->Call()->OnLocalSsrcUpdated(mRecvStream,
+ // mControl.mLocalSsrcs.Ref()[0]);
+ // }
+ }
+
+ if (mControl.mLocalCname.Ref() != mSendStreamConfig.rtp.c_name) {
+ mSendStreamConfig.rtp.c_name = mControl.mLocalCname.Ref();
+ sendStreamReconfigureNeeded = true;
+ }
+
+ if (mControl.mMid.Ref() != mSendStreamConfig.rtp.mid) {
+ mSendStreamConfig.rtp.mid = mControl.mMid.Ref();
+ sendStreamReconfigureNeeded = true;
+ }
+
+ if (mControl.mRemoteSsrc.Ref() != mControl.mConfiguredRemoteSsrc) {
+ mRecvStreamConfig.rtp.remote_ssrc = mControl.mConfiguredRemoteSsrc =
+ mControl.mRemoteSsrc.Ref();
+ recvStreamRecreationNeeded = true;
+ }
+
+ if (mControl.mSyncGroup.Ref() != mRecvStreamConfig.sync_group) {
+ mRecvStreamConfig.sync_group = mControl.mSyncGroup.Ref();
+ // For now...
+ recvStreamRecreationNeeded = true;
+ // In the future we can do this instead of recreating the recv stream:
+ // if (mRecvStream) {
+ // mCall->Call()->OnUpdateSyncGroup(mRecvStream,
+ // mRecvStreamConfig.sync_group);
+ // }
+ }
+
+ if (auto filteredExtensions = FilterExtensions(
+ LocalDirection::kRecv, mControl.mLocalRecvRtpExtensions);
+ filteredExtensions != mRecvStreamConfig.rtp.extensions) {
+ mRecvStreamConfig.rtp.extensions = std::move(filteredExtensions);
+ // For now...
+ recvStreamRecreationNeeded = true;
+ // In the future we can do this instead of recreating the recv stream:
+ // if (mRecvStream) {
+ // mRecvStream->SetRtpExtensions(mRecvStreamConfig.rtp.extensions);
+ //}
+ }
+
+ if (auto filteredExtensions = FilterExtensions(
+ LocalDirection::kSend, mControl.mLocalSendRtpExtensions);
+ filteredExtensions != mSendStreamConfig.rtp.extensions) {
+ // At the very least, we need a reconfigure. Recreation needed if the
+ // extmap for any extension has changed, but not for adding/removing
+ // extensions.
+ sendStreamReconfigureNeeded = true;
+
+ for (const auto& newExt : filteredExtensions) {
+ if (sendStreamRecreationNeeded) {
+ break;
+ }
+ for (const auto& oldExt : mSendStreamConfig.rtp.extensions) {
+ if (newExt.uri == oldExt.uri) {
+ if (newExt.id != oldExt.id) {
+ sendStreamRecreationNeeded = true;
+ }
+ // We're done handling newExt, one way or another
+ break;
+ }
+ }
+ }
+
+ mSendStreamConfig.rtp.extensions = std::move(filteredExtensions);
+ }
+
+ mControl.mSendCodec.Ref().apply([&](const auto& aConfig) {
+ if (mControl.mConfiguredSendCodec != mControl.mSendCodec.Ref()) {
+ mControl.mConfiguredSendCodec = mControl.mSendCodec;
+ if (ValidateCodecConfig(aConfig, true) == kMediaConduitNoError) {
+ mSendStreamConfig.encoder_factory =
+ webrtc::CreateBuiltinAudioEncoderFactory();
+
+ webrtc::AudioSendStream::Config::SendCodecSpec spec(
+ aConfig.mType, CodecConfigToLibwebrtcFormat(aConfig));
+ mSendStreamConfig.send_codec_spec = spec;
+
+ mDtmfEnabled = aConfig.mDtmfEnabled;
+ sendStreamReconfigureNeeded = true;
+ }
+ }
+ });
+
+ if (mControl.mConfiguredRecvCodecs != mControl.mRecvCodecs.Ref()) {
+ mControl.mConfiguredRecvCodecs = mControl.mRecvCodecs;
+ mRecvStreamConfig.decoder_factory = mCall->mAudioDecoderFactory;
+ mRecvStreamConfig.decoder_map.clear();
+
+ for (const auto& codec : mControl.mRecvCodecs.Ref()) {
+ if (ValidateCodecConfig(codec, false) != kMediaConduitNoError) {
+ continue;
+ }
+ mRecvStreamConfig.decoder_map.emplace(
+ codec.mType, CodecConfigToLibwebrtcFormat(codec));
+ }
+
+ recvStreamReconfigureNeeded = true;
+ }
+
+ if (!recvStreamReconfigureNeeded && !sendStreamReconfigureNeeded &&
+ !recvStreamRecreationNeeded && !sendStreamRecreationNeeded &&
+ mControl.mReceiving == mRecvStreamRunning &&
+ mControl.mTransmitting == mSendStreamRunning) {
+ // No changes applied -- no need to lock.
+ return;
+ }
+
+ if (recvStreamRecreationNeeded) {
+ recvStreamReconfigureNeeded = false;
+ }
+ if (sendStreamRecreationNeeded) {
+ sendStreamReconfigureNeeded = false;
+ }
+
+ {
+ AutoWriteLock lock(mLock);
+ // Recreate/Stop/Start streams as needed.
+ if (recvStreamRecreationNeeded) {
+ DeleteRecvStream();
+ }
+ if (mControl.mReceiving) {
+ CreateRecvStream();
+ }
+ if (sendStreamRecreationNeeded) {
+ DeleteSendStream();
+ }
+ if (mControl.mTransmitting) {
+ CreateSendStream();
+ }
+ }
+
+ // We make sure to not hold the lock while stopping/starting/reconfiguring
+ // streams, so as to not cause deadlocks. These methods can cause our platform
+ // codecs to dispatch sync runnables to main, and main may grab the lock.
+
+ if (mRecvStream && recvStreamReconfigureNeeded) {
+ MOZ_ASSERT(!recvStreamRecreationNeeded);
+ mRecvStream->SetDecoderMap(mRecvStreamConfig.decoder_map);
+ }
+
+ if (mSendStream && sendStreamReconfigureNeeded) {
+ MOZ_ASSERT(!sendStreamRecreationNeeded);
+ // TODO: Pass a callback here, so we can react to RTCErrors thrown by
+ // libwebrtc.
+ mSendStream->Reconfigure(mSendStreamConfig, nullptr);
+ }
+
+ if (!mControl.mReceiving) {
+ StopReceiving();
+ }
+ if (!mControl.mTransmitting) {
+ StopTransmitting();
+ }
+
+ if (mControl.mReceiving) {
+ StartReceiving();
+ }
+ if (mControl.mTransmitting) {
+ StartTransmitting();
+ }
+}
+
+std::vector<uint32_t> WebrtcAudioConduit::GetLocalSSRCs() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ return std::vector<uint32_t>(1, mRecvStreamConfig.rtp.local_ssrc);
+}
+
+bool WebrtcAudioConduit::OverrideRemoteSSRC(uint32_t aSsrc) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ if (mRecvStreamConfig.rtp.remote_ssrc == aSsrc) {
+ return true;
+ }
+ mRecvStreamConfig.rtp.remote_ssrc = aSsrc;
+
+ const bool wasReceiving = mRecvStreamRunning;
+ const bool hadRecvStream = mRecvStream;
+
+ StopReceiving();
+
+ if (hadRecvStream) {
+ AutoWriteLock lock(mLock);
+ DeleteRecvStream();
+ CreateRecvStream();
+ }
+
+ if (wasReceiving) {
+ StartReceiving();
+ }
+ return true;
+}
+
+Maybe<Ssrc> WebrtcAudioConduit::GetRemoteSSRC() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ // libwebrtc uses 0 to mean a lack of SSRC. That is not to spec.
+ return mRecvStreamConfig.rtp.remote_ssrc == 0
+ ? Nothing()
+ : Some(mRecvStreamConfig.rtp.remote_ssrc);
+}
+
+Maybe<webrtc::AudioReceiveStreamInterface::Stats>
+WebrtcAudioConduit::GetReceiverStats() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mRecvStream) {
+ return Nothing();
+ }
+ return Some(mRecvStream->GetStats());
+}
+
+Maybe<webrtc::AudioSendStream::Stats> WebrtcAudioConduit::GetSenderStats()
+ const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mSendStream) {
+ return Nothing();
+ }
+ return Some(mSendStream->GetStats());
+}
+
+Maybe<webrtc::CallBasicStats> WebrtcAudioConduit::GetCallStats() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mCall->Call()) {
+ return Nothing();
+ }
+ return Some(mCall->Call()->GetStats());
+}
+
+void WebrtcAudioConduit::OnRtcpBye() { mRtcpByeEvent.Notify(); }
+
+void WebrtcAudioConduit::OnRtcpTimeout() { mRtcpTimeoutEvent.Notify(); }
+
+void WebrtcAudioConduit::SetTransportActive(bool aActive) {
+ MOZ_ASSERT(mStsThread->IsOnCurrentThread());
+ if (mTransportActive == aActive) {
+ return;
+ }
+
+ // If false, This stops us from sending
+ mTransportActive = aActive;
+
+ // We queue this because there might be notifications to these listeners
+ // pending, and we don't want to drop them by letting this jump ahead of
+ // those notifications. We move the listeners into the lambda in case the
+ // transport comes back up before we disconnect them. (The Connect calls
+ // happen in MediaPipeline)
+ // We retain a strong reference to ourself, because the listeners are holding
+ // a non-refcounted reference to us, and moving them into the lambda could
+ // conceivably allow them to outlive us.
+ if (!aActive) {
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [self = RefPtr<WebrtcAudioConduit>(this),
+ recvRtpListener = std::move(mReceiverRtpEventListener),
+ recvRtcpListener = std::move(mReceiverRtcpEventListener),
+ sendRtcpListener = std::move(mSenderRtcpEventListener)]() mutable {
+ recvRtpListener.DisconnectIfExists();
+ recvRtcpListener.DisconnectIfExists();
+ sendRtcpListener.DisconnectIfExists();
+ })));
+ }
+}
+
+// AudioSessionConduit Implementation
+MediaConduitErrorCode WebrtcAudioConduit::SendAudioFrame(
+ std::unique_ptr<webrtc::AudioFrame> frame) {
+ CSFLogDebug(LOGTAG, "%s ", __FUNCTION__);
+ // Following checks need to be performed
+ // 1. Non null audio buffer pointer, and
+ // 2. Valid sample rate, and
+ // 3. Appropriate Sample Length for 10 ms audio-frame. This represents the
+ // block size used upstream for processing.
+ // Ex: for 16000 sample rate , valid block-length is 160.
+ // Similarly for 32000 sample rate, valid block length is 320.
+
+ if (!frame->data() ||
+ (IsSamplingFreqSupported(frame->sample_rate_hz()) == false) ||
+ ((frame->samples_per_channel() % (frame->sample_rate_hz() / 100) != 0))) {
+ CSFLogError(LOGTAG, "%s Invalid Parameters ", __FUNCTION__);
+ MOZ_ASSERT(PR_FALSE);
+ return kMediaConduitMalformedArgument;
+ }
+
+ // This is the AudioProxyThread, blocking it for a bit is fine.
+ AutoReadLock lock(mLock);
+ if (!mSendStreamRunning) {
+ CSFLogError(LOGTAG, "%s Engine not transmitting ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ mSendStream->SendAudioData(std::move(frame));
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode WebrtcAudioConduit::GetAudioFrame(
+ int32_t samplingFreqHz, webrtc::AudioFrame* frame) {
+ CSFLogDebug(LOGTAG, "%s ", __FUNCTION__);
+
+ // validate params
+ if (!frame) {
+ CSFLogError(LOGTAG, "%s Null Audio Buffer Pointer", __FUNCTION__);
+ MOZ_ASSERT(PR_FALSE);
+ return kMediaConduitMalformedArgument;
+ }
+
+ // Validate sample length
+ if (GetNum10msSamplesForFrequency(samplingFreqHz) == 0) {
+ CSFLogError(LOGTAG, "%s Invalid Sampling Frequency ", __FUNCTION__);
+ MOZ_ASSERT(PR_FALSE);
+ return kMediaConduitMalformedArgument;
+ }
+
+ // If the lock is taken, skip this chunk to avoid blocking the audio thread.
+ AutoTryReadLock tryLock(mLock);
+ if (!tryLock) {
+ CSFLogError(LOGTAG, "%s Conduit going through negotiation ", __FUNCTION__);
+ return kMediaConduitPlayoutError;
+ }
+
+ // Conduit should have reception enabled before we ask for decoded
+ // samples
+ if (!mRecvStreamRunning) {
+ CSFLogError(LOGTAG, "%s Engine not Receiving ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ // Unfortunate to have to cast to an internal class, but that looks like the
+ // only way short of interfacing with a layer above (which mixes all streams,
+ // which we don't want) or a layer below (which we try to avoid because it is
+ // less stable).
+ auto info = static_cast<webrtc::AudioReceiveStreamImpl*>(mRecvStream)
+ ->GetAudioFrameWithInfo(samplingFreqHz, frame);
+
+ if (info == webrtc::AudioMixer::Source::AudioFrameInfo::kError) {
+ CSFLogError(LOGTAG, "%s Getting audio frame failed", __FUNCTION__);
+ return kMediaConduitPlayoutError;
+ }
+
+ CSFLogDebug(LOGTAG, "%s Got %zu channels of %zu samples", __FUNCTION__,
+ frame->num_channels(), frame->samples_per_channel());
+ return kMediaConduitNoError;
+}
+
+// Transport Layer Callbacks
+void WebrtcAudioConduit::OnRtpReceived(webrtc::RtpPacketReceived&& aPacket,
+ webrtc::RTPHeader&& aHeader) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ if (mAllowSsrcChange && mRecvStreamConfig.rtp.remote_ssrc != aHeader.ssrc) {
+ CSFLogDebug(LOGTAG, "%s: switching from SSRC %u to %u", __FUNCTION__,
+ mRecvStreamConfig.rtp.remote_ssrc, aHeader.ssrc);
+ OverrideRemoteSSRC(aHeader.ssrc);
+ }
+
+ CSFLogVerbose(LOGTAG, "%s: seq# %u, Len %zu, SSRC %u (0x%x) ", __FUNCTION__,
+ aPacket.SequenceNumber(), aPacket.size(), aPacket.Ssrc(),
+ aPacket.Ssrc());
+
+ mRtpPacketEvent.Notify();
+ if (mCall->Call()) {
+ mCall->Call()->Receiver()->DeliverRtpPacket(
+ webrtc::MediaType::AUDIO, std::move(aPacket),
+ [self = RefPtr<WebrtcAudioConduit>(this)](
+ const webrtc::RtpPacketReceived& packet) {
+ CSFLogVerbose(
+ LOGTAG,
+ "AudioConduit %p: failed demuxing packet, ssrc: %u seq: %u",
+ self.get(), packet.Ssrc(), packet.SequenceNumber());
+ return false;
+ });
+ }
+}
+
+void WebrtcAudioConduit::OnRtcpReceived(MediaPacket&& aPacket) {
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ if (mCall->Call()) {
+ mCall->Call()->Receiver()->DeliverRtcpPacket(
+ rtc::CopyOnWriteBuffer(aPacket.data(), aPacket.len()));
+ }
+}
+
+Maybe<uint16_t> WebrtcAudioConduit::RtpSendBaseSeqFor(uint32_t aSsrc) const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ auto it = mRtpSendBaseSeqs.find(aSsrc);
+ if (it == mRtpSendBaseSeqs.end()) {
+ return Nothing();
+ }
+ return Some(it->second);
+}
+
+const dom::RTCStatsTimestampMaker& WebrtcAudioConduit::GetTimestampMaker()
+ const {
+ return mCall->GetTimestampMaker();
+}
+
+void WebrtcAudioConduit::StopTransmitting() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread());
+
+ if (!mSendStreamRunning) {
+ return;
+ }
+
+ if (mSendStream) {
+ CSFLogDebug(LOGTAG, "%s Stopping send stream", __FUNCTION__);
+ mSendStream->Stop();
+ }
+
+ mSendStreamRunning = false;
+}
+
+void WebrtcAudioConduit::StartTransmitting() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mSendStream);
+ MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread());
+
+ if (mSendStreamRunning) {
+ return;
+ }
+
+ CSFLogDebug(LOGTAG, "%s Starting send stream", __FUNCTION__);
+
+ mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::AUDIO,
+ webrtc::kNetworkUp);
+ mSendStream->Start();
+ mSendStreamRunning = true;
+}
+
+void WebrtcAudioConduit::StopReceiving() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread());
+
+ if (!mRecvStreamRunning) {
+ return;
+ }
+
+ if (mRecvStream) {
+ CSFLogDebug(LOGTAG, "%s Stopping recv stream", __FUNCTION__);
+ mRecvStream->Stop();
+ }
+
+ mRecvStreamRunning = false;
+}
+
+void WebrtcAudioConduit::StartReceiving() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mRecvStream);
+ MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread());
+
+ if (mRecvStreamRunning) {
+ return;
+ }
+
+ CSFLogDebug(LOGTAG, "%s Starting receive stream (SSRC %u (0x%x))",
+ __FUNCTION__, mRecvStreamConfig.rtp.remote_ssrc,
+ mRecvStreamConfig.rtp.remote_ssrc);
+
+ mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::AUDIO,
+ webrtc::kNetworkUp);
+ mRecvStream->Start();
+ mRecvStreamRunning = true;
+}
+
+bool WebrtcAudioConduit::SendRtp(const uint8_t* aData, size_t aLength,
+ const webrtc::PacketOptions& aOptions) {
+ MOZ_ASSERT(aLength >= 12);
+ const uint16_t seqno = ntohs(*((uint16_t*)&aData[2]));
+ const uint32_t ssrc = ntohl(*((uint32_t*)&aData[8]));
+
+ CSFLogVerbose(
+ LOGTAG,
+ "AudioConduit %p: Sending RTP Packet seq# %u, len %zu, SSRC %u (0x%x)",
+ this, seqno, aLength, ssrc, ssrc);
+
+ if (!mTransportActive) {
+ CSFLogError(LOGTAG, "AudioConduit %p: RTP Packet Send Failed ", this);
+ return false;
+ }
+
+ MediaPacket packet;
+ packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION);
+ packet.SetType(MediaPacket::RTP);
+ mSenderRtpSendEvent.Notify(std::move(packet));
+
+ // Parse the sequence number of the first rtp packet as base_seq.
+ const auto inserted = mRtpSendBaseSeqs_n.insert({ssrc, seqno}).second;
+
+ if (inserted || aOptions.packet_id >= 0) {
+ int64_t now_ms = PR_Now() / 1000;
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<WebrtcAudioConduit>(this),
+ packet_id = aOptions.packet_id, now_ms, ssrc, seqno] {
+ mRtpSendBaseSeqs.insert({ssrc, seqno});
+ if (packet_id >= 0) {
+ if (mCall->Call()) {
+ // TODO: This notification should ideally happen after the
+ // transport layer has sent the packet on the wire.
+ mCall->Call()->OnSentPacket({packet_id, now_ms});
+ }
+ }
+ })));
+ }
+ return true;
+}
+
+bool WebrtcAudioConduit::SendSenderRtcp(const uint8_t* aData, size_t aLength) {
+ CSFLogVerbose(
+ LOGTAG,
+ "AudioConduit %p: Sending RTCP SR Packet, len %zu, SSRC %u (0x%x)", this,
+ aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])),
+ (uint32_t)ntohl(*((uint32_t*)&aData[4])));
+
+ if (!mTransportActive) {
+ CSFLogError(LOGTAG, "%s RTCP SR Packet Send Failed ", __FUNCTION__);
+ return false;
+ }
+
+ MediaPacket packet;
+ packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION);
+ packet.SetType(MediaPacket::RTCP);
+ mSenderRtcpSendEvent.Notify(std::move(packet));
+ return true;
+}
+
+bool WebrtcAudioConduit::SendReceiverRtcp(const uint8_t* aData,
+ size_t aLength) {
+ CSFLogVerbose(
+ LOGTAG,
+ "AudioConduit %p: Sending RTCP RR Packet, len %zu, SSRC %u (0x%x)", this,
+ aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])),
+ (uint32_t)ntohl(*((uint32_t*)&aData[4])));
+
+ if (!mTransportActive) {
+ CSFLogError(LOGTAG, "AudioConduit %p: RTCP RR Packet Send Failed", this);
+ return false;
+ }
+
+ MediaPacket packet;
+ packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION);
+ packet.SetType(MediaPacket::RTCP);
+ mReceiverRtcpSendEvent.Notify(std::move(packet));
+ return true;
+}
+
+/**
+ * Supported Sampling Frequencies.
+ */
+bool WebrtcAudioConduit::IsSamplingFreqSupported(int freq) const {
+ return GetNum10msSamplesForFrequency(freq) != 0;
+}
+
+std::vector<webrtc::RtpSource> WebrtcAudioConduit::GetUpstreamRtpSources()
+ const {
+ MOZ_ASSERT(NS_IsMainThread());
+ std::vector<webrtc::RtpSource> sources;
+ {
+ AutoReadLock lock(mLock);
+ if (mRecvStream) {
+ sources = mRecvStream->GetSources();
+ }
+ }
+ return sources;
+}
+
+/* Return block-length of 10 ms audio frame in number of samples */
+unsigned int WebrtcAudioConduit::GetNum10msSamplesForFrequency(
+ int samplingFreqHz) const {
+ switch (samplingFreqHz) {
+ case 16000:
+ return 160; // 160 samples
+ case 32000:
+ return 320; // 320 samples
+ case 44100:
+ return 441; // 441 samples
+ case 48000:
+ return 480; // 480 samples
+ default:
+ return 0; // invalid or unsupported
+ }
+}
+
+/**
+ * Perform validation on the codecConfig to be applied.
+ * Verifies if the codec is already applied.
+ */
+MediaConduitErrorCode WebrtcAudioConduit::ValidateCodecConfig(
+ const AudioCodecConfig& codecInfo, bool send) {
+ if (codecInfo.mName.empty()) {
+ CSFLogError(LOGTAG, "%s Empty Payload Name ", __FUNCTION__);
+ return kMediaConduitMalformedArgument;
+ }
+
+ // Only mono or stereo channels supported
+ if ((codecInfo.mChannels != 1) && (codecInfo.mChannels != 2)) {
+ CSFLogError(LOGTAG, "%s Channel Unsupported ", __FUNCTION__);
+ return kMediaConduitMalformedArgument;
+ }
+
+ return kMediaConduitNoError;
+}
+
+RtpExtList WebrtcAudioConduit::FilterExtensions(LocalDirection aDirection,
+ const RtpExtList& aExtensions) {
+ const bool isSend = aDirection == LocalDirection::kSend;
+ RtpExtList filteredExtensions;
+
+ for (const auto& extension : aExtensions) {
+ // ssrc-audio-level RTP header extension
+ if (extension.uri == webrtc::RtpExtension::kAudioLevelUri) {
+ filteredExtensions.push_back(
+ webrtc::RtpExtension(extension.uri, extension.id));
+ }
+
+ // csrc-audio-level RTP header extension
+ if (extension.uri == webrtc::RtpExtension::kCsrcAudioLevelsUri) {
+ if (isSend) {
+ continue;
+ }
+ filteredExtensions.push_back(
+ webrtc::RtpExtension(extension.uri, extension.id));
+ }
+
+ // MID RTP header extension
+ if (extension.uri == webrtc::RtpExtension::kMidUri) {
+ if (!isSend) {
+ // TODO: recv mid support, see also bug 1727211
+ continue;
+ }
+ filteredExtensions.push_back(
+ webrtc::RtpExtension(extension.uri, extension.id));
+ }
+ }
+
+ return filteredExtensions;
+}
+
+webrtc::SdpAudioFormat WebrtcAudioConduit::CodecConfigToLibwebrtcFormat(
+ const AudioCodecConfig& aConfig) {
+ webrtc::SdpAudioFormat::Parameters parameters;
+ if (aConfig.mName == kOpusCodecName) {
+ if (aConfig.mChannels == 2) {
+ parameters[kCodecParamStereo] = kParamValueTrue;
+ }
+ if (aConfig.mFECEnabled) {
+ parameters[kCodecParamUseInbandFec] = kParamValueTrue;
+ }
+ if (aConfig.mDTXEnabled) {
+ parameters[kCodecParamUseDtx] = kParamValueTrue;
+ }
+ if (aConfig.mMaxPlaybackRate) {
+ parameters[kCodecParamMaxPlaybackRate] =
+ std::to_string(aConfig.mMaxPlaybackRate);
+ }
+ if (aConfig.mMaxAverageBitrate) {
+ parameters[kCodecParamMaxAverageBitrate] =
+ std::to_string(aConfig.mMaxAverageBitrate);
+ }
+ if (aConfig.mFrameSizeMs) {
+ parameters[kCodecParamPTime] = std::to_string(aConfig.mFrameSizeMs);
+ }
+ if (aConfig.mMinFrameSizeMs) {
+ parameters[kCodecParamMinPTime] = std::to_string(aConfig.mMinFrameSizeMs);
+ }
+ if (aConfig.mMaxFrameSizeMs) {
+ parameters[kCodecParamMaxPTime] = std::to_string(aConfig.mMaxFrameSizeMs);
+ }
+ if (aConfig.mCbrEnabled) {
+ parameters[kCodecParamCbr] = kParamValueTrue;
+ }
+ }
+
+ return webrtc::SdpAudioFormat(aConfig.mName, aConfig.mFreq, aConfig.mChannels,
+ parameters);
+}
+
+void WebrtcAudioConduit::DeleteSendStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+
+ if (!mSendStream) {
+ return;
+ }
+
+ mCall->Call()->DestroyAudioSendStream(mSendStream);
+ mSendStreamRunning = false;
+ mSendStream = nullptr;
+
+ // Reset base_seqs in case ssrcs get re-used.
+ mRtpSendBaseSeqs.clear();
+}
+
+void WebrtcAudioConduit::CreateSendStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+
+ if (mSendStream) {
+ return;
+ }
+
+ mSendStream = mCall->Call()->CreateAudioSendStream(mSendStreamConfig);
+}
+
+void WebrtcAudioConduit::DeleteRecvStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+
+ if (!mRecvStream) {
+ return;
+ }
+
+ mCall->Call()->DestroyAudioReceiveStream(mRecvStream);
+ mRecvStreamRunning = false;
+ mRecvStream = nullptr;
+}
+
+void WebrtcAudioConduit::CreateRecvStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+
+ if (mRecvStream) {
+ return;
+ }
+
+ mRecvStream = mCall->Call()->CreateAudioReceiveStream(mRecvStreamConfig);
+ // Ensure that we set the jitter buffer target on this stream.
+ mRecvStream->SetBaseMinimumPlayoutDelayMs(mJitterBufferTargetMs);
+}
+
+void WebrtcAudioConduit::SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) {
+ MOZ_RELEASE_ASSERT(aTargetMs <= std::numeric_limits<uint16_t>::max());
+ MOZ_RELEASE_ASSERT(aTargetMs >= 0);
+
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [this, self = RefPtr<WebrtcAudioConduit>(this), targetMs = aTargetMs] {
+ mJitterBufferTargetMs = static_cast<uint16_t>(targetMs);
+ if (mRecvStream) {
+ mRecvStream->SetBaseMinimumPlayoutDelayMs(targetMs);
+ }
+ })));
+}
+
+void WebrtcAudioConduit::DeliverPacket(rtc::CopyOnWriteBuffer packet,
+ PacketType type) {
+ // Currently unused.
+ MOZ_ASSERT(false);
+}
+
+Maybe<int> WebrtcAudioConduit::ActiveSendPayloadType() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ auto stats = GetSenderStats();
+ if (!stats) {
+ return Nothing();
+ }
+
+ if (!stats->codec_payload_type) {
+ return Nothing();
+ }
+
+ return Some(*stats->codec_payload_type);
+}
+
+Maybe<int> WebrtcAudioConduit::ActiveRecvPayloadType() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ auto stats = GetReceiverStats();
+ if (!stats) {
+ return Nothing();
+ }
+
+ if (!stats->codec_payload_type) {
+ return Nothing();
+ }
+
+ return Some(*stats->codec_payload_type);
+}
+
+} // namespace mozilla