RTCRtpSender.cpp (86016B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "RTCRtpSender.h" 6 7 #include <stdint.h> 8 9 #include <algorithm> 10 #include <iterator> 11 #include <set> 12 #include <sstream> 13 #include <string> 14 #include <utility> 15 #include <vector> 16 17 #include "MainThreadUtils.h" 18 #include "PeerConnectionImpl.h" 19 #include "RTCRtpTransceiver.h" 20 #include "RTCStatsReport.h" 21 #include "api/rtp_parameters.h" 22 #include "api/units/time_delta.h" 23 #include "api/units/timestamp.h" 24 #include "api/video/video_codec_constants.h" 25 #include "api/video_codecs/video_codec.h" 26 #include "call/audio_send_stream.h" 27 #include "call/call.h" 28 #include "call/video_send_stream.h" 29 #include "js/RootingAPI.h" 30 #include "jsep/JsepTransceiver.h" 31 #include "libwebrtcglue/CodecConfig.h" 32 #include "libwebrtcglue/MediaConduitControl.h" 33 #include "libwebrtcglue/MediaConduitInterface.h" 34 #include "modules/rtp_rtcp/include/report_block_data.h" 35 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" 36 #include "mozilla/AbstractThread.h" 37 #include "mozilla/AlreadyAddRefed.h" 38 #include "mozilla/Assertions.h" 39 #include "mozilla/Attributes.h" 40 #include "mozilla/Logging.h" 41 #include "mozilla/Maybe.h" 42 #include "mozilla/MozPromise.h" 43 #include "mozilla/OwningNonNull.h" 44 #include "mozilla/RefPtr.h" 45 #include "mozilla/StateMirroring.h" 46 #include "mozilla/StateWatching.h" 47 #include "mozilla/UniquePtr.h" 48 #include "mozilla/dom/MediaStreamTrack.h" 49 #include "mozilla/dom/MediaStreamTrackBinding.h" 50 #include "mozilla/dom/Nullable.h" 51 #include "mozilla/dom/Promise.h" 52 #include "mozilla/dom/RTCRtpParametersBinding.h" 53 #include "mozilla/dom/RTCRtpScriptTransform.h" 54 #include "mozilla/dom/RTCRtpSenderBinding.h" 55 #include "mozilla/dom/RTCStatsReportBinding.h" 56 #include "mozilla/dom/VideoStreamTrack.h" 57 #include "mozilla/fallible.h" 58 #include "mozilla/glean/DomMediaWebrtcMetrics.h" 59 #include "mozilla/mozalloc_oom.h" 60 #include "nsCOMPtr.h" 61 #include "nsContentUtils.h" 62 #include "nsCycleCollectionParticipant.h" 63 #include "nsDebug.h" 64 #include "nsISupports.h" 65 #include "nsLiteralString.h" 66 #include "nsPIDOMWindow.h" 67 #include "nsString.h" 68 #include "nsStringFwd.h" 69 #include "nsTArray.h" 70 #include "nsThreadUtils.h" 71 #include "nsWrapperCache.h" 72 #include "sdp/SdpAttribute.h" 73 #include "sdp/SdpEnum.h" 74 #include "system_wrappers/include/clock.h" 75 76 namespace mozilla::dom { 77 78 LazyLogModule gSenderLog("RTCRtpSender"); 79 80 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpSender) 81 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpSender) 82 tmp->Unlink(); 83 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 84 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 85 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpSender) 86 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSenderTrack, mTransceiver, 87 mStreams, mTransform, mDtmf) 88 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 89 90 NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpSender) 91 NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpSender) 92 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpSender) 93 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 94 NS_INTERFACE_MAP_ENTRY(nsISupports) 95 NS_INTERFACE_MAP_END 96 97 #define INIT_CANONICAL(name, val) \ 98 name(AbstractThread::MainThread(), val, "RTCRtpSender::" #name " (Canonical)") 99 100 RTCRtpSender::RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc, 101 MediaTransportHandler* aTransportHandler, 102 AbstractThread* aCallThread, 103 nsISerialEventTarget* aStsThread, 104 MediaSessionConduit* aConduit, 105 dom::MediaStreamTrack* aTrack, 106 const Sequence<RTCRtpEncodingParameters>& aEncodings, 107 RTCRtpTransceiver* aTransceiver) 108 : mWatchManager(this, AbstractThread::MainThread()), 109 mWindow(aWindow), 110 mPc(aPc), 111 mSenderTrack(aTrack), 112 mTransportHandler(aTransportHandler), 113 mTransceiver(aTransceiver), 114 INIT_CANONICAL(mSsrcs, Ssrcs()), 115 INIT_CANONICAL(mVideoRtxSsrcs, Ssrcs()), 116 INIT_CANONICAL(mLocalRtpExtensions, RtpExtList()), 117 INIT_CANONICAL(mAudioCodec, Nothing()), 118 INIT_CANONICAL(mVideoCodec, Nothing()), 119 INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()), 120 INIT_CANONICAL(mVideoCodecMode, webrtc::VideoCodecMode::kRealtimeVideo), 121 INIT_CANONICAL(mCname, std::string()), 122 INIT_CANONICAL(mTransmitting, false), 123 INIT_CANONICAL(mFrameTransformerProxy, nullptr), 124 INIT_CANONICAL(mVideoDegradationPreference, 125 webrtc::DegradationPreference::DISABLED) { 126 mPipeline = MediaPipelineTransmit::Create( 127 mPc->GetHandle(), aTransportHandler, aCallThread, aStsThread, 128 aConduit->type() == MediaSessionConduit::VIDEO, aConduit); 129 mPipeline->InitControl(this); 130 131 if (aConduit->type() == MediaSessionConduit::AUDIO) { 132 mDtmf = new RTCDTMFSender(aWindow, mTransceiver); 133 } 134 mPipeline->SetTrack(mSenderTrack); 135 136 mozilla::glean::rtcrtpsender::count.Add(1); 137 138 if (mPc->ShouldAllowOldSetParameters()) { 139 mAllowOldSetParameters = true; 140 mozilla::glean::rtcrtpsender::count_setparameters_compat.Add(1); 141 } 142 143 if (aEncodings.Length()) { 144 // This sender was created by addTransceiver with sendEncodings. 145 mParameters.mEncodings = aEncodings; 146 mSimulcastEnvelopeSet = true; 147 mozilla::glean::rtcrtpsender::used_sendencodings.AddToNumerator(1); 148 } else { 149 // This sender was created by addTrack, sRD(offer), or addTransceiver 150 // without sendEncodings. 151 RTCRtpEncodingParameters defaultEncoding; 152 defaultEncoding.mActive = true; 153 if (aConduit->type() == MediaSessionConduit::VIDEO) { 154 defaultEncoding.mScaleResolutionDownBy.Construct(1.0f); 155 } 156 (void)mParameters.mEncodings.AppendElement(defaultEncoding, fallible); 157 UpdateRestorableEncodings(mParameters.mEncodings); 158 MaybeGetJsepRids(); 159 } 160 161 mParameters.mCodecs.Construct(); 162 163 if (mDtmf) { 164 mWatchManager.Watch(mTransmitting, &RTCRtpSender::UpdateDtmfSender); 165 } 166 } 167 168 #undef INIT_CANONICAL 169 170 RTCRtpSender::~RTCRtpSender() = default; 171 172 JSObject* RTCRtpSender::WrapObject(JSContext* aCx, 173 JS::Handle<JSObject*> aGivenProto) { 174 return RTCRtpSender_Binding::Wrap(aCx, this, aGivenProto); 175 } 176 177 RTCDtlsTransport* RTCRtpSender::GetTransport() const { 178 if (!mTransceiver) { 179 return nullptr; 180 } 181 return mTransceiver->GetDtlsTransport(); 182 } 183 184 RTCDTMFSender* RTCRtpSender::GetDtmf() const { return mDtmf; } 185 186 already_AddRefed<Promise> RTCRtpSender::GetStats(ErrorResult& aError) { 187 RefPtr<Promise> promise = MakePromise(aError); 188 if (aError.Failed()) { 189 return nullptr; 190 } 191 if (NS_WARN_IF(!mPipeline)) { 192 // TODO(bug 1056433): When we stop nulling this out when the PC is closed 193 // (or when the transceiver is stopped), we can remove this code. We 194 // resolve instead of reject in order to make this eventual change in 195 // behavior a little smaller. 196 promise->MaybeResolve(new RTCStatsReport(mWindow)); 197 return promise.forget(); 198 } 199 200 mTransceiver->ChainToDomPromiseWithCodecStats(GetStatsInternal(), promise); 201 return promise.forget(); 202 } 203 204 nsTArray<RefPtr<dom::RTCStatsPromise>> RTCRtpSender::GetStatsInternal( 205 bool aSkipIceStats) { 206 MOZ_ASSERT(NS_IsMainThread()); 207 nsTArray<RefPtr<RTCStatsPromise>> promises(2); 208 if (!mPipeline) { 209 return promises; 210 } 211 212 nsAutoString trackName; 213 if (auto track = mPipeline->GetTrack()) { 214 track->GetId(trackName); 215 } 216 217 std::string mid = mTransceiver->GetMidAscii(); 218 std::map<uint32_t, std::string> videoSsrcToRidMap; 219 const auto encodings = mVideoCodec.Ref().andThen( 220 [](const auto& aCodec) { return SomeRef(aCodec.mEncodings); }); 221 if (encodings && !encodings->empty() && encodings->front().rid != "") { 222 for (size_t i = 0; i < std::min(mSsrcs.Ref().size(), encodings->size()); 223 ++i) { 224 videoSsrcToRidMap.insert({mSsrcs.Ref()[i], (*encodings)[i].rid}); 225 } 226 } 227 228 { 229 // Add bandwidth estimation stats 230 promises.AppendElement(InvokeAsync( 231 mPipeline->mCallThread, __func__, 232 [conduit = mPipeline->mConduit, trackName]() mutable { 233 auto report = MakeUnique<dom::RTCStatsCollection>(); 234 Maybe<webrtc::Call::Stats> stats = conduit->GetCallStats(); 235 stats.apply([&](const auto aStats) { 236 dom::RTCBandwidthEstimationInternal bw; 237 bw.mTrackIdentifier = trackName; 238 bw.mSendBandwidthBps.Construct(aStats.send_bandwidth_bps / 8); 239 bw.mMaxPaddingBps.Construct(aStats.max_padding_bitrate_bps / 8); 240 bw.mReceiveBandwidthBps.Construct(aStats.recv_bandwidth_bps / 8); 241 bw.mPacerDelayMs.Construct(aStats.pacer_delay_ms); 242 if (aStats.rtt_ms >= 0) { 243 bw.mRttMs.Construct(aStats.rtt_ms); 244 } 245 if (!report->mBandwidthEstimations.AppendElement(std::move(bw), 246 fallible)) { 247 mozalloc_handle_oom(0); 248 } 249 }); 250 return RTCStatsPromise::CreateAndResolve(std::move(report), __func__); 251 })); 252 } 253 254 const bool isSending = mTransceiver->HasBeenUsedToSend(); 255 256 promises.AppendElement(InvokeAsync( 257 mPipeline->mCallThread, __func__, 258 [pipeline = mPipeline, trackName, mid = std::move(mid), 259 videoSsrcToRidMap = std::move(videoSsrcToRidMap), isSending, 260 audioCodec = mAudioCodec.Ref()] { 261 auto report = MakeUnique<dom::RTCStatsCollection>(); 262 auto asAudio = pipeline->mConduit->AsAudioSessionConduit(); 263 auto asVideo = pipeline->mConduit->AsVideoSessionConduit(); 264 265 nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns; 266 nsString idstr = kind + u"_"_ns; 267 idstr.AppendInt(static_cast<uint32_t>(pipeline->Level())); 268 const bool isSendStable = 269 !pipeline->mConduit->IsShutdown() && isSending; 270 for (uint32_t ssrc : pipeline->mConduit->GetLocalSSRCs()) { 271 nsString localId = u"outbound_rtp_"_ns + idstr + u"_"_ns; 272 localId.AppendInt(ssrc); 273 nsString remoteId; 274 Maybe<uint16_t> base_seq = 275 pipeline->mConduit->RtpSendBaseSeqFor(ssrc); 276 277 auto constructCommonRemoteInboundRtpStats = 278 [&](RTCRemoteInboundRtpStreamStats& aRemote, 279 const webrtc::ReportBlockData& aRtcpData) { 280 remoteId = u"outbound_rtcp_"_ns + idstr + u"_"_ns; 281 remoteId.AppendInt(ssrc); 282 aRemote.mTimestamp.Construct( 283 RTCStatsTimestamp::FromNtp( 284 pipeline->GetTimestampMaker(), 285 webrtc::Timestamp::Micros( 286 aRtcpData.report_block_timestamp_utc().us()) + 287 webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) 288 .ToDom()); 289 aRemote.mId.Construct(remoteId); 290 aRemote.mType.Construct(RTCStatsType::Remote_inbound_rtp); 291 aRemote.mSsrc = ssrc; 292 aRemote.mKind = kind; 293 aRemote.mMediaType.Construct( 294 kind); // mediaType is the old name for kind. 295 aRemote.mLocalId.Construct(localId); 296 if (base_seq) { 297 if (aRtcpData.extended_highest_sequence_number() < 298 *base_seq) { 299 aRemote.mPacketsReceived.Construct(0); 300 aRemote.mPacketsLost.Construct(0); 301 } else { 302 aRemote.mPacketsReceived.Construct( 303 aRtcpData.extended_highest_sequence_number() - 304 aRtcpData.cumulative_lost() - *base_seq + 1); 305 aRemote.mPacketsLost.Construct(aRtcpData.cumulative_lost()); 306 } 307 } 308 if (aRtcpData.has_rtt()) { 309 aRemote.mRoundTripTime.Construct( 310 aRtcpData.last_rtt().template ms<double>() / 1000.0); 311 } 312 aRemote.mTotalRoundTripTime.Construct( 313 aRtcpData.sum_rtts().template ms<double>() / 1000.0); 314 aRemote.mFractionLost.Construct( 315 static_cast<float>(aRtcpData.fraction_lost_raw()) / 316 (1 << 8)); 317 aRemote.mRoundTripTimeMeasurements.Construct( 318 aRtcpData.num_rtts()); 319 }; 320 321 auto constructCommonOutboundRtpStats = 322 [&](RTCOutboundRtpStreamStats& aLocal) { 323 aLocal.mSsrc = ssrc; 324 aLocal.mTimestamp.Construct( 325 pipeline->GetTimestampMaker().GetNow().ToDom()); 326 aLocal.mId.Construct(localId); 327 aLocal.mType.Construct(RTCStatsType::Outbound_rtp); 328 aLocal.mKind = kind; 329 aLocal.mMediaType.Construct( 330 kind); // mediaType is the old name for kind. 331 if (remoteId.Length()) { 332 aLocal.mRemoteId.Construct(remoteId); 333 } 334 if (!mid.empty()) { 335 aLocal.mMid.Construct(NS_ConvertUTF8toUTF16(mid).get()); 336 } 337 }; 338 339 auto constructCommonMediaSourceStats = 340 [&](RTCMediaSourceStats& aStats) { 341 nsString id = u"mediasource_"_ns + idstr + trackName; 342 aStats.mTimestamp.Construct( 343 pipeline->GetTimestampMaker().GetNow().ToDom()); 344 aStats.mId.Construct(id); 345 aStats.mType.Construct(RTCStatsType::Media_source); 346 aStats.mTrackIdentifier = trackName; 347 aStats.mKind = kind; 348 }; 349 350 asAudio.apply([&](auto& aConduit) { 351 Maybe<webrtc::AudioSendStream::Stats> audioStats = 352 aConduit->GetSenderStats(); 353 if (audioStats.isNothing()) { 354 return; 355 } 356 357 if (!isSendStable) { 358 // See 359 // https://www.w3.org/TR/webrtc-stats/#the-rtp-statistics-hierarchy 360 // for a complete description of the RTP statistics lifetime 361 // rules. 362 return; 363 } 364 365 // First, fill in remote stat with rtcp receiver data, if present. 366 // ReceiverReports have less information than SenderReports, so fill 367 // in what we can. 368 Maybe<webrtc::ReportBlockData> reportBlockData; 369 { 370 if (const auto remoteSsrc = aConduit->GetRemoteSSRC(); 371 remoteSsrc) { 372 for (auto& data : audioStats->report_block_datas) { 373 if (data.source_ssrc() == ssrc && 374 data.sender_ssrc() == *remoteSsrc) { 375 reportBlockData.emplace(data); 376 break; 377 } 378 } 379 } 380 } 381 reportBlockData.apply([&](auto& aReportBlockData) { 382 RTCRemoteInboundRtpStreamStats remote; 383 constructCommonRemoteInboundRtpStats(remote, aReportBlockData); 384 if (aReportBlockData.jitter() >= 0 && audioCodec) { 385 remote.mJitter.Construct( 386 aReportBlockData.jitter(audioCodec->mFreq) 387 .template ms<double>() / 388 1000.0); 389 } 390 if (!report->mRemoteInboundRtpStreamStats.AppendElement( 391 std::move(remote), fallible)) { 392 mozalloc_handle_oom(0); 393 } 394 }); 395 396 // Then, fill in local side (with cross-link to remote only if 397 // present) 398 RTCOutboundRtpStreamStats local; 399 constructCommonOutboundRtpStats(local); 400 local.mPacketsSent.Construct(audioStats->packets_sent); 401 local.mBytesSent.Construct(audioStats->payload_bytes_sent); 402 local.mNackCount.Construct( 403 audioStats->rtcp_packet_type_counts.nack_packets); 404 local.mHeaderBytesSent.Construct( 405 audioStats->header_and_padding_bytes_sent); 406 local.mRetransmittedPacketsSent.Construct( 407 audioStats->retransmitted_packets_sent); 408 local.mRetransmittedBytesSent.Construct( 409 audioStats->retransmitted_bytes_sent); 410 /* 411 * Potential new stats that are now available upstream. 412 * Note: when we last tried exposing this we were getting 413 * targetBitrate for audio was ending up as 0. We did not 414 * investigate why. 415 local.mTargetBitrate.Construct(audioStats->target_bitrate_bps); 416 */ 417 if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local), 418 fallible)) { 419 mozalloc_handle_oom(0); 420 } 421 422 // TODO(bug 1804678): Use RTCAudioSourceStats 423 RTCMediaSourceStats mediaSourceStats; 424 constructCommonMediaSourceStats(mediaSourceStats); 425 if (!report->mMediaSourceStats.AppendElement( 426 std::move(mediaSourceStats), fallible)) { 427 mozalloc_handle_oom(0); 428 } 429 }); 430 431 asVideo.apply([&](auto& aConduit) { 432 Maybe<webrtc::VideoSendStream::Stats> videoStats = 433 aConduit->GetSenderStats(); 434 if (videoStats.isNothing()) { 435 return; 436 } 437 438 Maybe<webrtc::VideoSendStream::StreamStats> streamStats; 439 auto kv = videoStats->substreams.find(ssrc); 440 if (kv != videoStats->substreams.end()) { 441 streamStats = Some(kv->second); 442 } 443 444 if (!streamStats || !isSendStable) { 445 // By spec: "The lifetime of all RTP monitored objects starts 446 // when the RTP stream is first used: When the first RTP packet 447 // is sent or received on the SSRC it represents" 448 return; 449 } 450 451 aConduit->GetAssociatedLocalRtxSSRC(ssrc).apply( 452 [&](const auto rtxSsrc) { 453 auto kv = videoStats->substreams.find(rtxSsrc); 454 if (kv != videoStats->substreams.end()) { 455 streamStats->rtp_stats.Add(kv->second.rtp_stats); 456 } 457 }); 458 459 // First, fill in remote stat with rtcp receiver data, if present. 460 // ReceiverReports have less information than SenderReports, so fill 461 // in what we can. 462 if (streamStats->report_block_data) { 463 const webrtc::ReportBlockData& rtcpReportData = 464 *streamStats->report_block_data; 465 RTCRemoteInboundRtpStreamStats remote; 466 remote.mJitter.Construct( 467 static_cast<double>(rtcpReportData.jitter()) / 468 webrtc::kVideoPayloadTypeFrequency); 469 constructCommonRemoteInboundRtpStats(remote, rtcpReportData); 470 if (!report->mRemoteInboundRtpStreamStats.AppendElement( 471 std::move(remote), fallible)) { 472 mozalloc_handle_oom(0); 473 } 474 } 475 476 // Then, fill in local side (with cross-link to remote only if 477 // present) 478 RTCOutboundRtpStreamStats local; 479 constructCommonOutboundRtpStats(local); 480 if (auto it = videoSsrcToRidMap.find(ssrc); 481 it != videoSsrcToRidMap.end() && it->second != "") { 482 local.mRid.Construct(NS_ConvertUTF8toUTF16(it->second).get()); 483 } 484 local.mPacketsSent.Construct( 485 streamStats->rtp_stats.transmitted.packets); 486 local.mBytesSent.Construct( 487 streamStats->rtp_stats.transmitted.payload_bytes); 488 local.mNackCount.Construct( 489 streamStats->rtcp_packet_type_counts.nack_packets); 490 local.mFirCount.Construct( 491 streamStats->rtcp_packet_type_counts.fir_packets); 492 local.mPliCount.Construct( 493 streamStats->rtcp_packet_type_counts.pli_packets); 494 local.mFramesEncoded.Construct(streamStats->frames_encoded); 495 if (streamStats->qp_sum) { 496 local.mQpSum.Construct(*streamStats->qp_sum); 497 } 498 local.mHeaderBytesSent.Construct( 499 streamStats->rtp_stats.transmitted.header_bytes + 500 streamStats->rtp_stats.transmitted.padding_bytes); 501 local.mRetransmittedPacketsSent.Construct( 502 streamStats->rtp_stats.retransmitted.packets); 503 local.mRetransmittedBytesSent.Construct( 504 streamStats->rtp_stats.retransmitted.payload_bytes); 505 local.mTotalEncodedBytesTarget.Construct( 506 videoStats->total_encoded_bytes_target); 507 local.mFrameWidth.Construct(streamStats->width); 508 local.mFrameHeight.Construct(streamStats->height); 509 local.mFramesPerSecond.Construct(streamStats->encode_frame_rate); 510 local.mFramesSent.Construct(streamStats->frames_encoded); 511 local.mHugeFramesSent.Construct(streamStats->huge_frames_sent); 512 local.mTotalEncodeTime.Construct( 513 double(streamStats->total_encode_time_ms) / 1000.); 514 /* 515 * Potential new stats that are now available upstream. 516 local.mTargetBitrate.Construct(videoStats->target_media_bitrate_bps); 517 */ 518 if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local), 519 fallible)) { 520 mozalloc_handle_oom(0); 521 } 522 523 RTCVideoSourceStats videoSourceStats; 524 constructCommonMediaSourceStats(videoSourceStats); 525 // webrtc::VideoSendStream::Stats does not have width/height. We 526 // might be able to get this somewhere else? 527 // videoStats->frames is not documented, but looking at the 528 // implementation it appears to be the number of frames inputted to 529 // the encoder, which ought to work. 530 videoSourceStats.mFrames.Construct(videoStats->frames); 531 videoSourceStats.mFramesPerSecond.Construct( 532 videoStats->input_frame_rate); 533 auto resolution = aConduit->GetLastResolution(); 534 resolution.apply([&](const auto& aResolution) { 535 videoSourceStats.mWidth.Construct(aResolution.width); 536 videoSourceStats.mHeight.Construct(aResolution.height); 537 }); 538 if (!report->mVideoSourceStats.AppendElement( 539 std::move(videoSourceStats), fallible)) { 540 mozalloc_handle_oom(0); 541 } 542 }); 543 } 544 545 return RTCStatsPromise::CreateAndResolve(std::move(report), __func__); 546 })); 547 548 if (!aSkipIceStats && GetJsepTransceiver().mTransport.mComponents) { 549 promises.AppendElement(mTransportHandler->GetIceStats( 550 GetJsepTransceiver().mTransport.mTransportId, 551 mPipeline->GetTimestampMaker().GetNow().ToDom())); 552 } 553 554 return promises; 555 } 556 557 void RTCRtpSender::GetCapabilities(const GlobalObject&, const nsAString& aKind, 558 Nullable<dom::RTCRtpCapabilities>& aResult) { 559 PeerConnectionImpl::GetCapabilities(aKind, aResult, sdp::Direction::kSend); 560 } 561 562 void RTCRtpSender::WarnAboutBadSetParameters(const nsCString& aError) { 563 nsCString warning( 564 "WARNING! Invalid setParameters call detected! The good news? Firefox " 565 "supports sendEncodings in addTransceiver now, so we ask that you switch " 566 "over to using the parameters code you use for other browsers. Thank you " 567 "for your patience and support. The specific error was: "); 568 warning += aError; 569 mPc->SendWarningToConsole(warning); 570 } 571 572 nsCString RTCRtpSender::GetEffectiveTLDPlus1() const { 573 return mPc->GetEffectiveTLDPlus1(); 574 } 575 576 already_AddRefed<Promise> RTCRtpSender::SetParameters( 577 const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError) { 578 dom::RTCRtpSendParameters paramsCopy(aParameters); 579 // When the setParameters method is called, the user agent MUST run the 580 // following steps: 581 // Let parameters be the method's first argument. 582 // Let sender be the RTCRtpSender object on which setParameters is invoked. 583 // Let transceiver be the RTCRtpTransceiver object associated with sender 584 // (i.e.sender is transceiver.[[Sender]]). 585 586 RefPtr<dom::Promise> p = MakePromise(aError); 587 if (aError.Failed()) { 588 return nullptr; 589 } 590 591 if (mPc->IsClosed()) { 592 p->MaybeRejectWithInvalidStateError("Peer connection is closed"); 593 return p.forget(); 594 } 595 596 // If transceiver.[[Stopping]] is true, return a promise rejected with a newly 597 // created InvalidStateError. 598 if (mTransceiver->Stopping()) { 599 p->MaybeRejectWithInvalidStateError( 600 "This sender's transceiver is stopping/stopped"); 601 return p.forget(); 602 } 603 604 // If sender.[[LastReturnedParameters]] is null, return a promise rejected 605 // with a newly created InvalidStateError. 606 if (!mLastReturnedParameters.isSome()) { 607 nsCString error; 608 if (mLastTransactionId.isSome() && paramsCopy.mTransactionId.WasPassed() && 609 *mLastTransactionId == paramsCopy.mTransactionId.Value()) { 610 error = 611 "Event loop was relinquished between getParameters and setParameters " 612 "calls"; 613 } else { 614 error = "Cannot call setParameters without first calling getParameters"; 615 } 616 617 if (mAllowOldSetParameters) { 618 if (!mHaveWarnedBecauseNoGetParameters) { 619 mHaveWarnedBecauseNoGetParameters = true; 620 mozilla::glean::rtcrtpsender_setparameters::warn_no_getparameters 621 .AddToNumerator(1); 622 } 623 WarnAboutBadSetParameters(error); 624 } else { 625 if (!mHaveFailedBecauseNoGetParameters) { 626 mHaveFailedBecauseNoGetParameters = true; 627 mozilla::glean::rtcrtpsender_setparameters::fail_no_getparameters 628 .AddToNumerator(1); 629 } 630 p->MaybeRejectWithInvalidStateError(error); 631 return p.forget(); 632 } 633 } 634 635 // According to the spec, our consistency checking is based on 636 // [[LastReturnedParameters]], but if we're letting 637 // [[LastReturnedParameters]]==null slide, we still want to do 638 // consistency checking on _something_ so we can warn implementers if they 639 // are messing that up also. Just find something, _anything_, to do that 640 // checking with. 641 // TODO(bug 1803388): Remove this stuff once it is no longer needed. 642 // TODO(bug 1803389): Remove the glean errors once they are no longer needed. 643 Maybe<RTCRtpSendParameters> oldParams; 644 if (mAllowOldSetParameters) { 645 if (mLastReturnedParameters.isSome()) { 646 oldParams = mLastReturnedParameters; 647 } else if (mPendingParameters.isSome()) { 648 oldParams = mPendingParameters; 649 } else { 650 oldParams = Some(mParameters); 651 } 652 MOZ_ASSERT(oldParams.isSome()); 653 } else { 654 oldParams = mLastReturnedParameters; 655 } 656 MOZ_ASSERT(oldParams.isSome()); 657 658 // Validate parameters by running the following steps: 659 // Let encodings be parameters.encodings. 660 // Let codecs be parameters.codecs. 661 // Let N be the number of RTCRtpEncodingParameters stored in 662 // sender.[[SendEncodings]]. 663 // If any of the following conditions are met, 664 // return a promise rejected with a newly created InvalidModificationError: 665 666 bool pendingRidChangeFromCompatMode = false; 667 // encodings.length is different from N. 668 if (paramsCopy.mEncodings.Length() != oldParams->mEncodings.Length()) { 669 nsCString error("Cannot change the number of encodings with setParameters"); 670 if (!mAllowOldSetParameters) { 671 if (!mHaveFailedBecauseEncodingCountChange) { 672 mHaveFailedBecauseEncodingCountChange = true; 673 mozilla::glean::rtcrtpsender_setparameters::fail_length_changed 674 .AddToNumerator(1); 675 } 676 p->MaybeRejectWithInvalidModificationError(error); 677 return p.forget(); 678 } 679 // Make sure we don't use the old rids in SyncToJsep while we wait for the 680 // queued task below to update mParameters. 681 pendingRidChangeFromCompatMode = true; 682 mSimulcastEnvelopeSet = true; 683 if (!mHaveWarnedBecauseEncodingCountChange) { 684 mHaveWarnedBecauseEncodingCountChange = true; 685 mozilla::glean::rtcrtpsender_setparameters::warn_length_changed 686 .AddToNumerator(1); 687 } 688 WarnAboutBadSetParameters(error); 689 } else { 690 // encodings has been re-ordered. 691 for (size_t i = 0; i < paramsCopy.mEncodings.Length(); ++i) { 692 const auto& oldEncoding = oldParams->mEncodings[i]; 693 const auto& newEncoding = paramsCopy.mEncodings[i]; 694 if (oldEncoding.mRid != newEncoding.mRid) { 695 nsCString error("Cannot change rid, or reorder encodings"); 696 if (!mHaveFailedBecauseRidChange) { 697 mHaveFailedBecauseRidChange = true; 698 mozilla::glean::rtcrtpsender_setparameters::fail_rid_changed 699 .AddToNumerator(1); 700 } 701 p->MaybeRejectWithInvalidModificationError(error); 702 return p.forget(); 703 } 704 } 705 } 706 707 // TODO(bug 1803388): Handle this in webidl, once we stop allowing the old 708 // setParameters style. 709 if (!paramsCopy.mTransactionId.WasPassed()) { 710 nsCString error("transactionId is not set!"); 711 if (!mAllowOldSetParameters) { 712 if (!mHaveFailedBecauseNoTransactionId) { 713 mHaveFailedBecauseNoTransactionId = true; 714 mozilla::glean::rtcrtpsender_setparameters::fail_no_transactionid 715 .AddToNumerator(1); 716 } 717 p->MaybeRejectWithTypeError(error); 718 return p.forget(); 719 } 720 if (!mHaveWarnedBecauseNoTransactionId) { 721 mHaveWarnedBecauseNoTransactionId = true; 722 mozilla::glean::rtcrtpsender_setparameters::warn_no_transactionid 723 .AddToNumerator(1); 724 } 725 WarnAboutBadSetParameters(error); 726 } else if (oldParams->mTransactionId.WasPassed() && 727 oldParams->mTransactionId != paramsCopy.mTransactionId) { 728 // Any parameter in parameters is marked as a Read-only parameter (such as 729 // RID) and has a value that is different from the corresponding parameter 730 // value in sender.[[LastReturnedParameters]]. Note that this also applies 731 // to transactionId. 732 // Don't throw this error if letting the "stale getParameters" case slide. 733 nsCString error( 734 "Cannot change transaction id: call getParameters, modify the result, " 735 "and then call setParameters"); 736 if (!mHaveFailedBecauseStaleTransactionId) { 737 mHaveFailedBecauseStaleTransactionId = true; 738 mozilla::glean::rtcrtpsender_setparameters::fail_stale_transactionid 739 .AddToNumerator(1); 740 } 741 p->MaybeRejectWithInvalidModificationError(error); 742 return p.forget(); 743 } 744 745 // This could conceivably happen if we are allowing the old setParameters 746 // behavior. 747 if (!paramsCopy.mEncodings.Length()) { 748 nsCString error("Cannot set an empty encodings array"); 749 if (!mAllowOldSetParameters) { 750 if (!mHaveFailedBecauseNoEncodings) { 751 mHaveFailedBecauseNoEncodings = true; 752 mozilla::glean::rtcrtpsender_setparameters::fail_no_encodings 753 .AddToNumerator(1); 754 } 755 756 p->MaybeRejectWithInvalidModificationError(error); 757 return p.forget(); 758 } 759 // TODO: Add some warning telemetry here 760 WarnAboutBadSetParameters(error); 761 // Just don't do this; it's stupid. 762 paramsCopy.mEncodings = oldParams->mEncodings; 763 } 764 765 if (!(oldParams->mCodecs == paramsCopy.mCodecs)) { 766 nsCString error("RTCRtpParameters.codecs is a read-only parameter"); 767 if (!mAllowOldSetParameters) { 768 p->MaybeRejectWithInvalidModificationError(error); 769 return p.forget(); 770 } 771 WarnAboutBadSetParameters(error); 772 } 773 774 // Coverts a list of JsepCodecDescription to a list of 775 // dom::RTCRtpCodecParameters 776 auto toDomCodecParametersList = 777 [](const std::vector<UniquePtr<JsepCodecDescription>>& aJsepCodec) 778 -> dom::Sequence<RTCRtpCodecParameters> { 779 dom::Sequence<RTCRtpCodecParameters> codecs; 780 for (const auto& codec : aJsepCodec) { 781 if (codec) { 782 auto type = codec->Type(); 783 std::string typeStr; 784 switch (type) { 785 case SdpMediaSection::MediaType::kApplication: 786 typeStr = "application"; 787 break; 788 case SdpMediaSection::MediaType::kAudio: 789 typeStr = "audio"; 790 break; 791 case SdpMediaSection::MediaType::kVideo: 792 typeStr = "video"; 793 break; 794 case SdpMediaSection::MediaType::kMessage: 795 typeStr = "message"; 796 break; 797 case SdpMediaSection::MediaType::kText: 798 typeStr = "text"; 799 break; 800 default: 801 MOZ_CRASH("Unexpected SdpMediaSection::MediaType"); 802 }; 803 if (typeStr == "audio" || typeStr == "video") { 804 dom::RTCRtpCodecParameters domCodec; 805 RTCRtpTransceiver::ToDomRtpCodecParameters(*codec, &domCodec); 806 (void)codecs.AppendElement(domCodec, fallible); 807 } 808 } 809 } 810 return codecs; 811 }; 812 813 // TODO: Verify remaining read-only parameters 814 // headerExtensions (bug 1765851) 815 // rtcp (bug 1765852) 816 817 // CheckAndRectifyEncodings handles the following steps: 818 // If transceiver kind is "audio", remove the scaleResolutionDownBy member 819 // from all encodings that contain one. 820 // 821 // If transceiver kind is "video", and any encoding in encodings contains a 822 // scaleResolutionDownBy member whose value is less than 1.0, return a 823 // promise rejected with a newly created RangeError. 824 // 825 // Verify that each encoding in encodings has a maxFramerate member whose 826 // value is greater than or equal to 0.0. If one of the maxFramerate values 827 // does not meet this requirement, return a promise rejected with a newly 828 // created RangeError. 829 830 auto choosableCodecs = 831 mParameters.mCodecs.WasPassed() && mParameters.mCodecs.Value().Length() 832 ? mParameters.mCodecs.Value() 833 : Sequence<RTCRtpCodecParameters>(); 834 if (choosableCodecs.Length() == 0) { 835 // If choosableCodecs is still an empty list, set choosableCodecs to the 836 // list of implemented send codecs for transceiver's kind. 837 std::vector<UniquePtr<JsepCodecDescription>> codecs; 838 if (mTransceiver->IsVideo()) { 839 auto useRtx = 840 Preferences::GetBool("media.peerconnection.video.use_rtx", false) 841 ? OverrideRtxPreference::OverrideWithEnabled 842 : OverrideRtxPreference::OverrideWithDisabled; 843 PeerConnectionImpl::GetDefaultVideoCodecs(codecs, useRtx); 844 } else { 845 PeerConnectionImpl::GetDefaultAudioCodecs(codecs); 846 } 847 choosableCodecs = toDomCodecParametersList(codecs); 848 } 849 ErrorResult rv; 850 CheckAndRectifyEncodings(paramsCopy.mEncodings, mTransceiver->IsVideo(), 851 dom::Optional(choosableCodecs), true, false, 852 MatchGetCapabilities::NO, rv); 853 if (rv.Failed()) { 854 if (!mHaveFailedBecauseOtherError) { 855 mHaveFailedBecauseOtherError = true; 856 mozilla::glean::rtcrtpsender_setparameters::fail_other.AddToNumerator(1); 857 } 858 p->MaybeReject(std::move(rv)); 859 return p.forget(); 860 } 861 862 // If transceiver kind is "video", then for each encoding in encodings that 863 // doesn't contain a scaleResolutionDownBy member, add a 864 // scaleResolutionDownBy member with the value 1.0. 865 if (mTransceiver->IsVideo()) { 866 for (auto& encoding : paramsCopy.mEncodings) { 867 if (!encoding.mScaleResolutionDownBy.WasPassed()) { 868 encoding.mScaleResolutionDownBy.Construct(1.0); 869 } 870 } 871 } 872 873 // Let p be a new promise. (see above) 874 875 // In parallel, configure the media stack to use parameters to transmit 876 // sender.[[SenderTrack]]. 877 // Right now this is infallible. That may change someday. 878 879 // We need to put this in a member variable, since MaybeUpdateConduit needs it 880 // This also allows PeerConnectionImpl to detect when there is a pending 881 // setParameters, which has implcations for the handling of 882 // setRemoteDescription. 883 mPendingRidChangeFromCompatMode = pendingRidChangeFromCompatMode; 884 mPendingParameters = Some(paramsCopy); 885 uint32_t serialNumber = ++mNumSetParametersCalls; 886 MaybeUpdateConduit(); 887 888 // If we have a degradation value passed convert it from a 889 // dom::RTCDegradationPreference to a webrtc::DegradationPreference and set 890 // mVideoDegradationPreference which will trigger the onconfigure change in 891 // the videoConduit. 892 if (paramsCopy.mDegradationPreference.WasPassed()) { 893 const auto degradationPreference = [&] { 894 switch (paramsCopy.mDegradationPreference.Value()) { 895 case mozilla::dom::RTCDegradationPreference::Balanced: 896 return webrtc::DegradationPreference::BALANCED; 897 case mozilla::dom::RTCDegradationPreference::Maintain_framerate: 898 return webrtc::DegradationPreference::MAINTAIN_FRAMERATE; 899 case mozilla::dom::RTCDegradationPreference::Maintain_resolution: 900 return webrtc::DegradationPreference::MAINTAIN_RESOLUTION; 901 } 902 MOZ_CRASH("Unexpected RTCDegradationPreference"); 903 }; 904 mVideoDegradationPreference = degradationPreference(); 905 } else { 906 // Default to disabled when unset to allow for correct degradation 907 mVideoDegradationPreference = webrtc::DegradationPreference::DISABLED; 908 } 909 910 // If the media stack is successfully configured with parameters, 911 // queue a task to run the following steps: 912 GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( 913 __func__, 914 [this, self = RefPtr<RTCRtpSender>(this), p, paramsCopy, serialNumber] { 915 // Set sender.[[LastReturnedParameters]] to null. 916 mLastReturnedParameters = Nothing(); 917 // Set sender.[[SendEncodings]] to parameters.encodings. 918 mParameters.mEncodings = paramsCopy.mEncodings; 919 UpdateRestorableEncodings(mParameters.mEncodings); 920 // If we had a valid degradation preference passed in store it in 921 // mParameters so we can return it if needed via GetParameters calls. 922 mParameters.mDegradationPreference.Reset(); 923 if (paramsCopy.mDegradationPreference.WasPassed()) { 924 mParameters.mDegradationPreference.Construct( 925 paramsCopy.mDegradationPreference.Value()); 926 } 927 // Only clear mPendingParameters if it matches; there could have been 928 // back-to-back calls to setParameters, and we only want to clear this 929 // if no subsequent setParameters is pending. 930 if (serialNumber == mNumSetParametersCalls) { 931 mPendingParameters = Nothing(); 932 // Ok, nothing has called SyncToJsep while this async task was 933 // pending. No need for special handling anymore. 934 mPendingRidChangeFromCompatMode = false; 935 } 936 MOZ_ASSERT(mParameters.mEncodings.Length()); 937 // Resolve p with undefined. 938 p->MaybeResolveWithUndefined(); 939 })); 940 941 // Return p. 942 return p.forget(); 943 } 944 945 using FmtpParamKey = nsString; 946 using FmtpParamValue = nsString; 947 948 // Helper type for the codec dictionary matching functions. 949 // This stores the level & (h264) subprofile separately from the parameter set. 950 // Which parameters are considered part of the level is codec-specific. 951 // All of the matching logic is kept out of this struct, instead it all lives 952 // in the DoesCodecParameterMatchCodec function. This is to make it easier to 953 // read against the spec. 954 struct ParametersAndLevel { 955 Maybe<std::set<std::tuple<FmtpParamKey, FmtpParamValue>>> mSet = Nothing(); 956 Maybe<uint32_t> mLevel = Nothing(); 957 Maybe<uint32_t> mSubprofile = Nothing(); 958 959 // Helper function to get the default level for a codec. 960 static Maybe<uint32_t> DefaultLevelForCodec(const nsString& aMimeType) { 961 // AV1 has a defined default level-idx of 5, which is omittable by spec. 962 if (aMimeType.LowerCaseEqualsASCII("video/av1")) { 963 return Some(5); 964 } 965 // https://datatracker.ietf.org/doc/html/rfc6184 defines a default value 966 // for this parameter, 0x420010. 967 if (aMimeType.LowerCaseEqualsASCII("video/h264")) { 968 return Some(JsepVideoCodecDescription::GetSaneH264Level(0x420010)); 969 } 970 // VP8 and VP9 are not defined to have a level parameter. Though they do 971 // have a profile-id, which doesn't seem to be used in our negotiation. 972 if (aMimeType.LowerCaseEqualsASCII("video/vp8") || 973 aMimeType.LowerCaseEqualsASCII("video/vp9")) { 974 return Nothing(); // A little pedantic... 975 } 976 return Nothing(); 977 } 978 979 static Maybe<uint32_t> DefaultSubprofileForCodec(const nsString& aMimeType) { 980 // See DefaultLevelCodec for comments on the default level. 981 if (aMimeType.LowerCaseEqualsASCII("video/h264")) { 982 return Some(JsepVideoCodecDescription::GetSubprofile(0x420010)); 983 } 984 return Nothing(); 985 } 986 987 // A helper function to extract the level from a parameter set in a codec- 988 // specific way. If the level cannot be extracted, Nothing() is returned. 989 static Maybe<uint32_t> ExtractLevel(const nsString& aMimeType, 990 const FmtpParamKey& aKey, 991 const FmtpParamValue& aValue) { 992 if (aMimeType.LowerCaseEqualsASCII("video/h264") && 993 aKey.LowerCaseEqualsASCII("profile-level-id")) { 994 // The level is the last two characters of the value. 995 nsresult rv; 996 // Note the radix 997 auto val = aValue.ToUnsignedInteger(&rv, 16); 998 if (NS_FAILED(rv)) { 999 return Nothing(); 1000 } 1001 return Some(JsepVideoCodecDescription::GetSaneH264Level(val)); 1002 } 1003 if (aMimeType.LowerCaseEqualsASCII("video/av1") && 1004 aKey.EqualsLiteral("level-idx")) { 1005 nsresult rv; 1006 auto val = aValue.ToUnsignedInteger(&rv); 1007 if (NS_FAILED(rv)) { 1008 return Nothing(); 1009 } 1010 return Some(val); 1011 } 1012 return Nothing(); 1013 } 1014 1015 // Helper function to get the subprofile for a codec. 1016 static Maybe<uint32_t> ExtractSubprofile(const nsString& aMimeType, 1017 const FmtpParamKey& aKey, 1018 const FmtpParamValue& aValue) { 1019 if (aMimeType.LowerCaseEqualsASCII("video/h264") && 1020 aKey.EqualsLiteral("profile-level-id")) { 1021 nsresult rv; 1022 auto val = aValue.ToUnsignedInteger(&rv, 16); 1023 if (NS_FAILED(rv)) { 1024 return Nothing(); 1025 } 1026 return Some(JsepVideoCodecDescription::GetSubprofile(val)); 1027 } 1028 return Nothing(); 1029 } 1030 }; 1031 1032 // We can not directly compare H264 or AV1 FMTP parameter sets, since the level 1033 // and subprofile information must be treated seperately as a hiearchical value. 1034 // So we need to seperate the regular parameters from profile-level-id for 1035 // H264, and levelidx for AV1. This is done by parsing the FMTP line into a set 1036 // of key-value pairs and a level/subprofile value. If the FMTP line is not in 1037 // a key-value pair format, then we return an empty parameter set. 1038 ParametersAndLevel FmtpToParametersAndLevel(const nsString& aMimeType, 1039 const nsString& aFmtp) { 1040 auto resultParams = std::set<std::tuple<FmtpParamKey, FmtpParamValue>>(); 1041 Maybe<uint32_t> resultLevel = Nothing(); 1042 Maybe<uint32_t> resultSubprofile = Nothing(); 1043 nsTArray<nsString> parts; 1044 for (const auto& kvp : aFmtp.Split(';')) { 1045 auto parts = nsTArray<nsString>(); 1046 for (const auto& part : kvp.Split('=')) { 1047 parts.AppendElement(part); 1048 } 1049 // To be a valid key-value pair, there must be exactly two parts. 1050 if (parts.Length() == 2) { 1051 // Check to see if it is the level parameter. 1052 auto level = 1053 ParametersAndLevel::ExtractLevel(aMimeType, parts[0], parts[1]); 1054 if (level.isNothing()) { 1055 // If it is not the level parameter, then it is a regular parameter. 1056 // We store the key-value pair in the result parameter set. 1057 resultParams.insert(std::make_tuple(parts[0], parts[1])); 1058 } else { 1059 // It is the level parameter, so we do not store it in the result 1060 // parameter set. Instead we store it in the result level, and 1061 // subprofile (if provided). 1062 resultSubprofile = ParametersAndLevel::ExtractSubprofile( 1063 aMimeType, parts[0], parts[1]); 1064 // Store the level separately 1065 resultLevel = level; 1066 } 1067 } else { 1068 // This is not a valid key-value pair FMTP line, so we do not have 1069 // parameters. 1070 return ParametersAndLevel{ 1071 .mSet = Nothing(), 1072 .mLevel = resultLevel.orElse([&]() -> Maybe<uint32_t> { 1073 return ParametersAndLevel::DefaultLevelForCodec(aMimeType); 1074 }), 1075 .mSubprofile = resultSubprofile, 1076 }; 1077 } 1078 } 1079 return ParametersAndLevel{ 1080 .mSet = Some(resultParams), 1081 .mLevel = resultLevel.orElse([&]() -> Maybe<uint32_t> { 1082 return ParametersAndLevel::DefaultLevelForCodec(aMimeType); 1083 }), 1084 .mSubprofile = resultSubprofile, 1085 }; 1086 } 1087 1088 bool DoesCodecParameterMatchCodec(const RTCRtpCodec& aCodec1, 1089 const RTCRtpCodec& aCodec2, 1090 const bool aIgnoreLevels) { 1091 const auto compare = [](const nsString& aStr1, const nsString& aStr2) { 1092 return NS_LossyConvertUTF16toASCII(aStr1).EqualsIgnoreCase( 1093 NS_LossyConvertUTF16toASCII(aStr2).get()); 1094 }; 1095 if (!compare(aCodec1.mMimeType, aCodec2.mMimeType)) { 1096 return false; 1097 } 1098 if (aCodec1.mClockRate != aCodec2.mClockRate) { 1099 return false; 1100 } 1101 1102 if (aCodec1.mChannels != aCodec2.mChannels) { 1103 return false; 1104 } 1105 // To match both or neither should have a sdpFmtpLine. 1106 if (aCodec1.mSdpFmtpLine.WasPassed() != aCodec2.mSdpFmtpLine.WasPassed()) { 1107 return false; 1108 } 1109 // If both have a sdpFmtpLine, compare them, and conditionally take levels 1110 // into account. 1111 if (aCodec1.mSdpFmtpLine.WasPassed() && aCodec2.mSdpFmtpLine.WasPassed()) { 1112 // Get the key-value pairs from the sdpFmtpLine if they are in a key-value 1113 // pair format. 1114 const auto pset1 = FmtpToParametersAndLevel(aCodec1.mMimeType, 1115 aCodec1.mSdpFmtpLine.Value()); 1116 const auto pset2 = FmtpToParametersAndLevel(aCodec2.mMimeType, 1117 aCodec2.mSdpFmtpLine.Value()); 1118 if (pset1.mSet.isNothing() || pset2.mSet.isNothing()) { 1119 // If either or both are not in a key-value pair format, they should be 1120 // compared using string equality. 1121 if (aCodec1.mSdpFmtpLine != aCodec2.mSdpFmtpLine) { 1122 return false; 1123 } 1124 } else { 1125 // If both are in a key-value pair format, compare the key-value pairs. 1126 const auto& set1 = pset1.mSet.value(); 1127 const auto& set2 = pset2.mSet.value(); 1128 1129 if (set1.size() != set2.size()) { 1130 return false; 1131 } 1132 if (!aIgnoreLevels && (pset1.mLevel != pset2.mLevel || 1133 pset1.mSubprofile != pset2.mSubprofile)) { 1134 return false; 1135 } 1136 // Compare pair-wise the two parameter sets. 1137 for (const auto& pair : set1) { 1138 if (set2.find(pair) == set2.end()) { 1139 return false; 1140 } 1141 } 1142 } 1143 } 1144 return true; 1145 } 1146 1147 // static 1148 void RTCRtpSender::CheckAndRectifyEncodings( 1149 Sequence<RTCRtpEncodingParameters>& aEncodings, bool aVideo, 1150 const Optional<Sequence<RTCRtpCodecParameters>>& aCodecs, 1151 const bool aIgnoreLevels, const bool aCodecErasure, 1152 const MatchGetCapabilities aMatchGetCapabilities, ErrorResult& aRv) { 1153 // If any encoding contains a rid member whose value does not conform to the 1154 // grammar requirements specified in Section 10 of [RFC8851], throw a 1155 // TypeError. 1156 for (const auto& encoding : aEncodings) { 1157 if (encoding.mRid.WasPassed()) { 1158 std::string utf8Rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get(); 1159 std::string error; 1160 if (!SdpRidAttributeList::CheckRidValidity(utf8Rid, &error)) { 1161 aRv.ThrowTypeError(nsCString(error)); 1162 return; 1163 } 1164 if (utf8Rid.size() > SdpRidAttributeList::kMaxRidLength) { 1165 std::ostringstream ss; 1166 ss << "Rid can be at most " << SdpRidAttributeList::kMaxRidLength 1167 << " characters long (due to internal limitations)"; 1168 aRv.ThrowTypeError(nsCString(ss.str())); 1169 return; 1170 } 1171 } 1172 } 1173 1174 // Post-negotiation we should have a list of aCodecs, and for any encoding in 1175 // aEncodings that is using a codec that is not in aCodecs, we erase the codec 1176 // field from the encoding during set session description. 1177 // Yes. Really. This is what the spec says. 1178 // https://w3c.github.io/webrtc-pc/#set-the-session-description 4.6.13 1179 // Let codecs be transceiver.[[Sender]].[[SendCodecs]]. 1180 // If codecs is not an empty list: 1181 // For each encoding in transceiver.[[Sender]].[[SendEncodings]], if 1182 // encoding.codec does not match any entry in codecs, using the codec 1183 // dictionary match algorithm with ignoreLevels set to true, remove 1184 // encoding.codec. 1185 if (aCodecs.WasPassed() && aCodecs.Value().Length()) { 1186 for (auto& encoding : aEncodings) { 1187 if (encoding.mCodec.WasPassed()) { 1188 bool matched = false; 1189 for (const auto& codec : aCodecs.Value()) { 1190 if (DoesCodecParameterMatchCodec(encoding.mCodec.Value(), codec, 1191 aIgnoreLevels)) { 1192 matched = true; 1193 break; 1194 } 1195 } 1196 if (!matched) { 1197 if (aCodecErasure) { 1198 encoding.mCodec.Reset(); 1199 } else { 1200 std::stringstream ss; 1201 ss << "Codec " << encoding.mCodec.Value().mMimeType 1202 << " not found in send codecs"; 1203 aRv.ThrowInvalidModificationError(nsCString(ss.str())); 1204 return; 1205 } 1206 } 1207 } 1208 } 1209 } 1210 1211 // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver 1212 // Step 8.3 1213 // If any encoding contains a codec member whose value does not match any 1214 // codec in RTCRtpSender.getCapabilities(kind).codecs, throw an 1215 // OperationError. 1216 if (aMatchGetCapabilities == MatchGetCapabilities::YES) { 1217 MOZ_ASSERT(aCodecs.WasPassed(), 1218 "aCodecs must be passed if aMatchGetCapabilities is YES"); 1219 1220 bool found = false; 1221 for (const auto& encoding : aEncodings) { 1222 if (encoding.mCodec.WasPassed()) { 1223 for (const auto& codec : aCodecs.Value()) { 1224 if (DoesCodecParameterMatchCodec(encoding.mCodec.Value(), codec, 1225 aIgnoreLevels)) { 1226 found = true; 1227 break; 1228 } 1229 } 1230 if (!found) { 1231 aRv.ThrowOperationError("Codec not found in codecs"); 1232 return; 1233 } 1234 } 1235 } 1236 } 1237 1238 if (aEncodings.Length() > 1) { 1239 // If some but not all encodings contain a rid member, throw a TypeError. 1240 // rid must be set if there is more than one encoding 1241 // NOTE: Since rid is read-only, and the number of encodings cannot grow, 1242 // this should never happen in setParameters. 1243 for (const auto& encoding : aEncodings) { 1244 if (!encoding.mRid.WasPassed()) { 1245 aRv.ThrowTypeError("Missing rid"); 1246 return; 1247 } 1248 } 1249 1250 // If any encoding contains a rid member whose value is the same as that of 1251 // a rid contained in another encoding in sendEncodings, throw a TypeError. 1252 // NOTE: Since rid is read-only, and the number of encodings cannot grow, 1253 // this should never happen in setParameters. 1254 std::set<nsString> uniqueRids; 1255 for (const auto& encoding : aEncodings) { 1256 if (uniqueRids.count(encoding.mRid.Value())) { 1257 aRv.ThrowTypeError("Duplicate rid"); 1258 return; 1259 } 1260 uniqueRids.insert(encoding.mRid.Value()); 1261 } 1262 } 1263 // TODO: ptime/adaptivePtime validation (bug 1733647) 1264 1265 // If kind is "audio", remove the scaleResolutionDownBy member from all 1266 // encodings that contain one. 1267 if (!aVideo) { 1268 for (auto& encoding : aEncodings) { 1269 if (encoding.mScaleResolutionDownBy.WasPassed()) { 1270 encoding.mScaleResolutionDownBy.Reset(); 1271 } 1272 if (encoding.mMaxFramerate.WasPassed()) { 1273 encoding.mMaxFramerate.Reset(); 1274 } 1275 } 1276 } 1277 1278 // If any encoding contains a scaleResolutionDownBy member whose value is 1279 // less than 1.0, throw a RangeError. 1280 for (const auto& encoding : aEncodings) { 1281 if (encoding.mScaleResolutionDownBy.WasPassed()) { 1282 if (encoding.mScaleResolutionDownBy.Value() < 1.0f) { 1283 aRv.ThrowRangeError("scaleResolutionDownBy must be >= 1.0"); 1284 return; 1285 } 1286 } 1287 } 1288 1289 // Verify that the value of each maxFramerate member in sendEncodings that is 1290 // defined is greater than 0.0. If one of the maxFramerate values does not 1291 // meet this requirement, throw a RangeError. 1292 for (const auto& encoding : aEncodings) { 1293 if (encoding.mMaxFramerate.WasPassed()) { 1294 if (encoding.mMaxFramerate.Value() < 0.0f) { 1295 aRv.ThrowRangeError("maxFramerate must be non-negative"); 1296 return; 1297 } 1298 } 1299 } 1300 } 1301 1302 void RTCRtpSender::GetParameters(RTCRtpSendParameters& aParameters) { 1303 MOZ_ASSERT(mParameters.mEncodings.Length()); 1304 // If sender.[[LastReturnedParameters]] is not null, return 1305 // sender.[[LastReturnedParameters]], and abort these steps. 1306 if (mLastReturnedParameters.isSome()) { 1307 aParameters = *mLastReturnedParameters; 1308 return; 1309 } 1310 1311 // Let result be a new RTCRtpSendParameters dictionary constructed as follows: 1312 1313 // transactionId is set to a new unique identifier 1314 aParameters.mTransactionId.Construct(mPc->GenerateUUID()); 1315 1316 // encodings is set to the value of the [[SendEncodings]] internal slot. 1317 aParameters.mEncodings = mParameters.mEncodings; 1318 1319 // The headerExtensions sequence is populated based on the header extensions 1320 // that have been negotiated for sending 1321 // TODO(bug 1765851): We do not support this yet 1322 // aParameters.mHeaderExtensions.Construct(); 1323 1324 // rtcp.cname is set to the CNAME of the associated RTCPeerConnection. 1325 // rtcp.reducedSize is set to true if reduced-size RTCP has been negotiated 1326 // for sending, and false otherwise. 1327 // TODO(bug 1765852): We do not support this yet 1328 aParameters.mRtcp.Construct(); 1329 aParameters.mRtcp.Value().mCname.Construct(); 1330 aParameters.mRtcp.Value().mReducedSize.Construct(false); 1331 if (mParameters.mDegradationPreference.WasPassed()) { 1332 aParameters.mDegradationPreference.Construct( 1333 mParameters.mDegradationPreference.Value()); 1334 } 1335 aParameters.mHeaderExtensions.Construct(); 1336 if (mParameters.mCodecs.WasPassed()) { 1337 aParameters.mCodecs.Construct(mParameters.mCodecs.Value()); 1338 } 1339 1340 // Set sender.[[LastReturnedParameters]] to result. 1341 mLastReturnedParameters = Some(aParameters); 1342 1343 // Used to help with warning strings 1344 mLastTransactionId = Some(aParameters.mTransactionId.Value()); 1345 1346 // Queue a task that sets sender.[[LastReturnedParameters]] to null. 1347 GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( 1348 __func__, [this, self = RefPtr<RTCRtpSender>(this)] { 1349 mLastReturnedParameters = Nothing(); 1350 })); 1351 } 1352 1353 bool operator==(const RTCRtpEncodingParameters& a1, 1354 const RTCRtpEncodingParameters& a2) { 1355 // webidl does not generate types that are equality comparable 1356 return a1.mActive == a2.mActive && a1.mMaxBitrate == a2.mMaxBitrate && 1357 a1.mMaxFramerate == a2.mMaxFramerate && a1.mPriority == a2.mPriority && 1358 a1.mRid == a2.mRid && 1359 a1.mScaleResolutionDownBy == a2.mScaleResolutionDownBy; 1360 } 1361 1362 // static 1363 void RTCRtpSender::ApplyJsEncodingToConduitEncoding( 1364 const RTCRtpEncodingParameters& aJsEncoding, 1365 VideoCodecConfig::Encoding* aConduitEncoding) { 1366 aConduitEncoding->active = aJsEncoding.mActive; 1367 if (aJsEncoding.mMaxBitrate.WasPassed()) { 1368 aConduitEncoding->constraints.maxBr = aJsEncoding.mMaxBitrate.Value(); 1369 } 1370 if (aJsEncoding.mMaxFramerate.WasPassed()) { 1371 aConduitEncoding->constraints.maxFps = 1372 Some(aJsEncoding.mMaxFramerate.Value()); 1373 } 1374 if (aJsEncoding.mScaleResolutionDownBy.WasPassed()) { 1375 // Optional does not have a valueOr, despite being based on Maybe 1376 // :( 1377 aConduitEncoding->constraints.scaleDownBy = 1378 aJsEncoding.mScaleResolutionDownBy.Value(); 1379 } else { 1380 aConduitEncoding->constraints.scaleDownBy = 1.0f; 1381 } 1382 } 1383 1384 void RTCRtpSender::UpdateRestorableEncodings( 1385 const Sequence<RTCRtpEncodingParameters>& aEncodings) { 1386 MOZ_ASSERT(aEncodings.Length()); 1387 1388 if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) { 1389 // Once initial negotiation completes, we are no longer allowed to restore 1390 // the unicast encoding. 1391 mUnicastEncoding.reset(); 1392 } else if (mParameters.mEncodings.Length() == 1 && 1393 !mParameters.mEncodings[0].mRid.WasPassed()) { 1394 // If we have not completed the initial negotiation, and we currently are 1395 // ridless unicast, we need to save our unicast encoding in case a 1396 // rollback occurs. 1397 mUnicastEncoding = Some(mParameters.mEncodings[0]); 1398 } 1399 } 1400 1401 Sequence<RTCRtpEncodingParameters> RTCRtpSender::ToSendEncodings( 1402 const std::vector<std::string>& aRids) const { 1403 MOZ_ASSERT(!aRids.empty()); 1404 1405 Sequence<RTCRtpEncodingParameters> result; 1406 // If sendEncodings is given as input to this algorithm, and is non-empty, 1407 // set the [[SendEncodings]] slot to sendEncodings. 1408 for (const auto& rid : aRids) { 1409 MOZ_ASSERT(!rid.empty()); 1410 RTCRtpEncodingParameters encoding; 1411 encoding.mActive = true; 1412 encoding.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str())); 1413 (void)result.AppendElement(encoding, fallible); 1414 } 1415 1416 // If sendEncodings is non-empty, set each encoding's scaleResolutionDownBy 1417 // to 2^(length of sendEncodings - encoding index - 1). 1418 if (mTransceiver->IsVideo()) { 1419 double scale = 1.0f; 1420 for (auto it = result.rbegin(); it != result.rend(); ++it) { 1421 it->mScaleResolutionDownBy.Construct(scale); 1422 scale *= 2; 1423 } 1424 } 1425 1426 return result; 1427 } 1428 1429 void RTCRtpSender::MaybeGetJsepRids() { 1430 MOZ_ASSERT(!mSimulcastEnvelopeSet); 1431 MOZ_ASSERT(mParameters.mEncodings.Length()); 1432 1433 auto jsepRids = GetJsepTransceiver().mSendTrack.GetRids(); 1434 if (!jsepRids.empty()) { 1435 UpdateRestorableEncodings(mParameters.mEncodings); 1436 if (jsepRids.size() != 1 || !jsepRids[0].empty()) { 1437 // JSEP is using at least one rid. Stomp our single ridless encoding 1438 mParameters.mEncodings = ToSendEncodings(jsepRids); 1439 } 1440 mSimulcastEnvelopeSet = true; 1441 mSimulcastEnvelopeSetByJSEP = true; 1442 } 1443 } 1444 1445 Sequence<RTCRtpEncodingParameters> RTCRtpSender::GetMatchingEncodings( 1446 const std::vector<std::string>& aRids) const { 1447 Sequence<RTCRtpEncodingParameters> result; 1448 1449 if (!aRids.empty() && !aRids[0].empty()) { 1450 // Simulcast, or unicast with rid 1451 for (const auto& encoding : mParameters.mEncodings) { 1452 for (const auto& rid : aRids) { 1453 auto utf16Rid = NS_ConvertUTF8toUTF16(rid.c_str()); 1454 if (!encoding.mRid.WasPassed() || (utf16Rid == encoding.mRid.Value())) { 1455 auto encodingCopy(encoding); 1456 if (!encodingCopy.mRid.WasPassed()) { 1457 encodingCopy.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str())); 1458 } 1459 (void)result.AppendElement(encodingCopy, fallible); 1460 break; 1461 } 1462 } 1463 } 1464 } 1465 1466 // If we're allowing the old setParameters behavior, we _might_ be able to 1467 // get into this situation even if there were rids above. Be extra careful. 1468 // Under normal circumstances, this just handles the ridless case. 1469 if (!result.Length()) { 1470 // Unicast with no specified rid. Restore mUnicastEncoding, if 1471 // it exists, otherwise pick the first encoding. 1472 if (mUnicastEncoding.isSome()) { 1473 (void)result.AppendElement(*mUnicastEncoding, fallible); 1474 } else { 1475 (void)result.AppendElement(mParameters.mEncodings[0], fallible); 1476 } 1477 } 1478 1479 return result; 1480 } 1481 1482 void RTCRtpSender::SetStreams( 1483 const Sequence<OwningNonNull<DOMMediaStream>>& aStreams, ErrorResult& aRv) { 1484 if (mPc->IsClosed()) { 1485 aRv.ThrowInvalidStateError( 1486 "Cannot call setStreams if the peer connection is closed"); 1487 return; 1488 } 1489 1490 SetStreamsImpl(aStreams); 1491 mPc->UpdateNegotiationNeeded(); 1492 } 1493 1494 void RTCRtpSender::SetStreamsImpl( 1495 const Sequence<OwningNonNull<DOMMediaStream>>& aStreams) { 1496 mStreams.Clear(); 1497 std::set<nsString> ids; 1498 for (const auto& stream : aStreams) { 1499 nsString id; 1500 stream->GetId(id); 1501 if (!ids.count(id)) { 1502 ids.insert(id); 1503 mStreams.AppendElement(stream); 1504 } 1505 } 1506 } 1507 1508 void RTCRtpSender::GetStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreams) { 1509 aStreams = mStreams.Clone(); 1510 } 1511 1512 class ReplaceTrackOperation final : public PeerConnectionImpl::Operation { 1513 public: 1514 ReplaceTrackOperation(PeerConnectionImpl* aPc, 1515 const RefPtr<RTCRtpTransceiver>& aTransceiver, 1516 const RefPtr<MediaStreamTrack>& aTrack, 1517 ErrorResult& aError); 1518 NS_DECL_ISUPPORTS_INHERITED 1519 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ReplaceTrackOperation, 1520 PeerConnectionImpl::Operation) 1521 1522 private: 1523 MOZ_CAN_RUN_SCRIPT 1524 RefPtr<dom::Promise> CallImpl(ErrorResult& aError) override; 1525 ~ReplaceTrackOperation() = default; 1526 RefPtr<RTCRtpTransceiver> mTransceiver; 1527 RefPtr<MediaStreamTrack> mNewTrack; 1528 }; 1529 1530 NS_IMPL_CYCLE_COLLECTION_INHERITED(ReplaceTrackOperation, 1531 PeerConnectionImpl::Operation, mTransceiver, 1532 mNewTrack) 1533 1534 NS_IMPL_ADDREF_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation) 1535 NS_IMPL_RELEASE_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation) 1536 1537 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReplaceTrackOperation) 1538 NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation) 1539 1540 ReplaceTrackOperation::ReplaceTrackOperation( 1541 PeerConnectionImpl* aPc, const RefPtr<RTCRtpTransceiver>& aTransceiver, 1542 const RefPtr<MediaStreamTrack>& aTrack, ErrorResult& aError) 1543 : PeerConnectionImpl::Operation(aPc, aError), 1544 mTransceiver(aTransceiver), 1545 mNewTrack(aTrack) {} 1546 1547 RefPtr<dom::Promise> ReplaceTrackOperation::CallImpl(ErrorResult& aError) { 1548 RefPtr<RTCRtpSender> sender = mTransceiver->Sender(); 1549 // If transceiver.[[Stopping]] is true, return a promise rejected with a 1550 // newly created InvalidStateError. 1551 if (mTransceiver->Stopped() || mTransceiver->Stopping()) { 1552 RefPtr<dom::Promise> error = sender->MakePromise(aError); 1553 if (aError.Failed()) { 1554 return nullptr; 1555 } 1556 MOZ_LOG(gSenderLog, LogLevel::Debug, 1557 ("%s Cannot call replaceTrack when transceiver is stopping", 1558 __FUNCTION__)); 1559 error->MaybeRejectWithInvalidStateError( 1560 "Cannot call replaceTrack when transceiver is stopping"); 1561 return error; 1562 } 1563 1564 // Let p be a new promise. 1565 RefPtr<dom::Promise> p = sender->MakePromise(aError); 1566 if (aError.Failed()) { 1567 return nullptr; 1568 } 1569 1570 if (!sender->SeamlessTrackSwitch(mNewTrack)) { 1571 MOZ_LOG(gSenderLog, LogLevel::Info, 1572 ("%s Could not seamlessly replace track", __FUNCTION__)); 1573 p->MaybeRejectWithInvalidModificationError( 1574 "Could not seamlessly replace track"); 1575 return p; 1576 } 1577 1578 // Queue a task that runs the following steps: 1579 GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( 1580 __func__, [p, sender, track = mNewTrack]() MOZ_CAN_RUN_SCRIPT_BOUNDARY { 1581 // If connection.[[IsClosed]] is true, abort these steps. 1582 // Set sender.[[SenderTrack]] to withTrack. 1583 if (sender->SetSenderTrackWithClosedCheck(track)) { 1584 // Resolve p with undefined. 1585 p->MaybeResolveWithUndefined(); 1586 } 1587 })); 1588 1589 // Return p. 1590 return p; 1591 } 1592 1593 already_AddRefed<dom::Promise> RTCRtpSender::ReplaceTrack( 1594 dom::MediaStreamTrack* aWithTrack, ErrorResult& aError) { 1595 // If withTrack is non-null and withTrack.kind differs from the transceiver 1596 // kind of transceiver, return a promise rejected with a newly created 1597 // TypeError. 1598 if (aWithTrack) { 1599 nsString newKind; 1600 aWithTrack->GetKind(newKind); 1601 nsString oldKind; 1602 mTransceiver->GetKind(oldKind); 1603 if (newKind != oldKind) { 1604 RefPtr<dom::Promise> error = MakePromise(aError); 1605 if (aError.Failed()) { 1606 return nullptr; 1607 } 1608 error->MaybeRejectWithTypeError( 1609 "Cannot replaceTrack with a different kind!"); 1610 return error.forget(); 1611 } 1612 } 1613 1614 MOZ_LOG(gSenderLog, LogLevel::Debug, 1615 ("%s[%s]: %s (%p to %p)", mPc->GetHandle().c_str(), GetMid().c_str(), 1616 __FUNCTION__, mSenderTrack.get(), aWithTrack)); 1617 1618 // Return the result of chaining the following steps to connection's 1619 // operations chain: 1620 RefPtr<PeerConnectionImpl::Operation> op = 1621 new ReplaceTrackOperation(mPc, mTransceiver, aWithTrack, aError); 1622 if (aError.Failed()) { 1623 return nullptr; 1624 } 1625 // Static analysis forces us to use a temporary. 1626 auto pc = mPc; 1627 return pc->Chain(op, aError); 1628 } 1629 1630 nsPIDOMWindowInner* RTCRtpSender::GetParentObject() const { return mWindow; } 1631 1632 already_AddRefed<dom::Promise> RTCRtpSender::MakePromise( 1633 ErrorResult& aError) const { 1634 return mPc->MakePromise(aError); 1635 } 1636 1637 bool RTCRtpSender::SeamlessTrackSwitch( 1638 const RefPtr<MediaStreamTrack>& aWithTrack) { 1639 // We do not actually update mSenderTrack here! Spec says that happens in a 1640 // queued task after this is done (this happens in 1641 // SetSenderTrackWithClosedCheck). 1642 1643 mPipeline->SetTrack(aWithTrack); 1644 1645 MaybeUpdateConduit(); 1646 1647 // There may eventually be cases where a renegotiation is necessary to switch. 1648 return true; 1649 } 1650 1651 void RTCRtpSender::SetTrack(const RefPtr<MediaStreamTrack>& aTrack) { 1652 // Used for RTCPeerConnection.removeTrack and RTCPeerConnection.addTrack 1653 if (mTransceiver->Stopping()) { 1654 return; 1655 } 1656 mSenderTrack = aTrack; 1657 SeamlessTrackSwitch(aTrack); 1658 if (aTrack) { 1659 // RFC says (in the section on remote rollback): 1660 // However, an RtpTransceiver MUST NOT be removed if a track was attached 1661 // to the RtpTransceiver via the addTrack method. 1662 mSenderTrackSetByAddTrack = true; 1663 } 1664 } 1665 1666 bool RTCRtpSender::SetSenderTrackWithClosedCheck( 1667 const RefPtr<MediaStreamTrack>& aTrack) { 1668 if (!mPc->IsClosed()) { 1669 mSenderTrack = aTrack; 1670 return true; 1671 } 1672 1673 return false; 1674 } 1675 1676 void RTCRtpSender::Shutdown() { 1677 MOZ_ASSERT(NS_IsMainThread()); 1678 mWatchManager.Shutdown(); 1679 mPipeline->Shutdown(); 1680 mPipeline = nullptr; 1681 if (mTransform) { 1682 mTransform->GetProxy().SetSender(nullptr); 1683 } 1684 } 1685 1686 void RTCRtpSender::BreakCycles() { 1687 mWindow = nullptr; 1688 mPc = nullptr; 1689 mSenderTrack = nullptr; 1690 mTransceiver = nullptr; 1691 mStreams.Clear(); 1692 mDtmf = nullptr; 1693 } 1694 1695 void RTCRtpSender::Unlink() { 1696 if (mTransceiver) { 1697 mTransceiver->Unlink(); 1698 } 1699 } 1700 1701 void RTCRtpSender::UpdateTransport() { 1702 MOZ_ASSERT(NS_IsMainThread()); 1703 if (!mHaveSetupTransport) { 1704 mPipeline->SetLevel(GetJsepTransceiver().GetLevel()); 1705 mHaveSetupTransport = true; 1706 } 1707 1708 mPipeline->UpdateTransport_m(GetJsepTransceiver().mTransport.mTransportId, 1709 nullptr, true); 1710 } 1711 1712 void RTCRtpSender::MaybeUpdateConduit() { 1713 // NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be 1714 // set to a non-zero value or the CreateVideo...Stream call will fail. 1715 if (NS_WARN_IF(GetJsepTransceiver().mSendTrack.GetSsrcs().empty())) { 1716 MOZ_ASSERT( 1717 false, 1718 "No local ssrcs! This is a bug in the jsep engine, and should never " 1719 "happen!"); 1720 return; 1721 } 1722 1723 if (!mPipeline) { 1724 return; 1725 } 1726 1727 bool wasTransmitting = mTransmitting; 1728 1729 if (mPipeline->mConduit->type() == MediaSessionConduit::VIDEO) { 1730 Maybe<VideoConfig> newConfig = GetNewVideoConfig(); 1731 if (newConfig.isSome()) { 1732 ApplyVideoConfig(*newConfig); 1733 } 1734 } else { 1735 Maybe<AudioConfig> newConfig = GetNewAudioConfig(); 1736 if (newConfig.isSome()) { 1737 ApplyAudioConfig(*newConfig); 1738 } 1739 } 1740 1741 if (!mSenderTrack && !wasTransmitting && mTransmitting) { 1742 MOZ_LOG(gSenderLog, LogLevel::Debug, 1743 ("%s[%s]: %s Starting transmit conduit without send track!", 1744 mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); 1745 } 1746 } 1747 1748 void RTCRtpSender::UpdateParametersCodecs() { 1749 mParameters.mCodecs.Reset(); 1750 mParameters.mCodecs.Construct(); 1751 1752 if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) { 1753 const JsepTrackNegotiatedDetails details( 1754 *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); 1755 if (details.GetEncodingCount()) { 1756 for (const auto& jsepCodec : details.GetEncoding(0).GetCodecs()) { 1757 if (!jsepCodec->mEnabled || 1758 !jsepCodec->DirectionSupported(sdp::kSend)) { 1759 // This codec is disabled or sending it is unsupported. 1760 continue; 1761 } 1762 RTCRtpCodecParameters codec; 1763 RTCRtpTransceiver::ToDomRtpCodecParameters(*jsepCodec, &codec); 1764 (void)mParameters.mCodecs.Value().AppendElement(codec, fallible); 1765 if (jsepCodec->Type() == SdpMediaSection::kVideo) { 1766 const JsepVideoCodecDescription& videoJsepCodec = 1767 static_cast<JsepVideoCodecDescription&>(*jsepCodec); 1768 // In our JSEP implementation, RTX is an addon to an existing codec, 1769 // not a codec object in its own right. webrtc-pc treats RTX as a 1770 // separate codec, however. 1771 if (videoJsepCodec.mRtxEnabled) { 1772 RTCRtpCodecParameters rtx; 1773 RTCRtpTransceiver::ToDomRtpCodecParametersRtx(videoJsepCodec, &rtx); 1774 (void)mParameters.mCodecs.Value().AppendElement(rtx, fallible); 1775 } 1776 } 1777 } 1778 } 1779 1780 // Check to see if we have a codec that is not in the codecs list. 1781 const auto& hasCodecMatch = 1782 [&](const RTCRtpEncodingParameters& param) -> bool { 1783 if (mParameters.mCodecs.WasPassed()) { 1784 for (const auto& codec : mParameters.mCodecs.Value()) { 1785 if (DoesCodecParameterMatchCodec(param.mCodec.Value(), codec, true)) { 1786 return true; 1787 } 1788 } 1789 } 1790 return false; 1791 }; 1792 // If we have a codec that is not in the codecs list, remove it from the 1793 // encoding. 1794 for (auto& encoding : mParameters.mEncodings) { 1795 if (encoding.mCodec.WasPassed() && !hasCodecMatch(encoding)) { 1796 encoding.mCodec.Reset(); 1797 } 1798 } 1799 } 1800 } 1801 1802 void RTCRtpSender::SyncFromJsep(const JsepTransceiver& aJsepTransceiver) { 1803 if (!mSimulcastEnvelopeSet) { 1804 // JSEP is establishing the simulcast envelope for the first time, right now 1805 // This is the addTrack (or addTransceiver without sendEncodings) case. 1806 MaybeGetJsepRids(); 1807 } else if (!aJsepTransceiver.mSendTrack.GetNegotiatedDetails() || 1808 !aJsepTransceiver.mSendTrack.IsInHaveRemote()) { 1809 // Spec says that we do not update our encodings until we're in stable, 1810 // _unless_ this is the first negotiation. 1811 std::vector<std::string> rids = aJsepTransceiver.mSendTrack.GetRids(); 1812 if (mSimulcastEnvelopeSetByJSEP && rids.empty()) { 1813 // JSEP previously set the simulcast envelope, but now it has no opinion 1814 // regarding unicast/simulcast. This can only happen on rollback of the 1815 // initial remote offer. 1816 mParameters.mEncodings = GetMatchingEncodings(rids); 1817 MOZ_ASSERT(mParameters.mEncodings.Length()); 1818 mSimulcastEnvelopeSetByJSEP = false; 1819 mSimulcastEnvelopeSet = false; 1820 } else if (!rids.empty()) { 1821 // JSEP has an opinion on the simulcast envelope, which trumps anything 1822 // we have already. 1823 mParameters.mEncodings = GetMatchingEncodings(rids); 1824 MOZ_ASSERT(mParameters.mEncodings.Length()); 1825 } 1826 } 1827 UpdateParametersCodecs(); 1828 1829 MaybeUpdateConduit(); 1830 } 1831 1832 void RTCRtpSender::SyncToJsep(JsepTransceiver& aJsepTransceiver) const { 1833 std::vector<std::string> streamIds; 1834 for (const auto& stream : mStreams) { 1835 nsString wideStreamId; 1836 stream->GetId(wideStreamId); 1837 std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get(); 1838 MOZ_ASSERT(!streamId.empty()); 1839 streamIds.push_back(streamId); 1840 } 1841 1842 aJsepTransceiver.mSendTrack.UpdateStreamIds(streamIds); 1843 1844 if (mSimulcastEnvelopeSet) { 1845 std::vector<std::string> rids; 1846 Maybe<RTCRtpSendParameters> parameters; 1847 if (mPendingRidChangeFromCompatMode) { 1848 // *sigh* If we have just let a setParameters change our rids, but we have 1849 // not yet updated mParameters because the queued task hasn't run yet, 1850 // we want to set the _new_ rids on the JsepTrack. So, we are forced to 1851 // grab them from mPendingParameters. 1852 parameters = mPendingParameters; 1853 } else { 1854 parameters = Some(mParameters); 1855 } 1856 for (const auto& encoding : parameters->mEncodings) { 1857 if (encoding.mRid.WasPassed()) { 1858 rids.push_back(NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get()); 1859 } else { 1860 rids.push_back(""); 1861 } 1862 } 1863 1864 aJsepTransceiver.mSendTrack.SetRids(rids); 1865 } 1866 1867 if (mTransceiver->IsVideo()) { 1868 aJsepTransceiver.mSendTrack.SetMaxEncodings(webrtc::kMaxSimulcastStreams); 1869 } else { 1870 aJsepTransceiver.mSendTrack.SetMaxEncodings(1); 1871 } 1872 1873 if (mSenderTrackSetByAddTrack) { 1874 aJsepTransceiver.SetOnlyExistsBecauseOfSetRemote(false); 1875 } 1876 } 1877 1878 template <typename CodecConfigT> 1879 CodecConfigT& findMatchingCodec( 1880 std::vector<CodecConfigT>& aCodecs, 1881 Maybe<const RTCRtpEncodingParameters&> aParameters) { 1882 MOZ_ASSERT(aCodecs.size()); 1883 1884 if (!aParameters || !aParameters->mCodec.WasPassed()) { 1885 return aCodecs[0]; 1886 } 1887 1888 for (auto& codec : aCodecs) { 1889 if (aParameters->mCodec.Value().mMimeType.EqualsIgnoreCase( 1890 codec.MimeType())) { 1891 return codec; 1892 } 1893 } 1894 1895 return aCodecs[0]; 1896 } 1897 1898 Maybe<RTCRtpSender::VideoConfig> RTCRtpSender::GetNewVideoConfig() { 1899 // It is possible for SDP to signal that there is a send track, but there not 1900 // actually be a send track, according to the specification; all that needs to 1901 // happen is for the transceiver to be configured to send... 1902 if (!GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) { 1903 return Nothing(); 1904 } 1905 1906 VideoConfig oldConfig; 1907 oldConfig.mSsrcs = mSsrcs; 1908 oldConfig.mLocalRtpExtensions = mLocalRtpExtensions; 1909 oldConfig.mCname = mCname; 1910 oldConfig.mTransmitting = mTransmitting; 1911 oldConfig.mVideoRtxSsrcs = mVideoRtxSsrcs; 1912 oldConfig.mVideoCodec = mVideoCodec; 1913 oldConfig.mVideoRtpRtcpConfig = mVideoRtpRtcpConfig; 1914 oldConfig.mVideoCodecMode = mVideoCodecMode; 1915 1916 VideoConfig newConfig(oldConfig); 1917 1918 UpdateBaseConfig(&newConfig); 1919 1920 newConfig.mVideoRtxSsrcs = GetJsepTransceiver().mSendTrack.GetRtxSsrcs(); 1921 1922 const JsepTrackNegotiatedDetails details( 1923 *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); 1924 1925 if (mSenderTrack) { 1926 RefPtr<mozilla::dom::VideoStreamTrack> videotrack = 1927 mSenderTrack->AsVideoStreamTrack(); 1928 1929 if (!videotrack) { 1930 MOZ_CRASH( 1931 "In ConfigureVideoCodecMode, mSenderTrack is not video! This should " 1932 "never happen!"); 1933 } 1934 1935 dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource(); 1936 switch (source) { 1937 case dom::MediaSourceEnum::Browser: 1938 case dom::MediaSourceEnum::Screen: 1939 case dom::MediaSourceEnum::Window: 1940 case dom::MediaSourceEnum::Application: 1941 newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing; 1942 break; 1943 1944 case dom::MediaSourceEnum::Camera: 1945 case dom::MediaSourceEnum::Other: 1946 // Other is used by canvas capture, which we treat as realtime video. 1947 // This seems debatable, but we've been doing it this way for a long 1948 // time, so this is likely fine. 1949 newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kRealtimeVideo; 1950 break; 1951 1952 case dom::MediaSourceEnum::Microphone: 1953 case dom::MediaSourceEnum::AudioCapture: 1954 MOZ_ASSERT(false); 1955 break; 1956 } 1957 } 1958 1959 std::vector<VideoCodecConfig> configs; 1960 RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(details, &configs); 1961 1962 if (configs.empty()) { 1963 // TODO: Are we supposed to plumb this error back to JS? This does not 1964 // seem like a failure to set an answer, it just means that codec 1965 // negotiation failed. For now, we're just doing the same thing we do 1966 // if negotiation as a whole failed. 1967 MOZ_LOG(gSenderLog, LogLevel::Error, 1968 ("%s[%s]: %s No video codecs were negotiated (send).", 1969 mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); 1970 return Nothing(); 1971 } 1972 1973 // Spec says that we start using new parameters right away, _before_ we 1974 // update the parameters that are visible to JS (ie; mParameters). 1975 const auto& parameters = mPendingParameters.refOr(mParameters); 1976 const auto& encodings = parameters.mEncodings; 1977 newConfig.mVideoCodec = Some(findMatchingCodec( 1978 configs, encodings.IsEmpty() ? Nothing() : SomeRef(encodings[0]))); 1979 for (VideoCodecConfig::Encoding& conduitEncoding : 1980 newConfig.mVideoCodec->mEncodings) { 1981 for (const RTCRtpEncodingParameters& jsEncoding : parameters.mEncodings) { 1982 std::string rid; 1983 if (jsEncoding.mRid.WasPassed()) { 1984 rid = NS_ConvertUTF16toUTF8(jsEncoding.mRid.Value()).get(); 1985 } 1986 if (conduitEncoding.rid == rid) { 1987 ApplyJsEncodingToConduitEncoding(jsEncoding, &conduitEncoding); 1988 break; 1989 } 1990 } 1991 } 1992 1993 if (!mHaveLoggedUlpfecInfo) { 1994 bool ulpfecNegotiated = false; 1995 for (const auto& codec : configs) { 1996 if (nsCRT::strcasestr(codec.mName.c_str(), "ulpfec")) { 1997 ulpfecNegotiated = true; 1998 } 1999 } 2000 mozilla::glean::codec_stats::ulpfec_negotiated 2001 .Get(ulpfecNegotiated ? "negotiated"_ns : "not_negotiated"_ns) 2002 .Add(1); 2003 mHaveLoggedUlpfecInfo = true; 2004 } 2005 2006 // Log codec information we are tracking 2007 if (!mHaveLoggedOtherFec && 2008 !GetJsepTransceiver().mSendTrack.GetFecCodecName().empty()) { 2009 mozilla::glean::codec_stats::other_fec_signaled 2010 .Get(nsDependentCString( 2011 GetJsepTransceiver().mSendTrack.GetFecCodecName().c_str())) 2012 .Add(1); 2013 mHaveLoggedOtherFec = true; 2014 } 2015 if (!mHaveLoggedVideoPreferredCodec && 2016 !GetJsepTransceiver().mSendTrack.GetVideoPreferredCodec().empty()) { 2017 mozilla::glean::codec_stats::video_preferred_codec 2018 .Get(nsDependentCString( 2019 GetJsepTransceiver().mSendTrack.GetVideoPreferredCodec().c_str())) 2020 .Add(1); 2021 mHaveLoggedVideoPreferredCodec = true; 2022 } 2023 2024 newConfig.mVideoRtpRtcpConfig = Some(details.GetRtpRtcpConfig()); 2025 2026 if (newConfig == oldConfig) { 2027 MOZ_LOG(gSenderLog, LogLevel::Debug, 2028 ("%s[%s]: %s No change in video config", mPc->GetHandle().c_str(), 2029 GetMid().c_str(), __FUNCTION__)); 2030 return Nothing(); 2031 } 2032 2033 if (newConfig.mVideoCodec.isSome()) { 2034 MOZ_ASSERT(newConfig.mSsrcs.size() == 2035 newConfig.mVideoCodec->mEncodings.size()); 2036 } 2037 return Some(newConfig); 2038 } 2039 2040 Maybe<RTCRtpSender::AudioConfig> RTCRtpSender::GetNewAudioConfig() { 2041 AudioConfig oldConfig; 2042 oldConfig.mSsrcs = mSsrcs; 2043 oldConfig.mLocalRtpExtensions = mLocalRtpExtensions; 2044 oldConfig.mCname = mCname; 2045 oldConfig.mTransmitting = mTransmitting; 2046 oldConfig.mAudioCodec = mAudioCodec; 2047 2048 AudioConfig newConfig(oldConfig); 2049 2050 UpdateBaseConfig(&newConfig); 2051 2052 if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() && 2053 GetJsepTransceiver().mSendTrack.GetActive()) { 2054 const auto& details( 2055 *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); 2056 2057 std::vector<AudioCodecConfig> configs; 2058 RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(details, &configs); 2059 if (configs.empty()) { 2060 // TODO: Are we supposed to plumb this error back to JS? This does not 2061 // seem like a failure to set an answer, it just means that codec 2062 // negotiation failed. For now, we're just doing the same thing we do 2063 // if negotiation as a whole failed. 2064 MOZ_LOG(gSenderLog, LogLevel::Error, 2065 ("%s[%s]: %s No audio codecs were negotiated (send)", 2066 mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); 2067 return Nothing(); 2068 } 2069 2070 const auto& parameters = mPendingParameters.refOr(mParameters); 2071 const auto& encodings = parameters.mEncodings; 2072 auto encoding = encodings.IsEmpty() ? Nothing() : SomeRef(encodings[0]); 2073 AudioCodecConfig& sendCodec = findMatchingCodec(configs, encoding); 2074 2075 if (encoding) { 2076 if (encoding->mMaxBitrate.WasPassed()) { 2077 sendCodec.mEncodingConstraints.maxBitrateBps.emplace( 2078 encoding->mMaxBitrate.Value()); 2079 } 2080 } 2081 2082 std::vector<AudioCodecConfig> dtmfConfigs; 2083 std::copy_if( 2084 configs.begin(), configs.end(), std::back_inserter(dtmfConfigs), 2085 [](const auto& value) { return value.mName == "telephone-event"; }); 2086 if (!dtmfConfigs.empty()) { 2087 // There is at least one telephone-event codec. 2088 // We primarily choose the codec whose frequency matches the send codec. 2089 // Secondarily we choose the one with the lowest frequency. 2090 auto dtmfIterator = 2091 std::find_if(dtmfConfigs.begin(), dtmfConfigs.end(), 2092 [&sendCodec](const auto& dtmfCodec) { 2093 return dtmfCodec.mFreq == sendCodec.mFreq; 2094 }); 2095 if (dtmfIterator == dtmfConfigs.end()) { 2096 dtmfIterator = std::min_element( 2097 dtmfConfigs.begin(), dtmfConfigs.end(), 2098 [](const auto& a, const auto& b) { return a.mFreq < b.mFreq; }); 2099 } 2100 MOZ_ASSERT(dtmfIterator != dtmfConfigs.end()); 2101 newConfig.mDtmfPt = dtmfIterator->mType; 2102 newConfig.mDtmfFreq = dtmfIterator->mFreq; 2103 } 2104 2105 newConfig.mAudioCodec = Some(sendCodec); 2106 } 2107 2108 if (!mHaveLoggedAudioPreferredCodec && 2109 !GetJsepTransceiver().mSendTrack.GetAudioPreferredCodec().empty()) { 2110 mozilla::glean::codec_stats::audio_preferred_codec 2111 .Get(nsDependentCString( 2112 GetJsepTransceiver().mSendTrack.GetAudioPreferredCodec().c_str())) 2113 .Add(1); 2114 mHaveLoggedAudioPreferredCodec = true; 2115 } 2116 2117 if (newConfig == oldConfig) { 2118 MOZ_LOG(gSenderLog, LogLevel::Debug, 2119 ("%s[%s]: %s No change in audio config", mPc->GetHandle().c_str(), 2120 GetMid().c_str(), __FUNCTION__)); 2121 return Nothing(); 2122 } 2123 2124 return Some(newConfig); 2125 } 2126 2127 void RTCRtpSender::UpdateBaseConfig(BaseConfig* aConfig) { 2128 aConfig->mSsrcs = GetJsepTransceiver().mSendTrack.GetSsrcs(); 2129 aConfig->mCname = GetJsepTransceiver().mSendTrack.GetCNAME(); 2130 2131 if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() && 2132 GetJsepTransceiver().mSendTrack.GetActive()) { 2133 const auto& details( 2134 *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); 2135 { 2136 std::vector<webrtc::RtpExtension> extmaps; 2137 // @@NG read extmap from track 2138 details.ForEachRTPHeaderExtension( 2139 [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { 2140 extmaps.emplace_back(extmap.extensionname, extmap.entry); 2141 }); 2142 aConfig->mLocalRtpExtensions = extmaps; 2143 } 2144 } 2145 // RTCRtpTransceiver::IsSending is updated after negotiation completes, in a 2146 // queued task (which we may be in right now). Don't use 2147 // JsepTrack::GetActive, because that updates before the queued task, which 2148 // is too early for some of the things we interact with here (eg; 2149 // RTCDTMFSender). 2150 aConfig->mTransmitting = mTransceiver->IsSending(); 2151 } 2152 2153 void RTCRtpSender::ApplyVideoConfig(const VideoConfig& aConfig) { 2154 if (aConfig.mVideoCodec.isSome()) { 2155 MOZ_ASSERT(aConfig.mSsrcs.size() == aConfig.mVideoCodec->mEncodings.size()); 2156 } 2157 2158 mSsrcs = aConfig.mSsrcs; 2159 mCname = aConfig.mCname; 2160 mLocalRtpExtensions = aConfig.mLocalRtpExtensions; 2161 2162 mVideoRtxSsrcs = aConfig.mVideoRtxSsrcs; 2163 mVideoCodec = aConfig.mVideoCodec; 2164 mVideoRtpRtcpConfig = aConfig.mVideoRtpRtcpConfig; 2165 mVideoCodecMode = aConfig.mVideoCodecMode; 2166 2167 mTransmitting = aConfig.mTransmitting; 2168 } 2169 2170 void RTCRtpSender::ApplyAudioConfig(const AudioConfig& aConfig) { 2171 mSsrcs = aConfig.mSsrcs; 2172 mCname = aConfig.mCname; 2173 mLocalRtpExtensions = aConfig.mLocalRtpExtensions; 2174 2175 mAudioCodec = aConfig.mAudioCodec; 2176 2177 if (aConfig.mDtmfPt >= 0) { 2178 mDtmf->SetPayloadType(aConfig.mDtmfPt, aConfig.mDtmfFreq); 2179 } 2180 2181 mTransmitting = aConfig.mTransmitting; 2182 } 2183 2184 void RTCRtpSender::Stop() { 2185 MOZ_ASSERT(mTransceiver->Stopping()); 2186 mTransmitting = false; 2187 } 2188 2189 bool RTCRtpSender::HasTrack(const dom::MediaStreamTrack* aTrack) const { 2190 if (!mSenderTrack) { 2191 return false; 2192 } 2193 2194 if (!aTrack) { 2195 return true; 2196 } 2197 2198 return mSenderTrack.get() == aTrack; 2199 } 2200 2201 RefPtr<MediaPipelineTransmit> RTCRtpSender::GetPipeline() const { 2202 return mPipeline; 2203 } 2204 2205 std::string RTCRtpSender::GetMid() const { return mTransceiver->GetMidAscii(); } 2206 2207 JsepTransceiver& RTCRtpSender::GetJsepTransceiver() { 2208 return mTransceiver->GetJsepTransceiver(); 2209 } 2210 2211 void RTCRtpSender::UpdateDtmfSender() { 2212 if (!mDtmf) { 2213 return; 2214 } 2215 2216 if (mTransmitting) { 2217 return; 2218 } 2219 2220 mDtmf->StopPlayout(); 2221 } 2222 2223 void RTCRtpSender::SetTransform(RTCRtpScriptTransform* aTransform, 2224 ErrorResult& aError) { 2225 if (aTransform == mTransform.get()) { 2226 // Ok... smile and nod 2227 // TODO: Depending on spec, this might throw 2228 // https://github.com/w3c/webrtc-encoded-transform/issues/189 2229 return; 2230 } 2231 2232 if (aTransform && aTransform->IsClaimed()) { 2233 aError.ThrowInvalidStateError("transform has already been used elsewhere"); 2234 return; 2235 } 2236 2237 // Seamless switch for frames 2238 if (aTransform) { 2239 mFrameTransformerProxy = &aTransform->GetProxy(); 2240 } else { 2241 mFrameTransformerProxy = nullptr; 2242 } 2243 2244 if (mTransform) { 2245 mTransform->GetProxy().SetSender(nullptr); 2246 } 2247 2248 mTransform = const_cast<RTCRtpScriptTransform*>(aTransform); 2249 2250 if (mTransform) { 2251 mTransform->GetProxy().SetSender(this); 2252 mTransform->SetClaimed(); 2253 } 2254 } 2255 2256 bool RTCRtpSender::GenerateKeyFrame(const Maybe<std::string>& aRid) { 2257 if (!mTransform || !mPipeline) { 2258 return false; 2259 } 2260 2261 mPipeline->mConduit->AsVideoSessionConduit().apply([&](const auto& conduit) { 2262 conduit->GenerateKeyFrame(aRid, &mTransform->GetProxy()); 2263 }); 2264 return true; 2265 } 2266 2267 } // namespace mozilla::dom 2268 2269 #undef LOGTAG