OLD | NEW |
| (Empty) |
1 /* | |
2 * libjingle | |
3 * Copyright 2004 Google Inc. | |
4 * | |
5 * Redistribution and use in source and binary forms, with or without | |
6 * modification, are permitted provided that the following conditions are met: | |
7 * | |
8 * 1. Redistributions of source code must retain the above copyright notice, | |
9 * this list of conditions and the following disclaimer. | |
10 * 2. Redistributions in binary form must reproduce the above copyright notice, | |
11 * this list of conditions and the following disclaimer in the documentation | |
12 * and/or other materials provided with the distribution. | |
13 * 3. The name of the author may not be used to endorse or promote products | |
14 * derived from this software without specific prior written permission. | |
15 * | |
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | |
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
26 */ | |
27 | |
28 #include "talk/session/media/mediasession.h" | |
29 | |
30 #include <functional> | |
31 #include <map> | |
32 #include <set> | |
33 #include <utility> | |
34 | |
35 #include "talk/session/media/channelmanager.h" | |
36 #include "talk/session/media/srtpfilter.h" | |
37 #include "webrtc/base/helpers.h" | |
38 #include "webrtc/base/logging.h" | |
39 #include "webrtc/base/scoped_ptr.h" | |
40 #include "webrtc/base/stringutils.h" | |
41 #include "webrtc/media/base/constants.h" | |
42 #include "webrtc/media/base/cryptoparams.h" | |
43 #include "webrtc/p2p/base/constants.h" | |
44 | |
45 #ifdef HAVE_SCTP | |
46 #include "webrtc/media/sctp/sctpdataengine.h" | |
47 #else | |
48 static const uint32_t kMaxSctpSid = 1023; | |
49 #endif | |
50 | |
51 namespace { | |
52 const char kInline[] = "inline:"; | |
53 | |
54 void GetSupportedCryptoSuiteNames(void (*func)(std::vector<int>*), | |
55 std::vector<std::string>* names) { | |
56 #ifdef HAVE_SRTP | |
57 std::vector<int> crypto_suites; | |
58 func(&crypto_suites); | |
59 for (const auto crypto : crypto_suites) { | |
60 names->push_back(rtc::SrtpCryptoSuiteToName(crypto)); | |
61 } | |
62 #endif | |
63 } | |
64 } | |
65 | |
66 namespace cricket { | |
67 | |
68 using rtc::scoped_ptr; | |
69 | |
70 // RTP Profile names | |
71 // http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xml | |
72 // RFC4585 | |
73 const char kMediaProtocolAvpf[] = "RTP/AVPF"; | |
74 // RFC5124 | |
75 const char kMediaProtocolDtlsSavpf[] = "UDP/TLS/RTP/SAVPF"; | |
76 | |
77 // We always generate offers with "UDP/TLS/RTP/SAVPF" when using DTLS-SRTP, | |
78 // but we tolerate "RTP/SAVPF" in offers we receive, for compatibility. | |
79 const char kMediaProtocolSavpf[] = "RTP/SAVPF"; | |
80 | |
81 const char kMediaProtocolRtpPrefix[] = "RTP/"; | |
82 | |
83 const char kMediaProtocolSctp[] = "SCTP"; | |
84 const char kMediaProtocolDtlsSctp[] = "DTLS/SCTP"; | |
85 const char kMediaProtocolUdpDtlsSctp[] = "UDP/DTLS/SCTP"; | |
86 const char kMediaProtocolTcpDtlsSctp[] = "TCP/DTLS/SCTP"; | |
87 | |
88 static bool IsMediaContentOfType(const ContentInfo* content, | |
89 MediaType media_type) { | |
90 if (!IsMediaContent(content)) { | |
91 return false; | |
92 } | |
93 | |
94 const MediaContentDescription* mdesc = | |
95 static_cast<const MediaContentDescription*>(content->description); | |
96 return mdesc && mdesc->type() == media_type; | |
97 } | |
98 | |
99 static bool CreateCryptoParams(int tag, const std::string& cipher, | |
100 CryptoParams *out) { | |
101 std::string key; | |
102 key.reserve(SRTP_MASTER_KEY_BASE64_LEN); | |
103 | |
104 if (!rtc::CreateRandomString(SRTP_MASTER_KEY_BASE64_LEN, &key)) { | |
105 return false; | |
106 } | |
107 out->tag = tag; | |
108 out->cipher_suite = cipher; | |
109 out->key_params = kInline; | |
110 out->key_params += key; | |
111 return true; | |
112 } | |
113 | |
114 #ifdef HAVE_SRTP | |
115 static bool AddCryptoParams(const std::string& cipher_suite, | |
116 CryptoParamsVec *out) { | |
117 int size = static_cast<int>(out->size()); | |
118 | |
119 out->resize(size + 1); | |
120 return CreateCryptoParams(size, cipher_suite, &out->at(size)); | |
121 } | |
122 | |
123 void AddMediaCryptos(const CryptoParamsVec& cryptos, | |
124 MediaContentDescription* media) { | |
125 for (CryptoParamsVec::const_iterator crypto = cryptos.begin(); | |
126 crypto != cryptos.end(); ++crypto) { | |
127 media->AddCrypto(*crypto); | |
128 } | |
129 } | |
130 | |
131 bool CreateMediaCryptos(const std::vector<std::string>& crypto_suites, | |
132 MediaContentDescription* media) { | |
133 CryptoParamsVec cryptos; | |
134 for (std::vector<std::string>::const_iterator it = crypto_suites.begin(); | |
135 it != crypto_suites.end(); ++it) { | |
136 if (!AddCryptoParams(*it, &cryptos)) { | |
137 return false; | |
138 } | |
139 } | |
140 AddMediaCryptos(cryptos, media); | |
141 return true; | |
142 } | |
143 #endif | |
144 | |
145 const CryptoParamsVec* GetCryptos(const MediaContentDescription* media) { | |
146 if (!media) { | |
147 return NULL; | |
148 } | |
149 return &media->cryptos(); | |
150 } | |
151 | |
152 bool FindMatchingCrypto(const CryptoParamsVec& cryptos, | |
153 const CryptoParams& crypto, | |
154 CryptoParams* out) { | |
155 for (CryptoParamsVec::const_iterator it = cryptos.begin(); | |
156 it != cryptos.end(); ++it) { | |
157 if (crypto.Matches(*it)) { | |
158 *out = *it; | |
159 return true; | |
160 } | |
161 } | |
162 return false; | |
163 } | |
164 | |
165 // For audio, HMAC 32 is prefered because of the low overhead. | |
166 void GetSupportedAudioCryptoSuites(std::vector<int>* crypto_suites) { | |
167 #ifdef HAVE_SRTP | |
168 crypto_suites->push_back(rtc::SRTP_AES128_CM_SHA1_32); | |
169 crypto_suites->push_back(rtc::SRTP_AES128_CM_SHA1_80); | |
170 #endif | |
171 } | |
172 | |
173 void GetSupportedAudioCryptoSuiteNames( | |
174 std::vector<std::string>* crypto_suite_names) { | |
175 GetSupportedCryptoSuiteNames(GetSupportedAudioCryptoSuites, | |
176 crypto_suite_names); | |
177 } | |
178 | |
179 void GetSupportedVideoCryptoSuites(std::vector<int>* crypto_suites) { | |
180 GetDefaultSrtpCryptoSuites(crypto_suites); | |
181 } | |
182 | |
183 void GetSupportedVideoCryptoSuiteNames( | |
184 std::vector<std::string>* crypto_suite_names) { | |
185 GetSupportedCryptoSuiteNames(GetSupportedVideoCryptoSuites, | |
186 crypto_suite_names); | |
187 } | |
188 | |
189 void GetSupportedDataCryptoSuites(std::vector<int>* crypto_suites) { | |
190 GetDefaultSrtpCryptoSuites(crypto_suites); | |
191 } | |
192 | |
193 void GetSupportedDataCryptoSuiteNames( | |
194 std::vector<std::string>* crypto_suite_names) { | |
195 GetSupportedCryptoSuiteNames(GetSupportedDataCryptoSuites, | |
196 crypto_suite_names); | |
197 } | |
198 | |
199 void GetDefaultSrtpCryptoSuites(std::vector<int>* crypto_suites) { | |
200 #ifdef HAVE_SRTP | |
201 crypto_suites->push_back(rtc::SRTP_AES128_CM_SHA1_80); | |
202 #endif | |
203 } | |
204 | |
205 void GetDefaultSrtpCryptoSuiteNames( | |
206 std::vector<std::string>* crypto_suite_names) { | |
207 GetSupportedCryptoSuiteNames(GetDefaultSrtpCryptoSuites, crypto_suite_names); | |
208 } | |
209 | |
210 // For video support only 80-bit SHA1 HMAC. For audio 32-bit HMAC is | |
211 // tolerated unless bundle is enabled because it is low overhead. Pick the | |
212 // crypto in the list that is supported. | |
213 static bool SelectCrypto(const MediaContentDescription* offer, | |
214 bool bundle, | |
215 CryptoParams *crypto) { | |
216 bool audio = offer->type() == MEDIA_TYPE_AUDIO; | |
217 const CryptoParamsVec& cryptos = offer->cryptos(); | |
218 | |
219 for (CryptoParamsVec::const_iterator i = cryptos.begin(); | |
220 i != cryptos.end(); ++i) { | |
221 if (rtc::CS_AES_CM_128_HMAC_SHA1_80 == i->cipher_suite || | |
222 (rtc::CS_AES_CM_128_HMAC_SHA1_32 == i->cipher_suite && audio && | |
223 !bundle)) { | |
224 return CreateCryptoParams(i->tag, i->cipher_suite, crypto); | |
225 } | |
226 } | |
227 return false; | |
228 } | |
229 | |
230 static const StreamParams* FindFirstStreamParamsByCname( | |
231 const StreamParamsVec& params_vec, | |
232 const std::string& cname) { | |
233 for (StreamParamsVec::const_iterator it = params_vec.begin(); | |
234 it != params_vec.end(); ++it) { | |
235 if (cname == it->cname) | |
236 return &*it; | |
237 } | |
238 return NULL; | |
239 } | |
240 | |
241 // Generates a new CNAME or the CNAME of an already existing StreamParams | |
242 // if a StreamParams exist for another Stream in streams with sync_label | |
243 // sync_label. | |
244 static bool GenerateCname(const StreamParamsVec& params_vec, | |
245 const MediaSessionOptions::Streams& streams, | |
246 const std::string& synch_label, | |
247 std::string* cname) { | |
248 ASSERT(cname != NULL); | |
249 if (!cname) | |
250 return false; | |
251 | |
252 // Check if a CNAME exist for any of the other synched streams. | |
253 for (MediaSessionOptions::Streams::const_iterator stream_it = streams.begin(); | |
254 stream_it != streams.end() ; ++stream_it) { | |
255 if (synch_label != stream_it->sync_label) | |
256 continue; | |
257 | |
258 // groupid is empty for StreamParams generated using | |
259 // MediaSessionDescriptionFactory. | |
260 const StreamParams* param = GetStreamByIds(params_vec, "", stream_it->id); | |
261 if (param) { | |
262 *cname = param->cname; | |
263 return true; | |
264 } | |
265 } | |
266 // No other stream seems to exist that we should sync with. | |
267 // Generate a random string for the RTCP CNAME, as stated in RFC 6222. | |
268 // This string is only used for synchronization, and therefore is opaque. | |
269 do { | |
270 if (!rtc::CreateRandomString(16, cname)) { | |
271 ASSERT(false); | |
272 return false; | |
273 } | |
274 } while (FindFirstStreamParamsByCname(params_vec, *cname)); | |
275 | |
276 return true; | |
277 } | |
278 | |
279 // Generate random SSRC values that are not already present in |params_vec|. | |
280 // The generated values are added to |ssrcs|. | |
281 // |num_ssrcs| is the number of the SSRC will be generated. | |
282 static void GenerateSsrcs(const StreamParamsVec& params_vec, | |
283 int num_ssrcs, | |
284 std::vector<uint32_t>* ssrcs) { | |
285 for (int i = 0; i < num_ssrcs; i++) { | |
286 uint32_t candidate; | |
287 do { | |
288 candidate = rtc::CreateRandomNonZeroId(); | |
289 } while (GetStreamBySsrc(params_vec, candidate) || | |
290 std::count(ssrcs->begin(), ssrcs->end(), candidate) > 0); | |
291 ssrcs->push_back(candidate); | |
292 } | |
293 } | |
294 | |
295 // Returns false if we exhaust the range of SIDs. | |
296 static bool GenerateSctpSid(const StreamParamsVec& params_vec, uint32_t* sid) { | |
297 if (params_vec.size() > kMaxSctpSid) { | |
298 LOG(LS_WARNING) << | |
299 "Could not generate an SCTP SID: too many SCTP streams."; | |
300 return false; | |
301 } | |
302 while (true) { | |
303 uint32_t candidate = rtc::CreateRandomNonZeroId() % kMaxSctpSid; | |
304 if (!GetStreamBySsrc(params_vec, candidate)) { | |
305 *sid = candidate; | |
306 return true; | |
307 } | |
308 } | |
309 } | |
310 | |
311 static bool GenerateSctpSids(const StreamParamsVec& params_vec, | |
312 std::vector<uint32_t>* sids) { | |
313 uint32_t sid; | |
314 if (!GenerateSctpSid(params_vec, &sid)) { | |
315 LOG(LS_WARNING) << "Could not generated an SCTP SID."; | |
316 return false; | |
317 } | |
318 sids->push_back(sid); | |
319 return true; | |
320 } | |
321 | |
322 // Finds all StreamParams of all media types and attach them to stream_params. | |
323 static void GetCurrentStreamParams(const SessionDescription* sdesc, | |
324 StreamParamsVec* stream_params) { | |
325 if (!sdesc) | |
326 return; | |
327 | |
328 const ContentInfos& contents = sdesc->contents(); | |
329 for (ContentInfos::const_iterator content = contents.begin(); | |
330 content != contents.end(); ++content) { | |
331 if (!IsMediaContent(&*content)) { | |
332 continue; | |
333 } | |
334 const MediaContentDescription* media = | |
335 static_cast<const MediaContentDescription*>( | |
336 content->description); | |
337 const StreamParamsVec& streams = media->streams(); | |
338 for (StreamParamsVec::const_iterator it = streams.begin(); | |
339 it != streams.end(); ++it) { | |
340 stream_params->push_back(*it); | |
341 } | |
342 } | |
343 } | |
344 | |
345 // Filters the data codecs for the data channel type. | |
346 void FilterDataCodecs(std::vector<DataCodec>* codecs, bool sctp) { | |
347 // Filter RTP codec for SCTP and vice versa. | |
348 int codec_id = sctp ? kGoogleRtpDataCodecId : kGoogleSctpDataCodecId; | |
349 for (std::vector<DataCodec>::iterator iter = codecs->begin(); | |
350 iter != codecs->end();) { | |
351 if (iter->id == codec_id) { | |
352 iter = codecs->erase(iter); | |
353 } else { | |
354 ++iter; | |
355 } | |
356 } | |
357 } | |
358 | |
359 template <typename IdStruct> | |
360 class UsedIds { | |
361 public: | |
362 UsedIds(int min_allowed_id, int max_allowed_id) | |
363 : min_allowed_id_(min_allowed_id), | |
364 max_allowed_id_(max_allowed_id), | |
365 next_id_(max_allowed_id) { | |
366 } | |
367 | |
368 // Loops through all Id in |ids| and changes its id if it is | |
369 // already in use by another IdStruct. Call this methods with all Id | |
370 // in a session description to make sure no duplicate ids exists. | |
371 // Note that typename Id must be a type of IdStruct. | |
372 template <typename Id> | |
373 void FindAndSetIdUsed(std::vector<Id>* ids) { | |
374 for (typename std::vector<Id>::iterator it = ids->begin(); | |
375 it != ids->end(); ++it) { | |
376 FindAndSetIdUsed(&*it); | |
377 } | |
378 } | |
379 | |
380 // Finds and sets an unused id if the |idstruct| id is already in use. | |
381 void FindAndSetIdUsed(IdStruct* idstruct) { | |
382 const int original_id = idstruct->id; | |
383 int new_id = idstruct->id; | |
384 | |
385 if (original_id > max_allowed_id_ || original_id < min_allowed_id_) { | |
386 // If the original id is not in range - this is an id that can't be | |
387 // dynamically changed. | |
388 return; | |
389 } | |
390 | |
391 if (IsIdUsed(original_id)) { | |
392 new_id = FindUnusedId(); | |
393 LOG(LS_WARNING) << "Duplicate id found. Reassigning from " << original_id | |
394 << " to " << new_id; | |
395 idstruct->id = new_id; | |
396 } | |
397 SetIdUsed(new_id); | |
398 } | |
399 | |
400 private: | |
401 // Returns the first unused id in reverse order. | |
402 // This hopefully reduce the risk of more collisions. We want to change the | |
403 // default ids as little as possible. | |
404 int FindUnusedId() { | |
405 while (IsIdUsed(next_id_) && next_id_ >= min_allowed_id_) { | |
406 --next_id_; | |
407 } | |
408 ASSERT(next_id_ >= min_allowed_id_); | |
409 return next_id_; | |
410 } | |
411 | |
412 bool IsIdUsed(int new_id) { | |
413 return id_set_.find(new_id) != id_set_.end(); | |
414 } | |
415 | |
416 void SetIdUsed(int new_id) { | |
417 id_set_.insert(new_id); | |
418 } | |
419 | |
420 const int min_allowed_id_; | |
421 const int max_allowed_id_; | |
422 int next_id_; | |
423 std::set<int> id_set_; | |
424 }; | |
425 | |
426 // Helper class used for finding duplicate RTP payload types among audio, video | |
427 // and data codecs. When bundle is used the payload types may not collide. | |
428 class UsedPayloadTypes : public UsedIds<Codec> { | |
429 public: | |
430 UsedPayloadTypes() | |
431 : UsedIds<Codec>(kDynamicPayloadTypeMin, kDynamicPayloadTypeMax) { | |
432 } | |
433 | |
434 | |
435 private: | |
436 static const int kDynamicPayloadTypeMin = 96; | |
437 static const int kDynamicPayloadTypeMax = 127; | |
438 }; | |
439 | |
440 // Helper class used for finding duplicate RTP Header extension ids among | |
441 // audio and video extensions. | |
442 class UsedRtpHeaderExtensionIds : public UsedIds<RtpHeaderExtension> { | |
443 public: | |
444 UsedRtpHeaderExtensionIds() | |
445 : UsedIds<RtpHeaderExtension>(kLocalIdMin, kLocalIdMax) { | |
446 } | |
447 | |
448 private: | |
449 // Min and Max local identifier for one-byte header extensions, per RFC5285. | |
450 static const int kLocalIdMin = 1; | |
451 static const int kLocalIdMax = 14; | |
452 }; | |
453 | |
454 static bool IsSctp(const MediaContentDescription* desc) { | |
455 return ((desc->protocol() == kMediaProtocolSctp) || | |
456 (desc->protocol() == kMediaProtocolDtlsSctp)); | |
457 } | |
458 | |
459 // Adds a StreamParams for each Stream in Streams with media type | |
460 // media_type to content_description. | |
461 // |current_params| - All currently known StreamParams of any media type. | |
462 template <class C> | |
463 static bool AddStreamParams( | |
464 MediaType media_type, | |
465 const MediaSessionOptions::Streams& streams, | |
466 StreamParamsVec* current_streams, | |
467 MediaContentDescriptionImpl<C>* content_description, | |
468 const bool add_legacy_stream) { | |
469 const bool include_rtx_streams = | |
470 ContainsRtxCodec(content_description->codecs()); | |
471 | |
472 if (streams.empty() && add_legacy_stream) { | |
473 // TODO(perkj): Remove this legacy stream when all apps use StreamParams. | |
474 std::vector<uint32_t> ssrcs; | |
475 if (IsSctp(content_description)) { | |
476 GenerateSctpSids(*current_streams, &ssrcs); | |
477 } else { | |
478 int num_ssrcs = include_rtx_streams ? 2 : 1; | |
479 GenerateSsrcs(*current_streams, num_ssrcs, &ssrcs); | |
480 } | |
481 if (include_rtx_streams) { | |
482 content_description->AddLegacyStream(ssrcs[0], ssrcs[1]); | |
483 content_description->set_multistream(true); | |
484 } else { | |
485 content_description->AddLegacyStream(ssrcs[0]); | |
486 } | |
487 return true; | |
488 } | |
489 | |
490 MediaSessionOptions::Streams::const_iterator stream_it; | |
491 for (stream_it = streams.begin(); | |
492 stream_it != streams.end(); ++stream_it) { | |
493 if (stream_it->type != media_type) | |
494 continue; // Wrong media type. | |
495 | |
496 const StreamParams* param = | |
497 GetStreamByIds(*current_streams, "", stream_it->id); | |
498 // groupid is empty for StreamParams generated using | |
499 // MediaSessionDescriptionFactory. | |
500 if (!param) { | |
501 // This is a new stream. | |
502 // Get a CNAME. Either new or same as one of the other synched streams. | |
503 std::string cname; | |
504 if (!GenerateCname(*current_streams, streams, stream_it->sync_label, | |
505 &cname)) { | |
506 return false; | |
507 } | |
508 | |
509 std::vector<uint32_t> ssrcs; | |
510 if (IsSctp(content_description)) { | |
511 GenerateSctpSids(*current_streams, &ssrcs); | |
512 } else { | |
513 GenerateSsrcs(*current_streams, stream_it->num_sim_layers, &ssrcs); | |
514 } | |
515 StreamParams stream_param; | |
516 stream_param.id = stream_it->id; | |
517 // Add the generated ssrc. | |
518 for (size_t i = 0; i < ssrcs.size(); ++i) { | |
519 stream_param.ssrcs.push_back(ssrcs[i]); | |
520 } | |
521 if (stream_it->num_sim_layers > 1) { | |
522 SsrcGroup group(kSimSsrcGroupSemantics, stream_param.ssrcs); | |
523 stream_param.ssrc_groups.push_back(group); | |
524 } | |
525 // Generate extra ssrcs for include_rtx_streams case. | |
526 if (include_rtx_streams) { | |
527 // Generate an RTX ssrc for every ssrc in the group. | |
528 std::vector<uint32_t> rtx_ssrcs; | |
529 GenerateSsrcs(*current_streams, static_cast<int>(ssrcs.size()), | |
530 &rtx_ssrcs); | |
531 for (size_t i = 0; i < ssrcs.size(); ++i) { | |
532 stream_param.AddFidSsrc(ssrcs[i], rtx_ssrcs[i]); | |
533 } | |
534 content_description->set_multistream(true); | |
535 } | |
536 stream_param.cname = cname; | |
537 stream_param.sync_label = stream_it->sync_label; | |
538 content_description->AddStream(stream_param); | |
539 | |
540 // Store the new StreamParams in current_streams. | |
541 // This is necessary so that we can use the CNAME for other media types. | |
542 current_streams->push_back(stream_param); | |
543 } else { | |
544 content_description->AddStream(*param); | |
545 } | |
546 } | |
547 return true; | |
548 } | |
549 | |
550 // Updates the transport infos of the |sdesc| according to the given | |
551 // |bundle_group|. The transport infos of the content names within the | |
552 // |bundle_group| should be updated to use the ufrag, pwd and DTLS role of the | |
553 // first content within the |bundle_group|. | |
554 static bool UpdateTransportInfoForBundle(const ContentGroup& bundle_group, | |
555 SessionDescription* sdesc) { | |
556 // The bundle should not be empty. | |
557 if (!sdesc || !bundle_group.FirstContentName()) { | |
558 return false; | |
559 } | |
560 | |
561 // We should definitely have a transport for the first content. | |
562 const std::string& selected_content_name = *bundle_group.FirstContentName(); | |
563 const TransportInfo* selected_transport_info = | |
564 sdesc->GetTransportInfoByName(selected_content_name); | |
565 if (!selected_transport_info) { | |
566 return false; | |
567 } | |
568 | |
569 // Set the other contents to use the same ICE credentials. | |
570 const std::string& selected_ufrag = | |
571 selected_transport_info->description.ice_ufrag; | |
572 const std::string& selected_pwd = | |
573 selected_transport_info->description.ice_pwd; | |
574 ConnectionRole selected_connection_role = | |
575 selected_transport_info->description.connection_role; | |
576 for (TransportInfos::iterator it = | |
577 sdesc->transport_infos().begin(); | |
578 it != sdesc->transport_infos().end(); ++it) { | |
579 if (bundle_group.HasContentName(it->content_name) && | |
580 it->content_name != selected_content_name) { | |
581 it->description.ice_ufrag = selected_ufrag; | |
582 it->description.ice_pwd = selected_pwd; | |
583 it->description.connection_role = selected_connection_role; | |
584 } | |
585 } | |
586 return true; | |
587 } | |
588 | |
589 // Gets the CryptoParamsVec of the given |content_name| from |sdesc|, and | |
590 // sets it to |cryptos|. | |
591 static bool GetCryptosByName(const SessionDescription* sdesc, | |
592 const std::string& content_name, | |
593 CryptoParamsVec* cryptos) { | |
594 if (!sdesc || !cryptos) { | |
595 return false; | |
596 } | |
597 | |
598 const ContentInfo* content = sdesc->GetContentByName(content_name); | |
599 if (!IsMediaContent(content) || !content->description) { | |
600 return false; | |
601 } | |
602 | |
603 const MediaContentDescription* media_desc = | |
604 static_cast<const MediaContentDescription*>(content->description); | |
605 *cryptos = media_desc->cryptos(); | |
606 return true; | |
607 } | |
608 | |
609 // Predicate function used by the remove_if. | |
610 // Returns true if the |crypto|'s cipher_suite is not found in |filter|. | |
611 static bool CryptoNotFound(const CryptoParams crypto, | |
612 const CryptoParamsVec* filter) { | |
613 if (filter == NULL) { | |
614 return true; | |
615 } | |
616 for (CryptoParamsVec::const_iterator it = filter->begin(); | |
617 it != filter->end(); ++it) { | |
618 if (it->cipher_suite == crypto.cipher_suite) { | |
619 return false; | |
620 } | |
621 } | |
622 return true; | |
623 } | |
624 | |
625 // Prunes the |target_cryptos| by removing the crypto params (cipher_suite) | |
626 // which are not available in |filter|. | |
627 static void PruneCryptos(const CryptoParamsVec& filter, | |
628 CryptoParamsVec* target_cryptos) { | |
629 if (!target_cryptos) { | |
630 return; | |
631 } | |
632 target_cryptos->erase(std::remove_if(target_cryptos->begin(), | |
633 target_cryptos->end(), | |
634 bind2nd(ptr_fun(CryptoNotFound), | |
635 &filter)), | |
636 target_cryptos->end()); | |
637 } | |
638 | |
639 static bool IsRtpProtocol(const std::string& protocol) { | |
640 return protocol.empty() || | |
641 (protocol.find(cricket::kMediaProtocolRtpPrefix) != std::string::npos); | |
642 } | |
643 | |
644 static bool IsRtpContent(SessionDescription* sdesc, | |
645 const std::string& content_name) { | |
646 bool is_rtp = false; | |
647 ContentInfo* content = sdesc->GetContentByName(content_name); | |
648 if (IsMediaContent(content)) { | |
649 MediaContentDescription* media_desc = | |
650 static_cast<MediaContentDescription*>(content->description); | |
651 if (!media_desc) { | |
652 return false; | |
653 } | |
654 is_rtp = IsRtpProtocol(media_desc->protocol()); | |
655 } | |
656 return is_rtp; | |
657 } | |
658 | |
659 // Updates the crypto parameters of the |sdesc| according to the given | |
660 // |bundle_group|. The crypto parameters of all the contents within the | |
661 // |bundle_group| should be updated to use the common subset of the | |
662 // available cryptos. | |
663 static bool UpdateCryptoParamsForBundle(const ContentGroup& bundle_group, | |
664 SessionDescription* sdesc) { | |
665 // The bundle should not be empty. | |
666 if (!sdesc || !bundle_group.FirstContentName()) { | |
667 return false; | |
668 } | |
669 | |
670 bool common_cryptos_needed = false; | |
671 // Get the common cryptos. | |
672 const ContentNames& content_names = bundle_group.content_names(); | |
673 CryptoParamsVec common_cryptos; | |
674 for (ContentNames::const_iterator it = content_names.begin(); | |
675 it != content_names.end(); ++it) { | |
676 if (!IsRtpContent(sdesc, *it)) { | |
677 continue; | |
678 } | |
679 // The common cryptos are needed if any of the content does not have DTLS | |
680 // enabled. | |
681 if (!sdesc->GetTransportInfoByName(*it)->description.secure()) { | |
682 common_cryptos_needed = true; | |
683 } | |
684 if (it == content_names.begin()) { | |
685 // Initial the common_cryptos with the first content in the bundle group. | |
686 if (!GetCryptosByName(sdesc, *it, &common_cryptos)) { | |
687 return false; | |
688 } | |
689 if (common_cryptos.empty()) { | |
690 // If there's no crypto params, we should just return. | |
691 return true; | |
692 } | |
693 } else { | |
694 CryptoParamsVec cryptos; | |
695 if (!GetCryptosByName(sdesc, *it, &cryptos)) { | |
696 return false; | |
697 } | |
698 PruneCryptos(cryptos, &common_cryptos); | |
699 } | |
700 } | |
701 | |
702 if (common_cryptos.empty() && common_cryptos_needed) { | |
703 return false; | |
704 } | |
705 | |
706 // Update to use the common cryptos. | |
707 for (ContentNames::const_iterator it = content_names.begin(); | |
708 it != content_names.end(); ++it) { | |
709 if (!IsRtpContent(sdesc, *it)) { | |
710 continue; | |
711 } | |
712 ContentInfo* content = sdesc->GetContentByName(*it); | |
713 if (IsMediaContent(content)) { | |
714 MediaContentDescription* media_desc = | |
715 static_cast<MediaContentDescription*>(content->description); | |
716 if (!media_desc) { | |
717 return false; | |
718 } | |
719 media_desc->set_cryptos(common_cryptos); | |
720 } | |
721 } | |
722 return true; | |
723 } | |
724 | |
725 template <class C> | |
726 static bool ContainsRtxCodec(const std::vector<C>& codecs) { | |
727 typename std::vector<C>::const_iterator it; | |
728 for (it = codecs.begin(); it != codecs.end(); ++it) { | |
729 if (IsRtxCodec(*it)) { | |
730 return true; | |
731 } | |
732 } | |
733 return false; | |
734 } | |
735 | |
736 template <class C> | |
737 static bool IsRtxCodec(const C& codec) { | |
738 return stricmp(codec.name.c_str(), kRtxCodecName) == 0; | |
739 } | |
740 | |
741 // Create a media content to be offered in a session-initiate, | |
742 // according to the given options.rtcp_mux, options.is_muc, | |
743 // options.streams, codecs, secure_transport, crypto, and streams. If we don't | |
744 // currently have crypto (in current_cryptos) and it is enabled (in | |
745 // secure_policy), crypto is created (according to crypto_suites). If | |
746 // add_legacy_stream is true, and current_streams is empty, a legacy | |
747 // stream is created. The created content is added to the offer. | |
748 template <class C> | |
749 static bool CreateMediaContentOffer( | |
750 const MediaSessionOptions& options, | |
751 const std::vector<C>& codecs, | |
752 const SecurePolicy& secure_policy, | |
753 const CryptoParamsVec* current_cryptos, | |
754 const std::vector<std::string>& crypto_suites, | |
755 const RtpHeaderExtensions& rtp_extensions, | |
756 bool add_legacy_stream, | |
757 StreamParamsVec* current_streams, | |
758 MediaContentDescriptionImpl<C>* offer) { | |
759 offer->AddCodecs(codecs); | |
760 offer->SortCodecs(); | |
761 | |
762 if (secure_policy == SEC_REQUIRED) { | |
763 offer->set_crypto_required(CT_SDES); | |
764 } | |
765 offer->set_rtcp_mux(options.rtcp_mux_enabled); | |
766 // TODO(deadbeef): Once we're sure this works correctly, enable it in | |
767 // CreateOffer. | |
768 // if (offer->type() == cricket::MEDIA_TYPE_VIDEO) { | |
769 // offer->set_rtcp_reduced_size(true); | |
770 // } | |
771 offer->set_multistream(options.is_muc); | |
772 offer->set_rtp_header_extensions(rtp_extensions); | |
773 | |
774 if (!AddStreamParams( | |
775 offer->type(), options.streams, current_streams, | |
776 offer, add_legacy_stream)) { | |
777 return false; | |
778 } | |
779 | |
780 #ifdef HAVE_SRTP | |
781 if (secure_policy != SEC_DISABLED) { | |
782 if (current_cryptos) { | |
783 AddMediaCryptos(*current_cryptos, offer); | |
784 } | |
785 if (offer->cryptos().empty()) { | |
786 if (!CreateMediaCryptos(crypto_suites, offer)) { | |
787 return false; | |
788 } | |
789 } | |
790 } | |
791 #endif | |
792 | |
793 if (offer->crypto_required() == CT_SDES && offer->cryptos().empty()) { | |
794 return false; | |
795 } | |
796 return true; | |
797 } | |
798 | |
799 template <class C> | |
800 static bool ReferencedCodecsMatch(const std::vector<C>& codecs1, | |
801 const std::string& codec1_id_str, | |
802 const std::vector<C>& codecs2, | |
803 const std::string& codec2_id_str) { | |
804 int codec1_id; | |
805 int codec2_id; | |
806 C codec1; | |
807 C codec2; | |
808 if (!rtc::FromString(codec1_id_str, &codec1_id) || | |
809 !rtc::FromString(codec2_id_str, &codec2_id) || | |
810 !FindCodecById(codecs1, codec1_id, &codec1) || | |
811 !FindCodecById(codecs2, codec2_id, &codec2)) { | |
812 return false; | |
813 } | |
814 return codec1.Matches(codec2); | |
815 } | |
816 | |
817 template <class C> | |
818 static void NegotiateCodecs(const std::vector<C>& local_codecs, | |
819 const std::vector<C>& offered_codecs, | |
820 std::vector<C>* negotiated_codecs) { | |
821 typename std::vector<C>::const_iterator ours; | |
822 for (ours = local_codecs.begin(); | |
823 ours != local_codecs.end(); ++ours) { | |
824 typename std::vector<C>::const_iterator theirs; | |
825 for (theirs = offered_codecs.begin(); | |
826 theirs != offered_codecs.end(); ++theirs) { | |
827 if (ours->Matches(*theirs)) { | |
828 C negotiated = *ours; | |
829 negotiated.IntersectFeedbackParams(*theirs); | |
830 if (IsRtxCodec(negotiated)) { | |
831 std::string offered_apt_value; | |
832 std::string local_apt_value; | |
833 if (!ours->GetParam(kCodecParamAssociatedPayloadType, | |
834 &local_apt_value) || | |
835 !theirs->GetParam(kCodecParamAssociatedPayloadType, | |
836 &offered_apt_value)) { | |
837 LOG(LS_WARNING) << "RTX missing associated payload type."; | |
838 continue; | |
839 } | |
840 // Only negotiate RTX if kCodecParamAssociatedPayloadType has been | |
841 // set in local and remote codecs, and they match. | |
842 if (!ReferencedCodecsMatch(local_codecs, local_apt_value, | |
843 offered_codecs, offered_apt_value)) { | |
844 LOG(LS_WARNING) << "RTX associated codecs don't match."; | |
845 continue; | |
846 } | |
847 negotiated.SetParam(kCodecParamAssociatedPayloadType, | |
848 offered_apt_value); | |
849 } | |
850 | |
851 negotiated.id = theirs->id; | |
852 // RFC3264: Although the answerer MAY list the formats in their desired | |
853 // order of preference, it is RECOMMENDED that unless there is a | |
854 // specific reason, the answerer list formats in the same relative order | |
855 // they were present in the offer. | |
856 negotiated.preference = theirs->preference; | |
857 negotiated_codecs->push_back(negotiated); | |
858 } | |
859 } | |
860 } | |
861 } | |
862 | |
863 template <class C> | |
864 static bool FindMatchingCodec(const std::vector<C>& codecs, | |
865 const C& codec_to_match, | |
866 C* found_codec) { | |
867 for (typename std::vector<C>::const_iterator it = codecs.begin(); | |
868 it != codecs.end(); ++it) { | |
869 if (it->Matches(codec_to_match)) { | |
870 if (found_codec != NULL) { | |
871 *found_codec= *it; | |
872 } | |
873 return true; | |
874 } | |
875 } | |
876 return false; | |
877 } | |
878 | |
879 // Adds all codecs from |reference_codecs| to |offered_codecs| that dont' | |
880 // already exist in |offered_codecs| and ensure the payload types don't | |
881 // collide. | |
882 template <class C> | |
883 static void FindCodecsToOffer( | |
884 const std::vector<C>& reference_codecs, | |
885 std::vector<C>* offered_codecs, | |
886 UsedPayloadTypes* used_pltypes) { | |
887 | |
888 typedef std::map<int, C> RtxCodecReferences; | |
889 RtxCodecReferences new_rtx_codecs; | |
890 | |
891 // Find all new RTX codecs. | |
892 for (typename std::vector<C>::const_iterator it = reference_codecs.begin(); | |
893 it != reference_codecs.end(); ++it) { | |
894 if (!FindMatchingCodec<C>(*offered_codecs, *it, NULL) && IsRtxCodec(*it)) { | |
895 C rtx_codec = *it; | |
896 int referenced_pl_type = | |
897 rtc::FromString<int>(0, | |
898 rtx_codec.params[kCodecParamAssociatedPayloadType]); | |
899 new_rtx_codecs.insert(std::pair<int, C>(referenced_pl_type, | |
900 rtx_codec)); | |
901 } | |
902 } | |
903 | |
904 // Add all new codecs that are not RTX codecs. | |
905 for (typename std::vector<C>::const_iterator it = reference_codecs.begin(); | |
906 it != reference_codecs.end(); ++it) { | |
907 if (!FindMatchingCodec<C>(*offered_codecs, *it, NULL) && !IsRtxCodec(*it)) { | |
908 C codec = *it; | |
909 int original_payload_id = codec.id; | |
910 used_pltypes->FindAndSetIdUsed(&codec); | |
911 offered_codecs->push_back(codec); | |
912 | |
913 // If this codec is referenced by a new RTX codec, update the reference | |
914 // in the RTX codec with the new payload type. | |
915 typename RtxCodecReferences::iterator rtx_it = | |
916 new_rtx_codecs.find(original_payload_id); | |
917 if (rtx_it != new_rtx_codecs.end()) { | |
918 C& rtx_codec = rtx_it->second; | |
919 rtx_codec.params[kCodecParamAssociatedPayloadType] = | |
920 rtc::ToString(codec.id); | |
921 } | |
922 } | |
923 } | |
924 | |
925 // Add all new RTX codecs. | |
926 for (typename RtxCodecReferences::iterator it = new_rtx_codecs.begin(); | |
927 it != new_rtx_codecs.end(); ++it) { | |
928 C& rtx_codec = it->second; | |
929 used_pltypes->FindAndSetIdUsed(&rtx_codec); | |
930 offered_codecs->push_back(rtx_codec); | |
931 } | |
932 } | |
933 | |
934 | |
935 static bool FindByUri(const RtpHeaderExtensions& extensions, | |
936 const RtpHeaderExtension& ext_to_match, | |
937 RtpHeaderExtension* found_extension) { | |
938 for (RtpHeaderExtensions::const_iterator it = extensions.begin(); | |
939 it != extensions.end(); ++it) { | |
940 // We assume that all URIs are given in a canonical format. | |
941 if (it->uri == ext_to_match.uri) { | |
942 if (found_extension != NULL) { | |
943 *found_extension = *it; | |
944 } | |
945 return true; | |
946 } | |
947 } | |
948 return false; | |
949 } | |
950 | |
951 // Iterates through |offered_extensions|, adding each one to |all_extensions| | |
952 // and |used_ids|, and resolving ID conflicts. If an offered extension has the | |
953 // same URI as one in |all_extensions|, it will re-use the same ID and won't be | |
954 // treated as a conflict. | |
955 static void FindAndSetRtpHdrExtUsed(RtpHeaderExtensions* offered_extensions, | |
956 RtpHeaderExtensions* all_extensions, | |
957 UsedRtpHeaderExtensionIds* used_ids) { | |
958 for (auto& extension : *offered_extensions) { | |
959 RtpHeaderExtension existing; | |
960 if (FindByUri(*all_extensions, extension, &existing)) { | |
961 extension.id = existing.id; | |
962 } else { | |
963 used_ids->FindAndSetIdUsed(&extension); | |
964 all_extensions->push_back(extension); | |
965 } | |
966 } | |
967 } | |
968 | |
969 // Adds |reference_extensions| to |offered_extensions|, while updating | |
970 // |all_extensions| and |used_ids|. | |
971 static void FindRtpHdrExtsToOffer( | |
972 const RtpHeaderExtensions& reference_extensions, | |
973 RtpHeaderExtensions* offered_extensions, | |
974 RtpHeaderExtensions* all_extensions, | |
975 UsedRtpHeaderExtensionIds* used_ids) { | |
976 for (auto reference_extension : reference_extensions) { | |
977 if (!FindByUri(*offered_extensions, reference_extension, NULL)) { | |
978 RtpHeaderExtension existing; | |
979 if (FindByUri(*all_extensions, reference_extension, &existing)) { | |
980 offered_extensions->push_back(existing); | |
981 } else { | |
982 used_ids->FindAndSetIdUsed(&reference_extension); | |
983 all_extensions->push_back(reference_extension); | |
984 offered_extensions->push_back(reference_extension); | |
985 } | |
986 } | |
987 } | |
988 } | |
989 | |
990 static void NegotiateRtpHeaderExtensions( | |
991 const RtpHeaderExtensions& local_extensions, | |
992 const RtpHeaderExtensions& offered_extensions, | |
993 RtpHeaderExtensions* negotiated_extenstions) { | |
994 RtpHeaderExtensions::const_iterator ours; | |
995 for (ours = local_extensions.begin(); | |
996 ours != local_extensions.end(); ++ours) { | |
997 RtpHeaderExtension theirs; | |
998 if (FindByUri(offered_extensions, *ours, &theirs)) { | |
999 // We respond with their RTP header extension id. | |
1000 negotiated_extenstions->push_back(theirs); | |
1001 } | |
1002 } | |
1003 } | |
1004 | |
1005 static void StripCNCodecs(AudioCodecs* audio_codecs) { | |
1006 AudioCodecs::iterator iter = audio_codecs->begin(); | |
1007 while (iter != audio_codecs->end()) { | |
1008 if (stricmp(iter->name.c_str(), kComfortNoiseCodecName) == 0) { | |
1009 iter = audio_codecs->erase(iter); | |
1010 } else { | |
1011 ++iter; | |
1012 } | |
1013 } | |
1014 } | |
1015 | |
1016 // Create a media content to be answered in a session-accept, | |
1017 // according to the given options.rtcp_mux, options.streams, codecs, | |
1018 // crypto, and streams. If we don't currently have crypto (in | |
1019 // current_cryptos) and it is enabled (in secure_policy), crypto is | |
1020 // created (according to crypto_suites). If add_legacy_stream is | |
1021 // true, and current_streams is empty, a legacy stream is created. | |
1022 // The codecs, rtcp_mux, and crypto are all negotiated with the offer | |
1023 // from the incoming session-initiate. If the negotiation fails, this | |
1024 // method returns false. The created content is added to the offer. | |
1025 template <class C> | |
1026 static bool CreateMediaContentAnswer( | |
1027 const MediaContentDescriptionImpl<C>* offer, | |
1028 const MediaSessionOptions& options, | |
1029 const std::vector<C>& local_codecs, | |
1030 const SecurePolicy& sdes_policy, | |
1031 const CryptoParamsVec* current_cryptos, | |
1032 const RtpHeaderExtensions& local_rtp_extenstions, | |
1033 StreamParamsVec* current_streams, | |
1034 bool add_legacy_stream, | |
1035 bool bundle_enabled, | |
1036 MediaContentDescriptionImpl<C>* answer) { | |
1037 std::vector<C> negotiated_codecs; | |
1038 NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs); | |
1039 answer->AddCodecs(negotiated_codecs); | |
1040 answer->SortCodecs(); | |
1041 answer->set_protocol(offer->protocol()); | |
1042 RtpHeaderExtensions negotiated_rtp_extensions; | |
1043 NegotiateRtpHeaderExtensions(local_rtp_extenstions, | |
1044 offer->rtp_header_extensions(), | |
1045 &negotiated_rtp_extensions); | |
1046 answer->set_rtp_header_extensions(negotiated_rtp_extensions); | |
1047 | |
1048 answer->set_rtcp_mux(options.rtcp_mux_enabled && offer->rtcp_mux()); | |
1049 // TODO(deadbeef): Once we're sure this works correctly, enable it in | |
1050 // CreateAnswer. | |
1051 // if (answer->type() == cricket::MEDIA_TYPE_VIDEO) { | |
1052 // answer->set_rtcp_reduced_size(offer->rtcp_reduced_size()); | |
1053 // } | |
1054 | |
1055 if (sdes_policy != SEC_DISABLED) { | |
1056 CryptoParams crypto; | |
1057 if (SelectCrypto(offer, bundle_enabled, &crypto)) { | |
1058 if (current_cryptos) { | |
1059 FindMatchingCrypto(*current_cryptos, crypto, &crypto); | |
1060 } | |
1061 answer->AddCrypto(crypto); | |
1062 } | |
1063 } | |
1064 | |
1065 if (answer->cryptos().empty() && | |
1066 (offer->crypto_required() == CT_SDES || sdes_policy == SEC_REQUIRED)) { | |
1067 return false; | |
1068 } | |
1069 | |
1070 if (!AddStreamParams( | |
1071 answer->type(), options.streams, current_streams, | |
1072 answer, add_legacy_stream)) { | |
1073 return false; // Something went seriously wrong. | |
1074 } | |
1075 | |
1076 // Make sure the answer media content direction is per default set as | |
1077 // described in RFC3264 section 6.1. | |
1078 switch (offer->direction()) { | |
1079 case MD_INACTIVE: | |
1080 answer->set_direction(MD_INACTIVE); | |
1081 break; | |
1082 case MD_SENDONLY: | |
1083 answer->set_direction(MD_RECVONLY); | |
1084 break; | |
1085 case MD_RECVONLY: | |
1086 answer->set_direction(IsRtpProtocol(answer->protocol()) && | |
1087 answer->streams().empty() | |
1088 ? MD_INACTIVE | |
1089 : MD_SENDONLY); | |
1090 break; | |
1091 case MD_SENDRECV: | |
1092 answer->set_direction(IsRtpProtocol(answer->protocol()) && | |
1093 answer->streams().empty() | |
1094 ? MD_RECVONLY | |
1095 : MD_SENDRECV); | |
1096 break; | |
1097 default: | |
1098 RTC_DCHECK(false && "MediaContentDescription has unexpected direction."); | |
1099 break; | |
1100 } | |
1101 | |
1102 return true; | |
1103 } | |
1104 | |
1105 static bool IsMediaProtocolSupported(MediaType type, | |
1106 const std::string& protocol, | |
1107 bool secure_transport) { | |
1108 // Data channels can have a protocol of SCTP or SCTP/DTLS. | |
1109 if (type == MEDIA_TYPE_DATA && | |
1110 ((protocol == kMediaProtocolSctp && !secure_transport)|| | |
1111 (protocol == kMediaProtocolDtlsSctp && secure_transport))) { | |
1112 return true; | |
1113 } | |
1114 | |
1115 // Since not all applications serialize and deserialize the media protocol, | |
1116 // we will have to accept |protocol| to be empty. | |
1117 return protocol == kMediaProtocolAvpf || protocol.empty() || | |
1118 protocol == kMediaProtocolSavpf || | |
1119 (protocol == kMediaProtocolDtlsSavpf && secure_transport); | |
1120 } | |
1121 | |
1122 static void SetMediaProtocol(bool secure_transport, | |
1123 MediaContentDescription* desc) { | |
1124 if (!desc->cryptos().empty()) | |
1125 desc->set_protocol(kMediaProtocolSavpf); | |
1126 else if (secure_transport) | |
1127 desc->set_protocol(kMediaProtocolDtlsSavpf); | |
1128 else | |
1129 desc->set_protocol(kMediaProtocolAvpf); | |
1130 } | |
1131 | |
1132 // Gets the TransportInfo of the given |content_name| from the | |
1133 // |current_description|. If doesn't exist, returns a new one. | |
1134 static const TransportDescription* GetTransportDescription( | |
1135 const std::string& content_name, | |
1136 const SessionDescription* current_description) { | |
1137 const TransportDescription* desc = NULL; | |
1138 if (current_description) { | |
1139 const TransportInfo* info = | |
1140 current_description->GetTransportInfoByName(content_name); | |
1141 if (info) { | |
1142 desc = &info->description; | |
1143 } | |
1144 } | |
1145 return desc; | |
1146 } | |
1147 | |
1148 // Gets the current DTLS state from the transport description. | |
1149 static bool IsDtlsActive( | |
1150 const std::string& content_name, | |
1151 const SessionDescription* current_description) { | |
1152 if (!current_description) | |
1153 return false; | |
1154 | |
1155 const ContentInfo* content = | |
1156 current_description->GetContentByName(content_name); | |
1157 if (!content) | |
1158 return false; | |
1159 | |
1160 const TransportDescription* current_tdesc = | |
1161 GetTransportDescription(content_name, current_description); | |
1162 if (!current_tdesc) | |
1163 return false; | |
1164 | |
1165 return current_tdesc->secure(); | |
1166 } | |
1167 | |
1168 std::string MediaTypeToString(MediaType type) { | |
1169 std::string type_str; | |
1170 switch (type) { | |
1171 case MEDIA_TYPE_AUDIO: | |
1172 type_str = "audio"; | |
1173 break; | |
1174 case MEDIA_TYPE_VIDEO: | |
1175 type_str = "video"; | |
1176 break; | |
1177 case MEDIA_TYPE_DATA: | |
1178 type_str = "data"; | |
1179 break; | |
1180 default: | |
1181 ASSERT(false); | |
1182 break; | |
1183 } | |
1184 return type_str; | |
1185 } | |
1186 | |
1187 void MediaSessionOptions::AddSendStream(MediaType type, | |
1188 const std::string& id, | |
1189 const std::string& sync_label) { | |
1190 AddSendStreamInternal(type, id, sync_label, 1); | |
1191 } | |
1192 | |
1193 void MediaSessionOptions::AddSendVideoStream( | |
1194 const std::string& id, | |
1195 const std::string& sync_label, | |
1196 int num_sim_layers) { | |
1197 AddSendStreamInternal(MEDIA_TYPE_VIDEO, id, sync_label, num_sim_layers); | |
1198 } | |
1199 | |
1200 void MediaSessionOptions::AddSendStreamInternal( | |
1201 MediaType type, | |
1202 const std::string& id, | |
1203 const std::string& sync_label, | |
1204 int num_sim_layers) { | |
1205 streams.push_back(Stream(type, id, sync_label, num_sim_layers)); | |
1206 | |
1207 // If we haven't already set the data_channel_type, and we add a | |
1208 // stream, we assume it's an RTP data stream. | |
1209 if (type == MEDIA_TYPE_DATA && data_channel_type == DCT_NONE) | |
1210 data_channel_type = DCT_RTP; | |
1211 } | |
1212 | |
1213 void MediaSessionOptions::RemoveSendStream(MediaType type, | |
1214 const std::string& id) { | |
1215 Streams::iterator stream_it = streams.begin(); | |
1216 for (; stream_it != streams.end(); ++stream_it) { | |
1217 if (stream_it->type == type && stream_it->id == id) { | |
1218 streams.erase(stream_it); | |
1219 return; | |
1220 } | |
1221 } | |
1222 ASSERT(false); | |
1223 } | |
1224 | |
1225 bool MediaSessionOptions::HasSendMediaStream(MediaType type) const { | |
1226 Streams::const_iterator stream_it = streams.begin(); | |
1227 for (; stream_it != streams.end(); ++stream_it) { | |
1228 if (stream_it->type == type) { | |
1229 return true; | |
1230 } | |
1231 } | |
1232 return false; | |
1233 } | |
1234 | |
1235 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory( | |
1236 const TransportDescriptionFactory* transport_desc_factory) | |
1237 : secure_(SEC_DISABLED), | |
1238 add_legacy_(true), | |
1239 transport_desc_factory_(transport_desc_factory) { | |
1240 } | |
1241 | |
1242 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory( | |
1243 ChannelManager* channel_manager, | |
1244 const TransportDescriptionFactory* transport_desc_factory) | |
1245 : secure_(SEC_DISABLED), | |
1246 add_legacy_(true), | |
1247 transport_desc_factory_(transport_desc_factory) { | |
1248 channel_manager->GetSupportedAudioCodecs(&audio_codecs_); | |
1249 channel_manager->GetSupportedAudioRtpHeaderExtensions(&audio_rtp_extensions_); | |
1250 channel_manager->GetSupportedVideoCodecs(&video_codecs_); | |
1251 channel_manager->GetSupportedVideoRtpHeaderExtensions(&video_rtp_extensions_); | |
1252 channel_manager->GetSupportedDataCodecs(&data_codecs_); | |
1253 } | |
1254 | |
1255 SessionDescription* MediaSessionDescriptionFactory::CreateOffer( | |
1256 const MediaSessionOptions& options, | |
1257 const SessionDescription* current_description) const { | |
1258 scoped_ptr<SessionDescription> offer(new SessionDescription()); | |
1259 | |
1260 StreamParamsVec current_streams; | |
1261 GetCurrentStreamParams(current_description, ¤t_streams); | |
1262 | |
1263 AudioCodecs audio_codecs; | |
1264 VideoCodecs video_codecs; | |
1265 DataCodecs data_codecs; | |
1266 GetCodecsToOffer(current_description, &audio_codecs, &video_codecs, | |
1267 &data_codecs); | |
1268 | |
1269 if (!options.vad_enabled) { | |
1270 // If application doesn't want CN codecs in offer. | |
1271 StripCNCodecs(&audio_codecs); | |
1272 } | |
1273 | |
1274 RtpHeaderExtensions audio_rtp_extensions; | |
1275 RtpHeaderExtensions video_rtp_extensions; | |
1276 GetRtpHdrExtsToOffer(current_description, &audio_rtp_extensions, | |
1277 &video_rtp_extensions); | |
1278 | |
1279 bool audio_added = false; | |
1280 bool video_added = false; | |
1281 bool data_added = false; | |
1282 | |
1283 // Iterate through the contents of |current_description| to maintain the order | |
1284 // of the m-lines in the new offer. | |
1285 if (current_description) { | |
1286 ContentInfos::const_iterator it = current_description->contents().begin(); | |
1287 for (; it != current_description->contents().end(); ++it) { | |
1288 if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) { | |
1289 if (!AddAudioContentForOffer(options, current_description, | |
1290 audio_rtp_extensions, audio_codecs, | |
1291 ¤t_streams, offer.get())) { | |
1292 return NULL; | |
1293 } | |
1294 audio_added = true; | |
1295 } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) { | |
1296 if (!AddVideoContentForOffer(options, current_description, | |
1297 video_rtp_extensions, video_codecs, | |
1298 ¤t_streams, offer.get())) { | |
1299 return NULL; | |
1300 } | |
1301 video_added = true; | |
1302 } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)) { | |
1303 MediaSessionOptions options_copy(options); | |
1304 if (IsSctp(static_cast<const MediaContentDescription*>( | |
1305 it->description))) { | |
1306 options_copy.data_channel_type = DCT_SCTP; | |
1307 } | |
1308 if (!AddDataContentForOffer(options_copy, current_description, | |
1309 &data_codecs, ¤t_streams, | |
1310 offer.get())) { | |
1311 return NULL; | |
1312 } | |
1313 data_added = true; | |
1314 } else { | |
1315 ASSERT(false); | |
1316 } | |
1317 } | |
1318 } | |
1319 | |
1320 // Append contents that are not in |current_description|. | |
1321 if (!audio_added && options.has_audio() && | |
1322 !AddAudioContentForOffer(options, current_description, | |
1323 audio_rtp_extensions, audio_codecs, | |
1324 ¤t_streams, offer.get())) { | |
1325 return NULL; | |
1326 } | |
1327 if (!video_added && options.has_video() && | |
1328 !AddVideoContentForOffer(options, current_description, | |
1329 video_rtp_extensions, video_codecs, | |
1330 ¤t_streams, offer.get())) { | |
1331 return NULL; | |
1332 } | |
1333 if (!data_added && options.has_data() && | |
1334 !AddDataContentForOffer(options, current_description, &data_codecs, | |
1335 ¤t_streams, offer.get())) { | |
1336 return NULL; | |
1337 } | |
1338 | |
1339 // Bundle the contents together, if we've been asked to do so, and update any | |
1340 // parameters that need to be tweaked for BUNDLE. | |
1341 if (options.bundle_enabled) { | |
1342 ContentGroup offer_bundle(GROUP_TYPE_BUNDLE); | |
1343 for (ContentInfos::const_iterator content = offer->contents().begin(); | |
1344 content != offer->contents().end(); ++content) { | |
1345 offer_bundle.AddContentName(content->name); | |
1346 } | |
1347 offer->AddGroup(offer_bundle); | |
1348 if (!UpdateTransportInfoForBundle(offer_bundle, offer.get())) { | |
1349 LOG(LS_ERROR) << "CreateOffer failed to UpdateTransportInfoForBundle."; | |
1350 return NULL; | |
1351 } | |
1352 if (!UpdateCryptoParamsForBundle(offer_bundle, offer.get())) { | |
1353 LOG(LS_ERROR) << "CreateOffer failed to UpdateCryptoParamsForBundle."; | |
1354 return NULL; | |
1355 } | |
1356 } | |
1357 | |
1358 return offer.release(); | |
1359 } | |
1360 | |
1361 SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( | |
1362 const SessionDescription* offer, const MediaSessionOptions& options, | |
1363 const SessionDescription* current_description) const { | |
1364 // The answer contains the intersection of the codecs in the offer with the | |
1365 // codecs we support, ordered by our local preference. As indicated by | |
1366 // XEP-0167, we retain the same payload ids from the offer in the answer. | |
1367 scoped_ptr<SessionDescription> answer(new SessionDescription()); | |
1368 | |
1369 StreamParamsVec current_streams; | |
1370 GetCurrentStreamParams(current_description, ¤t_streams); | |
1371 | |
1372 if (offer) { | |
1373 ContentInfos::const_iterator it = offer->contents().begin(); | |
1374 for (; it != offer->contents().end(); ++it) { | |
1375 if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) { | |
1376 if (!AddAudioContentForAnswer(offer, options, current_description, | |
1377 ¤t_streams, answer.get())) { | |
1378 return NULL; | |
1379 } | |
1380 } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) { | |
1381 if (!AddVideoContentForAnswer(offer, options, current_description, | |
1382 ¤t_streams, answer.get())) { | |
1383 return NULL; | |
1384 } | |
1385 } else { | |
1386 ASSERT(IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)); | |
1387 if (!AddDataContentForAnswer(offer, options, current_description, | |
1388 ¤t_streams, answer.get())) { | |
1389 return NULL; | |
1390 } | |
1391 } | |
1392 } | |
1393 } | |
1394 | |
1395 // If the offer supports BUNDLE, and we want to use it too, create a BUNDLE | |
1396 // group in the answer with the appropriate content names. | |
1397 if (offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled) { | |
1398 const ContentGroup* offer_bundle = offer->GetGroupByName(GROUP_TYPE_BUNDLE); | |
1399 ContentGroup answer_bundle(GROUP_TYPE_BUNDLE); | |
1400 for (ContentInfos::const_iterator content = answer->contents().begin(); | |
1401 content != answer->contents().end(); ++content) { | |
1402 if (!content->rejected && offer_bundle->HasContentName(content->name)) { | |
1403 answer_bundle.AddContentName(content->name); | |
1404 } | |
1405 } | |
1406 if (answer_bundle.FirstContentName()) { | |
1407 answer->AddGroup(answer_bundle); | |
1408 | |
1409 // Share the same ICE credentials and crypto params across all contents, | |
1410 // as BUNDLE requires. | |
1411 if (!UpdateTransportInfoForBundle(answer_bundle, answer.get())) { | |
1412 LOG(LS_ERROR) << "CreateAnswer failed to UpdateTransportInfoForBundle."; | |
1413 return NULL; | |
1414 } | |
1415 | |
1416 if (!UpdateCryptoParamsForBundle(answer_bundle, answer.get())) { | |
1417 LOG(LS_ERROR) << "CreateAnswer failed to UpdateCryptoParamsForBundle."; | |
1418 return NULL; | |
1419 } | |
1420 } | |
1421 } | |
1422 | |
1423 return answer.release(); | |
1424 } | |
1425 | |
1426 void MediaSessionDescriptionFactory::GetCodecsToOffer( | |
1427 const SessionDescription* current_description, | |
1428 AudioCodecs* audio_codecs, | |
1429 VideoCodecs* video_codecs, | |
1430 DataCodecs* data_codecs) const { | |
1431 UsedPayloadTypes used_pltypes; | |
1432 audio_codecs->clear(); | |
1433 video_codecs->clear(); | |
1434 data_codecs->clear(); | |
1435 | |
1436 | |
1437 // First - get all codecs from the current description if the media type | |
1438 // is used. | |
1439 // Add them to |used_pltypes| so the payloadtype is not reused if a new media | |
1440 // type is added. | |
1441 if (current_description) { | |
1442 const AudioContentDescription* audio = | |
1443 GetFirstAudioContentDescription(current_description); | |
1444 if (audio) { | |
1445 *audio_codecs = audio->codecs(); | |
1446 used_pltypes.FindAndSetIdUsed<AudioCodec>(audio_codecs); | |
1447 } | |
1448 const VideoContentDescription* video = | |
1449 GetFirstVideoContentDescription(current_description); | |
1450 if (video) { | |
1451 *video_codecs = video->codecs(); | |
1452 used_pltypes.FindAndSetIdUsed<VideoCodec>(video_codecs); | |
1453 } | |
1454 const DataContentDescription* data = | |
1455 GetFirstDataContentDescription(current_description); | |
1456 if (data) { | |
1457 *data_codecs = data->codecs(); | |
1458 used_pltypes.FindAndSetIdUsed<DataCodec>(data_codecs); | |
1459 } | |
1460 } | |
1461 | |
1462 // Add our codecs that are not in |current_description|. | |
1463 FindCodecsToOffer<AudioCodec>(audio_codecs_, audio_codecs, &used_pltypes); | |
1464 FindCodecsToOffer<VideoCodec>(video_codecs_, video_codecs, &used_pltypes); | |
1465 FindCodecsToOffer<DataCodec>(data_codecs_, data_codecs, &used_pltypes); | |
1466 } | |
1467 | |
1468 void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer( | |
1469 const SessionDescription* current_description, | |
1470 RtpHeaderExtensions* audio_extensions, | |
1471 RtpHeaderExtensions* video_extensions) const { | |
1472 // All header extensions allocated from the same range to avoid potential | |
1473 // issues when using BUNDLE. | |
1474 UsedRtpHeaderExtensionIds used_ids; | |
1475 RtpHeaderExtensions all_extensions; | |
1476 audio_extensions->clear(); | |
1477 video_extensions->clear(); | |
1478 | |
1479 // First - get all extensions from the current description if the media type | |
1480 // is used. | |
1481 // Add them to |used_ids| so the local ids are not reused if a new media | |
1482 // type is added. | |
1483 if (current_description) { | |
1484 const AudioContentDescription* audio = | |
1485 GetFirstAudioContentDescription(current_description); | |
1486 if (audio) { | |
1487 *audio_extensions = audio->rtp_header_extensions(); | |
1488 FindAndSetRtpHdrExtUsed(audio_extensions, &all_extensions, &used_ids); | |
1489 } | |
1490 const VideoContentDescription* video = | |
1491 GetFirstVideoContentDescription(current_description); | |
1492 if (video) { | |
1493 *video_extensions = video->rtp_header_extensions(); | |
1494 FindAndSetRtpHdrExtUsed(video_extensions, &all_extensions, &used_ids); | |
1495 } | |
1496 } | |
1497 | |
1498 // Add our default RTP header extensions that are not in | |
1499 // |current_description|. | |
1500 FindRtpHdrExtsToOffer(audio_rtp_header_extensions(), audio_extensions, | |
1501 &all_extensions, &used_ids); | |
1502 FindRtpHdrExtsToOffer(video_rtp_header_extensions(), video_extensions, | |
1503 &all_extensions, &used_ids); | |
1504 } | |
1505 | |
1506 bool MediaSessionDescriptionFactory::AddTransportOffer( | |
1507 const std::string& content_name, | |
1508 const TransportOptions& transport_options, | |
1509 const SessionDescription* current_desc, | |
1510 SessionDescription* offer_desc) const { | |
1511 if (!transport_desc_factory_) | |
1512 return false; | |
1513 const TransportDescription* current_tdesc = | |
1514 GetTransportDescription(content_name, current_desc); | |
1515 rtc::scoped_ptr<TransportDescription> new_tdesc( | |
1516 transport_desc_factory_->CreateOffer(transport_options, current_tdesc)); | |
1517 bool ret = (new_tdesc.get() != NULL && | |
1518 offer_desc->AddTransportInfo(TransportInfo(content_name, *new_tdesc))); | |
1519 if (!ret) { | |
1520 LOG(LS_ERROR) | |
1521 << "Failed to AddTransportOffer, content name=" << content_name; | |
1522 } | |
1523 return ret; | |
1524 } | |
1525 | |
1526 TransportDescription* MediaSessionDescriptionFactory::CreateTransportAnswer( | |
1527 const std::string& content_name, | |
1528 const SessionDescription* offer_desc, | |
1529 const TransportOptions& transport_options, | |
1530 const SessionDescription* current_desc) const { | |
1531 if (!transport_desc_factory_) | |
1532 return NULL; | |
1533 const TransportDescription* offer_tdesc = | |
1534 GetTransportDescription(content_name, offer_desc); | |
1535 const TransportDescription* current_tdesc = | |
1536 GetTransportDescription(content_name, current_desc); | |
1537 return | |
1538 transport_desc_factory_->CreateAnswer(offer_tdesc, transport_options, | |
1539 current_tdesc); | |
1540 } | |
1541 | |
1542 bool MediaSessionDescriptionFactory::AddTransportAnswer( | |
1543 const std::string& content_name, | |
1544 const TransportDescription& transport_desc, | |
1545 SessionDescription* answer_desc) const { | |
1546 if (!answer_desc->AddTransportInfo(TransportInfo(content_name, | |
1547 transport_desc))) { | |
1548 LOG(LS_ERROR) | |
1549 << "Failed to AddTransportAnswer, content name=" << content_name; | |
1550 return false; | |
1551 } | |
1552 return true; | |
1553 } | |
1554 | |
1555 bool MediaSessionDescriptionFactory::AddAudioContentForOffer( | |
1556 const MediaSessionOptions& options, | |
1557 const SessionDescription* current_description, | |
1558 const RtpHeaderExtensions& audio_rtp_extensions, | |
1559 const AudioCodecs& audio_codecs, | |
1560 StreamParamsVec* current_streams, | |
1561 SessionDescription* desc) const { | |
1562 const ContentInfo* current_audio_content = | |
1563 GetFirstAudioContent(current_description); | |
1564 std::string content_name = | |
1565 current_audio_content ? current_audio_content->name : CN_AUDIO; | |
1566 | |
1567 cricket::SecurePolicy sdes_policy = | |
1568 IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED | |
1569 : secure(); | |
1570 | |
1571 scoped_ptr<AudioContentDescription> audio(new AudioContentDescription()); | |
1572 std::vector<std::string> crypto_suites; | |
1573 GetSupportedAudioCryptoSuiteNames(&crypto_suites); | |
1574 if (!CreateMediaContentOffer( | |
1575 options, | |
1576 audio_codecs, | |
1577 sdes_policy, | |
1578 GetCryptos(GetFirstAudioContentDescription(current_description)), | |
1579 crypto_suites, | |
1580 audio_rtp_extensions, | |
1581 add_legacy_, | |
1582 current_streams, | |
1583 audio.get())) { | |
1584 return false; | |
1585 } | |
1586 audio->set_lang(lang_); | |
1587 | |
1588 bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); | |
1589 SetMediaProtocol(secure_transport, audio.get()); | |
1590 | |
1591 if (!audio->streams().empty()) { | |
1592 if (options.recv_audio) { | |
1593 audio->set_direction(MD_SENDRECV); | |
1594 } else { | |
1595 audio->set_direction(MD_SENDONLY); | |
1596 } | |
1597 } else { | |
1598 if (options.recv_audio) { | |
1599 audio->set_direction(MD_RECVONLY); | |
1600 } else { | |
1601 audio->set_direction(MD_INACTIVE); | |
1602 } | |
1603 } | |
1604 | |
1605 desc->AddContent(content_name, NS_JINGLE_RTP, audio.release()); | |
1606 if (!AddTransportOffer(content_name, options.audio_transport_options, | |
1607 current_description, desc)) { | |
1608 return false; | |
1609 } | |
1610 | |
1611 return true; | |
1612 } | |
1613 | |
1614 bool MediaSessionDescriptionFactory::AddVideoContentForOffer( | |
1615 const MediaSessionOptions& options, | |
1616 const SessionDescription* current_description, | |
1617 const RtpHeaderExtensions& video_rtp_extensions, | |
1618 const VideoCodecs& video_codecs, | |
1619 StreamParamsVec* current_streams, | |
1620 SessionDescription* desc) const { | |
1621 const ContentInfo* current_video_content = | |
1622 GetFirstVideoContent(current_description); | |
1623 std::string content_name = | |
1624 current_video_content ? current_video_content->name : CN_VIDEO; | |
1625 | |
1626 cricket::SecurePolicy sdes_policy = | |
1627 IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED | |
1628 : secure(); | |
1629 | |
1630 scoped_ptr<VideoContentDescription> video(new VideoContentDescription()); | |
1631 std::vector<std::string> crypto_suites; | |
1632 GetSupportedVideoCryptoSuiteNames(&crypto_suites); | |
1633 if (!CreateMediaContentOffer( | |
1634 options, | |
1635 video_codecs, | |
1636 sdes_policy, | |
1637 GetCryptos(GetFirstVideoContentDescription(current_description)), | |
1638 crypto_suites, | |
1639 video_rtp_extensions, | |
1640 add_legacy_, | |
1641 current_streams, | |
1642 video.get())) { | |
1643 return false; | |
1644 } | |
1645 | |
1646 video->set_bandwidth(options.video_bandwidth); | |
1647 | |
1648 bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); | |
1649 SetMediaProtocol(secure_transport, video.get()); | |
1650 | |
1651 if (!video->streams().empty()) { | |
1652 if (options.recv_video) { | |
1653 video->set_direction(MD_SENDRECV); | |
1654 } else { | |
1655 video->set_direction(MD_SENDONLY); | |
1656 } | |
1657 } else { | |
1658 if (options.recv_video) { | |
1659 video->set_direction(MD_RECVONLY); | |
1660 } else { | |
1661 video->set_direction(MD_INACTIVE); | |
1662 } | |
1663 } | |
1664 | |
1665 desc->AddContent(content_name, NS_JINGLE_RTP, video.release()); | |
1666 if (!AddTransportOffer(content_name, options.video_transport_options, | |
1667 current_description, desc)) { | |
1668 return false; | |
1669 } | |
1670 | |
1671 return true; | |
1672 } | |
1673 | |
1674 bool MediaSessionDescriptionFactory::AddDataContentForOffer( | |
1675 const MediaSessionOptions& options, | |
1676 const SessionDescription* current_description, | |
1677 DataCodecs* data_codecs, | |
1678 StreamParamsVec* current_streams, | |
1679 SessionDescription* desc) const { | |
1680 bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); | |
1681 | |
1682 scoped_ptr<DataContentDescription> data(new DataContentDescription()); | |
1683 bool is_sctp = (options.data_channel_type == DCT_SCTP); | |
1684 | |
1685 FilterDataCodecs(data_codecs, is_sctp); | |
1686 | |
1687 const ContentInfo* current_data_content = | |
1688 GetFirstDataContent(current_description); | |
1689 std::string content_name = | |
1690 current_data_content ? current_data_content->name : CN_DATA; | |
1691 | |
1692 cricket::SecurePolicy sdes_policy = | |
1693 IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED | |
1694 : secure(); | |
1695 std::vector<std::string> crypto_suites; | |
1696 if (is_sctp) { | |
1697 // SDES doesn't make sense for SCTP, so we disable it, and we only | |
1698 // get SDES crypto suites for RTP-based data channels. | |
1699 sdes_policy = cricket::SEC_DISABLED; | |
1700 // Unlike SetMediaProtocol below, we need to set the protocol | |
1701 // before we call CreateMediaContentOffer. Otherwise, | |
1702 // CreateMediaContentOffer won't know this is SCTP and will | |
1703 // generate SSRCs rather than SIDs. | |
1704 data->set_protocol( | |
1705 secure_transport ? kMediaProtocolDtlsSctp : kMediaProtocolSctp); | |
1706 } else { | |
1707 GetSupportedDataCryptoSuiteNames(&crypto_suites); | |
1708 } | |
1709 | |
1710 if (!CreateMediaContentOffer( | |
1711 options, | |
1712 *data_codecs, | |
1713 sdes_policy, | |
1714 GetCryptos(GetFirstDataContentDescription(current_description)), | |
1715 crypto_suites, | |
1716 RtpHeaderExtensions(), | |
1717 add_legacy_, | |
1718 current_streams, | |
1719 data.get())) { | |
1720 return false; | |
1721 } | |
1722 | |
1723 if (is_sctp) { | |
1724 desc->AddContent(content_name, NS_JINGLE_DRAFT_SCTP, data.release()); | |
1725 } else { | |
1726 data->set_bandwidth(options.data_bandwidth); | |
1727 SetMediaProtocol(secure_transport, data.get()); | |
1728 desc->AddContent(content_name, NS_JINGLE_RTP, data.release()); | |
1729 } | |
1730 if (!AddTransportOffer(content_name, options.data_transport_options, | |
1731 current_description, desc)) { | |
1732 return false; | |
1733 } | |
1734 return true; | |
1735 } | |
1736 | |
1737 bool MediaSessionDescriptionFactory::AddAudioContentForAnswer( | |
1738 const SessionDescription* offer, | |
1739 const MediaSessionOptions& options, | |
1740 const SessionDescription* current_description, | |
1741 StreamParamsVec* current_streams, | |
1742 SessionDescription* answer) const { | |
1743 const ContentInfo* audio_content = GetFirstAudioContent(offer); | |
1744 | |
1745 scoped_ptr<TransportDescription> audio_transport(CreateTransportAnswer( | |
1746 audio_content->name, offer, options.audio_transport_options, | |
1747 current_description)); | |
1748 if (!audio_transport) { | |
1749 return false; | |
1750 } | |
1751 | |
1752 AudioCodecs audio_codecs = audio_codecs_; | |
1753 if (!options.vad_enabled) { | |
1754 StripCNCodecs(&audio_codecs); | |
1755 } | |
1756 | |
1757 bool bundle_enabled = | |
1758 offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled; | |
1759 scoped_ptr<AudioContentDescription> audio_answer( | |
1760 new AudioContentDescription()); | |
1761 // Do not require or create SDES cryptos if DTLS is used. | |
1762 cricket::SecurePolicy sdes_policy = | |
1763 audio_transport->secure() ? cricket::SEC_DISABLED : secure(); | |
1764 if (!CreateMediaContentAnswer( | |
1765 static_cast<const AudioContentDescription*>( | |
1766 audio_content->description), | |
1767 options, | |
1768 audio_codecs, | |
1769 sdes_policy, | |
1770 GetCryptos(GetFirstAudioContentDescription(current_description)), | |
1771 audio_rtp_extensions_, | |
1772 current_streams, | |
1773 add_legacy_, | |
1774 bundle_enabled, | |
1775 audio_answer.get())) { | |
1776 return false; // Fails the session setup. | |
1777 } | |
1778 | |
1779 bool rejected = !options.has_audio() || audio_content->rejected || | |
1780 !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO, | |
1781 audio_answer->protocol(), | |
1782 audio_transport->secure()); | |
1783 if (!rejected) { | |
1784 AddTransportAnswer(audio_content->name, *(audio_transport.get()), answer); | |
1785 } else { | |
1786 // RFC 3264 | |
1787 // The answer MUST contain the same number of m-lines as the offer. | |
1788 LOG(LS_INFO) << "Audio is not supported in the answer."; | |
1789 } | |
1790 | |
1791 answer->AddContent(audio_content->name, audio_content->type, rejected, | |
1792 audio_answer.release()); | |
1793 return true; | |
1794 } | |
1795 | |
1796 bool MediaSessionDescriptionFactory::AddVideoContentForAnswer( | |
1797 const SessionDescription* offer, | |
1798 const MediaSessionOptions& options, | |
1799 const SessionDescription* current_description, | |
1800 StreamParamsVec* current_streams, | |
1801 SessionDescription* answer) const { | |
1802 const ContentInfo* video_content = GetFirstVideoContent(offer); | |
1803 scoped_ptr<TransportDescription> video_transport(CreateTransportAnswer( | |
1804 video_content->name, offer, options.video_transport_options, | |
1805 current_description)); | |
1806 if (!video_transport) { | |
1807 return false; | |
1808 } | |
1809 | |
1810 scoped_ptr<VideoContentDescription> video_answer( | |
1811 new VideoContentDescription()); | |
1812 // Do not require or create SDES cryptos if DTLS is used. | |
1813 cricket::SecurePolicy sdes_policy = | |
1814 video_transport->secure() ? cricket::SEC_DISABLED : secure(); | |
1815 bool bundle_enabled = | |
1816 offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled; | |
1817 if (!CreateMediaContentAnswer( | |
1818 static_cast<const VideoContentDescription*>( | |
1819 video_content->description), | |
1820 options, | |
1821 video_codecs_, | |
1822 sdes_policy, | |
1823 GetCryptos(GetFirstVideoContentDescription(current_description)), | |
1824 video_rtp_extensions_, | |
1825 current_streams, | |
1826 add_legacy_, | |
1827 bundle_enabled, | |
1828 video_answer.get())) { | |
1829 return false; | |
1830 } | |
1831 bool rejected = !options.has_video() || video_content->rejected || | |
1832 !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO, | |
1833 video_answer->protocol(), | |
1834 video_transport->secure()); | |
1835 if (!rejected) { | |
1836 if (!AddTransportAnswer(video_content->name, *(video_transport.get()), | |
1837 answer)) { | |
1838 return false; | |
1839 } | |
1840 video_answer->set_bandwidth(options.video_bandwidth); | |
1841 } else { | |
1842 // RFC 3264 | |
1843 // The answer MUST contain the same number of m-lines as the offer. | |
1844 LOG(LS_INFO) << "Video is not supported in the answer."; | |
1845 } | |
1846 answer->AddContent(video_content->name, video_content->type, rejected, | |
1847 video_answer.release()); | |
1848 return true; | |
1849 } | |
1850 | |
1851 bool MediaSessionDescriptionFactory::AddDataContentForAnswer( | |
1852 const SessionDescription* offer, | |
1853 const MediaSessionOptions& options, | |
1854 const SessionDescription* current_description, | |
1855 StreamParamsVec* current_streams, | |
1856 SessionDescription* answer) const { | |
1857 const ContentInfo* data_content = GetFirstDataContent(offer); | |
1858 scoped_ptr<TransportDescription> data_transport(CreateTransportAnswer( | |
1859 data_content->name, offer, options.data_transport_options, | |
1860 current_description)); | |
1861 if (!data_transport) { | |
1862 return false; | |
1863 } | |
1864 bool is_sctp = (options.data_channel_type == DCT_SCTP); | |
1865 std::vector<DataCodec> data_codecs(data_codecs_); | |
1866 FilterDataCodecs(&data_codecs, is_sctp); | |
1867 | |
1868 scoped_ptr<DataContentDescription> data_answer( | |
1869 new DataContentDescription()); | |
1870 // Do not require or create SDES cryptos if DTLS is used. | |
1871 cricket::SecurePolicy sdes_policy = | |
1872 data_transport->secure() ? cricket::SEC_DISABLED : secure(); | |
1873 bool bundle_enabled = | |
1874 offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled; | |
1875 if (!CreateMediaContentAnswer( | |
1876 static_cast<const DataContentDescription*>( | |
1877 data_content->description), | |
1878 options, | |
1879 data_codecs_, | |
1880 sdes_policy, | |
1881 GetCryptos(GetFirstDataContentDescription(current_description)), | |
1882 RtpHeaderExtensions(), | |
1883 current_streams, | |
1884 add_legacy_, | |
1885 bundle_enabled, | |
1886 data_answer.get())) { | |
1887 return false; // Fails the session setup. | |
1888 } | |
1889 | |
1890 bool rejected = !options.has_data() || data_content->rejected || | |
1891 !IsMediaProtocolSupported(MEDIA_TYPE_DATA, | |
1892 data_answer->protocol(), | |
1893 data_transport->secure()); | |
1894 if (!rejected) { | |
1895 data_answer->set_bandwidth(options.data_bandwidth); | |
1896 if (!AddTransportAnswer(data_content->name, *(data_transport.get()), | |
1897 answer)) { | |
1898 return false; | |
1899 } | |
1900 } else { | |
1901 // RFC 3264 | |
1902 // The answer MUST contain the same number of m-lines as the offer. | |
1903 LOG(LS_INFO) << "Data is not supported in the answer."; | |
1904 } | |
1905 answer->AddContent(data_content->name, data_content->type, rejected, | |
1906 data_answer.release()); | |
1907 return true; | |
1908 } | |
1909 | |
1910 bool IsMediaContent(const ContentInfo* content) { | |
1911 return (content && | |
1912 (content->type == NS_JINGLE_RTP || | |
1913 content->type == NS_JINGLE_DRAFT_SCTP)); | |
1914 } | |
1915 | |
1916 bool IsAudioContent(const ContentInfo* content) { | |
1917 return IsMediaContentOfType(content, MEDIA_TYPE_AUDIO); | |
1918 } | |
1919 | |
1920 bool IsVideoContent(const ContentInfo* content) { | |
1921 return IsMediaContentOfType(content, MEDIA_TYPE_VIDEO); | |
1922 } | |
1923 | |
1924 bool IsDataContent(const ContentInfo* content) { | |
1925 return IsMediaContentOfType(content, MEDIA_TYPE_DATA); | |
1926 } | |
1927 | |
1928 static const ContentInfo* GetFirstMediaContent(const ContentInfos& contents, | |
1929 MediaType media_type) { | |
1930 for (ContentInfos::const_iterator content = contents.begin(); | |
1931 content != contents.end(); content++) { | |
1932 if (IsMediaContentOfType(&*content, media_type)) { | |
1933 return &*content; | |
1934 } | |
1935 } | |
1936 return NULL; | |
1937 } | |
1938 | |
1939 const ContentInfo* GetFirstAudioContent(const ContentInfos& contents) { | |
1940 return GetFirstMediaContent(contents, MEDIA_TYPE_AUDIO); | |
1941 } | |
1942 | |
1943 const ContentInfo* GetFirstVideoContent(const ContentInfos& contents) { | |
1944 return GetFirstMediaContent(contents, MEDIA_TYPE_VIDEO); | |
1945 } | |
1946 | |
1947 const ContentInfo* GetFirstDataContent(const ContentInfos& contents) { | |
1948 return GetFirstMediaContent(contents, MEDIA_TYPE_DATA); | |
1949 } | |
1950 | |
1951 static const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc, | |
1952 MediaType media_type) { | |
1953 if (sdesc == NULL) | |
1954 return NULL; | |
1955 | |
1956 return GetFirstMediaContent(sdesc->contents(), media_type); | |
1957 } | |
1958 | |
1959 const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc) { | |
1960 return GetFirstMediaContent(sdesc, MEDIA_TYPE_AUDIO); | |
1961 } | |
1962 | |
1963 const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc) { | |
1964 return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO); | |
1965 } | |
1966 | |
1967 const ContentInfo* GetFirstDataContent(const SessionDescription* sdesc) { | |
1968 return GetFirstMediaContent(sdesc, MEDIA_TYPE_DATA); | |
1969 } | |
1970 | |
1971 const MediaContentDescription* GetFirstMediaContentDescription( | |
1972 const SessionDescription* sdesc, MediaType media_type) { | |
1973 const ContentInfo* content = GetFirstMediaContent(sdesc, media_type); | |
1974 const ContentDescription* description = content ? content->description : NULL; | |
1975 return static_cast<const MediaContentDescription*>(description); | |
1976 } | |
1977 | |
1978 const AudioContentDescription* GetFirstAudioContentDescription( | |
1979 const SessionDescription* sdesc) { | |
1980 return static_cast<const AudioContentDescription*>( | |
1981 GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO)); | |
1982 } | |
1983 | |
1984 const VideoContentDescription* GetFirstVideoContentDescription( | |
1985 const SessionDescription* sdesc) { | |
1986 return static_cast<const VideoContentDescription*>( | |
1987 GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO)); | |
1988 } | |
1989 | |
1990 const DataContentDescription* GetFirstDataContentDescription( | |
1991 const SessionDescription* sdesc) { | |
1992 return static_cast<const DataContentDescription*>( | |
1993 GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA)); | |
1994 } | |
1995 | |
1996 } // namespace cricket | |
OLD | NEW |