diff options
Diffstat (limited to 'dom/media/webrtc/jsapi/TransceiverImpl.cpp')
-rw-r--r-- | dom/media/webrtc/jsapi/TransceiverImpl.cpp | 900 |
1 files changed, 900 insertions, 0 deletions
diff --git a/dom/media/webrtc/jsapi/TransceiverImpl.cpp b/dom/media/webrtc/jsapi/TransceiverImpl.cpp new file mode 100644 index 0000000000..6db46af15e --- /dev/null +++ b/dom/media/webrtc/jsapi/TransceiverImpl.cpp @@ -0,0 +1,900 @@ +/* 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 "jsapi/TransceiverImpl.h" +#include "mozilla/UniquePtr.h" +#include <string> +#include <vector> +#include "libwebrtcglue/AudioConduit.h" +#include "libwebrtcglue/VideoConduit.h" +#include "MediaTrackGraph.h" +#include "transportbridge/MediaPipeline.h" +#include "transportbridge/MediaPipelineFilter.h" +#include "jsep/JsepTrack.h" +#include "sdp/SdpHelper.h" +#include "MediaTrackGraphImpl.h" +#include "transport/logging.h" +#include "MediaEngine.h" +#include "nsIPrincipal.h" +#include "MediaSegment.h" +#include "RemoteTrackSource.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +#include "MediaTransportHandler.h" +#include "mozilla/dom/RTCRtpReceiverBinding.h" +#include "mozilla/dom/RTCRtpSenderBinding.h" +#include "mozilla/dom/RTCRtpTransceiverBinding.h" +#include "mozilla/dom/TransceiverImplBinding.h" +#include "RTCDtlsTransport.h" +#include "RTCRtpReceiver.h" +#include "RTCDTMFSender.h" +#include "libwebrtcglue/WebrtcGmpVideoCodec.h" + +namespace mozilla { + +using namespace dom; + +MOZ_MTLOG_MODULE("transceiverimpl") + +using LocalDirection = MediaSessionConduitLocalDirection; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransceiverImpl, mWindow, mSendTrack, + mReceiver, mDtmf, mDtlsTransport, + mLastStableDtlsTransport) +NS_IMPL_CYCLE_COLLECTING_ADDREF(TransceiverImpl) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TransceiverImpl) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransceiverImpl) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TransceiverImpl::TransceiverImpl( + nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, + const std::string& aPCHandle, MediaTransportHandler* aTransportHandler, + JsepTransceiver* aJsepTransceiver, nsISerialEventTarget* aMainThread, + nsISerialEventTarget* aStsThread, dom::MediaStreamTrack* aSendTrack, + WebRtcCallWrapper* aCallWrapper) + : mWindow(aWindow), + mPCHandle(aPCHandle), + mTransportHandler(aTransportHandler), + mJsepTransceiver(aJsepTransceiver), + mHaveSetupTransport(false), + mMainThread(aMainThread), + mStsThread(aStsThread), + mSendTrack(aSendTrack), + mCallWrapper(aCallWrapper) { + if (IsVideo()) { + InitVideo(); + } else { + InitAudio(); + } + + if (!IsValid()) { + return; + } + + mConduit->SetPCHandle(mPCHandle); + + mReceiver = new RTCRtpReceiver(aWindow, aPrivacyNeeded, aPCHandle, + aTransportHandler, aJsepTransceiver, + aMainThread, aStsThread, mConduit, this); + + if (!IsVideo()) { + mDtmf = new RTCDTMFSender( + aWindow, this, static_cast<AudioSessionConduit*>(mConduit.get())); + } + + mTransmitPipeline = + new MediaPipelineTransmit(mPCHandle, mTransportHandler, mMainThread.get(), + mStsThread.get(), IsVideo(), mConduit); + + mTransmitPipeline->SetTrack(mSendTrack); + + auto self = nsMainThreadPtrHandle<TransceiverImpl>( + new nsMainThreadPtrHolder<TransceiverImpl>( + "TransceiverImpl::TransceiverImpl::self", this, false)); + mStsThread->Dispatch( + NS_NewRunnableFunction("TransceiverImpl::TransceiverImpl", [self] { + self->mTransportHandler->SignalStateChange.connect( + self.get(), &TransceiverImpl::UpdateDtlsTransportState); + self->mTransportHandler->SignalRtcpStateChange.connect( + self.get(), &TransceiverImpl::UpdateDtlsTransportState); + })); +} + +TransceiverImpl::~TransceiverImpl() = default; + +void TransceiverImpl::SetDtlsTransport(dom::RTCDtlsTransport* aDtlsTransport, + bool aStable) { + mDtlsTransport = aDtlsTransport; + if (aStable) { + mLastStableDtlsTransport = mDtlsTransport; + } +} + +void TransceiverImpl::RollbackToStableDtlsTransport() { + mDtlsTransport = mLastStableDtlsTransport; +} + +void TransceiverImpl::UpdateDtlsTransportState(const std::string& aTransportId, + TransportLayer::State aState) { + if (!mMainThread->IsOnCurrentThread()) { + mMainThread->Dispatch( + WrapRunnable(this, &TransceiverImpl::UpdateDtlsTransportState, + aTransportId, aState), + NS_DISPATCH_NORMAL); + return; + } + + if (!mDtlsTransport) { + return; + } + + mDtlsTransport->UpdateState(aState); +} + +void TransceiverImpl::InitAudio() { + mConduit = AudioSessionConduit::Create(mCallWrapper, mStsThread); + + if (!mConduit) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << ": Failed to create AudioSessionConduit"); + // TODO(bug 1422897): We need a way to record this when it happens in the + // wild. + } +} + +void TransceiverImpl::InitVideo() { + mConduit = VideoSessionConduit::Create(mCallWrapper, mStsThread); + + if (!mConduit) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << ": Failed to create VideoSessionConduit"); + // TODO(bug 1422897): We need a way to record this when it happens in the + // wild. + } +} + +nsresult TransceiverImpl::UpdateSinkIdentity( + const dom::MediaStreamTrack* aTrack, nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity) { + if (mJsepTransceiver->IsStopped()) { + return NS_OK; + } + + mTransmitPipeline->UpdateSinkIdentity_m(aTrack, aPrincipal, aSinkIdentity); + return NS_OK; +} + +void TransceiverImpl::Shutdown_m() { + // Called via PCImpl::Close -> PCImpl::CloseInt -> PCImpl::ShutdownMedia -> + // PCMedia::SelfDestruct. Satisfies step 7 of + // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-close + if (mDtlsTransport) { + mDtlsTransport->UpdateState(TransportLayer::TS_CLOSED); + } + Stop(); + mTransmitPipeline = nullptr; + auto self = nsMainThreadPtrHandle<TransceiverImpl>( + new nsMainThreadPtrHolder<TransceiverImpl>( + "TransceiverImpl::Shutdown_m::self", this, false)); + mStsThread->Dispatch(NS_NewRunnableFunction(__func__, [self] { + self->disconnect_all(); + self->mTransportHandler = nullptr; + })); +} + +nsresult TransceiverImpl::UpdateSendTrack(dom::MediaStreamTrack* aSendTrack) { + if (mJsepTransceiver->IsStopped()) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ << "(" + << aSendTrack << ")"); + mSendTrack = aSendTrack; + return mTransmitPipeline->SetTrack(mSendTrack); +} + +nsresult TransceiverImpl::UpdateTransport() { + if (!mJsepTransceiver->HasLevel() || mJsepTransceiver->IsStopped()) { + return NS_OK; + } + + mReceiver->UpdateTransport(); + + if (!mHaveSetupTransport) { + mTransmitPipeline->SetLevel(mJsepTransceiver->GetLevel()); + mHaveSetupTransport = true; + } + + mTransmitPipeline->UpdateTransport_m( + mJsepTransceiver->mTransport.mTransportId, nullptr); + return NS_OK; +} + +nsresult TransceiverImpl::UpdateConduit() { + if (mJsepTransceiver->IsStopped()) { + return NS_OK; + } + + if (mJsepTransceiver->IsAssociated()) { + mMid = mJsepTransceiver->GetMid(); + } else { + mMid.clear(); + } + + mReceiver->Stop(); + + mTransmitPipeline->Stop(); + + // NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be + // set to a non-zero value or the CreateVideo...Stream call will fail. + if (mJsepTransceiver->mSendTrack.GetSsrcs().empty()) { + MOZ_MTLOG(ML_ERROR, + mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " No local SSRC set! (Should be set regardless of " + "whether we're sending RTP; we need a local SSRC in " + "all cases)"); + return NS_ERROR_FAILURE; + } + + if (!mConduit->SetLocalSSRCs(mJsepTransceiver->mSendTrack.GetSsrcs(), + mJsepTransceiver->mSendTrack.GetRtxSsrcs())) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " SetLocalSSRCs failed"); + return NS_ERROR_FAILURE; + } + + mConduit->SetLocalCNAME(mJsepTransceiver->mSendTrack.GetCNAME().c_str()); + mConduit->SetLocalMID(mMid); + + nsresult rv; + + mReceiver->UpdateConduit(); + + // TODO(bug 1616937): Move this stuff into RTCRtpSender. + if (IsVideo()) { + rv = UpdateVideoConduit(); + } else { + rv = UpdateAudioConduit(); + } + + if (NS_FAILED(rv)) { + return rv; + } + + if (mJsepTransceiver->mRecvTrack.GetActive()) { + mReceiver->Start(); + } + + if (mJsepTransceiver->mSendTrack.GetActive()) { + if (!mSendTrack) { + MOZ_MTLOG(ML_WARNING, + mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " Starting transmit conduit without send track!"); + } + mTransmitPipeline->Start(); + } + + return NS_OK; +} + +void TransceiverImpl::ResetSync() { + if (mConduit) { + mConduit->SetSyncGroup(""); + } +} + +nsresult TransceiverImpl::SyncWithMatchingVideoConduits( + std::vector<RefPtr<TransceiverImpl>>& transceivers) { + if (mJsepTransceiver->IsStopped()) { + return NS_OK; + } + + if (IsVideo()) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " called when transceiver is not " + "video! This should never happen."); + MOZ_CRASH(); + return NS_ERROR_UNEXPECTED; + } + + std::set<std::string> myReceiveStreamIds; + myReceiveStreamIds.insert(mJsepTransceiver->mRecvTrack.GetStreamIds().begin(), + mJsepTransceiver->mRecvTrack.GetStreamIds().end()); + + for (RefPtr<TransceiverImpl>& transceiver : transceivers) { + if (!transceiver->IsValid()) { + continue; + } + + if (!transceiver->IsVideo()) { + // |this| is an audio transceiver, so we skip other audio transceivers + continue; + } + + // Maybe could make this more efficient by cacheing this set, but probably + // not worth it. + for (const std::string& streamId : + transceiver->mJsepTransceiver->mRecvTrack.GetStreamIds()) { + if (myReceiveStreamIds.count(streamId)) { + // Ok, we have one video, one non-video - cross the streams! + mConduit->SetSyncGroup(streamId); + transceiver->mConduit->SetSyncGroup(streamId); + + MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " Syncing " << mConduit.get() << " to " + << transceiver->mConduit.get()); + + // The sync code in call.cc only permits sync between audio stream and + // one video stream. They take the first match, so there's no point in + // continuing here. If we want to change the default, we should sort + // video streams here and only call SetSyncGroup on the chosen stream. + break; + } + } + } + + return NS_OK; +} + +bool TransceiverImpl::ConduitHasPluginID(uint64_t aPluginID) { + return mConduit ? mConduit->CodecPluginID() == aPluginID : false; +} + +bool TransceiverImpl::HasSendTrack( + const dom::MediaStreamTrack* aSendTrack) const { + if (!mSendTrack) { + return false; + } + + if (!aSendTrack) { + return true; + } + + return mSendTrack.get() == aSendTrack; +} + +void TransceiverImpl::SyncWithJS(dom::RTCRtpTransceiver& aJsTransceiver, + ErrorResult& aRv) { + MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " Syncing with JS transceiver"); + + if (!mTransmitPipeline) { + // Shutdown_m has already been called, probably due to pc.close(). Just + // nod and smile. + return; + } + + // Update stopped, both ways, since either JSEP or JS can stop these + if (mJsepTransceiver->IsStopped()) { + // We don't call RTCRtpTransceiver::Stop(), because that causes another sync + aJsTransceiver.SetStopped(aRv); + Stop(); + } else if (aJsTransceiver.GetStopped(aRv)) { + mJsepTransceiver->Stop(); + Stop(); + } + + // Lots of this in here for simple getters that should never fail. Lame. + // Just propagate the exception and let JS log it. + if (aRv.Failed()) { + return; + } + + // Update direction from JS only + dom::RTCRtpTransceiverDirection direction = aJsTransceiver.GetDirection(aRv); + + if (aRv.Failed()) { + return; + } + + switch (direction) { + case dom::RTCRtpTransceiverDirection::Sendrecv: + mJsepTransceiver->mJsDirection = + SdpDirectionAttribute::Direction::kSendrecv; + break; + case dom::RTCRtpTransceiverDirection::Sendonly: + mJsepTransceiver->mJsDirection = + SdpDirectionAttribute::Direction::kSendonly; + break; + case dom::RTCRtpTransceiverDirection::Recvonly: + mJsepTransceiver->mJsDirection = + SdpDirectionAttribute::Direction::kRecvonly; + break; + case dom::RTCRtpTransceiverDirection::Inactive: + mJsepTransceiver->mJsDirection = + SdpDirectionAttribute::Direction::kInactive; + break; + default: + MOZ_ASSERT(false); + aRv = NS_ERROR_INVALID_ARG; + return; + } + + // Update send track ids in JSEP + RefPtr<dom::RTCRtpSender> sender = aJsTransceiver.GetSender(aRv); + if (aRv.Failed()) { + return; + } + + nsTArray<RefPtr<DOMMediaStream>> streams; + sender->GetStreams(streams, aRv); + if (aRv.Failed()) { + return; + } + + std::vector<std::string> streamIds; + for (const auto& stream : streams) { + nsString wideStreamId; + stream->GetId(wideStreamId); + std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get(); + MOZ_ASSERT(!streamId.empty()); + streamIds.push_back(streamId); + } + + mJsepTransceiver->mSendTrack.UpdateStreamIds(streamIds); + + // Update RTCRtpParameters + // TODO: Both ways for things like ssrc, codecs, header extensions, etc + + dom::RTCRtpParameters parameters; + sender->GetParameters(parameters, aRv); + + if (aRv.Failed()) { + return; + } + + std::vector<JsepTrack::JsConstraints> constraints; + + if (parameters.mEncodings.WasPassed()) { + for (auto& encoding : parameters.mEncodings.Value()) { + JsepTrack::JsConstraints constraint; + if (encoding.mRid.WasPassed()) { + // TODO: Either turn on the RID RTP header extension in JsepSession, or + // just leave that extension on all the time? + constraint.rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get(); + } + if (encoding.mMaxBitrate.WasPassed()) { + constraint.constraints.maxBr = encoding.mMaxBitrate.Value(); + } + constraint.constraints.scaleDownBy = encoding.mScaleResolutionDownBy; + constraints.push_back(constraint); + } + } + + if (mJsepTransceiver->mSendTrack.SetJsConstraints(constraints)) { + if (mTransmitPipeline->Transmitting()) { + WebrtcGmpPCHandleSetter setter(mPCHandle); + DebugOnly<nsresult> rv = UpdateConduit(); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + } + + // If a SRD has unset the receive bit, stop the receive pipeline so incoming + // RTP does not unmute the receive track. + if (!mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit() || + !mJsepTransceiver->mRecvTrack.GetActive()) { + mReceiver->Stop(); + } + + // mid from JSEP + if (mJsepTransceiver->IsAssociated()) { + aJsTransceiver.SetMid( + NS_ConvertUTF8toUTF16(mJsepTransceiver->GetMid().c_str()), aRv); + } else { + aJsTransceiver.UnsetMid(aRv); + } + + if (aRv.Failed()) { + return; + } + + // currentDirection from JSEP, but not if "this transceiver has never been + // represented in an offer/answer exchange" + if (mJsepTransceiver->HasLevel() && mJsepTransceiver->IsNegotiated()) { + if (IsReceiving()) { + if (IsSending()) { + aJsTransceiver.SetCurrentDirection( + dom::RTCRtpTransceiverDirection::Sendrecv, aRv); + } else { + aJsTransceiver.SetCurrentDirection( + dom::RTCRtpTransceiverDirection::Recvonly, aRv); + } + } else { + if (IsSending()) { + aJsTransceiver.SetCurrentDirection( + dom::RTCRtpTransceiverDirection::Sendonly, aRv); + } else { + aJsTransceiver.SetCurrentDirection( + dom::RTCRtpTransceiverDirection::Inactive, aRv); + } + } + + if (aRv.Failed()) { + return; + } + } + + // AddTrack magic from JS + if (aJsTransceiver.GetAddTrackMagic(aRv)) { + mJsepTransceiver->SetAddTrackMagic(); + } + + if (aRv.Failed()) { + return; + } + + if (mJsepTransceiver->IsRemoved()) { + aJsTransceiver.SetShouldRemove(true, aRv); + } +} + +bool TransceiverImpl::CanSendDTMF() const { + // Spec says: "If connection's RTCPeerConnectionState is not "connected" + // return false." We don't support that right now. This is supposed to be + // true once ICE is complete, and _all_ DTLS handshakes are also complete. We + // don't really have access to the state of _all_ of our DTLS states either. + // Our pipeline _does_ know whether SRTP/SRTCP is ready, which happens + // immediately after our transport finishes DTLS (unless there was an error), + // so this is pretty close. + // TODO (bug 1265827): Base this on RTCPeerConnectionState instead. + // TODO (bug 1623193): Tighten this up + if (!IsSending() || !mSendTrack) { + return false; + } + + // Ok, it looks like the connection is up and sending. Did we negotiate + // telephone-event? + JsepTrackNegotiatedDetails* details = + mJsepTransceiver->mSendTrack.GetNegotiatedDetails(); + if (NS_WARN_IF(!details || !details->GetEncodingCount())) { + // What? + return false; + } + + for (size_t i = 0; i < details->GetEncodingCount(); ++i) { + const auto& encoding = details->GetEncoding(i); + for (const auto& codec : encoding.GetCodecs()) { + if (codec->mName == "telephone-event") { + return true; + } + } + } + + return false; +} + +JSObject* TransceiverImpl::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return dom::TransceiverImpl_Binding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* TransceiverImpl::GetParentObject() const { return mWindow; } + +RefPtr<MediaPipelineTransmit> TransceiverImpl::GetSendPipeline() { + return mTransmitPipeline; +} + +static nsresult JsepCodecDescToAudioCodecConfig( + const JsepCodecDescription& aCodec, UniquePtr<AudioCodecConfig>* aConfig) { + MOZ_ASSERT(aCodec.mType == SdpMediaSection::kAudio); + if (aCodec.mType != SdpMediaSection::kAudio) return NS_ERROR_INVALID_ARG; + + const JsepAudioCodecDescription& desc = + static_cast<const JsepAudioCodecDescription&>(aCodec); + + uint16_t pt; + + if (!desc.GetPtAsInt(&pt)) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt); + return NS_ERROR_INVALID_ARG; + } + + aConfig->reset(new AudioCodecConfig(pt, desc.mName, desc.mClock, + desc.mForceMono ? 1 : desc.mChannels, + desc.mFECEnabled)); + (*aConfig)->mMaxPlaybackRate = desc.mMaxPlaybackRate; + (*aConfig)->mDtmfEnabled = desc.mDtmfEnabled; + (*aConfig)->mDTXEnabled = desc.mDTXEnabled; + (*aConfig)->mMaxAverageBitrate = desc.mMaxAverageBitrate; + (*aConfig)->mFrameSizeMs = desc.mFrameSizeMs; + (*aConfig)->mMinFrameSizeMs = desc.mMinFrameSizeMs; + (*aConfig)->mMaxFrameSizeMs = desc.mMaxFrameSizeMs; + (*aConfig)->mCbrEnabled = desc.mCbrEnabled; + + return NS_OK; +} + +// TODO: Maybe move this someplace else? +/*static*/ +nsresult TransceiverImpl::NegotiatedDetailsToAudioCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<UniquePtr<AudioCodecConfig>>* aConfigs) { + UniquePtr<AudioCodecConfig> telephoneEvent; + + if (aDetails.GetEncodingCount()) { + for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) { + UniquePtr<AudioCodecConfig> config; + if (NS_FAILED(JsepCodecDescToAudioCodecConfig(*codec, &config))) { + return NS_ERROR_INVALID_ARG; + } + if (config->mName == "telephone-event") { + telephoneEvent = std::move(config); + } else { + aConfigs->push_back(std::move(config)); + } + } + } + + // Put telephone event at the back, because webrtc.org crashes if we don't + // If we need to do even more sorting, we should use std::sort. + if (telephoneEvent) { + aConfigs->push_back(std::move(telephoneEvent)); + } + + if (aConfigs->empty()) { + MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult TransceiverImpl::UpdateAudioConduit() { + MOZ_ASSERT(IsValid()); + + RefPtr<AudioSessionConduit> conduit = + static_cast<AudioSessionConduit*>(mConduit.get()); + + if (mJsepTransceiver->mSendTrack.GetNegotiatedDetails() && + mJsepTransceiver->mSendTrack.GetActive()) { + const auto& details(*mJsepTransceiver->mSendTrack.GetNegotiatedDetails()); + std::vector<UniquePtr<AudioCodecConfig>> configs; + nsresult rv = TransceiverImpl::NegotiatedDetailsToAudioCodecConfigs( + details, &configs); + + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " Failed to convert JsepCodecDescriptions to " + "AudioCodecConfigs (send)."); + return rv; + } + + for (const auto& value : configs) { + if (value->mName == "telephone-event") { + // we have a telephone event codec, so we need to make sure + // the dynamic pt is set properly + conduit->SetDtmfPayloadType(value->mType, value->mFreq); + break; + } + } + + auto error = conduit->ConfigureSendMediaCodec(configs[0].get()); + if (error) { + MOZ_MTLOG(ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " ConfigureSendMediaCodec failed: " << error); + return NS_ERROR_FAILURE; + } + UpdateConduitRtpExtmap(*conduit, details, LocalDirection::kSend); + } + + return NS_OK; +} + +static nsresult JsepCodecDescToVideoCodecConfig( + const JsepCodecDescription& aCodec, UniquePtr<VideoCodecConfig>* aConfig) { + MOZ_ASSERT(aCodec.mType == SdpMediaSection::kVideo); + if (aCodec.mType != SdpMediaSection::kVideo) { + MOZ_ASSERT(false, "JsepCodecDescription has wrong type"); + return NS_ERROR_INVALID_ARG; + } + + const JsepVideoCodecDescription& desc = + static_cast<const JsepVideoCodecDescription&>(aCodec); + + uint16_t pt; + + if (!desc.GetPtAsInt(&pt)) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt); + return NS_ERROR_INVALID_ARG; + } + + UniquePtr<VideoCodecConfigH264> h264Config; + + if (desc.mName == "H264") { + h264Config = MakeUnique<VideoCodecConfigH264>(); + size_t spropSize = sizeof(h264Config->sprop_parameter_sets); + strncpy(h264Config->sprop_parameter_sets, desc.mSpropParameterSets.c_str(), + spropSize); + h264Config->sprop_parameter_sets[spropSize - 1] = '\0'; + h264Config->packetization_mode = desc.mPacketizationMode; + h264Config->profile_level_id = desc.mProfileLevelId; + h264Config->tias_bw = 0; // TODO(bug 1403206) + } + + aConfig->reset(new VideoCodecConfig(pt, desc.mName, desc.mConstraints, + h264Config.get())); + + (*aConfig)->mAckFbTypes = desc.mAckFbTypes; + (*aConfig)->mNackFbTypes = desc.mNackFbTypes; + (*aConfig)->mCcmFbTypes = desc.mCcmFbTypes; + (*aConfig)->mRembFbSet = desc.RtcpFbRembIsSet(); + (*aConfig)->mFECFbSet = desc.mFECEnabled; + (*aConfig)->mTransportCCFbSet = desc.RtcpFbTransportCCIsSet(); + if (desc.mFECEnabled) { + uint16_t pt; + if (SdpHelper::GetPtAsInt(desc.mREDPayloadType, &pt)) { + (*aConfig)->mREDPayloadType = pt; + } + if (SdpHelper::GetPtAsInt(desc.mULPFECPayloadType, &pt)) { + (*aConfig)->mULPFECPayloadType = pt; + } + } + if (desc.mRtxEnabled) { + uint16_t pt; + if (SdpHelper::GetPtAsInt(desc.mRtxPayloadType, &pt)) { + (*aConfig)->mRTXPayloadType = pt; + } + } + + return NS_OK; +} + +// TODO: Maybe move this someplace else? +/*static*/ +nsresult TransceiverImpl::NegotiatedDetailsToVideoCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<UniquePtr<VideoCodecConfig>>* aConfigs) { + if (aDetails.GetEncodingCount()) { + for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) { + UniquePtr<VideoCodecConfig> config; + if (NS_FAILED(JsepCodecDescToVideoCodecConfig(*codec, &config))) { + return NS_ERROR_INVALID_ARG; + } + + config->mTias = aDetails.GetTias(); + + for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) { + const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i)); + if (jsepEncoding.HasFormat(codec->mDefaultPt)) { + VideoCodecConfig::Encoding encoding; + encoding.rid = jsepEncoding.mRid; + encoding.constraints = jsepEncoding.mConstraints; + config->mEncodings.push_back(encoding); + } + } + + aConfigs->push_back(std::move(config)); + } + } + + return NS_OK; +} + +nsresult TransceiverImpl::UpdateVideoConduit() { + MOZ_ASSERT(IsValid()); + + RefPtr<VideoSessionConduit> conduit = + static_cast<VideoSessionConduit*>(mConduit.get()); + + // It is possible for SDP to signal that there is a send track, but there not + // actually be a send track, according to the specification; all that needs to + // happen is for the transceiver to be configured to send... + if (mJsepTransceiver->mSendTrack.GetNegotiatedDetails() && + mJsepTransceiver->mSendTrack.GetActive() && mSendTrack) { + const auto& details(*mJsepTransceiver->mSendTrack.GetNegotiatedDetails()); + + UpdateConduitRtpExtmap(*conduit, details, LocalDirection::kSend); + + nsresult rv = ConfigureVideoCodecMode(*conduit); + if (NS_FAILED(rv)) { + return rv; + } + + std::vector<UniquePtr<VideoCodecConfig>> configs; + rv = TransceiverImpl::NegotiatedDetailsToVideoCodecConfigs(details, + &configs); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " Failed to convert JsepCodecDescriptions to " + "VideoCodecConfigs (send)."); + return rv; + } + + if (configs.empty()) { + MOZ_MTLOG(ML_INFO, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " No codecs were negotiated (send)."); + return NS_OK; + } + + auto error = conduit->ConfigureSendMediaCodec(configs[0].get(), + details.GetRtpRtcpConfig()); + if (error) { + MOZ_MTLOG(ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " ConfigureSendMediaCodec failed: " << error); + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +nsresult TransceiverImpl::ConfigureVideoCodecMode( + VideoSessionConduit& aConduit) { + RefPtr<mozilla::dom::VideoStreamTrack> videotrack = + mSendTrack->AsVideoStreamTrack(); + + if (!videotrack) { + MOZ_MTLOG( + ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " mSendTrack is not video! This should never happen!"); + MOZ_CRASH(); + return NS_ERROR_FAILURE; + } + + dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource(); + webrtc::VideoCodecMode mode = webrtc::kRealtimeVideo; + switch (source) { + case dom::MediaSourceEnum::Browser: + case dom::MediaSourceEnum::Screen: + case dom::MediaSourceEnum::Window: + mode = webrtc::kScreensharing; + break; + + case dom::MediaSourceEnum::Camera: + default: + mode = webrtc::kRealtimeVideo; + break; + } + + auto error = aConduit.ConfigureCodecMode(mode); + if (error) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " ConfigureCodecMode failed: " << error); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void TransceiverImpl::UpdateConduitRtpExtmap( + MediaSessionConduit& aConduit, const JsepTrackNegotiatedDetails& aDetails, + const LocalDirection aDirection) { + std::vector<webrtc::RtpExtension> extmaps; + // @@NG read extmap from track + aDetails.ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + if (!extmaps.empty()) { + aConduit.SetLocalRTPExtensions(aDirection, extmaps); + } +} + +void TransceiverImpl::Stop() { + mTransmitPipeline->Shutdown_m(); + mReceiver->Shutdown(); + // Make sure that stats queries stop working on this transceiver. + UpdateSendTrack(nullptr); + + if (mConduit) { + mConduit->DeleteStreams(); + } + mConduit = nullptr; + + if (mDtmf) { + mDtmf->StopPlayout(); + } +} + +bool TransceiverImpl::IsVideo() const { + return mJsepTransceiver->GetMediaType() == SdpMediaSection::MediaType::kVideo; +} + +} // namespace mozilla |