Line data Source code
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 "signaling/src/jsep/JsepTrack.h"
6 : #include "signaling/src/jsep/JsepCodecDescription.h"
7 : #include "signaling/src/jsep/JsepTrackEncoding.h"
8 :
9 : #include <algorithm>
10 :
11 : namespace mozilla
12 : {
13 : void
14 0 : JsepTrack::GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes)
15 : {
16 0 : if (!mNegotiatedDetails) {
17 0 : return;
18 : }
19 :
20 0 : for (const auto* encoding : mNegotiatedDetails->mEncodings.values) {
21 0 : GetPayloadTypes(encoding->GetCodecs(), payloadTypes);
22 : }
23 :
24 : // Prune out dupes
25 0 : std::sort(payloadTypes->begin(), payloadTypes->end());
26 0 : auto newEnd = std::unique(payloadTypes->begin(), payloadTypes->end());
27 0 : payloadTypes->erase(newEnd, payloadTypes->end());
28 : }
29 :
30 : /* static */
31 : void
32 0 : JsepTrack::GetPayloadTypes(
33 : const std::vector<JsepCodecDescription*>& codecs,
34 : std::vector<uint16_t>* payloadTypes)
35 : {
36 0 : for (JsepCodecDescription* codec : codecs) {
37 : uint16_t pt;
38 0 : if (!codec->GetPtAsInt(&pt)) {
39 0 : MOZ_ASSERT(false);
40 : continue;
41 : }
42 0 : payloadTypes->push_back(pt);
43 : }
44 0 : }
45 :
46 : void
47 0 : JsepTrack::EnsureNoDuplicatePayloadTypes(
48 : std::vector<JsepCodecDescription*>* codecs)
49 : {
50 0 : std::set<uint16_t> uniquePayloadTypes;
51 :
52 0 : for (JsepCodecDescription* codec : *codecs) {
53 : // We assume there are no dupes in negotiated codecs; unnegotiated codecs
54 : // need to change if there is a clash.
55 0 : if (!codec->mEnabled ||
56 : // We only support one datachannel per m-section
57 0 : !codec->mName.compare("webrtc-datachannel")) {
58 0 : continue;
59 : }
60 :
61 : // Disable, and only re-enable if we can ensure it has a unique pt.
62 0 : codec->mEnabled = false;
63 :
64 : uint16_t currentPt;
65 0 : if (!codec->GetPtAsInt(¤tPt)) {
66 0 : MOZ_ASSERT(false);
67 : continue;
68 : }
69 :
70 0 : if (!uniquePayloadTypes.count(currentPt)) {
71 0 : codec->mEnabled = true;
72 0 : uniquePayloadTypes.insert(currentPt);
73 0 : continue;
74 : }
75 :
76 : // |codec| cannot use its current payload type. Try to find another.
77 0 : for (uint16_t freePt = 96; freePt <= 127; ++freePt) {
78 : // Not super efficient, but readability is probably more important.
79 0 : if (!uniquePayloadTypes.count(freePt)) {
80 0 : uniquePayloadTypes.insert(freePt);
81 0 : codec->mEnabled = true;
82 0 : std::ostringstream os;
83 0 : os << freePt;
84 0 : codec->mDefaultPt = os.str();
85 0 : break;
86 : }
87 : }
88 : }
89 0 : }
90 :
91 : void
92 0 : JsepTrack::PopulateCodecs(const std::vector<JsepCodecDescription*>& prototype)
93 : {
94 0 : for (const JsepCodecDescription* prototypeCodec : prototype) {
95 0 : if (prototypeCodec->mType == mType) {
96 0 : mPrototypeCodecs.values.push_back(prototypeCodec->Clone());
97 0 : mPrototypeCodecs.values.back()->mDirection = mDirection;
98 : }
99 : }
100 :
101 0 : EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values);
102 0 : }
103 :
104 : void
105 0 : JsepTrack::AddToOffer(SdpMediaSection* offer) const
106 : {
107 0 : AddToMsection(mPrototypeCodecs.values, offer);
108 0 : if (mDirection == sdp::kSend) {
109 0 : AddToMsection(mJsEncodeConstraints, sdp::kSend, offer);
110 : }
111 0 : }
112 :
113 : void
114 0 : JsepTrack::AddToAnswer(const SdpMediaSection& offer,
115 : SdpMediaSection* answer) const
116 : {
117 : // We do not modify mPrototypeCodecs here, since we're only creating an
118 : // answer. Once offer/answer concludes, we will update mPrototypeCodecs.
119 0 : PtrVector<JsepCodecDescription> codecs;
120 0 : codecs.values = GetCodecClones();
121 0 : NegotiateCodecs(offer, &codecs.values);
122 0 : if (codecs.values.empty()) {
123 0 : return;
124 : }
125 :
126 0 : AddToMsection(codecs.values, answer);
127 :
128 0 : if (mDirection == sdp::kSend) {
129 0 : std::vector<JsConstraints> constraints(mJsEncodeConstraints);
130 0 : std::vector<SdpRidAttributeList::Rid> rids;
131 0 : GetRids(offer, sdp::kRecv, &rids);
132 0 : NegotiateRids(rids, &constraints);
133 0 : AddToMsection(constraints, sdp::kSend, answer);
134 : }
135 : }
136 :
137 : void
138 0 : JsepTrack::AddToMsection(const std::vector<JsepCodecDescription*>& codecs,
139 : SdpMediaSection* msection) const
140 : {
141 0 : MOZ_ASSERT(msection->GetMediaType() == mType);
142 0 : MOZ_ASSERT(!codecs.empty());
143 :
144 0 : for (const JsepCodecDescription* codec : codecs) {
145 0 : codec->AddToMediaSection(*msection);
146 : }
147 :
148 0 : if (mDirection == sdp::kSend) {
149 0 : if (msection->GetMediaType() != SdpMediaSection::kApplication) {
150 0 : msection->SetSsrcs(mSsrcs, mCNAME);
151 0 : msection->AddMsid(mStreamId, mTrackId);
152 : }
153 0 : msection->SetSending(true);
154 : } else {
155 0 : msection->SetReceiving(true);
156 : }
157 0 : }
158 :
159 : // Updates the |id| values in |constraintsList| with the rid values in |rids|,
160 : // where necessary.
161 : void
162 0 : JsepTrack::NegotiateRids(const std::vector<SdpRidAttributeList::Rid>& rids,
163 : std::vector<JsConstraints>* constraintsList) const
164 : {
165 0 : for (const SdpRidAttributeList::Rid& rid : rids) {
166 0 : if (!FindConstraints(rid.id, *constraintsList)) {
167 : // Pair up the first JsConstraints with an empty id, if it exists.
168 0 : JsConstraints* constraints = FindConstraints("", *constraintsList);
169 0 : if (constraints) {
170 0 : constraints->rid = rid.id;
171 : }
172 : }
173 : }
174 0 : }
175 :
176 : /* static */
177 : void
178 0 : JsepTrack::AddToMsection(const std::vector<JsConstraints>& constraintsList,
179 : sdp::Direction direction,
180 : SdpMediaSection* msection)
181 : {
182 0 : UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute);
183 0 : UniquePtr<SdpRidAttributeList> rids(new SdpRidAttributeList);
184 0 : for (const JsConstraints& constraints : constraintsList) {
185 0 : if (!constraints.rid.empty()) {
186 0 : SdpRidAttributeList::Rid rid;
187 0 : rid.id = constraints.rid;
188 0 : rid.direction = direction;
189 0 : rids->mRids.push_back(rid);
190 :
191 0 : SdpSimulcastAttribute::Version version;
192 0 : version.choices.push_back(constraints.rid);
193 0 : if (direction == sdp::kSend) {
194 0 : simulcast->sendVersions.push_back(version);
195 : } else {
196 0 : simulcast->recvVersions.push_back(version);
197 : }
198 : }
199 : }
200 :
201 0 : if (!rids->mRids.empty()) {
202 0 : msection->GetAttributeList().SetAttribute(simulcast.release());
203 0 : msection->GetAttributeList().SetAttribute(rids.release());
204 : }
205 0 : }
206 :
207 : void
208 0 : JsepTrack::GetRids(const SdpMediaSection& msection,
209 : sdp::Direction direction,
210 : std::vector<SdpRidAttributeList::Rid>* rids) const
211 : {
212 0 : rids->clear();
213 0 : if (!msection.GetAttributeList().HasAttribute(
214 : SdpAttribute::kSimulcastAttribute)) {
215 0 : return;
216 : }
217 :
218 : const SdpSimulcastAttribute& simulcast(
219 0 : msection.GetAttributeList().GetSimulcast());
220 :
221 0 : const SdpSimulcastAttribute::Versions* versions = nullptr;
222 0 : switch (direction) {
223 : case sdp::kSend:
224 0 : versions = &simulcast.sendVersions;
225 0 : break;
226 : case sdp::kRecv:
227 0 : versions = &simulcast.recvVersions;
228 0 : break;
229 : }
230 :
231 0 : if (!versions->IsSet()) {
232 0 : return;
233 : }
234 :
235 0 : if (versions->type != SdpSimulcastAttribute::Versions::kRid) {
236 : // No support for PT-based simulcast, yet.
237 0 : return;
238 : }
239 :
240 0 : for (const SdpSimulcastAttribute::Version& version : *versions) {
241 0 : if (!version.choices.empty()) {
242 : // We validate that rids are present (and sane) elsewhere.
243 0 : rids->push_back(*msection.FindRid(version.choices[0]));
244 : }
245 : }
246 : }
247 :
248 : JsepTrack::JsConstraints*
249 0 : JsepTrack::FindConstraints(const std::string& id,
250 : std::vector<JsConstraints>& constraintsList) const
251 : {
252 0 : for (JsConstraints& constraints : constraintsList) {
253 0 : if (constraints.rid == id) {
254 0 : return &constraints;
255 : }
256 : }
257 0 : return nullptr;
258 : }
259 :
260 : void
261 0 : JsepTrack::CreateEncodings(
262 : const SdpMediaSection& remote,
263 : const std::vector<JsepCodecDescription*>& negotiatedCodecs,
264 : JsepTrackNegotiatedDetails* negotiatedDetails)
265 : {
266 0 : negotiatedDetails->mTias = remote.GetBandwidth("TIAS");
267 : // TODO add support for b=AS if TIAS is not set (bug 976521)
268 :
269 0 : std::vector<SdpRidAttributeList::Rid> rids;
270 0 : GetRids(remote, sdp::kRecv, &rids); // Get rids we will send
271 0 : NegotiateRids(rids, &mJsEncodeConstraints);
272 0 : if (rids.empty()) {
273 : // Add dummy value with an empty id to make sure we get a single unicast
274 : // stream.
275 0 : rids.push_back(SdpRidAttributeList::Rid());
276 : }
277 :
278 0 : size_t max_streams = 1;
279 :
280 0 : if (mJsEncodeConstraints.size()) {
281 0 : max_streams = std::min(rids.size(), mJsEncodeConstraints.size());
282 : }
283 : // Drop SSRCs if less RIDs were offered than we have encoding constraints
284 0 : if (mSsrcs.size() > max_streams) {
285 0 : mSsrcs.resize(max_streams);
286 : }
287 :
288 : // For each stream make sure we have an encoding, and configure
289 : // that encoding appropriately.
290 0 : for (size_t i = 0; i < max_streams; ++i) {
291 0 : if (i == negotiatedDetails->mEncodings.values.size()) {
292 0 : negotiatedDetails->mEncodings.values.push_back(new JsepTrackEncoding);
293 : }
294 :
295 0 : JsepTrackEncoding* encoding = negotiatedDetails->mEncodings.values[i];
296 :
297 0 : for (const JsepCodecDescription* codec : negotiatedCodecs) {
298 0 : if (rids[i].HasFormat(codec->mDefaultPt)) {
299 0 : encoding->AddCodec(*codec);
300 : }
301 : }
302 :
303 0 : encoding->mRid = rids[i].id;
304 : // If we end up supporting params for rid, we would handle that here.
305 :
306 : // Incorporate the corresponding JS encoding constraints, if they exist
307 0 : for (const JsConstraints& jsConstraints : mJsEncodeConstraints) {
308 0 : if (jsConstraints.rid == rids[i].id) {
309 0 : encoding->mConstraints = jsConstraints.constraints;
310 : }
311 : }
312 : }
313 0 : }
314 :
315 : std::vector<JsepCodecDescription*>
316 0 : JsepTrack::GetCodecClones() const
317 : {
318 0 : std::vector<JsepCodecDescription*> clones;
319 0 : for (const JsepCodecDescription* codec : mPrototypeCodecs.values) {
320 0 : clones.push_back(codec->Clone());
321 : }
322 0 : return clones;
323 : }
324 :
325 : static bool
326 0 : CompareCodec(const JsepCodecDescription* lhs, const JsepCodecDescription* rhs)
327 : {
328 0 : return lhs->mStronglyPreferred && !rhs->mStronglyPreferred;
329 : }
330 :
331 : void
332 0 : JsepTrack::NegotiateCodecs(
333 : const SdpMediaSection& remote,
334 : std::vector<JsepCodecDescription*>* codecs,
335 : std::map<std::string, std::string>* formatChanges) const
336 : {
337 0 : PtrVector<JsepCodecDescription> unnegotiatedCodecs;
338 0 : std::swap(unnegotiatedCodecs.values, *codecs);
339 :
340 : // Outer loop establishes the remote side's preference
341 0 : for (const std::string& fmt : remote.GetFormats()) {
342 0 : for (size_t i = 0; i < unnegotiatedCodecs.values.size(); ++i) {
343 0 : JsepCodecDescription* codec = unnegotiatedCodecs.values[i];
344 0 : if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) {
345 0 : continue;
346 : }
347 :
348 0 : std::string originalFormat = codec->mDefaultPt;
349 0 : if(codec->Negotiate(fmt, remote)) {
350 0 : codecs->push_back(codec);
351 0 : unnegotiatedCodecs.values[i] = nullptr;
352 0 : if (formatChanges) {
353 0 : (*formatChanges)[originalFormat] = codec->mDefaultPt;
354 : }
355 0 : break;
356 : }
357 : }
358 : }
359 :
360 : // Find the (potential) red codec and ulpfec codec or telephone-event
361 0 : JsepVideoCodecDescription* red = nullptr;
362 0 : JsepVideoCodecDescription* ulpfec = nullptr;
363 0 : JsepAudioCodecDescription* dtmf = nullptr;
364 : // We can safely cast here since JsepTrack has a MediaType and only codecs
365 : // that match that MediaType (kAudio or kVideo) are added.
366 0 : for (auto codec : *codecs) {
367 0 : if (codec->mName == "red") {
368 0 : red = static_cast<JsepVideoCodecDescription*>(codec);
369 : }
370 0 : else if (codec->mName == "ulpfec") {
371 0 : ulpfec = static_cast<JsepVideoCodecDescription*>(codec);
372 : }
373 0 : else if (codec->mName == "telephone-event") {
374 0 : dtmf = static_cast<JsepAudioCodecDescription*>(codec);
375 : }
376 : }
377 : // if we have a red codec remove redundant encodings that don't exist
378 0 : if (red) {
379 : // Since we could have an externally specified redundant endcodings
380 : // list, we shouldn't simply rebuild the redundant encodings list
381 : // based on the current list of codecs.
382 0 : std::vector<uint8_t> unnegotiatedEncodings;
383 0 : std::swap(unnegotiatedEncodings, red->mRedundantEncodings);
384 0 : for (auto redundantPt : unnegotiatedEncodings) {
385 0 : std::string pt = std::to_string(redundantPt);
386 0 : for (auto codec : *codecs) {
387 0 : if (pt == codec->mDefaultPt) {
388 0 : red->mRedundantEncodings.push_back(redundantPt);
389 0 : break;
390 : }
391 : }
392 : }
393 : }
394 : // Video FEC is indicated by the existence of the red and ulpfec
395 : // codecs and not an attribute on the particular video codec (like in
396 : // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC
397 : // on all the other codecs.
398 0 : if (red && ulpfec) {
399 0 : for (auto codec : *codecs) {
400 0 : if (codec->mName != "red" && codec->mName != "ulpfec") {
401 : JsepVideoCodecDescription* videoCodec =
402 0 : static_cast<JsepVideoCodecDescription*>(codec);
403 0 : videoCodec->EnableFec(red->mDefaultPt, ulpfec->mDefaultPt);
404 : }
405 : }
406 : }
407 :
408 : // Dtmf support is indicated by the existence of the telephone-event
409 : // codec, and not an attribute on the particular audio codec (like in a
410 : // rtcpfb attr). If we see the telephone-event codec, we enabled dtmf
411 : // support on all the other audio codecs.
412 0 : if (dtmf) {
413 0 : for (auto codec : *codecs) {
414 : JsepAudioCodecDescription* audioCodec =
415 0 : static_cast<JsepAudioCodecDescription*>(codec);
416 0 : audioCodec->mDtmfEnabled = true;
417 : }
418 : }
419 :
420 : // Make sure strongly preferred codecs are up front, overriding the remote
421 : // side's preference.
422 0 : std::stable_sort(codecs->begin(), codecs->end(), CompareCodec);
423 :
424 : // TODO(bug 814227): Remove this once we're ready to put multiple codecs in an
425 : // answer. For now, remove all but the first codec unless the red codec
426 : // exists, and then we include the others per RFC 5109, section 14.2.
427 : // Note: now allows keeping the telephone-event codec, if it appears, as the
428 : // last codec in the list.
429 0 : if (!codecs->empty() && !red) {
430 0 : int newSize = dtmf ? 2 : 1;
431 0 : for (size_t i = 1; i < codecs->size(); ++i) {
432 0 : if (!dtmf || dtmf != (*codecs)[i]) {
433 0 : delete (*codecs)[i];
434 0 : (*codecs)[i] = nullptr;
435 : }
436 : }
437 0 : if (dtmf) {
438 0 : (*codecs)[newSize-1] = dtmf;
439 : }
440 0 : codecs->resize(newSize);
441 : }
442 0 : }
443 :
444 : void
445 0 : JsepTrack::Negotiate(const SdpMediaSection& answer,
446 : const SdpMediaSection& remote)
447 : {
448 0 : PtrVector<JsepCodecDescription> negotiatedCodecs;
449 0 : negotiatedCodecs.values = GetCodecClones();
450 :
451 0 : std::map<std::string, std::string> formatChanges;
452 : NegotiateCodecs(remote,
453 : &negotiatedCodecs.values,
454 0 : &formatChanges);
455 :
456 : // Use |formatChanges| to update mPrototypeCodecs
457 0 : size_t insertPos = 0;
458 0 : for (size_t i = 0; i < mPrototypeCodecs.values.size(); ++i) {
459 0 : if (formatChanges.count(mPrototypeCodecs.values[i]->mDefaultPt)) {
460 : // Update the payload type to what was negotiated
461 0 : mPrototypeCodecs.values[i]->mDefaultPt =
462 0 : formatChanges[mPrototypeCodecs.values[i]->mDefaultPt];
463 : // Move this negotiated codec up front
464 0 : std::swap(mPrototypeCodecs.values[insertPos],
465 0 : mPrototypeCodecs.values[i]);
466 0 : ++insertPos;
467 : }
468 : }
469 :
470 0 : EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values);
471 :
472 : UniquePtr<JsepTrackNegotiatedDetails> negotiatedDetails =
473 0 : MakeUnique<JsepTrackNegotiatedDetails>();
474 :
475 0 : CreateEncodings(remote, negotiatedCodecs.values, negotiatedDetails.get());
476 :
477 0 : if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
478 0 : for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) {
479 0 : SdpDirectionAttribute::Direction direction = extmapAttr.direction;
480 0 : if (&remote == &answer) {
481 : // Answer is remote, we need to flip this.
482 0 : direction = reverse(direction);
483 : }
484 :
485 0 : if (direction & mDirection) {
486 0 : negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr;
487 : }
488 : }
489 : }
490 :
491 0 : if (mDirection == sdp::kRecv) {
492 0 : mSsrcs.clear();
493 0 : if (remote.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
494 0 : for (auto& ssrcAttr : remote.GetAttributeList().GetSsrc().mSsrcs) {
495 0 : AddSsrc(ssrcAttr.ssrc);
496 : }
497 : }
498 : }
499 :
500 0 : mNegotiatedDetails = Move(negotiatedDetails);
501 0 : }
502 :
503 : // When doing bundle, if all else fails we can try to figure out which m-line a
504 : // given RTP packet belongs to by looking at the payload type field. This only
505 : // works, however, if that payload type appeared in only one m-section.
506 : // We figure that out here.
507 : /* static */
508 : void
509 0 : JsepTrack::SetUniquePayloadTypes(const std::vector<RefPtr<JsepTrack>>& tracks)
510 : {
511 : // Maps to track details if no other track contains the payload type,
512 : // otherwise maps to nullptr.
513 0 : std::map<uint16_t, JsepTrackNegotiatedDetails*> payloadTypeToDetailsMap;
514 :
515 0 : for (const RefPtr<JsepTrack>& track : tracks) {
516 0 : if (track->GetMediaType() == SdpMediaSection::kApplication) {
517 0 : continue;
518 : }
519 :
520 0 : auto* details = track->GetNegotiatedDetails();
521 0 : if (!details) {
522 : // Can happen if negotiation fails on a track
523 0 : continue;
524 : }
525 :
526 0 : std::vector<uint16_t> payloadTypesForTrack;
527 0 : track->GetNegotiatedPayloadTypes(&payloadTypesForTrack);
528 :
529 0 : for (uint16_t pt : payloadTypesForTrack) {
530 0 : if (payloadTypeToDetailsMap.count(pt)) {
531 : // Found in more than one track, not unique
532 0 : payloadTypeToDetailsMap[pt] = nullptr;
533 : } else {
534 0 : payloadTypeToDetailsMap[pt] = details;
535 : }
536 : }
537 : }
538 :
539 0 : for (auto ptAndDetails : payloadTypeToDetailsMap) {
540 0 : uint16_t uniquePt = ptAndDetails.first;
541 0 : MOZ_ASSERT(uniquePt <= UINT8_MAX);
542 0 : auto trackDetails = ptAndDetails.second;
543 :
544 0 : if (trackDetails) {
545 0 : trackDetails->mUniquePayloadTypes.push_back(
546 0 : static_cast<uint8_t>(uniquePt));
547 : }
548 : }
549 0 : }
550 :
551 : } // namespace mozilla
552 :
|