JsepTrack.cpp (27974B)
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 "jsep/JsepTrack.h" 6 7 #include <algorithm> 8 9 #include "jsep/JsepCodecDescription.h" 10 #include "jsep/JsepTrackEncoding.h" 11 12 namespace mozilla { 13 void JsepTrack::GetNegotiatedPayloadTypes( 14 std::vector<uint16_t>* payloadTypes) const { 15 if (!mNegotiatedDetails) { 16 return; 17 } 18 19 for (const auto& encoding : mNegotiatedDetails->mEncodings) { 20 GetPayloadTypes(encoding->GetCodecs(), payloadTypes); 21 } 22 23 // Prune out dupes 24 std::sort(payloadTypes->begin(), payloadTypes->end()); 25 auto newEnd = std::unique(payloadTypes->begin(), payloadTypes->end()); 26 payloadTypes->erase(newEnd, payloadTypes->end()); 27 } 28 29 /* static */ 30 void JsepTrack::GetPayloadTypes( 31 const std::vector<UniquePtr<JsepCodecDescription>>& codecs, 32 std::vector<uint16_t>* payloadTypes) { 33 for (const auto& codec : codecs) { 34 uint16_t pt; 35 if (!codec->GetPtAsInt(&pt)) { 36 MOZ_ASSERT(false); 37 continue; 38 } 39 payloadTypes->push_back(pt); 40 } 41 } 42 43 void JsepTrack::EnsureNoDuplicatePayloadTypes( 44 std::vector<UniquePtr<JsepCodecDescription>>* codecs) { 45 std::set<std::string> uniquePayloadTypes; 46 for (auto& codec : *codecs) { 47 codec->EnsureNoDuplicatePayloadTypes(uniquePayloadTypes); 48 } 49 } 50 51 void JsepTrack::EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber) { 52 while (mSsrcs.size() < aNumber) { 53 uint32_t ssrc, rtxSsrc; 54 if (!ssrcGenerator.GenerateSsrc(&ssrc) || 55 !ssrcGenerator.GenerateSsrc(&rtxSsrc)) { 56 return; 57 } 58 mSsrcs.push_back(ssrc); 59 mSsrcToRtxSsrc[ssrc] = rtxSsrc; 60 MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size()); 61 } 62 } 63 64 void JsepTrack::PopulateCodecs( 65 const std::vector<UniquePtr<JsepCodecDescription>>& prototype, 66 bool aUsePreferredCodecsOrder) { 67 mPrototypeCodecs.clear(); 68 mUsePreferredCodecsOrder = aUsePreferredCodecsOrder; 69 for (const auto& prototypeCodec : prototype) { 70 if (prototypeCodec->Type() == mType) { 71 mPrototypeCodecs.emplace_back(prototypeCodec->Clone()); 72 mPrototypeCodecs.back()->mDirection = mDirection; 73 } 74 } 75 76 EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs); 77 } 78 79 void JsepTrack::AddToOffer(SsrcGenerator& ssrcGenerator, 80 SdpMediaSection* offer) { 81 AddToMsection(mPrototypeCodecs, offer); 82 83 for (const auto& codec : mPrototypeCodecs) { 84 uint16_t pt; 85 if (SdpHelper::GetPtAsInt(codec->mDefaultPt, &pt)) { 86 mReceivePayloadTypes.push_back(pt); 87 } 88 } 89 90 if (mDirection == sdp::kSend) { 91 std::vector<std::string> rids; 92 if (offer->IsSending()) { 93 rids = mRids; 94 } 95 96 AddToMsection(rids, sdp::kSend, ssrcGenerator, 97 IsRtxEnabled(mPrototypeCodecs), offer); 98 } 99 } 100 101 void JsepTrack::AddToAnswer(const SdpMediaSection& offer, 102 SsrcGenerator& ssrcGenerator, 103 SdpMediaSection* answer) { 104 // We do not modify mPrototypeCodecs here, since we're only creating an 105 // answer. Once offer/answer concludes, we will update mPrototypeCodecs. 106 std::vector<UniquePtr<JsepCodecDescription>> codecs = 107 NegotiateCodecs(offer, true, Nothing()); 108 if (codecs.empty()) { 109 return; 110 } 111 112 AddToMsection(codecs, answer); 113 114 if (mDirection == sdp::kSend) { 115 AddToMsection(mRids, sdp::kSend, ssrcGenerator, IsRtxEnabled(codecs), 116 answer); 117 } 118 } 119 120 void JsepTrack::SetRids(const std::vector<std::string>& aRids) { 121 MOZ_ASSERT(!aRids.empty()); 122 if (!mRids.empty()) { 123 return; 124 } 125 mRids = aRids; 126 } 127 128 void JsepTrack::SetMaxEncodings(size_t aMax) { 129 mMaxEncodings = aMax; 130 if (mRids.size() > mMaxEncodings) { 131 mRids.resize(mMaxEncodings); 132 } 133 } 134 135 void JsepTrack::RecvTrackSetRemote(const Sdp& aSdp, 136 const SdpMediaSection& aMsection) { 137 mInHaveRemote = true; 138 MOZ_ASSERT(mDirection == sdp::kRecv); 139 MOZ_ASSERT(aMsection.GetMediaType() != 140 SdpMediaSection::MediaType::kApplication); 141 std::string error; 142 SdpHelper helper(&error); 143 144 mRemoteSetSendBit = aMsection.IsSending(); 145 if (!mRemoteSetSendBit) { 146 mReceptive = false; 147 } 148 149 if (aMsection.IsSending()) { 150 (void)helper.GetIdsFromMsid(aSdp, aMsection, &mStreamIds); 151 } else { 152 mStreamIds.clear(); 153 } 154 155 // We do this whether or not the track is active 156 SetCNAME(helper.GetCNAME(aMsection)); 157 mSsrcs.clear(); 158 // Storage of mSsrcs and mSsrcToRtxSsrc could be improved, see Bug 1990364 159 // Each `a=ssrc ssrc-attr:value` line can contain the same SSRC. We should 160 // only add unique SSRCs to mSsrcs. 161 std::set<uint32_t> ssrcsSet; 162 if (aMsection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) { 163 for (const auto& s : aMsection.GetAttributeList().GetSsrc().mSsrcs) { 164 if (ssrcsSet.find(s.ssrc) != ssrcsSet.end()) { 165 continue; 166 } 167 ssrcsSet.insert(s.ssrc); 168 // Preserve order of ssrcs as they appear in the m-section 169 mSsrcs.push_back(s.ssrc); 170 } 171 } 172 173 // Use FID ssrc-group to associate rtx ssrcs with "regular" ssrcs. Despite 174 // not being part of RFC 4588, this is how rtx is negotiated by libwebrtc 175 // and jitsi. 176 mSsrcToRtxSsrc.clear(); 177 if (aMsection.GetAttributeList().HasAttribute( 178 SdpAttribute::kSsrcGroupAttribute)) { 179 for (const auto& group : 180 aMsection.GetAttributeList().GetSsrcGroup().mSsrcGroups) { 181 if (group.semantics == SdpSsrcGroupAttributeList::kFid && 182 group.ssrcs.size() == 2) { 183 // Ensure we have a "regular" ssrc for each rtx ssrc. 184 if (std::find(mSsrcs.begin(), mSsrcs.end(), group.ssrcs[0]) != 185 mSsrcs.end()) { 186 mSsrcToRtxSsrc[group.ssrcs[0]] = group.ssrcs[1]; 187 188 // Remove rtx ssrcs from mSsrcs 189 auto res = std::remove_if( 190 mSsrcs.begin(), mSsrcs.end(), 191 [group](uint32_t ssrc) { return ssrc == group.ssrcs[1]; }); 192 mSsrcs.erase(res, mSsrcs.end()); 193 } 194 } 195 } 196 } 197 } 198 199 void JsepTrack::RecvTrackSetLocal(const SdpMediaSection& aMsection) { 200 MOZ_ASSERT(mDirection == sdp::kRecv); 201 202 // TODO: Should more stuff live in here? Anything that needs to happen when we 203 // decide we're ready to receive packets should probably go in here. 204 mReceptive = aMsection.IsReceiving(); 205 } 206 207 void JsepTrack::SendTrackSetRemote(SsrcGenerator& aSsrcGenerator, 208 const SdpMediaSection& aRemoteMsection) { 209 mInHaveRemote = true; 210 if (mType == SdpMediaSection::kApplication) { 211 return; 212 } 213 214 std::vector<SdpRidAttributeList::Rid> rids; 215 216 // TODO: Current language in webrtc-pc is completely broken, and so I will 217 // not be quoting it here. 218 if ((mType == SdpMediaSection::kVideo) && 219 aRemoteMsection.GetAttributeList().HasAttribute( 220 SdpAttribute::kSimulcastAttribute)) { 221 // Note: webrtc-pc does not appear to support the full IETF simulcast 222 // spec. In particular, the IETF simulcast spec supports requesting 223 // multiple different sets of encodings. For example, "a=simulcast:send 224 // 1,2;3,4;5,6" means that there are three simulcast streams, the first of 225 // which can use either rid 1 or 2 (but not both), the second of which can 226 // use rid 3 or 4 (but not both), and the third of which can use rid 5 or 227 // 6 (but not both). webrtc-pc does not support this either/or stuff for 228 // rid; each simulcast stream gets exactly one rid. 229 // Also, webrtc-pc does not support the '~' pause syntax at all 230 // See https://github.com/w3c/webrtc-pc/issues/2769 231 GetRids(aRemoteMsection, sdp::kRecv, &rids); 232 } 233 234 if (mRids.empty()) { 235 // Initial configuration 236 for (const auto& ridAttr : rids) { 237 // TODO: Spec might change, making a length > 16 invalid SDP. 238 std::string dummy; 239 if (SdpRidAttributeList::CheckRidValidity(ridAttr.id, &dummy) && 240 ridAttr.id.size() <= SdpRidAttributeList::kMaxRidLength) { 241 mRids.push_back(ridAttr.id); 242 } 243 } 244 if (mRids.size() > mMaxEncodings) { 245 mRids.resize(mMaxEncodings); 246 } 247 } else { 248 // JSEP is allowed to remove or reorder rids. RTCRtpSender won't pay 249 // attention to reordering. 250 std::vector<std::string> newRids; 251 for (const auto& ridAttr : rids) { 252 for (const auto& oldRid : mRids) { 253 if (oldRid == ridAttr.id) { 254 newRids.push_back(oldRid); 255 break; 256 } 257 } 258 } 259 mRids = std::move(newRids); 260 } 261 262 if (mRids.empty()) { 263 mRids.push_back(""); 264 } 265 266 UpdateSsrcs(aSsrcGenerator, mRids.size()); 267 } 268 269 void JsepTrack::AddToMsection( 270 const std::vector<UniquePtr<JsepCodecDescription>>& codecs, 271 SdpMediaSection* msection) const { 272 MOZ_ASSERT(msection->GetMediaType() == mType); 273 MOZ_ASSERT(!codecs.empty()); 274 275 for (const auto& codec : codecs) { 276 codec->AddToMediaSection(*msection); 277 } 278 279 if ((mDirection == sdp::kSend) && (mType != SdpMediaSection::kApplication) && 280 msection->IsSending()) { 281 if (mStreamIds.empty()) { 282 msection->AddMsid("-", mTrackId); 283 } else { 284 for (const std::string& streamId : mStreamIds) { 285 msection->AddMsid(streamId, mTrackId); 286 } 287 } 288 } 289 } 290 291 void JsepTrack::UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings) { 292 MOZ_ASSERT(mDirection == sdp::kSend); 293 MOZ_ASSERT(mType != SdpMediaSection::kApplication); 294 size_t numSsrcs = std::max<size_t>(encodings, 1U); 295 296 EnsureSsrcs(ssrcGenerator, numSsrcs); 297 PruneSsrcs(numSsrcs); 298 if (mNegotiatedDetails && mNegotiatedDetails->GetEncodingCount() > numSsrcs) { 299 mNegotiatedDetails->TruncateEncodings(numSsrcs); 300 } 301 302 MOZ_ASSERT(!mSsrcs.empty()); 303 } 304 305 void JsepTrack::PruneSsrcs(size_t aNumSsrcs) { 306 mSsrcs.resize(aNumSsrcs); 307 308 // We might have duplicate entries in mSsrcs, so we need to resize first and 309 // then remove ummatched rtx ssrcs. 310 auto itor = mSsrcToRtxSsrc.begin(); 311 while (itor != mSsrcToRtxSsrc.end()) { 312 if (std::find(mSsrcs.begin(), mSsrcs.end(), itor->first) == mSsrcs.end()) { 313 itor = mSsrcToRtxSsrc.erase(itor); 314 } else { 315 ++itor; 316 } 317 } 318 } 319 320 bool JsepTrack::IsRtxEnabled( 321 const std::vector<UniquePtr<JsepCodecDescription>>& codecs) const { 322 for (const auto& codec : codecs) { 323 if (codec->Type() == SdpMediaSection::kVideo && 324 static_cast<const JsepVideoCodecDescription*>(codec.get()) 325 ->mRtxEnabled) { 326 return true; 327 } 328 } 329 330 return false; 331 } 332 333 void JsepTrack::AddToMsection(const std::vector<std::string>& aRids, 334 sdp::Direction direction, 335 SsrcGenerator& ssrcGenerator, bool rtxEnabled, 336 SdpMediaSection* msection) { 337 if (aRids.size() > 1) { 338 UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute); 339 UniquePtr<SdpRidAttributeList> ridAttrs(new SdpRidAttributeList); 340 for (const std::string& rid : aRids) { 341 SdpRidAttributeList::Rid ridAttr; 342 ridAttr.id = rid; 343 ridAttr.direction = direction; 344 ridAttrs->mRids.push_back(ridAttr); 345 346 SdpSimulcastAttribute::Version version; 347 version.choices.push_back(SdpSimulcastAttribute::Encoding(rid, false)); 348 if (direction == sdp::kSend) { 349 simulcast->sendVersions.push_back(version); 350 } else { 351 simulcast->recvVersions.push_back(version); 352 } 353 } 354 355 msection->GetAttributeList().SetAttribute(simulcast.release()); 356 msection->GetAttributeList().SetAttribute(ridAttrs.release()); 357 } 358 359 bool requireRtxSsrcs = rtxEnabled && msection->IsSending(); 360 361 if (mType != SdpMediaSection::kApplication && mDirection == sdp::kSend) { 362 UpdateSsrcs(ssrcGenerator, aRids.size()); 363 364 if (requireRtxSsrcs) { 365 MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size()); 366 std::vector<uint32_t> allSsrcs; 367 UniquePtr<SdpSsrcGroupAttributeList> group(new SdpSsrcGroupAttributeList); 368 for (const auto& ssrc : mSsrcs) { 369 const auto rtxSsrc = mSsrcToRtxSsrc[ssrc]; 370 allSsrcs.push_back(ssrc); 371 allSsrcs.push_back(rtxSsrc); 372 group->PushEntry(SdpSsrcGroupAttributeList::kFid, {ssrc, rtxSsrc}); 373 } 374 msection->SetSsrcs(allSsrcs, mCNAME); 375 msection->GetAttributeList().SetAttribute(group.release()); 376 } else { 377 msection->SetSsrcs(mSsrcs, mCNAME); 378 } 379 } 380 } 381 382 void JsepTrack::GetRids(const SdpMediaSection& msection, 383 sdp::Direction direction, 384 std::vector<SdpRidAttributeList::Rid>* rids) const { 385 rids->clear(); 386 if (!msection.GetAttributeList().HasAttribute( 387 SdpAttribute::kSimulcastAttribute)) { 388 return; 389 } 390 391 const SdpSimulcastAttribute& simulcast( 392 msection.GetAttributeList().GetSimulcast()); 393 394 const SdpSimulcastAttribute::Versions* versions = nullptr; 395 switch (direction) { 396 case sdp::kSend: 397 versions = &simulcast.sendVersions; 398 break; 399 case sdp::kRecv: 400 versions = &simulcast.recvVersions; 401 break; 402 } 403 404 if (!versions->IsSet()) { 405 return; 406 } 407 408 // RFC 8853 does not seem to forbid duplicate rids in a simulcast attribute. 409 // So, while this is obviously silly, we should be prepared for it and 410 // ignore those duplicate rids. 411 std::set<std::string> uniqueRids; 412 for (const SdpSimulcastAttribute::Version& version : *versions) { 413 if (!version.choices.empty() && !uniqueRids.count(version.choices[0].rid)) { 414 // We validate that rids are present (and sane) elsewhere. 415 rids->push_back(*msection.FindRid(version.choices[0].rid)); 416 uniqueRids.insert(version.choices[0].rid); 417 } 418 } 419 } 420 421 void JsepTrack::CreateEncodings( 422 const SdpMediaSection& remote, 423 const std::vector<UniquePtr<JsepCodecDescription>>& negotiatedCodecs, 424 JsepTrackNegotiatedDetails* negotiatedDetails) { 425 negotiatedDetails->mTias = remote.GetBandwidth("TIAS"); 426 427 webrtc::RtcpMode rtcpMode = webrtc::RtcpMode::kCompound; 428 // rtcp-rsize (video only) 429 if (remote.GetMediaType() == SdpMediaSection::kVideo && 430 remote.GetAttributeList().HasAttribute( 431 SdpAttribute::kRtcpRsizeAttribute)) { 432 rtcpMode = webrtc::RtcpMode::kReducedSize; 433 } 434 // extmap-allow-mixed which can be at the media level or the session level 435 constexpr bool SESSION_FALLBACK = true; 436 bool extmapAllowMixed = remote.GetAttributeList().HasAttribute( 437 SdpAttribute::kExtmapAllowMixedAttribute, SESSION_FALLBACK); 438 negotiatedDetails->mRtpRtcpConf = RtpRtcpConfig(rtcpMode, extmapAllowMixed); 439 440 // TODO add support for b=AS if TIAS is not set (bug 976521) 441 442 if (mRids.empty()) { 443 mRids.push_back(""); 444 } 445 446 size_t numEncodings = mRids.size(); 447 448 // Drop SSRCs if fewer RIDs were offered than we have encodings 449 if (mSsrcs.size() > numEncodings) { 450 PruneSsrcs(numEncodings); 451 } 452 453 // For each stream make sure we have an encoding, and configure 454 // that encoding appropriately. 455 for (size_t i = 0; i < numEncodings; ++i) { 456 UniquePtr<JsepTrackEncoding> encoding(new JsepTrackEncoding); 457 if (mRids.size() > i) { 458 encoding->mRid = mRids[i]; 459 } 460 for (const auto& codec : negotiatedCodecs) { 461 encoding->AddCodec(*codec); 462 } 463 negotiatedDetails->mEncodings.push_back(std::move(encoding)); 464 } 465 } 466 467 std::vector<UniquePtr<JsepCodecDescription>> JsepTrack::GetCodecClones() const { 468 std::vector<UniquePtr<JsepCodecDescription>> clones; 469 for (const auto& codec : mPrototypeCodecs) { 470 clones.emplace_back(codec->Clone()); 471 } 472 return clones; 473 } 474 475 static bool CompareCodec(const UniquePtr<JsepCodecDescription>& lhs, 476 const UniquePtr<JsepCodecDescription>& rhs) { 477 return lhs->mStronglyPreferred && !rhs->mStronglyPreferred; 478 } 479 480 void JsepTrack::MaybeStoreCodecToLog(const std::string& codec, 481 SdpMediaSection::MediaType type) { 482 // We are logging ulpfec and red elsewhere and will not log rtx. 483 if (!nsCRT::strcasecmp(codec.c_str(), "ulpfec") || 484 !nsCRT::strcasecmp(codec.c_str(), "red") || 485 !nsCRT::strcasecmp(codec.c_str(), "rtx")) { 486 return; 487 } 488 489 if (type == SdpMediaSection::kVideo) { 490 if (nsCRT::strcasestr(codec.c_str(), "fec") && mFecCodec.empty()) { 491 mFecCodec = codec; 492 } else if (!nsCRT::strcasestr(codec.c_str(), "fec") && 493 mVideoPreferredCodec.empty()) { 494 mVideoPreferredCodec = codec; 495 } 496 } else if (type == SdpMediaSection::kAudio && mAudioPreferredCodec.empty()) { 497 mAudioPreferredCodec = codec; 498 } 499 } 500 501 std::vector<UniquePtr<JsepCodecDescription>> JsepTrack::NegotiateCodecs( 502 const SdpMediaSection& remote, bool remoteIsOffer, 503 Maybe<const SdpMediaSection&> local) { 504 // See Bug 1927371 - :ng 505 // This also effects the ordering of codecs in RTCRTPSender::GetParameters, 506 // but occurs after the initial local description codec ordering is 507 // established in the SDP. We should protect against this in the future by 508 // applying the same ordering logic to the initial local description. 509 510 std::vector<UniquePtr<JsepCodecDescription>> negotiatedCodecs; 511 std::vector<UniquePtr<JsepCodecDescription>> newPrototypeCodecs; 512 // "Pseudo codecs" include things that aren't actually stand-alone codecs (ie; 513 // ulpfec, red, rtx). 514 std::vector<UniquePtr<JsepCodecDescription>> negotiatedPseudoCodecs; 515 std::vector<UniquePtr<JsepCodecDescription>> newPrototypePseudoCodecs; 516 517 std::vector<std::string> remoteFormats; 518 519 // If setCodecPreferences has been used we need to ensure the order of codecs 520 // matches what was set. 521 if (mUsePreferredCodecsOrder) { 522 for (auto& codec : mPrototypeCodecs) { 523 if (!codec || !codec->mEnabled) { 524 continue; 525 } 526 for (const std::string& fmt : remote.GetFormats()) { 527 if (!codec->Matches(fmt, remote)) { 528 continue; 529 } 530 remoteFormats.push_back(fmt); 531 break; 532 } 533 } 534 } else { 535 remoteFormats = remote.GetFormats(); 536 } 537 538 // Outer loop establishes the remote side's preference 539 for (const std::string& fmt : remoteFormats) { 540 // Decide if we want to store this codec for logging. 541 const auto* entry = remote.FindRtpmap(fmt); 542 if (entry) { 543 MaybeStoreCodecToLog(entry->name, remote.GetMediaType()); 544 } 545 546 for (auto& codec : mPrototypeCodecs) { 547 if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) { 548 continue; 549 } 550 551 // First codec of ours that matches. See if we can negotiate it. 552 UniquePtr<JsepCodecDescription> clone(codec->Clone()); 553 if (clone->Negotiate(fmt, remote, remoteIsOffer, local)) { 554 // If negotiation succeeded, remember the payload type the other side 555 // used for reoffers. 556 codec->mDefaultPt = fmt; 557 558 // Remember whether we negotiated rtx and the associated pt for later. 559 if (codec->Type() == SdpMediaSection::kVideo) { 560 JsepVideoCodecDescription* videoCodec = 561 static_cast<JsepVideoCodecDescription*>(codec.get()); 562 JsepVideoCodecDescription* cloneVideoCodec = 563 static_cast<JsepVideoCodecDescription*>(clone.get()); 564 bool useRtx = 565 mRtxIsAllowed && 566 Preferences::GetBool("media.peerconnection.video.use_rtx", false); 567 videoCodec->mRtxEnabled = useRtx && cloneVideoCodec->mRtxEnabled; 568 videoCodec->mRtxPayloadType = cloneVideoCodec->mRtxPayloadType; 569 } 570 571 // Moves the codec out of mPrototypeCodecs, leaving an empty 572 // UniquePtr, so we don't use it again. Also causes successfully 573 // negotiated codecs to be placed up front in the future. 574 if (codec->mName == "red" || codec->mName == "ulpfec" || 575 codec->mName == "rtx") { 576 newPrototypePseudoCodecs.emplace_back(std::move(codec)); 577 negotiatedPseudoCodecs.emplace_back(std::move(clone)); 578 } else { 579 newPrototypeCodecs.emplace_back(std::move(codec)); 580 negotiatedCodecs.emplace_back(std::move(clone)); 581 } 582 break; 583 } 584 } 585 } 586 587 // If we are the offerer we need to be prepared to receive all codecs we 588 // offered even codecs missing from the answer. To achieve this we add the 589 // remaining codecs from mPrototypeCodecs to the back of the negotiated 590 // Codecs/PseudoCodecs. 591 for (auto& codec : mPrototypeCodecs) { 592 bool codecEnabled = codec && codec->mEnabled; 593 bool addAllCodecs = 594 !remoteIsOffer && mDirection != sdp::kSend && remote.IsSending(); 595 // 0 is a valid PT but not a dynamic PT so we validate if we see 0 that it 596 // is for PCMU 597 bool validPT = codecEnabled && (codec->mDefaultPt.compare("0") != 0 || 598 (codec->mName.compare("PCMU") == 0)); 599 if (!codecEnabled || (!addAllCodecs || !validPT)) { 600 continue; 601 } 602 603 UniquePtr<JsepCodecDescription> clone(codec->Clone()); 604 // Moves the codec out of mPrototypeCodecs, leaving an empty 605 // UniquePtr, so we don't use it again. 606 if (codec->mName == "red" || codec->mName == "ulpfec" || 607 codec->mName == "rtx") { 608 newPrototypePseudoCodecs.emplace_back(std::move(codec)); 609 negotiatedPseudoCodecs.emplace_back(std::move(clone)); 610 } else { 611 newPrototypeCodecs.emplace_back(std::move(codec)); 612 negotiatedCodecs.emplace_back(std::move(clone)); 613 } 614 } 615 616 if (negotiatedCodecs.empty()) { 617 // We don't have any real codecs. Clearing so we don't attempt to create a 618 // connection signaling only RED and/or ULPFEC. 619 negotiatedPseudoCodecs.clear(); 620 } 621 622 // Put the pseudo codecs at the back 623 for (auto& pseudoCodec : negotiatedPseudoCodecs) { 624 negotiatedCodecs.emplace_back(std::move(pseudoCodec)); 625 } 626 627 for (auto& pseudoCodec : newPrototypePseudoCodecs) { 628 newPrototypeCodecs.emplace_back(std::move(pseudoCodec)); 629 } 630 631 // newPrototypeCodecs contains just the negotiated stuff so far. Add the rest. 632 for (auto& codec : mPrototypeCodecs) { 633 if (codec) { 634 newPrototypeCodecs.emplace_back(std::move(codec)); 635 } 636 } 637 638 // Negotiated stuff is up front, so it will take precedence when ensuring 639 // there are no duplicate payload types. 640 EnsureNoDuplicatePayloadTypes(&newPrototypeCodecs); 641 std::swap(newPrototypeCodecs, mPrototypeCodecs); 642 643 // Find the (potential) red codec and ulpfec codec or telephone-event 644 JsepVideoCodecDescription* red = nullptr; 645 JsepVideoCodecDescription* ulpfec = nullptr; 646 JsepAudioCodecDescription* dtmf = nullptr; 647 // We can safely cast here since JsepTrack has a MediaType and only codecs 648 // that match that MediaType (kAudio or kVideo) are added. 649 for (auto& codec : negotiatedCodecs) { 650 if (codec->mName == "red") { 651 red = static_cast<JsepVideoCodecDescription*>(codec.get()); 652 } else if (codec->mName == "ulpfec") { 653 ulpfec = static_cast<JsepVideoCodecDescription*>(codec.get()); 654 } else if (codec->mName == "telephone-event") { 655 dtmf = static_cast<JsepAudioCodecDescription*>(codec.get()); 656 } 657 } 658 // Video FEC is indicated by the existence of the red and ulpfec 659 // codecs and not an attribute on the particular video codec (like in 660 // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC 661 // on all the other codecs. 662 if (red && ulpfec) { 663 for (auto& codec : negotiatedCodecs) { 664 if (codec->mName != "red" && codec->mName != "ulpfec") { 665 JsepVideoCodecDescription* videoCodec = 666 static_cast<JsepVideoCodecDescription*>(codec.get()); 667 videoCodec->EnableFec(red->mDefaultPt, ulpfec->mDefaultPt, 668 red->mRtxPayloadType); 669 } 670 } 671 } 672 673 // Dtmf support is indicated by the existence of the telephone-event 674 // codec, and not an attribute on the particular audio codec (like in a 675 // rtcpfb attr). If we see the telephone-event codec, we enabled dtmf 676 // support on all the other audio codecs. 677 if (dtmf) { 678 for (auto& codec : negotiatedCodecs) { 679 JsepAudioCodecDescription* audioCodec = 680 static_cast<JsepAudioCodecDescription*>(codec.get()); 681 audioCodec->mDtmfEnabled = true; 682 } 683 } 684 685 // Make sure strongly preferred codecs are up front, overriding the remote 686 // side's preference. 687 std::stable_sort(negotiatedCodecs.begin(), negotiatedCodecs.end(), 688 CompareCodec); 689 690 if (!red) { 691 // No red, remove ulpfec 692 negotiatedCodecs.erase( 693 std::remove_if(negotiatedCodecs.begin(), negotiatedCodecs.end(), 694 [ulpfec](const UniquePtr<JsepCodecDescription>& codec) { 695 return codec.get() == ulpfec; 696 }), 697 negotiatedCodecs.end()); 698 // Make sure there's no dangling ptr here 699 ulpfec = nullptr; 700 } 701 702 return negotiatedCodecs; 703 } 704 705 nsresult JsepTrack::Negotiate(const SdpMediaSection& answer, 706 const SdpMediaSection& remote, 707 const SdpMediaSection& local) { 708 std::vector<UniquePtr<JsepCodecDescription>> negotiatedCodecs = 709 NegotiateCodecs(remote, &answer != &remote, SomeRef(local)); 710 711 if (negotiatedCodecs.empty()) { 712 return NS_ERROR_FAILURE; 713 } 714 715 UniquePtr<JsepTrackNegotiatedDetails> negotiatedDetails = 716 MakeUnique<JsepTrackNegotiatedDetails>(); 717 718 CreateEncodings(remote, negotiatedCodecs, negotiatedDetails.get()); 719 720 if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) { 721 for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) { 722 SdpDirectionAttribute::Direction direction = extmapAttr.direction; 723 if (&remote == &answer) { 724 // Answer is remote, we need to flip this. 725 direction = reverse(direction); 726 } 727 728 if (direction & mDirection) { 729 negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr; 730 } 731 } 732 } 733 734 mInHaveRemote = false; 735 mNegotiatedDetails = std::move(negotiatedDetails); 736 return NS_OK; 737 } 738 739 // When doing bundle, if all else fails we can try to figure out which m-line a 740 // given RTP packet belongs to by looking at the payload type field. This only 741 // works, however, if that payload type appeared in only one m-section. 742 // We figure that out here. 743 /* static */ 744 void JsepTrack::SetReceivePayloadTypes(std::vector<JsepTrack*>& tracks, 745 bool localOffer) { 746 // Maps payload types to: 747 // - Nothing() temporarily when just initialized 748 // - Some(nullptr) when the payload type is registered to multiple tracks 749 // - Some(track) when the payload type is unique on track 750 std::map<uint16_t, Maybe<JsepTrack*>> payloadTypeToTrackCount; 751 752 for (JsepTrack* track : tracks) { 753 track->mUniqueReceivePayloadTypes.clear(); 754 track->mOtherReceivePayloadTypes.clear(); 755 756 if (track->GetMediaType() == SdpMediaSection::kApplication) { 757 continue; 758 } 759 760 std::vector<uint16_t> payloadTypesForTrack; 761 if (!localOffer) { 762 auto* details = track->GetNegotiatedDetails(); 763 if (!details) { 764 // Can happen if negotiation fails on a track 765 continue; 766 } 767 track->GetNegotiatedPayloadTypes(&payloadTypesForTrack); 768 } else { 769 payloadTypesForTrack = track->mReceivePayloadTypes; 770 } 771 772 for (uint16_t pt : payloadTypesForTrack) { 773 // Note std::map::operator[] inserts a default-initialized value, i.e. 774 // Nothing(), if one doesn't exist. 775 auto& entry = payloadTypeToTrackCount[pt]; 776 entry = entry 777 // If unique, i.e. Some(track), set it to Some(nullptr) 778 .andThen([](JsepTrack*) { return Some<JsepTrack*>(nullptr); }) 779 // If not unique, i.e. Nothing(), set it to Some(track) 780 .orElse([track] { return Some(track); }); 781 } 782 } 783 784 for (const auto& [key, track] : payloadTypeToTrackCount) { 785 const auto pt = AssertedCast<uint8_t>(key); 786 JsepTrack* uniqueTrack = *track; 787 if (uniqueTrack) { 788 uniqueTrack->mUniqueReceivePayloadTypes.push_back(pt); 789 } 790 for (JsepTrack* track : tracks) { 791 if (track != uniqueTrack) { 792 track->mOtherReceivePayloadTypes.push_back(pt); 793 } 794 } 795 } 796 } 797 798 } // namespace mozilla