nsresult SdpHelper::GetMsids(const SdpMediaSection& msection, std::vector<SdpMsidAttributeList::Msid>* msids) { if (msection.GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) { *msids = msection.GetAttributeList().GetMsid().mMsids; } // Can we find some additional msids in ssrc attributes? // (Chrome does not put plain-old msid attributes in its SDP) if (msection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) { auto& ssrcs = msection.GetAttributeList().GetSsrc().mSsrcs; for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) { if (i->attribute.find("msid:") == 0) { std::string streamId; std::string trackId; nsresult rv = ParseMsid(i->attribute, &streamId, &trackId); NS_ENSURE_SUCCESS(rv, rv); msids->push_back({streamId, trackId}); } } } return NS_OK; }
void SdpHelper::AddCommonExtmaps( const SdpMediaSection& remoteMsection, const std::vector<SdpExtmapAttributeList::Extmap>& localExtensions, SdpMediaSection* localMsection) { if (!remoteMsection.GetAttributeList().HasAttribute( SdpAttribute::kExtmapAttribute)) { return; } UniquePtr<SdpExtmapAttributeList> localExtmap(new SdpExtmapAttributeList); auto& theirExtmap = remoteMsection.GetAttributeList().GetExtmap().mExtmaps; for (auto i = theirExtmap.begin(); i != theirExtmap.end(); ++i) { for (auto j = localExtensions.begin(); j != localExtensions.end(); ++j) { if (i->extensionname == j->extensionname) { localExtmap->mExtmaps.push_back(*i); // RFC 5285 says that ids >= 4096 can be used by the offerer to // force the answerer to pick, otherwise the value in the offer is // used. if (localExtmap->mExtmaps.back().entry >= 4096) { localExtmap->mExtmaps.back().entry = j->entry; } } } } if (!localExtmap->mExtmaps.empty()) { localMsection->GetAttributeList().SetAttribute(localExtmap.release()); } }
nsresult SdpHelper::GetIdsFromMsid(const Sdp& sdp, const SdpMediaSection& msection, std::string* streamId, std::string* trackId) { if (!sdp.GetAttributeList().HasAttribute( SdpAttribute::kMsidSemanticAttribute)) { return NS_ERROR_NOT_AVAILABLE; } auto& msidSemantics = sdp.GetAttributeList().GetMsidSemantic().mMsidSemantics; std::vector<SdpMsidAttributeList::Msid> allMsids; nsresult rv = GetMsids(msection, &allMsids); NS_ENSURE_SUCCESS(rv, rv); bool allMsidsAreWebrtc = false; std::set<std::string> webrtcMsids; for (auto i = msidSemantics.begin(); i != msidSemantics.end(); ++i) { if (i->semantic == "WMS") { for (auto j = i->msids.begin(); j != i->msids.end(); ++j) { if (*j == "*") { allMsidsAreWebrtc = true; } else { webrtcMsids.insert(*j); } } break; } } bool found = false; for (auto i = allMsids.begin(); i != allMsids.end(); ++i) { if (allMsidsAreWebrtc || webrtcMsids.count(i->identifier)) { if (i->appdata.empty()) { SDP_SET_ERROR("Invalid webrtc msid at level " << msection.GetLevel() << ": Missing track id."); return NS_ERROR_INVALID_ARG; } if (!found) { *streamId = i->identifier; *trackId = i->appdata; found = true; } else if ((*streamId != i->identifier) || (*trackId != i->appdata)) { SDP_SET_ERROR("Found multiple different webrtc msids in m-section " << msection.GetLevel() << ". The behavior here is " "undefined."); return NS_ERROR_INVALID_ARG; } } } if (!found) { return NS_ERROR_NOT_AVAILABLE; } return NS_OK; }
bool SdpHelper::MsectionIsDisabled(const SdpMediaSection& msection) const { return !msection.GetPort() && !msection.GetAttributeList().HasAttribute( SdpAttribute::kBundleOnlyAttribute); }
std::string SdpHelper::GetCNAME(const SdpMediaSection& msection) const { if (msection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) { auto& ssrcs = msection.GetAttributeList().GetSsrc().mSsrcs; for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) { if (i->attribute.find("cname:") == 0) { return i->attribute.substr(6); } } } return ""; }
void JsepTrack::Negotiate(const SdpMediaSection& answer, const SdpMediaSection& remote) { PtrVector<JsepCodecDescription> negotiatedCodecs; negotiatedCodecs.values = GetCodecClones(); std::map<std::string, std::string> formatChanges; NegotiateCodecs(remote, &negotiatedCodecs.values, &formatChanges); // Use |formatChanges| to update mPrototypeCodecs size_t insertPos = 0; for (size_t i = 0; i < mPrototypeCodecs.values.size(); ++i) { if (formatChanges.count(mPrototypeCodecs.values[i]->mDefaultPt)) { // Update the payload type to what was negotiated mPrototypeCodecs.values[i]->mDefaultPt = formatChanges[mPrototypeCodecs.values[i]->mDefaultPt]; // Move this negotiated codec up front std::swap(mPrototypeCodecs.values[insertPos], mPrototypeCodecs.values[i]); ++insertPos; } } EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values); UniquePtr<JsepTrackNegotiatedDetails> negotiatedDetails = MakeUnique<JsepTrackNegotiatedDetails>(); CreateEncodings(remote, negotiatedCodecs.values, negotiatedDetails.get()); if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) { for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) { negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr; } } if (mDirection == sdp::kRecv) { mSsrcs.clear(); if (remote.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) { for (auto& ssrcAttr : remote.GetAttributeList().GetSsrc().mSsrcs) { AddSsrc(ssrcAttr.ssrc); } } } mNegotiatedDetails = Move(negotiatedDetails); }
void JsepTrack::GetRids(const SdpMediaSection& msection, sdp::Direction direction, std::vector<SdpRidAttributeList::Rid>* rids) const { rids->clear(); if (!msection.GetAttributeList().HasAttribute( SdpAttribute::kSimulcastAttribute)) { return; } const SdpSimulcastAttribute& simulcast( msection.GetAttributeList().GetSimulcast()); const SdpSimulcastAttribute::Versions* versions = nullptr; switch (direction) { case sdp::kSend: versions = &simulcast.sendVersions; break; case sdp::kRecv: versions = &simulcast.recvVersions; break; } if (!versions->IsSet()) { return; } if (versions->type != SdpSimulcastAttribute::Versions::kRid) { // No support for PT-based simulcast, yet. return; } for (const SdpSimulcastAttribute::Version& version : *versions) { if (!version.choices.empty()) { // We validate that rids are present (and sane) elsewhere. rids->push_back(*msection.FindRid(version.choices[0])); } } }
nsresult SdpHelper::CopyTransportParams(size_t numComponents, const SdpMediaSection& oldLocal, SdpMediaSection* newLocal) { // Copy over m-section details newLocal->SetPort(oldLocal.GetPort()); newLocal->GetConnection() = oldLocal.GetConnection(); const SdpAttributeList& oldLocalAttrs = oldLocal.GetAttributeList(); SdpAttributeList& newLocalAttrs = newLocal->GetAttributeList(); // Now we copy over attributes that won't be added by the usual logic if (oldLocalAttrs.HasAttribute(SdpAttribute::kCandidateAttribute) && numComponents) { UniquePtr<SdpMultiStringAttribute> candidateAttrs( new SdpMultiStringAttribute(SdpAttribute::kCandidateAttribute)); for (const std::string& candidate : oldLocalAttrs.GetCandidate()) { size_t component; nsresult rv = GetComponent(candidate, &component); NS_ENSURE_SUCCESS(rv, rv); if (numComponents >= component) { candidateAttrs->mValues.push_back(candidate); } } if (candidateAttrs->mValues.size()) { newLocalAttrs.SetAttribute(candidateAttrs.release()); } } if (numComponents == 2 && oldLocalAttrs.HasAttribute(SdpAttribute::kRtcpAttribute)) { // copy rtcp attribute if we had one that we are using newLocalAttrs.SetAttribute(new SdpRtcpAttribute(oldLocalAttrs.GetRtcp())); } return NS_OK; }
void JsepTrack::NegotiateCodecs( const SdpMediaSection& remote, std::vector<JsepCodecDescription*>* codecs, std::map<std::string, std::string>* formatChanges) const { PtrVector<JsepCodecDescription> unnegotiatedCodecs; std::swap(unnegotiatedCodecs.values, *codecs); // Outer loop establishes the remote side's preference for (const std::string& fmt : remote.GetFormats()) { for (size_t i = 0; i < unnegotiatedCodecs.values.size(); ++i) { JsepCodecDescription* codec = unnegotiatedCodecs.values[i]; if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) { continue; } std::string originalFormat = codec->mDefaultPt; if(codec->Negotiate(fmt, remote)) { codecs->push_back(codec); unnegotiatedCodecs.values[i] = nullptr; if (formatChanges) { (*formatChanges)[originalFormat] = codec->mDefaultPt; } break; } } } // Make sure strongly preferred codecs are up front, overriding the remote // side's preference. std::stable_sort(codecs->begin(), codecs->end(), CompareCodec); // TODO(bug 814227): Remove this once we're ready to put multiple codecs in an // answer if (!codecs->empty()) { for (size_t i = 1; i < codecs->size(); ++i) { delete (*codecs)[i]; (*codecs)[i] = nullptr; } codecs->resize(1); } }
nsresult SdpHelper::CopyStickyParams(const SdpMediaSection& source, SdpMediaSection* dest) { auto& sourceAttrs = source.GetAttributeList(); auto& destAttrs = dest->GetAttributeList(); // There's no reason to renegotiate rtcp-mux if (sourceAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) { destAttrs.SetAttribute( new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute)); } // mid should stay the same if (sourceAttrs.HasAttribute(SdpAttribute::kMidAttribute)) { destAttrs.SetAttribute( new SdpStringAttribute(SdpAttribute::kMidAttribute, sourceAttrs.GetMid())); } return NS_OK; }
nsresult SdpHelper::AddCandidateToSdp(Sdp* sdp, const std::string& candidateUntrimmed, const std::string& mid, uint16_t level) { if (level >= sdp->GetMediaSectionCount()) { SDP_SET_ERROR("Index " << level << " out of range"); return NS_ERROR_INVALID_ARG; } // Trim off '[a=]candidate:' size_t begin = candidateUntrimmed.find(':'); if (begin == std::string::npos) { SDP_SET_ERROR("Invalid candidate, no ':' (" << candidateUntrimmed << ")"); return NS_ERROR_INVALID_ARG; } ++begin; std::string candidate = candidateUntrimmed.substr(begin); // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-11#section-3.4.2.1 // Implementations receiving an ICE Candidate object MUST use the MID if // present, or the m= line index, if not (as it could have come from a // non-JSEP endpoint). (bug 1095793) SdpMediaSection* msection = 0; if (!mid.empty()) { // FindMsectionByMid could return nullptr msection = FindMsectionByMid(*sdp, mid); // Check to make sure mid matches what we'd get by // looking up the m= line using the level. (mjf) std::string checkMid; nsresult rv = GetMidFromLevel(*sdp, level, &checkMid); if (NS_FAILED(rv)) { return rv; } if (mid != checkMid) { SDP_SET_ERROR("Mismatch between mid and level - \"" << mid << "\" is not the mid for level " << level << "; \"" << checkMid << "\" is"); return NS_ERROR_INVALID_ARG; } } if (!msection) { msection = &(sdp->GetMediaSection(level)); } SdpAttributeList& attrList = msection->GetAttributeList(); UniquePtr<SdpMultiStringAttribute> candidates; if (!attrList.HasAttribute(SdpAttribute::kCandidateAttribute)) { // Create new candidates.reset( new SdpMultiStringAttribute(SdpAttribute::kCandidateAttribute)); } else { // Copy existing candidates.reset(new SdpMultiStringAttribute( *static_cast<const SdpMultiStringAttribute*>( attrList.GetAttribute(SdpAttribute::kCandidateAttribute)))); } candidates->PushEntry(candidate); attrList.SetAttribute(candidates.release()); return NS_OK; }
void JsepTrack::NegotiateCodecs( const SdpMediaSection& remote, std::vector<JsepCodecDescription*>* codecs, std::map<std::string, std::string>* formatChanges) const { PtrVector<JsepCodecDescription> unnegotiatedCodecs; std::swap(unnegotiatedCodecs.values, *codecs); // Outer loop establishes the remote side's preference for (const std::string& fmt : remote.GetFormats()) { for (size_t i = 0; i < unnegotiatedCodecs.values.size(); ++i) { JsepCodecDescription* codec = unnegotiatedCodecs.values[i]; if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) { continue; } std::string originalFormat = codec->mDefaultPt; if(codec->Negotiate(fmt, remote)) { codecs->push_back(codec); unnegotiatedCodecs.values[i] = nullptr; if (formatChanges) { (*formatChanges)[originalFormat] = codec->mDefaultPt; } break; } } } // Find the (potential) red codec and ulpfec codec JsepVideoCodecDescription* red = nullptr; JsepVideoCodecDescription* ulpfec = nullptr; for (auto codec : *codecs) { if (codec->mName == "red") { red = static_cast<JsepVideoCodecDescription*>(codec); } else if (codec->mName == "ulpfec") { ulpfec = static_cast<JsepVideoCodecDescription*>(codec); } } // if we have a red codec remove redundant encodings that don't exist if (red) { // Since we could have an externally specified redundant endcodings // list, we shouldn't simply rebuild the redundant encodings list // based on the current list of codecs. std::vector<uint8_t> unnegotiatedEncodings; std::swap(unnegotiatedEncodings, red->mRedundantEncodings); for (auto redundantPt : unnegotiatedEncodings) { std::string pt = std::to_string(redundantPt); for (auto codec : *codecs) { if (pt == codec->mDefaultPt) { red->mRedundantEncodings.push_back(redundantPt); break; } } } } // Video FEC is indicated by the existence of the red and ulpfec // codecs and not an attribute on the particular video codec (like in // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC // on all the other codecs. if (red && ulpfec) { for (auto codec : *codecs) { if (codec->mName != "red" && codec->mName != "ulpfec") { JsepVideoCodecDescription* videoCodec = static_cast<JsepVideoCodecDescription*>(codec); videoCodec->EnableFec(); } } } // Make sure strongly preferred codecs are up front, overriding the remote // side's preference. std::stable_sort(codecs->begin(), codecs->end(), CompareCodec); // TODO(bug 814227): Remove this once we're ready to put multiple codecs in an // answer. For now, remove all but the first codec unless the red codec // exists, and then we include the others per RFC 5109, section 14.2. if (!codecs->empty() && !red) { for (size_t i = 1; i < codecs->size(); ++i) { delete (*codecs)[i]; (*codecs)[i] = nullptr; } codecs->resize(1); } }