1 /* 2 * libjingle 3 * Copyright 2011, 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/app/webrtc/webrtcsdp.h" 29 30 #include <limits.h> 31 #include <stdio.h> 32 #include <algorithm> 33 #include <string> 34 #include <vector> 35 36 #include "talk/app/webrtc/jsepicecandidate.h" 37 #include "talk/app/webrtc/jsepsessiondescription.h" 38 #include "talk/base/common.h" 39 #include "talk/base/logging.h" 40 #include "talk/base/messagedigest.h" 41 #include "talk/base/stringutils.h" 42 #include "talk/media/base/codec.h" 43 #include "talk/media/base/constants.h" 44 #include "talk/media/base/cryptoparams.h" 45 #include "talk/media/sctp/sctpdataengine.h" 46 #include "talk/p2p/base/candidate.h" 47 #include "talk/p2p/base/constants.h" 48 #include "talk/p2p/base/port.h" 49 #include "talk/session/media/mediasession.h" 50 #include "talk/session/media/mediasessionclient.h" 51 52 using cricket::AudioContentDescription; 53 using cricket::Candidate; 54 using cricket::Candidates; 55 using cricket::ContentDescription; 56 using cricket::ContentInfo; 57 using cricket::CryptoParams; 58 using cricket::DataContentDescription; 59 using cricket::ICE_CANDIDATE_COMPONENT_RTP; 60 using cricket::ICE_CANDIDATE_COMPONENT_RTCP; 61 using cricket::kCodecParamMaxBitrate; 62 using cricket::kCodecParamMaxPTime; 63 using cricket::kCodecParamMaxQuantization; 64 using cricket::kCodecParamMinBitrate; 65 using cricket::kCodecParamMinPTime; 66 using cricket::kCodecParamPTime; 67 using cricket::kCodecParamSPropStereo; 68 using cricket::kCodecParamStereo; 69 using cricket::kCodecParamUseInbandFec; 70 using cricket::kCodecParamSctpProtocol; 71 using cricket::kCodecParamSctpStreams; 72 using cricket::kCodecParamMaxAverageBitrate; 73 using cricket::kWildcardPayloadType; 74 using cricket::MediaContentDescription; 75 using cricket::MediaType; 76 using cricket::NS_JINGLE_ICE_UDP; 77 using cricket::RtpHeaderExtension; 78 using cricket::SsrcGroup; 79 using cricket::StreamParams; 80 using cricket::StreamParamsVec; 81 using cricket::TransportDescription; 82 using cricket::TransportInfo; 83 using cricket::VideoContentDescription; 84 using talk_base::SocketAddress; 85 86 typedef std::vector<RtpHeaderExtension> RtpHeaderExtensions; 87 88 namespace cricket { 89 class SessionDescription; 90 } 91 92 namespace webrtc { 93 94 // Line type 95 // RFC 4566 96 // An SDP session description consists of a number of lines of text of 97 // the form: 98 // <type>=<value> 99 // where <type> MUST be exactly one case-significant character. 100 static const int kLinePrefixLength = 2; // Lenght of <type>= 101 static const char kLineTypeVersion = 'v'; 102 static const char kLineTypeOrigin = 'o'; 103 static const char kLineTypeSessionName = 's'; 104 static const char kLineTypeSessionInfo = 'i'; 105 static const char kLineTypeSessionUri = 'u'; 106 static const char kLineTypeSessionEmail = 'e'; 107 static const char kLineTypeSessionPhone = 'p'; 108 static const char kLineTypeSessionBandwidth = 'b'; 109 static const char kLineTypeTiming = 't'; 110 static const char kLineTypeRepeatTimes = 'r'; 111 static const char kLineTypeTimeZone = 'z'; 112 static const char kLineTypeEncryptionKey = 'k'; 113 static const char kLineTypeMedia = 'm'; 114 static const char kLineTypeConnection = 'c'; 115 static const char kLineTypeAttributes = 'a'; 116 117 // Attributes 118 static const char kAttributeGroup[] = "group"; 119 static const char kAttributeMid[] = "mid"; 120 static const char kAttributeRtcpMux[] = "rtcp-mux"; 121 static const char kAttributeSsrc[] = "ssrc"; 122 static const char kSsrcAttributeCname[] = "cname"; 123 static const char kAttributeExtmap[] = "extmap"; 124 // draft-alvestrand-mmusic-msid-01 125 // a=msid-semantic: WMS 126 static const char kAttributeMsidSemantics[] = "msid-semantic"; 127 static const char kMediaStreamSemantic[] = "WMS"; 128 static const char kSsrcAttributeMsid[] = "msid"; 129 static const char kDefaultMsid[] = "default"; 130 static const char kMsidAppdataAudio[] = "a"; 131 static const char kMsidAppdataVideo[] = "v"; 132 static const char kMsidAppdataData[] = "d"; 133 static const char kSsrcAttributeMslabel[] = "mslabel"; 134 static const char kSSrcAttributeLabel[] = "label"; 135 static const char kAttributeSsrcGroup[] = "ssrc-group"; 136 static const char kAttributeCrypto[] = "crypto"; 137 static const char kAttributeCandidate[] = "candidate"; 138 static const char kAttributeCandidateTyp[] = "typ"; 139 static const char kAttributeCandidateRaddr[] = "raddr"; 140 static const char kAttributeCandidateRport[] = "rport"; 141 static const char kAttributeCandidateUsername[] = "username"; 142 static const char kAttributeCandidatePassword[] = "password"; 143 static const char kAttributeCandidateGeneration[] = "generation"; 144 static const char kAttributeFingerprint[] = "fingerprint"; 145 static const char kAttributeSetup[] = "setup"; 146 static const char kAttributeFmtp[] = "fmtp"; 147 static const char kAttributeRtpmap[] = "rtpmap"; 148 static const char kAttributeSctpmap[] = "sctpmap"; 149 static const char kAttributeRtcp[] = "rtcp"; 150 static const char kAttributeIceUfrag[] = "ice-ufrag"; 151 static const char kAttributeIcePwd[] = "ice-pwd"; 152 static const char kAttributeIceLite[] = "ice-lite"; 153 static const char kAttributeIceOption[] = "ice-options"; 154 static const char kAttributeSendOnly[] = "sendonly"; 155 static const char kAttributeRecvOnly[] = "recvonly"; 156 static const char kAttributeRtcpFb[] = "rtcp-fb"; 157 static const char kAttributeSendRecv[] = "sendrecv"; 158 static const char kAttributeInactive[] = "inactive"; 159 160 // Experimental flags 161 static const char kAttributeXGoogleFlag[] = "x-google-flag"; 162 static const char kValueConference[] = "conference"; 163 static const char kAttributeXGoogleBufferLatency[] = 164 "x-google-buffer-latency"; 165 166 // Candidate 167 static const char kCandidateHost[] = "host"; 168 static const char kCandidateSrflx[] = "srflx"; 169 // TODO: How to map the prflx with circket candidate type 170 // static const char kCandidatePrflx[] = "prflx"; 171 static const char kCandidateRelay[] = "relay"; 172 173 static const char kSdpDelimiterEqual = '='; 174 static const char kSdpDelimiterSpace = ' '; 175 static const char kSdpDelimiterColon = ':'; 176 static const char kSdpDelimiterSemicolon = ';'; 177 static const char kSdpDelimiterSlash = '/'; 178 static const char kNewLine = '\n'; 179 static const char kReturn = '\r'; 180 static const char kLineBreak[] = "\r\n"; 181 182 // TODO: Generate the Session and Time description 183 // instead of hardcoding. 184 static const char kSessionVersion[] = "v=0"; 185 // RFC 4566 186 static const char kSessionOriginUsername[] = "-"; 187 static const char kSessionOriginSessionId[] = "0"; 188 static const char kSessionOriginSessionVersion[] = "0"; 189 static const char kSessionOriginNettype[] = "IN"; 190 static const char kSessionOriginAddrtype[] = "IP4"; 191 static const char kSessionOriginAddress[] = "127.0.0.1"; 192 static const char kSessionName[] = "s=-"; 193 static const char kTimeDescription[] = "t=0 0"; 194 static const char kAttrGroup[] = "a=group:BUNDLE"; 195 static const char kConnectionNettype[] = "IN"; 196 static const char kConnectionAddrtype[] = "IP4"; 197 static const char kMediaTypeVideo[] = "video"; 198 static const char kMediaTypeAudio[] = "audio"; 199 static const char kMediaTypeData[] = "application"; 200 static const char kMediaPortRejected[] = "0"; 201 static const char kDefaultAddress[] = "0.0.0.0"; 202 static const char kDefaultPort[] = "1"; 203 // RFC 3556 204 static const char kApplicationSpecificMaximum[] = "AS"; 205 206 static const int kDefaultVideoClockrate = 90000; 207 208 // ISAC special-case. 209 static const char kIsacCodecName[] = "ISAC"; // From webrtcvoiceengine.cc 210 static const int kIsacWbDefaultRate = 32000; // From acm_common_defs.h 211 static const int kIsacSwbDefaultRate = 56000; // From acm_common_defs.h 212 213 static const int kDefaultSctpFmt = 5000; 214 static const char kDefaultSctpmapProtocol[] = "webrtc-datachannel"; 215 216 struct SsrcInfo { 217 SsrcInfo() 218 : msid_identifier(kDefaultMsid), 219 // TODO(ronghuawu): What should we do if the appdata doesn't appear? 220 // Create random string (which will be used as track label later)? 221 msid_appdata(talk_base::CreateRandomString(8)) { 222 } 223 uint32 ssrc_id; 224 std::string cname; 225 std::string msid_identifier; 226 std::string msid_appdata; 227 228 // For backward compatibility. 229 // TODO(ronghuawu): Remove below 2 fields once all the clients support msid. 230 std::string label; 231 std::string mslabel; 232 }; 233 typedef std::vector<SsrcInfo> SsrcInfoVec; 234 typedef std::vector<SsrcGroup> SsrcGroupVec; 235 236 // Serializes the passed in SessionDescription to a SDP string. 237 // desc - The SessionDescription object to be serialized. 238 static std::string SdpSerializeSessionDescription( 239 const JsepSessionDescription& jdesc); 240 template <class T> 241 static void AddFmtpLine(const T& codec, std::string* message); 242 static void BuildMediaDescription(const ContentInfo* content_info, 243 const TransportInfo* transport_info, 244 const MediaType media_type, 245 std::string* message); 246 static void BuildSctpContentAttributes(std::string* message); 247 static void BuildRtpContentAttributes( 248 const MediaContentDescription* media_desc, 249 const MediaType media_type, 250 std::string* message); 251 static void BuildRtpMap(const MediaContentDescription* media_desc, 252 const MediaType media_type, 253 std::string* message); 254 static void BuildCandidate(const std::vector<Candidate>& candidates, 255 std::string* message); 256 static void BuildIceOptions(const std::vector<std::string>& transport_options, 257 std::string* message); 258 259 static bool ParseSessionDescription(const std::string& message, size_t* pos, 260 std::string* session_id, 261 std::string* session_version, 262 bool* supports_msid, 263 TransportDescription* session_td, 264 RtpHeaderExtensions* session_extmaps, 265 cricket::SessionDescription* desc, 266 SdpParseError* error); 267 static bool ParseGroupAttribute(const std::string& line, 268 cricket::SessionDescription* desc, 269 SdpParseError* error); 270 static bool ParseMediaDescription( 271 const std::string& message, 272 const TransportDescription& session_td, 273 const RtpHeaderExtensions& session_extmaps, 274 bool supports_msid, 275 size_t* pos, cricket::SessionDescription* desc, 276 std::vector<JsepIceCandidate*>* candidates, 277 SdpParseError* error); 278 static bool ParseContent(const std::string& message, 279 const MediaType media_type, 280 int mline_index, 281 const std::string& protocol, 282 const std::vector<int>& codec_preference, 283 size_t* pos, 284 std::string* content_name, 285 MediaContentDescription* media_desc, 286 TransportDescription* transport, 287 std::vector<JsepIceCandidate*>* candidates, 288 SdpParseError* error); 289 static bool ParseSsrcAttribute(const std::string& line, 290 SsrcInfoVec* ssrc_infos, 291 SdpParseError* error); 292 static bool ParseSsrcGroupAttribute(const std::string& line, 293 SsrcGroupVec* ssrc_groups, 294 SdpParseError* error); 295 static bool ParseCryptoAttribute(const std::string& line, 296 MediaContentDescription* media_desc, 297 SdpParseError* error); 298 static bool ParseRtpmapAttribute(const std::string& line, 299 const MediaType media_type, 300 const std::vector<int>& codec_preference, 301 MediaContentDescription* media_desc, 302 SdpParseError* error); 303 static bool ParseFmtpAttributes(const std::string& line, 304 const MediaType media_type, 305 MediaContentDescription* media_desc, 306 SdpParseError* error); 307 static bool ParseFmtpParam(const std::string& line, std::string* parameter, 308 std::string* value, SdpParseError* error); 309 static bool ParseCandidate(const std::string& message, Candidate* candidate, 310 SdpParseError* error, bool is_raw); 311 static bool ParseRtcpFbAttribute(const std::string& line, 312 const MediaType media_type, 313 MediaContentDescription* media_desc, 314 SdpParseError* error); 315 static bool ParseIceOptions(const std::string& line, 316 std::vector<std::string>* transport_options, 317 SdpParseError* error); 318 static bool ParseExtmap(const std::string& line, 319 RtpHeaderExtension* extmap, 320 SdpParseError* error); 321 static bool ParseFingerprintAttribute(const std::string& line, 322 talk_base::SSLFingerprint** fingerprint, 323 SdpParseError* error); 324 static bool ParseDtlsSetup(const std::string& line, 325 cricket::ConnectionRole* role, 326 SdpParseError* error); 327 328 // Helper functions 329 330 // Below ParseFailed*** functions output the line that caused the parsing 331 // failure and the detailed reason (|description|) of the failure to |error|. 332 // The functions always return false so that they can be used directly in the 333 // following way when error happens: 334 // "return ParseFailed***(...);" 335 336 // The line starting at |line_start| of |message| is the failing line. 337 // The reason for the failure should be provided in the |description|. 338 // An example of a description could be "unknown character". 339 static bool ParseFailed(const std::string& message, 340 size_t line_start, 341 const std::string& description, 342 SdpParseError* error) { 343 // Get the first line of |message| from |line_start|. 344 std::string first_line; 345 size_t line_end = message.find(kNewLine, line_start); 346 if (line_end != std::string::npos) { 347 if (line_end > 0 && (message.at(line_end - 1) == kReturn)) { 348 --line_end; 349 } 350 first_line = message.substr(line_start, (line_end - line_start)); 351 } else { 352 first_line = message.substr(line_start); 353 } 354 355 if (error) { 356 error->line = first_line; 357 error->description = description; 358 } 359 LOG(LS_ERROR) << "Failed to parse: \"" << first_line 360 << "\". Reason: " << description; 361 return false; 362 } 363 364 // |line| is the failing line. The reason for the failure should be 365 // provided in the |description|. 366 static bool ParseFailed(const std::string& line, 367 const std::string& description, 368 SdpParseError* error) { 369 return ParseFailed(line, 0, description, error); 370 } 371 372 // Parses failure where the failing SDP line isn't know or there are multiple 373 // failing lines. 374 static bool ParseFailed(const std::string& description, 375 SdpParseError* error) { 376 return ParseFailed("", description, error); 377 } 378 379 // |line| is the failing line. The failure is due to the fact that |line| 380 // doesn't have |expected_fields| fields. 381 static bool ParseFailedExpectFieldNum(const std::string& line, 382 int expected_fields, 383 SdpParseError* error) { 384 std::ostringstream description; 385 description << "Expects " << expected_fields << " fields."; 386 return ParseFailed(line, description.str(), error); 387 } 388 389 // |line| is the failing line. The failure is due to the fact that |line| has 390 // less than |expected_min_fields| fields. 391 static bool ParseFailedExpectMinFieldNum(const std::string& line, 392 int expected_min_fields, 393 SdpParseError* error) { 394 std::ostringstream description; 395 description << "Expects at least " << expected_min_fields << " fields."; 396 return ParseFailed(line, description.str(), error); 397 } 398 399 // |line| is the failing line. The failure is due to the fact that it failed to 400 // get the value of |attribute|. 401 static bool ParseFailedGetValue(const std::string& line, 402 const std::string& attribute, 403 SdpParseError* error) { 404 std::ostringstream description; 405 description << "Failed to get the value of attribute: " << attribute; 406 return ParseFailed(line, description.str(), error); 407 } 408 409 // The line starting at |line_start| of |message| is the failing line. The 410 // failure is due to the line type (e.g. the "m" part of the "m-line") 411 // not matching what is expected. The expected line type should be 412 // provided as |line_type|. 413 static bool ParseFailedExpectLine(const std::string& message, 414 size_t line_start, 415 const char line_type, 416 const std::string& line_value, 417 SdpParseError* error) { 418 std::ostringstream description; 419 description << "Expect line: " << line_type << "=" << line_value; 420 return ParseFailed(message, line_start, description.str(), error); 421 } 422 423 static bool AddLine(const std::string& line, std::string* message) { 424 if (!message) 425 return false; 426 427 message->append(line); 428 message->append(kLineBreak); 429 return true; 430 } 431 432 static bool GetLine(const std::string& message, 433 size_t* pos, 434 std::string* line) { 435 size_t line_begin = *pos; 436 size_t line_end = message.find(kNewLine, line_begin); 437 if (line_end == std::string::npos) { 438 return false; 439 } 440 // Update the new start position 441 *pos = line_end + 1; 442 if (line_end > 0 && (message.at(line_end - 1) == kReturn)) { 443 --line_end; 444 } 445 *line = message.substr(line_begin, (line_end - line_begin)); 446 const char* cline = line->c_str(); 447 // RFC 4566 448 // An SDP session description consists of a number of lines of text of 449 // the form: 450 // <type>=<value> 451 // where <type> MUST be exactly one case-significant character and 452 // <value> is structured text whose format depends on <type>. 453 // Whitespace MUST NOT be used on either side of the "=" sign. 454 if (cline[0] == kSdpDelimiterSpace || 455 cline[1] != kSdpDelimiterEqual || 456 cline[2] == kSdpDelimiterSpace) { 457 *pos = line_begin; 458 return false; 459 } 460 return true; 461 } 462 463 // Init |os| to "|type|=|value|". 464 static void InitLine(const char type, 465 const std::string& value, 466 std::ostringstream* os) { 467 os->str(""); 468 *os << type << kSdpDelimiterEqual << value; 469 } 470 471 // Init |os| to "a=|attribute|". 472 static void InitAttrLine(const std::string& attribute, std::ostringstream* os) { 473 InitLine(kLineTypeAttributes, attribute, os); 474 } 475 476 // Writes a SDP attribute line based on |attribute| and |value| to |message|. 477 static void AddAttributeLine(const std::string& attribute, int value, 478 std::string* message) { 479 std::ostringstream os; 480 InitAttrLine(attribute, &os); 481 os << kSdpDelimiterColon << value; 482 AddLine(os.str(), message); 483 } 484 485 // Returns the first line of the message without the line breaker. 486 static bool GetFirstLine(const std::string& message, std::string* line) { 487 size_t pos = 0; 488 if (!GetLine(message, &pos, line)) { 489 // If GetLine failed, just return the full |message|. 490 *line = message; 491 } 492 return true; 493 } 494 495 static bool IsLineType(const std::string& message, 496 const char type, 497 size_t line_start) { 498 if (message.size() < line_start + kLinePrefixLength) { 499 return false; 500 } 501 const char* cmessage = message.c_str(); 502 return (cmessage[line_start] == type && 503 cmessage[line_start + 1] == kSdpDelimiterEqual); 504 } 505 506 static bool IsLineType(const std::string& line, 507 const char type) { 508 return IsLineType(line, type, 0); 509 } 510 511 static bool GetLineWithType(const std::string& message, size_t* pos, 512 std::string* line, const char type) { 513 if (!IsLineType(message, type, *pos)) { 514 return false; 515 } 516 517 if (!GetLine(message, pos, line)) 518 return false; 519 520 return true; 521 } 522 523 static bool HasAttribute(const std::string& line, 524 const std::string& attribute) { 525 return (line.compare(kLinePrefixLength, attribute.size(), attribute) == 0); 526 } 527 528 // Verifies the candiate to be of the format candidate:<blah> 529 static bool IsRawCandidate(const std::string& line) { 530 // Checking candiadte-attribute is starting with "candidate" str. 531 if (line.compare(0, strlen(kAttributeCandidate), kAttributeCandidate) != 0) { 532 return false; 533 } 534 const size_t first_candidate = line.find(kSdpDelimiterColon); 535 if (first_candidate == std::string::npos) 536 return false; 537 // In this format we only expecting one candiate. If any additional 538 // candidates present, whole string will be discared. 539 const size_t any_other = line.find(kSdpDelimiterColon, first_candidate + 1); 540 return (any_other == std::string::npos); 541 } 542 543 static bool AddSsrcLine(uint32 ssrc_id, const std::string& attribute, 544 const std::string& value, std::string* message) { 545 // RFC 5576 546 // a=ssrc:<ssrc-id> <attribute>:<value> 547 std::ostringstream os; 548 InitAttrLine(kAttributeSsrc, &os); 549 os << kSdpDelimiterColon << ssrc_id << kSdpDelimiterSpace 550 << attribute << kSdpDelimiterColon << value; 551 return AddLine(os.str(), message); 552 } 553 554 // Split the message into two parts by the first delimiter. 555 static bool SplitByDelimiter(const std::string& message, 556 const char delimiter, 557 std::string* field1, 558 std::string* field2) { 559 // Find the first delimiter 560 size_t pos = message.find(delimiter); 561 if (pos == std::string::npos) { 562 return false; 563 } 564 *field1 = message.substr(0, pos); 565 // The rest is the value. 566 *field2 = message.substr(pos + 1); 567 return true; 568 } 569 570 // Get value only from <attribute>:<value>. 571 static bool GetValue(const std::string& message, const std::string& attribute, 572 std::string* value, SdpParseError* error) { 573 std::string leftpart; 574 if (!SplitByDelimiter(message, kSdpDelimiterColon, &leftpart, value)) { 575 return ParseFailedGetValue(message, attribute, error); 576 } 577 // The left part should end with the expected attribute. 578 if (leftpart.length() < attribute.length() || 579 leftpart.compare(leftpart.length() - attribute.length(), 580 attribute.length(), attribute) != 0) { 581 return ParseFailedGetValue(message, attribute, error); 582 } 583 return true; 584 } 585 586 static bool CaseInsensitiveFind(std::string str1, std::string str2) { 587 std::transform(str1.begin(), str1.end(), str1.begin(), 588 ::tolower); 589 std::transform(str2.begin(), str2.end(), str2.begin(), 590 ::tolower); 591 return str1.find(str2) != std::string::npos; 592 } 593 594 void CreateTracksFromSsrcInfos(const SsrcInfoVec& ssrc_infos, 595 StreamParamsVec* tracks) { 596 ASSERT(tracks != NULL); 597 for (SsrcInfoVec::const_iterator ssrc_info = ssrc_infos.begin(); 598 ssrc_info != ssrc_infos.end(); ++ssrc_info) { 599 if (ssrc_info->cname.empty()) { 600 continue; 601 } 602 603 std::string sync_label; 604 std::string track_id; 605 if (ssrc_info->msid_identifier == kDefaultMsid && 606 !ssrc_info->mslabel.empty()) { 607 // If there's no msid and there's mslabel, we consider this is a sdp from 608 // a older version of client that doesn't support msid. 609 // In that case, we use the mslabel and label to construct the track. 610 sync_label = ssrc_info->mslabel; 611 track_id = ssrc_info->label; 612 } else { 613 sync_label = ssrc_info->msid_identifier; 614 // The appdata consists of the "id" attribute of a MediaStreamTrack, which 615 // is corresponding to the "id" attribute of StreamParams. 616 track_id = ssrc_info->msid_appdata; 617 } 618 if (sync_label.empty() || track_id.empty()) { 619 ASSERT(false); 620 continue; 621 } 622 623 StreamParamsVec::iterator track = tracks->begin(); 624 for (; track != tracks->end(); ++track) { 625 if (track->id == track_id) { 626 break; 627 } 628 } 629 if (track == tracks->end()) { 630 // If we don't find an existing track, create a new one. 631 tracks->push_back(StreamParams()); 632 track = tracks->end() - 1; 633 } 634 track->add_ssrc(ssrc_info->ssrc_id); 635 track->cname = ssrc_info->cname; 636 track->sync_label = sync_label; 637 track->id = track_id; 638 } 639 } 640 641 void GetMediaStreamLabels(const ContentInfo* content, 642 std::set<std::string>* labels) { 643 const MediaContentDescription* media_desc = 644 static_cast<const MediaContentDescription*>( 645 content->description); 646 const cricket::StreamParamsVec& streams = media_desc->streams(); 647 for (cricket::StreamParamsVec::const_iterator it = streams.begin(); 648 it != streams.end(); ++it) { 649 labels->insert(it->sync_label); 650 } 651 } 652 653 // RFC 5245 654 // It is RECOMMENDED that default candidates be chosen based on the 655 // likelihood of those candidates to work with the peer that is being 656 // contacted. It is RECOMMENDED that relayed > reflexive > host. 657 static const int kPreferenceUnknown = 0; 658 static const int kPreferenceHost = 1; 659 static const int kPreferenceReflexive = 2; 660 static const int kPreferenceRelayed = 3; 661 662 static int GetCandidatePreferenceFromType(const std::string& type) { 663 int preference = kPreferenceUnknown; 664 if (type == cricket::LOCAL_PORT_TYPE) { 665 preference = kPreferenceHost; 666 } else if (type == cricket::STUN_PORT_TYPE) { 667 preference = kPreferenceReflexive; 668 } else if (type == cricket::RELAY_PORT_TYPE) { 669 preference = kPreferenceRelayed; 670 } else { 671 ASSERT(false); 672 } 673 return preference; 674 } 675 676 // Get ip and port of the default destination from the |candidates| with 677 // the given value of |component_id|. 678 // RFC 5245 679 // The value of |component_id| currently supported are 1 (RTP) and 2 (RTCP). 680 // TODO: Decide the default destination in webrtcsession and 681 // pass it down via SessionDescription. 682 static bool GetDefaultDestination(const std::vector<Candidate>& candidates, 683 int component_id, std::string* port, std::string* ip) { 684 *port = kDefaultPort; 685 *ip = kDefaultAddress; 686 int current_preference = kPreferenceUnknown; 687 for (std::vector<Candidate>::const_iterator it = candidates.begin(); 688 it != candidates.end(); ++it) { 689 if (it->component() != component_id) { 690 continue; 691 } 692 const int preference = GetCandidatePreferenceFromType(it->type()); 693 // See if this candidate is more preferable then the current one. 694 if (preference <= current_preference) { 695 continue; 696 } 697 current_preference = preference; 698 *port = it->address().PortAsString(); 699 *ip = it->address().ipaddr().ToString(); 700 } 701 return true; 702 } 703 704 // Update the media default destination. 705 static void UpdateMediaDefaultDestination( 706 const std::vector<Candidate>& candidates, std::string* mline) { 707 // RFC 4566 708 // m=<media> <port> <proto> <fmt> ... 709 std::vector<std::string> fields; 710 talk_base::split(*mline, kSdpDelimiterSpace, &fields); 711 if (fields.size() < 3) { 712 return; 713 } 714 715 bool is_rtp = 716 fields[2].empty() || 717 talk_base::starts_with(fields[2].data(), 718 cricket::kMediaProtocolRtpPrefix); 719 720 std::ostringstream os; 721 std::string rtp_port, rtp_ip; 722 if (GetDefaultDestination(candidates, ICE_CANDIDATE_COMPONENT_RTP, 723 &rtp_port, &rtp_ip)) { 724 // Found default RTP candidate. 725 // RFC 5245 726 // The default candidates are added to the SDP as the default 727 // destination for media. For streams based on RTP, this is done by 728 // placing the IP address and port of the RTP candidate into the c and m 729 // lines, respectively. 730 731 // Update the port in the m line. 732 // If this is a m-line with port equal to 0, we don't change it. 733 if (fields[1] != kMediaPortRejected) { 734 mline->replace(fields[0].size() + 1, 735 fields[1].size(), 736 rtp_port); 737 } 738 // Add the c line. 739 // RFC 4566 740 // c=<nettype> <addrtype> <connection-address> 741 InitLine(kLineTypeConnection, kConnectionNettype, &os); 742 os << " " << kConnectionAddrtype << " " << rtp_ip; 743 AddLine(os.str(), mline); 744 } 745 746 if (is_rtp) { 747 std::string rtcp_port, rtcp_ip; 748 if (GetDefaultDestination(candidates, ICE_CANDIDATE_COMPONENT_RTCP, 749 &rtcp_port, &rtcp_ip)) { 750 // Found default RTCP candidate. 751 // RFC 5245 752 // If the agent is utilizing RTCP, it MUST encode the RTCP candidate 753 // using the a=rtcp attribute as defined in RFC 3605. 754 755 // RFC 3605 756 // rtcp-attribute = "a=rtcp:" port [nettype space addrtype space 757 // connection-address] CRLF 758 InitAttrLine(kAttributeRtcp, &os); 759 os << kSdpDelimiterColon 760 << rtcp_port << " " 761 << kConnectionNettype << " " 762 << kConnectionAddrtype << " " 763 << rtcp_ip; 764 AddLine(os.str(), mline); 765 } 766 } 767 } 768 769 // Get candidates according to the mline index from SessionDescriptionInterface. 770 static void GetCandidatesByMindex(const SessionDescriptionInterface& desci, 771 int mline_index, 772 std::vector<Candidate>* candidates) { 773 if (!candidates) { 774 return; 775 } 776 const IceCandidateCollection* cc = desci.candidates(mline_index); 777 for (size_t i = 0; i < cc->count(); ++i) { 778 const IceCandidateInterface* candidate = cc->at(i); 779 candidates->push_back(candidate->candidate()); 780 } 781 } 782 783 std::string SdpSerialize(const JsepSessionDescription& jdesc) { 784 std::string sdp = SdpSerializeSessionDescription(jdesc); 785 786 std::string sdp_with_candidates; 787 size_t pos = 0; 788 std::string line; 789 int mline_index = -1; 790 while (GetLine(sdp, &pos, &line)) { 791 if (IsLineType(line, kLineTypeMedia)) { 792 ++mline_index; 793 std::vector<Candidate> candidates; 794 GetCandidatesByMindex(jdesc, mline_index, &candidates); 795 // Media line may append other lines inside the 796 // UpdateMediaDefaultDestination call, so add the kLineBreak here first. 797 line.append(kLineBreak); 798 UpdateMediaDefaultDestination(candidates, &line); 799 sdp_with_candidates.append(line); 800 // Build the a=candidate lines. 801 BuildCandidate(candidates, &sdp_with_candidates); 802 } else { 803 // Copy old line to new sdp without change. 804 AddLine(line, &sdp_with_candidates); 805 } 806 } 807 sdp = sdp_with_candidates; 808 809 return sdp; 810 } 811 812 std::string SdpSerializeSessionDescription( 813 const JsepSessionDescription& jdesc) { 814 const cricket::SessionDescription* desc = jdesc.description(); 815 if (!desc) { 816 return ""; 817 } 818 819 std::string message; 820 821 // Session Description. 822 AddLine(kSessionVersion, &message); 823 // Session Origin 824 // RFC 4566 825 // o=<username> <sess-id> <sess-version> <nettype> <addrtype> 826 // <unicast-address> 827 std::ostringstream os; 828 InitLine(kLineTypeOrigin, kSessionOriginUsername, &os); 829 const std::string session_id = jdesc.session_id().empty() ? 830 kSessionOriginSessionId : jdesc.session_id(); 831 const std::string session_version = jdesc.session_version().empty() ? 832 kSessionOriginSessionVersion : jdesc.session_version(); 833 os << " " << session_id << " " << session_version << " " 834 << kSessionOriginNettype << " " << kSessionOriginAddrtype << " " 835 << kSessionOriginAddress; 836 AddLine(os.str(), &message); 837 AddLine(kSessionName, &message); 838 839 // Time Description. 840 AddLine(kTimeDescription, &message); 841 842 // Group 843 if (desc->HasGroup(cricket::GROUP_TYPE_BUNDLE)) { 844 std::string group_line = kAttrGroup; 845 const cricket::ContentGroup* group = 846 desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); 847 ASSERT(group != NULL); 848 const cricket::ContentNames& content_names = group->content_names(); 849 for (cricket::ContentNames::const_iterator it = content_names.begin(); 850 it != content_names.end(); ++it) { 851 group_line.append(" "); 852 group_line.append(*it); 853 } 854 AddLine(group_line, &message); 855 } 856 857 // MediaStream semantics 858 InitAttrLine(kAttributeMsidSemantics, &os); 859 os << kSdpDelimiterColon << " " << kMediaStreamSemantic; 860 std::set<std::string> media_stream_labels; 861 const ContentInfo* audio_content = GetFirstAudioContent(desc); 862 if (audio_content) 863 GetMediaStreamLabels(audio_content, &media_stream_labels); 864 const ContentInfo* video_content = GetFirstVideoContent(desc); 865 if (video_content) 866 GetMediaStreamLabels(video_content, &media_stream_labels); 867 for (std::set<std::string>::const_iterator it = 868 media_stream_labels.begin(); it != media_stream_labels.end(); ++it) { 869 os << " " << *it; 870 } 871 AddLine(os.str(), &message); 872 873 if (audio_content) { 874 BuildMediaDescription(audio_content, 875 desc->GetTransportInfoByName(audio_content->name), 876 cricket::MEDIA_TYPE_AUDIO, &message); 877 } 878 879 880 if (video_content) { 881 BuildMediaDescription(video_content, 882 desc->GetTransportInfoByName(video_content->name), 883 cricket::MEDIA_TYPE_VIDEO, &message); 884 } 885 886 const ContentInfo* data_content = GetFirstDataContent(desc); 887 if (data_content) { 888 BuildMediaDescription(data_content, 889 desc->GetTransportInfoByName(data_content->name), 890 cricket::MEDIA_TYPE_DATA, &message); 891 } 892 893 894 return message; 895 } 896 897 // Serializes the passed in IceCandidateInterface to a SDP string. 898 // candidate - The candidate to be serialized. 899 std::string SdpSerializeCandidate( 900 const IceCandidateInterface& candidate) { 901 std::string message; 902 std::vector<cricket::Candidate> candidates; 903 candidates.push_back(candidate.candidate()); 904 BuildCandidate(candidates, &message); 905 return message; 906 } 907 908 bool SdpDeserialize(const std::string& message, 909 JsepSessionDescription* jdesc, 910 SdpParseError* error) { 911 std::string session_id; 912 std::string session_version; 913 TransportDescription session_td(NS_JINGLE_ICE_UDP, 914 std::string(), std::string()); 915 RtpHeaderExtensions session_extmaps; 916 cricket::SessionDescription* desc = new cricket::SessionDescription(); 917 std::vector<JsepIceCandidate*> candidates; 918 size_t current_pos = 0; 919 bool supports_msid = false; 920 921 // Session Description 922 if (!ParseSessionDescription(message, ¤t_pos, &session_id, 923 &session_version, &supports_msid, &session_td, 924 &session_extmaps, desc, error)) { 925 delete desc; 926 return false; 927 } 928 929 // Media Description 930 if (!ParseMediaDescription(message, session_td, session_extmaps, 931 supports_msid, ¤t_pos, desc, &candidates, 932 error)) { 933 delete desc; 934 for (std::vector<JsepIceCandidate*>::const_iterator 935 it = candidates.begin(); it != candidates.end(); ++it) { 936 delete *it; 937 } 938 return false; 939 } 940 941 jdesc->Initialize(desc, session_id, session_version); 942 943 for (std::vector<JsepIceCandidate*>::const_iterator 944 it = candidates.begin(); it != candidates.end(); ++it) { 945 jdesc->AddCandidate(*it); 946 delete *it; 947 } 948 return true; 949 } 950 951 bool SdpDeserializeCandidate(const std::string& message, 952 JsepIceCandidate* jcandidate, 953 SdpParseError* error) { 954 ASSERT(jcandidate != NULL); 955 Candidate candidate; 956 if (!ParseCandidate(message, &candidate, error, true)) { 957 return false; 958 } 959 jcandidate->SetCandidate(candidate); 960 return true; 961 } 962 963 bool ParseCandidate(const std::string& message, Candidate* candidate, 964 SdpParseError* error, bool is_raw) { 965 ASSERT(candidate != NULL); 966 967 // Get the first line from |message|. 968 std::string first_line; 969 GetFirstLine(message, &first_line); 970 971 size_t start_pos = kLinePrefixLength; // Starting position to parse. 972 if (IsRawCandidate(first_line)) { 973 // From WebRTC draft section 4.8.1.1 candidate-attribute will be 974 // just candidate:<candidate> not a=candidate:<blah>CRLF 975 start_pos = 0; 976 } else if (!IsLineType(first_line, kLineTypeAttributes) || 977 !HasAttribute(first_line, kAttributeCandidate)) { 978 // Must start with a=candidate line. 979 // Expecting to be of the format a=candidate:<blah>CRLF. 980 if (is_raw) { 981 std::ostringstream description; 982 description << "Expect line: " 983 << kAttributeCandidate 984 << ":" << "<candidate-str>"; 985 return ParseFailed(first_line, 0, description.str(), error); 986 } else { 987 return ParseFailedExpectLine(first_line, 0, kLineTypeAttributes, 988 kAttributeCandidate, error); 989 } 990 } 991 992 std::vector<std::string> fields; 993 talk_base::split(first_line.substr(start_pos), 994 kSdpDelimiterSpace, &fields); 995 // RFC 5245 996 // a=candidate:<foundation> <component-id> <transport> <priority> 997 // <connection-address> <port> typ <candidate-types> 998 // [raddr <connection-address>] [rport <port>] 999 // *(SP extension-att-name SP extension-att-value) 1000 const size_t expected_min_fields = 8; 1001 if (fields.size() < expected_min_fields || 1002 (fields[6] != kAttributeCandidateTyp)) { 1003 return ParseFailedExpectMinFieldNum(first_line, expected_min_fields, error); 1004 } 1005 std::string foundation; 1006 if (!GetValue(fields[0], kAttributeCandidate, &foundation, error)) { 1007 return false; 1008 } 1009 const int component_id = talk_base::FromString<int>(fields[1]); 1010 const std::string transport = fields[2]; 1011 const uint32 priority = talk_base::FromString<uint32>(fields[3]); 1012 const std::string connection_address = fields[4]; 1013 const int port = talk_base::FromString<int>(fields[5]); 1014 SocketAddress address(connection_address, port); 1015 1016 cricket::ProtocolType protocol; 1017 if (!StringToProto(transport.c_str(), &protocol)) { 1018 return ParseFailed(first_line, "Unsupported transport type.", error); 1019 } 1020 1021 std::string candidate_type; 1022 const std::string type = fields[7]; 1023 if (type == kCandidateHost) { 1024 candidate_type = cricket::LOCAL_PORT_TYPE; 1025 } else if (type == kCandidateSrflx) { 1026 candidate_type = cricket::STUN_PORT_TYPE; 1027 } else if (type == kCandidateRelay) { 1028 candidate_type = cricket::RELAY_PORT_TYPE; 1029 } else { 1030 return ParseFailed(first_line, "Unsupported candidate type.", error); 1031 } 1032 1033 size_t current_position = expected_min_fields; 1034 SocketAddress related_address; 1035 // The 2 optional fields for related address 1036 // [raddr <connection-address>] [rport <port>] 1037 if (fields.size() >= (current_position + 2) && 1038 fields[current_position] == kAttributeCandidateRaddr) { 1039 related_address.SetIP(fields[++current_position]); 1040 ++current_position; 1041 } 1042 if (fields.size() >= (current_position + 2) && 1043 fields[current_position] == kAttributeCandidateRport) { 1044 related_address.SetPort( 1045 talk_base::FromString<int>(fields[++current_position])); 1046 ++current_position; 1047 } 1048 1049 // Extension 1050 // Empty string as the candidate username and password. 1051 // Will be updated later with the ice-ufrag and ice-pwd. 1052 // TODO: Remove the username/password extension, which is currently 1053 // kept for backwards compatibility. 1054 std::string username; 1055 std::string password; 1056 uint32 generation = 0; 1057 for (size_t i = current_position; i + 1 < fields.size(); ++i) { 1058 // RFC 5245 1059 // *(SP extension-att-name SP extension-att-value) 1060 if (fields[i] == kAttributeCandidateGeneration) { 1061 generation = talk_base::FromString<uint32>(fields[++i]); 1062 } else if (fields[i] == kAttributeCandidateUsername) { 1063 username = fields[++i]; 1064 } else if (fields[i] == kAttributeCandidatePassword) { 1065 password = fields[++i]; 1066 } else { 1067 // Skip the unknown extension. 1068 ++i; 1069 } 1070 } 1071 1072 // Empty string as the candidate id and network name. 1073 const std::string id; 1074 const std::string network_name; 1075 *candidate = Candidate(id, component_id, cricket::ProtoToString(protocol), 1076 address, priority, username, password, candidate_type, network_name, 1077 generation, foundation); 1078 candidate->set_related_address(related_address); 1079 return true; 1080 } 1081 1082 bool ParseIceOptions(const std::string& line, 1083 std::vector<std::string>* transport_options, 1084 SdpParseError* error) { 1085 std::string ice_options; 1086 if (!GetValue(line, kAttributeIceOption, &ice_options, error)) { 1087 return false; 1088 } 1089 std::vector<std::string> fields; 1090 talk_base::split(ice_options, kSdpDelimiterSpace, &fields); 1091 for (size_t i = 0; i < fields.size(); ++i) { 1092 transport_options->push_back(fields[i]); 1093 } 1094 return true; 1095 } 1096 1097 bool ParseExtmap(const std::string& line, RtpHeaderExtension* extmap, 1098 SdpParseError* error) { 1099 // RFC 5285 1100 // a=extmap:<value>["/"<direction>] <URI> <extensionattributes> 1101 std::vector<std::string> fields; 1102 talk_base::split(line.substr(kLinePrefixLength), 1103 kSdpDelimiterSpace, &fields); 1104 const size_t expected_min_fields = 2; 1105 if (fields.size() < expected_min_fields) { 1106 return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); 1107 } 1108 std::string uri = fields[1]; 1109 1110 std::string value_direction; 1111 if (!GetValue(fields[0], kAttributeExtmap, &value_direction, error)) { 1112 return false; 1113 } 1114 std::vector<std::string> sub_fields; 1115 talk_base::split(value_direction, kSdpDelimiterSlash, &sub_fields); 1116 int value = talk_base::FromString<int>(sub_fields[0]); 1117 1118 *extmap = RtpHeaderExtension(uri, value); 1119 return true; 1120 } 1121 1122 void BuildMediaDescription(const ContentInfo* content_info, 1123 const TransportInfo* transport_info, 1124 const MediaType media_type, 1125 std::string* message) { 1126 ASSERT(message != NULL); 1127 if (content_info == NULL || message == NULL) { 1128 return; 1129 } 1130 // TODO: Rethink if we should use sprintfn instead of stringstream. 1131 // According to the style guide, streams should only be used for logging. 1132 // http://google-styleguide.googlecode.com/svn/ 1133 // trunk/cppguide.xml?showone=Streams#Streams 1134 std::ostringstream os; 1135 const MediaContentDescription* media_desc = 1136 static_cast<const MediaContentDescription*>( 1137 content_info->description); 1138 ASSERT(media_desc != NULL); 1139 1140 bool is_sctp = (media_desc->protocol() == cricket::kMediaProtocolDtlsSctp); 1141 1142 // RFC 4566 1143 // m=<media> <port> <proto> <fmt> 1144 // fmt is a list of payload type numbers that MAY be used in the session. 1145 const char* type = NULL; 1146 if (media_type == cricket::MEDIA_TYPE_AUDIO) 1147 type = kMediaTypeAudio; 1148 else if (media_type == cricket::MEDIA_TYPE_VIDEO) 1149 type = kMediaTypeVideo; 1150 else if (media_type == cricket::MEDIA_TYPE_DATA) 1151 type = kMediaTypeData; 1152 else 1153 ASSERT(false); 1154 1155 std::string fmt; 1156 if (media_type == cricket::MEDIA_TYPE_VIDEO) { 1157 const VideoContentDescription* video_desc = 1158 static_cast<const VideoContentDescription*>(media_desc); 1159 for (std::vector<cricket::VideoCodec>::const_iterator it = 1160 video_desc->codecs().begin(); 1161 it != video_desc->codecs().end(); ++it) { 1162 fmt.append(" "); 1163 fmt.append(talk_base::ToString<int>(it->id)); 1164 } 1165 } else if (media_type == cricket::MEDIA_TYPE_AUDIO) { 1166 const AudioContentDescription* audio_desc = 1167 static_cast<const AudioContentDescription*>(media_desc); 1168 for (std::vector<cricket::AudioCodec>::const_iterator it = 1169 audio_desc->codecs().begin(); 1170 it != audio_desc->codecs().end(); ++it) { 1171 fmt.append(" "); 1172 fmt.append(talk_base::ToString<int>(it->id)); 1173 } 1174 } else if (media_type == cricket::MEDIA_TYPE_DATA) { 1175 if (is_sctp) { 1176 fmt.append(" "); 1177 // TODO(jiayl): Replace the hard-coded string with the fmt read out of the 1178 // ContentDescription. 1179 fmt.append(talk_base::ToString<int>(kDefaultSctpFmt)); 1180 } else { 1181 const DataContentDescription* data_desc = 1182 static_cast<const DataContentDescription*>(media_desc); 1183 for (std::vector<cricket::DataCodec>::const_iterator it = 1184 data_desc->codecs().begin(); 1185 it != data_desc->codecs().end(); ++it) { 1186 fmt.append(" "); 1187 fmt.append(talk_base::ToString<int>(it->id)); 1188 } 1189 } 1190 } 1191 // The fmt must never be empty. If no codecs are found, set the fmt attribute 1192 // to 0. 1193 if (fmt.empty()) { 1194 fmt = " 0"; 1195 } 1196 1197 // The port number in the m line will be updated later when associate with 1198 // the candidates. 1199 // RFC 3264 1200 // To reject an offered stream, the port number in the corresponding stream in 1201 // the answer MUST be set to zero. 1202 const std::string port = content_info->rejected ? 1203 kMediaPortRejected : kDefaultPort; 1204 1205 talk_base::SSLFingerprint* fp = (transport_info) ? 1206 transport_info->description.identity_fingerprint.get() : NULL; 1207 1208 InitLine(kLineTypeMedia, type, &os); 1209 os << " " << port << " " << media_desc->protocol() << fmt; 1210 AddLine(os.str(), message); 1211 1212 // Use the transport_info to build the media level ice-ufrag and ice-pwd. 1213 if (transport_info) { 1214 // RFC 5245 1215 // ice-pwd-att = "ice-pwd" ":" password 1216 // ice-ufrag-att = "ice-ufrag" ":" ufrag 1217 // ice-ufrag 1218 InitAttrLine(kAttributeIceUfrag, &os); 1219 os << kSdpDelimiterColon << transport_info->description.ice_ufrag; 1220 AddLine(os.str(), message); 1221 // ice-pwd 1222 InitAttrLine(kAttributeIcePwd, &os); 1223 os << kSdpDelimiterColon << transport_info->description.ice_pwd; 1224 AddLine(os.str(), message); 1225 1226 // draft-petithuguenin-mmusic-ice-attributes-level-03 1227 BuildIceOptions(transport_info->description.transport_options, message); 1228 1229 // RFC 4572 1230 // fingerprint-attribute = 1231 // "fingerprint" ":" hash-func SP fingerprint 1232 if (fp) { 1233 // Insert the fingerprint attribute. 1234 InitAttrLine(kAttributeFingerprint, &os); 1235 os << kSdpDelimiterColon 1236 << fp->algorithm << kSdpDelimiterSpace 1237 << fp->GetRfc4572Fingerprint(); 1238 AddLine(os.str(), message); 1239 1240 // Inserting setup attribute. 1241 if (transport_info->description.connection_role != 1242 cricket::CONNECTIONROLE_NONE) { 1243 // Making sure we are not using "passive" mode. 1244 cricket::ConnectionRole role = 1245 transport_info->description.connection_role; 1246 std::string dtls_role_str; 1247 VERIFY(cricket::ConnectionRoleToString(role, &dtls_role_str)); 1248 InitAttrLine(kAttributeSetup, &os); 1249 os << kSdpDelimiterColon << dtls_role_str; 1250 AddLine(os.str(), message); 1251 } 1252 } 1253 } 1254 1255 // RFC 3388 1256 // mid-attribute = "a=mid:" identification-tag 1257 // identification-tag = token 1258 // Use the content name as the mid identification-tag. 1259 InitAttrLine(kAttributeMid, &os); 1260 os << kSdpDelimiterColon << content_info->name; 1261 AddLine(os.str(), message); 1262 1263 if (is_sctp) { 1264 BuildSctpContentAttributes(message); 1265 } else { 1266 BuildRtpContentAttributes(media_desc, media_type, message); 1267 } 1268 } 1269 1270 void BuildSctpContentAttributes(std::string* message) { 1271 // draft-ietf-mmusic-sctp-sdp-04 1272 // a=sctpmap:sctpmap-number protocol [streams] 1273 std::ostringstream os; 1274 InitAttrLine(kAttributeSctpmap, &os); 1275 os << kSdpDelimiterColon << kDefaultSctpFmt << kSdpDelimiterSpace 1276 << kDefaultSctpmapProtocol << kSdpDelimiterSpace 1277 << (cricket::kMaxSctpSid + 1); 1278 AddLine(os.str(), message); 1279 } 1280 1281 void BuildRtpContentAttributes( 1282 const MediaContentDescription* media_desc, 1283 const MediaType media_type, 1284 std::string* message) { 1285 std::ostringstream os; 1286 // RFC 5285 1287 // a=extmap:<value>["/"<direction>] <URI> <extensionattributes> 1288 // The definitions MUST be either all session level or all media level. This 1289 // implementation uses all media level. 1290 for (size_t i = 0; i < media_desc->rtp_header_extensions().size(); ++i) { 1291 InitAttrLine(kAttributeExtmap, &os); 1292 os << kSdpDelimiterColon << media_desc->rtp_header_extensions()[i].id 1293 << kSdpDelimiterSpace << media_desc->rtp_header_extensions()[i].uri; 1294 AddLine(os.str(), message); 1295 } 1296 1297 // RFC 3264 1298 // a=sendrecv || a=sendonly || a=sendrecv || a=inactive 1299 1300 cricket::MediaContentDirection direction = media_desc->direction(); 1301 if (media_desc->streams().empty() && direction == cricket::MD_SENDRECV) { 1302 direction = cricket::MD_RECVONLY; 1303 } 1304 1305 switch (direction) { 1306 case cricket::MD_INACTIVE: 1307 InitAttrLine(kAttributeInactive, &os); 1308 break; 1309 case cricket::MD_SENDONLY: 1310 InitAttrLine(kAttributeSendOnly, &os); 1311 break; 1312 case cricket::MD_RECVONLY: 1313 InitAttrLine(kAttributeRecvOnly, &os); 1314 break; 1315 case cricket::MD_SENDRECV: 1316 default: 1317 InitAttrLine(kAttributeSendRecv, &os); 1318 break; 1319 } 1320 AddLine(os.str(), message); 1321 1322 // RFC 4566 1323 // b=AS:<bandwidth> 1324 // We should always use the default bandwidth for RTP-based data 1325 // channels. Don't allow SDP to set the bandwidth, because that 1326 // would give JS the opportunity to "break the Internet". 1327 // TODO(pthatcher): But we need to temporarily allow the SDP to control 1328 // this for backwards-compatibility. Once we don't need that any 1329 // more, remove this. 1330 bool support_dc_sdp_bandwidth_temporarily = true; 1331 if (media_desc->bandwidth() >= 1000 && 1332 (media_type != cricket::MEDIA_TYPE_DATA || 1333 support_dc_sdp_bandwidth_temporarily)) { 1334 InitLine(kLineTypeSessionBandwidth, kApplicationSpecificMaximum, &os); 1335 os << kSdpDelimiterColon << (media_desc->bandwidth() / 1000); 1336 AddLine(os.str(), message); 1337 } 1338 1339 // RFC 5761 1340 // a=rtcp-mux 1341 if (media_desc->rtcp_mux()) { 1342 InitAttrLine(kAttributeRtcpMux, &os); 1343 AddLine(os.str(), message); 1344 } 1345 1346 // RFC 4568 1347 // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] 1348 for (std::vector<CryptoParams>::const_iterator it = 1349 media_desc->cryptos().begin(); 1350 it != media_desc->cryptos().end(); ++it) { 1351 InitAttrLine(kAttributeCrypto, &os); 1352 os << kSdpDelimiterColon << it->tag << " " << it->cipher_suite << " " 1353 << it->key_params; 1354 if (!it->session_params.empty()) { 1355 os << " " << it->session_params; 1356 } 1357 AddLine(os.str(), message); 1358 } 1359 1360 // RFC 4566 1361 // a=rtpmap:<payload type> <encoding name>/<clock rate> 1362 // [/<encodingparameters>] 1363 BuildRtpMap(media_desc, media_type, message); 1364 1365 // Specify latency for buffered mode. 1366 // a=x-google-buffer-latency:<value> 1367 if (media_desc->buffered_mode_latency() != cricket::kBufferedModeDisabled) { 1368 std::ostringstream os; 1369 InitAttrLine(kAttributeXGoogleBufferLatency, &os); 1370 os << kSdpDelimiterColon << media_desc->buffered_mode_latency(); 1371 AddLine(os.str(), message); 1372 } 1373 1374 for (StreamParamsVec::const_iterator track = media_desc->streams().begin(); 1375 track != media_desc->streams().end(); ++track) { 1376 // Require that the track belongs to a media stream, 1377 // ie the sync_label is set. This extra check is necessary since the 1378 // MediaContentDescription always contains a streamparam with an ssrc even 1379 // if no track or media stream have been created. 1380 if (track->sync_label.empty()) continue; 1381 1382 // Build the ssrc-group lines. 1383 for (size_t i = 0; i < track->ssrc_groups.size(); ++i) { 1384 // RFC 5576 1385 // a=ssrc-group:<semantics> <ssrc-id> ... 1386 if (track->ssrc_groups[i].ssrcs.empty()) { 1387 continue; 1388 } 1389 std::ostringstream os; 1390 InitAttrLine(kAttributeSsrcGroup, &os); 1391 os << kSdpDelimiterColon << track->ssrc_groups[i].semantics; 1392 std::vector<uint32>::const_iterator ssrc = 1393 track->ssrc_groups[i].ssrcs.begin(); 1394 for (; ssrc != track->ssrc_groups[i].ssrcs.end(); ++ssrc) { 1395 os << kSdpDelimiterSpace << talk_base::ToString<uint32>(*ssrc); 1396 } 1397 AddLine(os.str(), message); 1398 } 1399 // Build the ssrc lines for each ssrc. 1400 for (size_t i = 0; i < track->ssrcs.size(); ++i) { 1401 uint32 ssrc = track->ssrcs[i]; 1402 // RFC 5576 1403 // a=ssrc:<ssrc-id> cname:<value> 1404 AddSsrcLine(ssrc, kSsrcAttributeCname, 1405 track->cname, message); 1406 1407 // draft-alvestrand-mmusic-msid-00 1408 // a=ssrc:<ssrc-id> msid:identifier [appdata] 1409 // The appdata consists of the "id" attribute of a MediaStreamTrack, which 1410 // is corresponding to the "name" attribute of StreamParams. 1411 std::string appdata = track->id; 1412 std::ostringstream os; 1413 InitAttrLine(kAttributeSsrc, &os); 1414 os << kSdpDelimiterColon << ssrc << kSdpDelimiterSpace 1415 << kSsrcAttributeMsid << kSdpDelimiterColon << track->sync_label 1416 << kSdpDelimiterSpace << appdata; 1417 AddLine(os.str(), message); 1418 1419 // TODO(ronghuawu): Remove below code which is for backward compatibility. 1420 // draft-alvestrand-rtcweb-mid-01 1421 // a=ssrc:<ssrc-id> mslabel:<value> 1422 // The label isn't yet defined. 1423 // a=ssrc:<ssrc-id> label:<value> 1424 AddSsrcLine(ssrc, kSsrcAttributeMslabel, track->sync_label, message); 1425 AddSsrcLine(ssrc, kSSrcAttributeLabel, track->id, message); 1426 } 1427 } 1428 } 1429 1430 void WriteFmtpHeader(int payload_type, std::ostringstream* os) { 1431 // fmtp header: a=fmtp:|payload_type| <parameters> 1432 // Add a=fmtp 1433 InitAttrLine(kAttributeFmtp, os); 1434 // Add :|payload_type| 1435 *os << kSdpDelimiterColon << payload_type; 1436 } 1437 1438 void WriteRtcpFbHeader(int payload_type, std::ostringstream* os) { 1439 // rtcp-fb header: a=rtcp-fb:|payload_type| 1440 // <parameters>/<ccm <ccm_parameters>> 1441 // Add a=rtcp-fb 1442 InitAttrLine(kAttributeRtcpFb, os); 1443 // Add : 1444 *os << kSdpDelimiterColon; 1445 if (payload_type == kWildcardPayloadType) { 1446 *os << "*"; 1447 } else { 1448 *os << payload_type; 1449 } 1450 } 1451 1452 void WriteFmtpParameter(const std::string& parameter_name, 1453 const std::string& parameter_value, 1454 std::ostringstream* os) { 1455 // fmtp parameters: |parameter_name|=|parameter_value| 1456 *os << parameter_name << kSdpDelimiterEqual << parameter_value; 1457 } 1458 1459 void WriteFmtpParameters(const cricket::CodecParameterMap& parameters, 1460 std::ostringstream* os) { 1461 for (cricket::CodecParameterMap::const_iterator fmtp = parameters.begin(); 1462 fmtp != parameters.end(); ++fmtp) { 1463 // Each new parameter, except the first one starts with ";" and " ". 1464 if (fmtp != parameters.begin()) { 1465 *os << kSdpDelimiterSemicolon; 1466 } 1467 *os << kSdpDelimiterSpace; 1468 WriteFmtpParameter(fmtp->first, fmtp->second, os); 1469 } 1470 } 1471 1472 bool IsFmtpParam(const std::string& name) { 1473 const char* kFmtpParams[] = { 1474 kCodecParamMinPTime, kCodecParamSPropStereo, 1475 kCodecParamStereo, kCodecParamUseInbandFec, 1476 kCodecParamMaxBitrate, kCodecParamMinBitrate, kCodecParamMaxQuantization, 1477 kCodecParamSctpProtocol, kCodecParamSctpStreams, 1478 kCodecParamMaxAverageBitrate 1479 }; 1480 for (size_t i = 0; i < ARRAY_SIZE(kFmtpParams); ++i) { 1481 if (_stricmp(name.c_str(), kFmtpParams[i]) == 0) { 1482 return true; 1483 } 1484 } 1485 return false; 1486 } 1487 1488 // Retreives fmtp parameters from |params|, which may contain other parameters 1489 // as well, and puts them in |fmtp_parameters|. 1490 void GetFmtpParams(const cricket::CodecParameterMap& params, 1491 cricket::CodecParameterMap* fmtp_parameters) { 1492 for (cricket::CodecParameterMap::const_iterator iter = params.begin(); 1493 iter != params.end(); ++iter) { 1494 if (IsFmtpParam(iter->first)) { 1495 (*fmtp_parameters)[iter->first] = iter->second; 1496 } 1497 } 1498 } 1499 1500 template <class T> 1501 void AddFmtpLine(const T& codec, std::string* message) { 1502 cricket::CodecParameterMap fmtp_parameters; 1503 GetFmtpParams(codec.params, &fmtp_parameters); 1504 if (fmtp_parameters.empty()) { 1505 // No need to add an fmtp if it will have no (optional) parameters. 1506 return; 1507 } 1508 std::ostringstream os; 1509 WriteFmtpHeader(codec.id, &os); 1510 WriteFmtpParameters(fmtp_parameters, &os); 1511 AddLine(os.str(), message); 1512 return; 1513 } 1514 1515 template <class T> 1516 void AddRtcpFbLines(const T& codec, std::string* message) { 1517 for (std::vector<cricket::FeedbackParam>::const_iterator iter = 1518 codec.feedback_params.params().begin(); 1519 iter != codec.feedback_params.params().end(); ++iter) { 1520 std::ostringstream os; 1521 WriteRtcpFbHeader(codec.id, &os); 1522 os << " " << iter->id(); 1523 if (!iter->param().empty()) { 1524 os << " " << iter->param(); 1525 } 1526 AddLine(os.str(), message); 1527 } 1528 } 1529 1530 bool GetMinValue(const std::vector<int>& values, int* value) { 1531 if (values.empty()) { 1532 return false; 1533 } 1534 std::vector<int>::const_iterator found = 1535 std::min_element(values.begin(), values.end()); 1536 *value = *found; 1537 return true; 1538 } 1539 1540 bool GetParameter(const std::string& name, 1541 const cricket::CodecParameterMap& params, int* value) { 1542 std::map<std::string, std::string>::const_iterator found = 1543 params.find(name); 1544 if (found == params.end()) { 1545 return false; 1546 } 1547 *value = talk_base::FromString<int>(found->second); 1548 return true; 1549 } 1550 1551 void BuildRtpMap(const MediaContentDescription* media_desc, 1552 const MediaType media_type, 1553 std::string* message) { 1554 ASSERT(message != NULL); 1555 ASSERT(media_desc != NULL); 1556 std::ostringstream os; 1557 if (media_type == cricket::MEDIA_TYPE_VIDEO) { 1558 const VideoContentDescription* video_desc = 1559 static_cast<const VideoContentDescription*>(media_desc); 1560 for (std::vector<cricket::VideoCodec>::const_iterator it = 1561 video_desc->codecs().begin(); 1562 it != video_desc->codecs().end(); ++it) { 1563 // RFC 4566 1564 // a=rtpmap:<payload type> <encoding name>/<clock rate> 1565 // [/<encodingparameters>] 1566 if (it->id != kWildcardPayloadType) { 1567 InitAttrLine(kAttributeRtpmap, &os); 1568 os << kSdpDelimiterColon << it->id << " " << it->name 1569 << "/" << kDefaultVideoClockrate; 1570 AddLine(os.str(), message); 1571 } 1572 AddRtcpFbLines(*it, message); 1573 AddFmtpLine(*it, message); 1574 } 1575 } else if (media_type == cricket::MEDIA_TYPE_AUDIO) { 1576 const AudioContentDescription* audio_desc = 1577 static_cast<const AudioContentDescription*>(media_desc); 1578 std::vector<int> ptimes; 1579 std::vector<int> maxptimes; 1580 int max_minptime = 0; 1581 for (std::vector<cricket::AudioCodec>::const_iterator it = 1582 audio_desc->codecs().begin(); 1583 it != audio_desc->codecs().end(); ++it) { 1584 ASSERT(!it->name.empty()); 1585 // RFC 4566 1586 // a=rtpmap:<payload type> <encoding name>/<clock rate> 1587 // [/<encodingparameters>] 1588 InitAttrLine(kAttributeRtpmap, &os); 1589 os << kSdpDelimiterColon << it->id << " "; 1590 os << it->name << "/" << it->clockrate; 1591 if (it->channels != 1) { 1592 os << "/" << it->channels; 1593 } 1594 AddLine(os.str(), message); 1595 AddRtcpFbLines(*it, message); 1596 AddFmtpLine(*it, message); 1597 int minptime = 0; 1598 if (GetParameter(kCodecParamMinPTime, it->params, &minptime)) { 1599 max_minptime = std::max(minptime, max_minptime); 1600 } 1601 int ptime; 1602 if (GetParameter(kCodecParamPTime, it->params, &ptime)) { 1603 ptimes.push_back(ptime); 1604 } 1605 int maxptime; 1606 if (GetParameter(kCodecParamMaxPTime, it->params, &maxptime)) { 1607 maxptimes.push_back(maxptime); 1608 } 1609 } 1610 // Populate the maxptime attribute with the smallest maxptime of all codecs 1611 // under the same m-line. 1612 int min_maxptime = INT_MAX; 1613 if (GetMinValue(maxptimes, &min_maxptime)) { 1614 AddAttributeLine(kCodecParamMaxPTime, min_maxptime, message); 1615 } 1616 ASSERT(min_maxptime > max_minptime); 1617 // Populate the ptime attribute with the smallest ptime or the largest 1618 // minptime, whichever is the largest, for all codecs under the same m-line. 1619 int ptime = INT_MAX; 1620 if (GetMinValue(ptimes, &ptime)) { 1621 ptime = std::min(ptime, min_maxptime); 1622 ptime = std::max(ptime, max_minptime); 1623 AddAttributeLine(kCodecParamPTime, ptime, message); 1624 } 1625 } else if (media_type == cricket::MEDIA_TYPE_DATA) { 1626 const DataContentDescription* data_desc = 1627 static_cast<const DataContentDescription*>(media_desc); 1628 for (std::vector<cricket::DataCodec>::const_iterator it = 1629 data_desc->codecs().begin(); 1630 it != data_desc->codecs().end(); ++it) { 1631 // RFC 4566 1632 // a=rtpmap:<payload type> <encoding name>/<clock rate> 1633 // [/<encodingparameters>] 1634 InitAttrLine(kAttributeRtpmap, &os); 1635 os << kSdpDelimiterColon << it->id << " " 1636 << it->name << "/" << it->clockrate; 1637 AddLine(os.str(), message); 1638 } 1639 } 1640 } 1641 1642 void BuildCandidate(const std::vector<Candidate>& candidates, 1643 std::string* message) { 1644 std::ostringstream os; 1645 1646 for (std::vector<Candidate>::const_iterator it = candidates.begin(); 1647 it != candidates.end(); ++it) { 1648 // RFC 5245 1649 // a=candidate:<foundation> <component-id> <transport> <priority> 1650 // <connection-address> <port> typ <candidate-types> 1651 // [raddr <connection-address>] [rport <port>] 1652 // *(SP extension-att-name SP extension-att-value) 1653 std::string type; 1654 // Map the cricket candidate type to "host" / "srflx" / "prflx" / "relay" 1655 if (it->type() == cricket::LOCAL_PORT_TYPE) { 1656 type = kCandidateHost; 1657 } else if (it->type() == cricket::STUN_PORT_TYPE) { 1658 type = kCandidateSrflx; 1659 } else if (it->type() == cricket::RELAY_PORT_TYPE) { 1660 type = kCandidateRelay; 1661 } else { 1662 ASSERT(false); 1663 } 1664 1665 InitAttrLine(kAttributeCandidate, &os); 1666 os << kSdpDelimiterColon 1667 << it->foundation() << " " << it->component() << " " 1668 << it->protocol() << " " << it->priority() << " " 1669 << it->address().ipaddr().ToString() << " " 1670 << it->address().PortAsString() << " " 1671 << kAttributeCandidateTyp << " " << type << " "; 1672 1673 // Related address 1674 if (!it->related_address().IsNil()) { 1675 os << kAttributeCandidateRaddr << " " 1676 << it->related_address().ipaddr().ToString() << " " 1677 << kAttributeCandidateRport << " " 1678 << it->related_address().PortAsString() << " "; 1679 } 1680 1681 // Extensions 1682 os << kAttributeCandidateGeneration << " " << it->generation(); 1683 1684 AddLine(os.str(), message); 1685 } 1686 } 1687 1688 void BuildIceOptions(const std::vector<std::string>& transport_options, 1689 std::string* message) { 1690 if (!transport_options.empty()) { 1691 std::ostringstream os; 1692 InitAttrLine(kAttributeIceOption, &os); 1693 os << kSdpDelimiterColon << transport_options[0]; 1694 for (size_t i = 1; i < transport_options.size(); ++i) { 1695 os << kSdpDelimiterSpace << transport_options[i]; 1696 } 1697 AddLine(os.str(), message); 1698 } 1699 } 1700 1701 bool ParseSessionDescription(const std::string& message, size_t* pos, 1702 std::string* session_id, 1703 std::string* session_version, 1704 bool* supports_msid, 1705 TransportDescription* session_td, 1706 RtpHeaderExtensions* session_extmaps, 1707 cricket::SessionDescription* desc, 1708 SdpParseError* error) { 1709 std::string line; 1710 1711 // RFC 4566 1712 // v= (protocol version) 1713 if (!GetLineWithType(message, pos, &line, kLineTypeVersion)) { 1714 return ParseFailedExpectLine(message, *pos, kLineTypeVersion, 1715 std::string(), error); 1716 } 1717 // RFC 4566 1718 // o=<username> <sess-id> <sess-version> <nettype> <addrtype> 1719 // <unicast-address> 1720 if (!GetLineWithType(message, pos, &line, kLineTypeOrigin)) { 1721 return ParseFailedExpectLine(message, *pos, kLineTypeOrigin, 1722 std::string(), error); 1723 } 1724 std::vector<std::string> fields; 1725 talk_base::split(line.substr(kLinePrefixLength), 1726 kSdpDelimiterSpace, &fields); 1727 const size_t expected_fields = 6; 1728 if (fields.size() != expected_fields) { 1729 return ParseFailedExpectFieldNum(line, expected_fields, error); 1730 } 1731 *session_id = fields[1]; 1732 *session_version = fields[2]; 1733 1734 // RFC 4566 1735 // s= (session name) 1736 if (!GetLineWithType(message, pos, &line, kLineTypeSessionName)) { 1737 return ParseFailedExpectLine(message, *pos, kLineTypeSessionName, 1738 std::string(), error); 1739 } 1740 1741 // Optional lines 1742 // Those are the optional lines, so shouldn't return false if not present. 1743 // RFC 4566 1744 // i=* (session information) 1745 GetLineWithType(message, pos, &line, kLineTypeSessionInfo); 1746 1747 // RFC 4566 1748 // u=* (URI of description) 1749 GetLineWithType(message, pos, &line, kLineTypeSessionUri); 1750 1751 // RFC 4566 1752 // e=* (email address) 1753 GetLineWithType(message, pos, &line, kLineTypeSessionEmail); 1754 1755 // RFC 4566 1756 // p=* (phone number) 1757 GetLineWithType(message, pos, &line, kLineTypeSessionPhone); 1758 1759 // RFC 4566 1760 // c=* (connection information -- not required if included in 1761 // all media) 1762 GetLineWithType(message, pos, &line, kLineTypeConnection); 1763 1764 // RFC 4566 1765 // b=* (zero or more bandwidth information lines) 1766 while (GetLineWithType(message, pos, &line, kLineTypeSessionBandwidth)) { 1767 // By pass zero or more b lines. 1768 } 1769 1770 // RFC 4566 1771 // One or more time descriptions ("t=" and "r=" lines; see below) 1772 // t= (time the session is active) 1773 // r=* (zero or more repeat times) 1774 // Ensure there's at least one time description 1775 if (!GetLineWithType(message, pos, &line, kLineTypeTiming)) { 1776 return ParseFailedExpectLine(message, *pos, kLineTypeTiming, std::string(), 1777 error); 1778 } 1779 1780 while (GetLineWithType(message, pos, &line, kLineTypeRepeatTimes)) { 1781 // By pass zero or more r lines. 1782 } 1783 1784 // Go through the rest of the time descriptions 1785 while (GetLineWithType(message, pos, &line, kLineTypeTiming)) { 1786 while (GetLineWithType(message, pos, &line, kLineTypeRepeatTimes)) { 1787 // By pass zero or more r lines. 1788 } 1789 } 1790 1791 // RFC 4566 1792 // z=* (time zone adjustments) 1793 GetLineWithType(message, pos, &line, kLineTypeTimeZone); 1794 1795 // RFC 4566 1796 // k=* (encryption key) 1797 GetLineWithType(message, pos, &line, kLineTypeEncryptionKey); 1798 1799 // RFC 4566 1800 // a=* (zero or more session attribute lines) 1801 while (GetLineWithType(message, pos, &line, kLineTypeAttributes)) { 1802 if (HasAttribute(line, kAttributeGroup)) { 1803 if (!ParseGroupAttribute(line, desc, error)) { 1804 return false; 1805 } 1806 } else if (HasAttribute(line, kAttributeIceUfrag)) { 1807 if (!GetValue(line, kAttributeIceUfrag, 1808 &(session_td->ice_ufrag), error)) { 1809 return false; 1810 } 1811 } else if (HasAttribute(line, kAttributeIcePwd)) { 1812 if (!GetValue(line, kAttributeIcePwd, &(session_td->ice_pwd), error)) { 1813 return false; 1814 } 1815 } else if (HasAttribute(line, kAttributeIceLite)) { 1816 session_td->ice_mode = cricket::ICEMODE_LITE; 1817 } else if (HasAttribute(line, kAttributeIceOption)) { 1818 if (!ParseIceOptions(line, &(session_td->transport_options), error)) { 1819 return false; 1820 } 1821 } else if (HasAttribute(line, kAttributeFingerprint)) { 1822 if (session_td->identity_fingerprint.get()) { 1823 return ParseFailed( 1824 line, 1825 "Can't have multiple fingerprint attributes at the same level.", 1826 error); 1827 } 1828 talk_base::SSLFingerprint* fingerprint = NULL; 1829 if (!ParseFingerprintAttribute(line, &fingerprint, error)) { 1830 return false; 1831 } 1832 session_td->identity_fingerprint.reset(fingerprint); 1833 } else if (HasAttribute(line, kAttributeSetup)) { 1834 if (!ParseDtlsSetup(line, &(session_td->connection_role), error)) { 1835 return false; 1836 } 1837 } else if (HasAttribute(line, kAttributeMsidSemantics)) { 1838 std::string semantics; 1839 if (!GetValue(line, kAttributeMsidSemantics, &semantics, error)) { 1840 return false; 1841 } 1842 *supports_msid = CaseInsensitiveFind(semantics, kMediaStreamSemantic); 1843 } else if (HasAttribute(line, kAttributeExtmap)) { 1844 RtpHeaderExtension extmap; 1845 if (!ParseExtmap(line, &extmap, error)) { 1846 return false; 1847 } 1848 session_extmaps->push_back(extmap); 1849 } 1850 } 1851 1852 return true; 1853 } 1854 1855 bool ParseGroupAttribute(const std::string& line, 1856 cricket::SessionDescription* desc, 1857 SdpParseError* error) { 1858 ASSERT(desc != NULL); 1859 1860 // RFC 5888 and draft-holmberg-mmusic-sdp-bundle-negotiation-00 1861 // a=group:BUNDLE video voice 1862 std::vector<std::string> fields; 1863 talk_base::split(line.substr(kLinePrefixLength), 1864 kSdpDelimiterSpace, &fields); 1865 std::string semantics; 1866 if (!GetValue(fields[0], kAttributeGroup, &semantics, error)) { 1867 return false; 1868 } 1869 cricket::ContentGroup group(semantics); 1870 for (size_t i = 1; i < fields.size(); ++i) { 1871 group.AddContentName(fields[i]); 1872 } 1873 desc->AddGroup(group); 1874 return true; 1875 } 1876 1877 static bool ParseFingerprintAttribute(const std::string& line, 1878 talk_base::SSLFingerprint** fingerprint, 1879 SdpParseError* error) { 1880 if (!IsLineType(line, kLineTypeAttributes) || 1881 !HasAttribute(line, kAttributeFingerprint)) { 1882 return ParseFailedExpectLine(line, 0, kLineTypeAttributes, 1883 kAttributeFingerprint, error); 1884 } 1885 1886 std::vector<std::string> fields; 1887 talk_base::split(line.substr(kLinePrefixLength), 1888 kSdpDelimiterSpace, &fields); 1889 const size_t expected_fields = 2; 1890 if (fields.size() != expected_fields) { 1891 return ParseFailedExpectFieldNum(line, expected_fields, error); 1892 } 1893 1894 // The first field here is "fingerprint:<hash>. 1895 std::string algorithm; 1896 if (!GetValue(fields[0], kAttributeFingerprint, &algorithm, error)) { 1897 return false; 1898 } 1899 1900 // Downcase the algorithm. Note that we don't need to downcase the 1901 // fingerprint because hex_decode can handle upper-case. 1902 std::transform(algorithm.begin(), algorithm.end(), algorithm.begin(), 1903 ::tolower); 1904 1905 // The second field is the digest value. De-hexify it. 1906 *fingerprint = talk_base::SSLFingerprint::CreateFromRfc4572( 1907 algorithm, fields[1]); 1908 if (!*fingerprint) { 1909 return ParseFailed(line, 1910 "Failed to create fingerprint from the digest.", 1911 error); 1912 } 1913 1914 return true; 1915 } 1916 1917 static bool ParseDtlsSetup(const std::string& line, 1918 cricket::ConnectionRole* role, 1919 SdpParseError* error) { 1920 // setup-attr = "a=setup:" role 1921 // role = "active" / "passive" / "actpass" / "holdconn" 1922 std::vector<std::string> fields; 1923 talk_base::split(line.substr(kLinePrefixLength), kSdpDelimiterColon, &fields); 1924 const size_t expected_fields = 2; 1925 if (fields.size() != expected_fields) { 1926 return ParseFailedExpectFieldNum(line, expected_fields, error); 1927 } 1928 std::string role_str = fields[1]; 1929 if (!cricket::StringToConnectionRole(role_str, role)) { 1930 return ParseFailed(line, "Invalid attribute value.", error); 1931 } 1932 return true; 1933 } 1934 1935 // RFC 3551 1936 // PT encoding media type clock rate channels 1937 // name (Hz) 1938 // 0 PCMU A 8,000 1 1939 // 1 reserved A 1940 // 2 reserved A 1941 // 3 GSM A 8,000 1 1942 // 4 G723 A 8,000 1 1943 // 5 DVI4 A 8,000 1 1944 // 6 DVI4 A 16,000 1 1945 // 7 LPC A 8,000 1 1946 // 8 PCMA A 8,000 1 1947 // 9 G722 A 8,000 1 1948 // 10 L16 A 44,100 2 1949 // 11 L16 A 44,100 1 1950 // 12 QCELP A 8,000 1 1951 // 13 CN A 8,000 1 1952 // 14 MPA A 90,000 (see text) 1953 // 15 G728 A 8,000 1 1954 // 16 DVI4 A 11,025 1 1955 // 17 DVI4 A 22,050 1 1956 // 18 G729 A 8,000 1 1957 struct StaticPayloadAudioCodec { 1958 const char* name; 1959 int clockrate; 1960 int channels; 1961 }; 1962 static const StaticPayloadAudioCodec kStaticPayloadAudioCodecs[] = { 1963 { "PCMU", 8000, 1 }, 1964 { "reserved", 0, 0 }, 1965 { "reserved", 0, 0 }, 1966 { "GSM", 8000, 1 }, 1967 { "G723", 8000, 1 }, 1968 { "DVI4", 8000, 1 }, 1969 { "DVI4", 16000, 1 }, 1970 { "LPC", 8000, 1 }, 1971 { "PCMA", 8000, 1 }, 1972 { "G722", 8000, 1 }, 1973 { "L16", 44100, 2 }, 1974 { "L16", 44100, 1 }, 1975 { "QCELP", 8000, 1 }, 1976 { "CN", 8000, 1 }, 1977 { "MPA", 90000, 1 }, 1978 { "G728", 8000, 1 }, 1979 { "DVI4", 11025, 1 }, 1980 { "DVI4", 22050, 1 }, 1981 { "G729", 8000, 1 }, 1982 }; 1983 1984 void MaybeCreateStaticPayloadAudioCodecs( 1985 const std::vector<int>& fmts, AudioContentDescription* media_desc) { 1986 if (!media_desc) { 1987 return; 1988 } 1989 int preference = static_cast<int>(fmts.size()); 1990 std::vector<int>::const_iterator it = fmts.begin(); 1991 bool add_new_codec = false; 1992 for (; it != fmts.end(); ++it) { 1993 int payload_type = *it; 1994 if (!media_desc->HasCodec(payload_type) && 1995 payload_type >= 0 && 1996 payload_type < ARRAY_SIZE(kStaticPayloadAudioCodecs)) { 1997 std::string encoding_name = kStaticPayloadAudioCodecs[payload_type].name; 1998 int clock_rate = kStaticPayloadAudioCodecs[payload_type].clockrate; 1999 int channels = kStaticPayloadAudioCodecs[payload_type].channels; 2000 media_desc->AddCodec(cricket::AudioCodec(payload_type, encoding_name, 2001 clock_rate, 0, channels, 2002 preference)); 2003 add_new_codec = true; 2004 } 2005 --preference; 2006 } 2007 if (add_new_codec) { 2008 media_desc->SortCodecs(); 2009 } 2010 } 2011 2012 template <class C> 2013 static C* ParseContentDescription(const std::string& message, 2014 const MediaType media_type, 2015 int mline_index, 2016 const std::string& protocol, 2017 const std::vector<int>& codec_preference, 2018 size_t* pos, 2019 std::string* content_name, 2020 TransportDescription* transport, 2021 std::vector<JsepIceCandidate*>* candidates, 2022 webrtc::SdpParseError* error) { 2023 C* media_desc = new C(); 2024 switch (media_type) { 2025 case cricket::MEDIA_TYPE_AUDIO: 2026 *content_name = cricket::CN_AUDIO; 2027 break; 2028 case cricket::MEDIA_TYPE_VIDEO: 2029 *content_name = cricket::CN_VIDEO; 2030 break; 2031 case cricket::MEDIA_TYPE_DATA: 2032 *content_name = cricket::CN_DATA; 2033 break; 2034 default: 2035 ASSERT(false); 2036 break; 2037 } 2038 if (!ParseContent(message, media_type, mline_index, protocol, 2039 codec_preference, pos, content_name, 2040 media_desc, transport, candidates, error)) { 2041 delete media_desc; 2042 return NULL; 2043 } 2044 // Sort the codecs according to the m-line fmt list. 2045 media_desc->SortCodecs(); 2046 return media_desc; 2047 } 2048 2049 bool ParseMediaDescription(const std::string& message, 2050 const TransportDescription& session_td, 2051 const RtpHeaderExtensions& session_extmaps, 2052 bool supports_msid, 2053 size_t* pos, 2054 cricket::SessionDescription* desc, 2055 std::vector<JsepIceCandidate*>* candidates, 2056 SdpParseError* error) { 2057 ASSERT(desc != NULL); 2058 std::string line; 2059 int mline_index = -1; 2060 2061 // Zero or more media descriptions 2062 // RFC 4566 2063 // m=<media> <port> <proto> <fmt> 2064 while (GetLineWithType(message, pos, &line, kLineTypeMedia)) { 2065 ++mline_index; 2066 2067 std::vector<std::string> fields; 2068 talk_base::split(line.substr(kLinePrefixLength), 2069 kSdpDelimiterSpace, &fields); 2070 const size_t expected_min_fields = 4; 2071 if (fields.size() < expected_min_fields) { 2072 return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); 2073 } 2074 bool rejected = false; 2075 // RFC 3264 2076 // To reject an offered stream, the port number in the corresponding stream 2077 // in the answer MUST be set to zero. 2078 if (fields[1] == kMediaPortRejected) { 2079 rejected = true; 2080 } 2081 2082 std::string protocol = fields[2]; 2083 bool is_sctp = (protocol == cricket::kMediaProtocolDtlsSctp); 2084 2085 // <fmt> 2086 std::vector<int> codec_preference; 2087 for (size_t j = 3 ; j < fields.size(); ++j) { 2088 codec_preference.push_back(talk_base::FromString<int>(fields[j])); 2089 } 2090 2091 // Make a temporary TransportDescription based on |session_td|. 2092 // Some of this gets overwritten by ParseContent. 2093 TransportDescription transport(NS_JINGLE_ICE_UDP, 2094 session_td.transport_options, 2095 session_td.ice_ufrag, 2096 session_td.ice_pwd, 2097 session_td.ice_mode, 2098 session_td.connection_role, 2099 session_td.identity_fingerprint.get(), 2100 Candidates()); 2101 2102 talk_base::scoped_ptr<MediaContentDescription> content; 2103 std::string content_name; 2104 if (HasAttribute(line, kMediaTypeVideo)) { 2105 content.reset(ParseContentDescription<VideoContentDescription>( 2106 message, cricket::MEDIA_TYPE_VIDEO, mline_index, protocol, 2107 codec_preference, pos, &content_name, 2108 &transport, candidates, error)); 2109 } else if (HasAttribute(line, kMediaTypeAudio)) { 2110 content.reset(ParseContentDescription<AudioContentDescription>( 2111 message, cricket::MEDIA_TYPE_AUDIO, mline_index, protocol, 2112 codec_preference, pos, &content_name, 2113 &transport, candidates, error)); 2114 MaybeCreateStaticPayloadAudioCodecs( 2115 codec_preference, 2116 static_cast<AudioContentDescription*>(content.get())); 2117 } else if (HasAttribute(line, kMediaTypeData)) { 2118 DataContentDescription* desc = 2119 ParseContentDescription<DataContentDescription>( 2120 message, cricket::MEDIA_TYPE_DATA, mline_index, protocol, 2121 codec_preference, pos, &content_name, 2122 &transport, candidates, error); 2123 2124 if (protocol == cricket::kMediaProtocolDtlsSctp) { 2125 // Add the SCTP Port number as a pseudo-codec "port" parameter 2126 cricket::DataCodec codec_port( 2127 cricket::kGoogleSctpDataCodecId, cricket::kGoogleSctpDataCodecName, 2128 0); 2129 codec_port.SetParam(cricket::kCodecParamPort, fields[3]); 2130 LOG(INFO) << "ParseMediaDescription: Got SCTP Port Number " 2131 << fields[3]; 2132 desc->AddCodec(codec_port); 2133 } 2134 2135 content.reset(desc); 2136 2137 // We should always use the default bandwidth for RTP-based data 2138 // channels. Don't allow SDP to set the bandwidth, because that 2139 // would give JS the opportunity to "break the Internet". 2140 // TODO(pthatcher): But we need to temporarily allow the SDP to control 2141 // this for backwards-compatibility. Once we don't need that any 2142 // more, remove this. 2143 bool support_dc_sdp_bandwidth_temporarily = true; 2144 if (content.get() && !support_dc_sdp_bandwidth_temporarily) { 2145 content->set_bandwidth(cricket::kAutoBandwidth); 2146 } 2147 } else { 2148 LOG(LS_WARNING) << "Unsupported media type: " << line; 2149 continue; 2150 } 2151 if (!content.get()) { 2152 // ParseContentDescription returns NULL if failed. 2153 return false; 2154 } 2155 2156 if (!is_sctp) { 2157 // Make sure to set the media direction correctly. If the direction is not 2158 // MD_RECVONLY or Inactive and no streams are parsed, 2159 // a default MediaStream will be created to prepare for receiving media. 2160 if (supports_msid && content->streams().empty() && 2161 content->direction() == cricket::MD_SENDRECV) { 2162 content->set_direction(cricket::MD_RECVONLY); 2163 } 2164 2165 // Set the extmap. 2166 if (!session_extmaps.empty() && 2167 !content->rtp_header_extensions().empty()) { 2168 return ParseFailed("", 2169 "The a=extmap MUST be either all session level or " 2170 "all media level.", 2171 error); 2172 } 2173 for (size_t i = 0; i < session_extmaps.size(); ++i) { 2174 content->AddRtpHeaderExtension(session_extmaps[i]); 2175 } 2176 } 2177 content->set_protocol(protocol); 2178 desc->AddContent(content_name, 2179 is_sctp ? cricket::NS_JINGLE_DRAFT_SCTP : 2180 cricket::NS_JINGLE_RTP, 2181 rejected, 2182 content.release()); 2183 // Create TransportInfo with the media level "ice-pwd" and "ice-ufrag". 2184 TransportInfo transport_info(content_name, transport); 2185 2186 if (!desc->AddTransportInfo(transport_info)) { 2187 std::ostringstream description; 2188 description << "Failed to AddTransportInfo with content name: " 2189 << content_name; 2190 return ParseFailed("", description.str(), error); 2191 } 2192 } 2193 2194 size_t end_of_message = message.size(); 2195 if (mline_index == -1 && *pos != end_of_message) { 2196 ParseFailed(message, *pos, "Expects m line.", error); 2197 return false; 2198 } 2199 return true; 2200 } 2201 2202 bool VerifyCodec(const cricket::Codec& codec) { 2203 // Codec has not been populated correctly unless the name has been set. This 2204 // can happen if an SDP has an fmtp or rtcp-fb with a payload type but doesn't 2205 // have a corresponding "rtpmap" line. 2206 cricket::Codec default_codec; 2207 return default_codec.name != codec.name; 2208 } 2209 2210 bool VerifyAudioCodecs(const AudioContentDescription* audio_desc) { 2211 const std::vector<cricket::AudioCodec>& codecs = audio_desc->codecs(); 2212 for (std::vector<cricket::AudioCodec>::const_iterator iter = codecs.begin(); 2213 iter != codecs.end(); ++iter) { 2214 if (!VerifyCodec(*iter)) { 2215 return false; 2216 } 2217 } 2218 return true; 2219 } 2220 2221 bool VerifyVideoCodecs(const VideoContentDescription* video_desc) { 2222 const std::vector<cricket::VideoCodec>& codecs = video_desc->codecs(); 2223 for (std::vector<cricket::VideoCodec>::const_iterator iter = codecs.begin(); 2224 iter != codecs.end(); ++iter) { 2225 if (!VerifyCodec(*iter)) { 2226 return false; 2227 } 2228 } 2229 return true; 2230 } 2231 2232 void AddParameters(const cricket::CodecParameterMap& parameters, 2233 cricket::Codec* codec) { 2234 for (cricket::CodecParameterMap::const_iterator iter = 2235 parameters.begin(); iter != parameters.end(); ++iter) { 2236 codec->SetParam(iter->first, iter->second); 2237 } 2238 } 2239 2240 void AddFeedbackParameter(const cricket::FeedbackParam& feedback_param, 2241 cricket::Codec* codec) { 2242 codec->AddFeedbackParam(feedback_param); 2243 } 2244 2245 void AddFeedbackParameters(const cricket::FeedbackParams& feedback_params, 2246 cricket::Codec* codec) { 2247 for (std::vector<cricket::FeedbackParam>::const_iterator iter = 2248 feedback_params.params().begin(); 2249 iter != feedback_params.params().end(); ++iter) { 2250 codec->AddFeedbackParam(*iter); 2251 } 2252 } 2253 2254 // Gets the current codec setting associated with |payload_type|. If there 2255 // is no AudioCodec associated with that payload type it returns an empty codec 2256 // with that payload type. 2257 template <class T> 2258 T GetCodec(const std::vector<T>& codecs, int payload_type) { 2259 for (typename std::vector<T>::const_iterator codec = codecs.begin(); 2260 codec != codecs.end(); ++codec) { 2261 if (codec->id == payload_type) { 2262 return *codec; 2263 } 2264 } 2265 T ret_val = T(); 2266 ret_val.id = payload_type; 2267 return ret_val; 2268 } 2269 2270 // Updates or creates a new codec entry in the audio description. 2271 template <class T, class U> 2272 void AddOrReplaceCodec(MediaContentDescription* content_desc, const U& codec) { 2273 T* desc = static_cast<T*>(content_desc); 2274 std::vector<U> codecs = desc->codecs(); 2275 bool found = false; 2276 2277 typename std::vector<U>::iterator iter; 2278 for (iter = codecs.begin(); iter != codecs.end(); ++iter) { 2279 if (iter->id == codec.id) { 2280 *iter = codec; 2281 found = true; 2282 break; 2283 } 2284 } 2285 if (!found) { 2286 desc->AddCodec(codec); 2287 return; 2288 } 2289 desc->set_codecs(codecs); 2290 } 2291 2292 // Adds or updates existing codec corresponding to |payload_type| according 2293 // to |parameters|. 2294 template <class T, class U> 2295 void UpdateCodec(MediaContentDescription* content_desc, int payload_type, 2296 const cricket::CodecParameterMap& parameters) { 2297 // Codec might already have been populated (from rtpmap). 2298 U new_codec = GetCodec(static_cast<T*>(content_desc)->codecs(), payload_type); 2299 AddParameters(parameters, &new_codec); 2300 AddOrReplaceCodec<T, U>(content_desc, new_codec); 2301 } 2302 2303 // Adds or updates existing codec corresponding to |payload_type| according 2304 // to |feedback_param|. 2305 template <class T, class U> 2306 void UpdateCodec(MediaContentDescription* content_desc, int payload_type, 2307 const cricket::FeedbackParam& feedback_param) { 2308 // Codec might already have been populated (from rtpmap). 2309 U new_codec = GetCodec(static_cast<T*>(content_desc)->codecs(), payload_type); 2310 AddFeedbackParameter(feedback_param, &new_codec); 2311 AddOrReplaceCodec<T, U>(content_desc, new_codec); 2312 } 2313 2314 bool PopWildcardCodec(std::vector<cricket::VideoCodec>* codecs, 2315 cricket::VideoCodec* wildcard_codec) { 2316 for (std::vector<cricket::VideoCodec>::iterator iter = codecs->begin(); 2317 iter != codecs->end(); ++iter) { 2318 if (iter->id == kWildcardPayloadType) { 2319 *wildcard_codec = *iter; 2320 codecs->erase(iter); 2321 return true; 2322 } 2323 } 2324 return false; 2325 } 2326 2327 void UpdateFromWildcardVideoCodecs(VideoContentDescription* video_desc) { 2328 std::vector<cricket::VideoCodec> codecs = video_desc->codecs(); 2329 cricket::VideoCodec wildcard_codec; 2330 if (!PopWildcardCodec(&codecs, &wildcard_codec)) { 2331 return; 2332 } 2333 for (std::vector<cricket::VideoCodec>::iterator iter = codecs.begin(); 2334 iter != codecs.end(); ++iter) { 2335 cricket::VideoCodec& codec = *iter; 2336 AddFeedbackParameters(wildcard_codec.feedback_params, &codec); 2337 } 2338 video_desc->set_codecs(codecs); 2339 } 2340 2341 void AddAudioAttribute(const std::string& name, const std::string& value, 2342 AudioContentDescription* audio_desc) { 2343 if (value.empty()) { 2344 return; 2345 } 2346 std::vector<cricket::AudioCodec> codecs = audio_desc->codecs(); 2347 for (std::vector<cricket::AudioCodec>::iterator iter = codecs.begin(); 2348 iter != codecs.end(); ++iter) { 2349 iter->params[name] = value; 2350 } 2351 audio_desc->set_codecs(codecs); 2352 } 2353 2354 bool ParseContent(const std::string& message, 2355 const MediaType media_type, 2356 int mline_index, 2357 const std::string& protocol, 2358 const std::vector<int>& codec_preference, 2359 size_t* pos, 2360 std::string* content_name, 2361 MediaContentDescription* media_desc, 2362 TransportDescription* transport, 2363 std::vector<JsepIceCandidate*>* candidates, 2364 SdpParseError* error) { 2365 ASSERT(media_desc != NULL); 2366 ASSERT(content_name != NULL); 2367 ASSERT(transport != NULL); 2368 2369 // The media level "ice-ufrag" and "ice-pwd". 2370 // The candidates before update the media level "ice-pwd" and "ice-ufrag". 2371 Candidates candidates_orig; 2372 std::string line; 2373 std::string mline_id; 2374 // Tracks created out of the ssrc attributes. 2375 StreamParamsVec tracks; 2376 SsrcInfoVec ssrc_infos; 2377 SsrcGroupVec ssrc_groups; 2378 std::string maxptime_as_string; 2379 std::string ptime_as_string; 2380 2381 bool is_rtp = 2382 protocol.empty() || 2383 talk_base::starts_with(protocol.data(), 2384 cricket::kMediaProtocolRtpPrefix); 2385 2386 // Loop until the next m line 2387 while (!IsLineType(message, kLineTypeMedia, *pos)) { 2388 if (!GetLine(message, pos, &line)) { 2389 if (*pos >= message.size()) { 2390 break; // Done parsing 2391 } else { 2392 return ParseFailed(message, *pos, "Invalid SDP line.", error); 2393 } 2394 } 2395 2396 if (IsLineType(line, kLineTypeSessionBandwidth)) { 2397 std::string bandwidth; 2398 if (HasAttribute(line, kApplicationSpecificMaximum)) { 2399 if (!GetValue(line, kApplicationSpecificMaximum, &bandwidth, error)) { 2400 return false; 2401 } else { 2402 media_desc->set_bandwidth( 2403 talk_base::FromString<int>(bandwidth) * 1000); 2404 } 2405 } 2406 continue; 2407 } 2408 2409 // RFC 4566 2410 // b=* (zero or more bandwidth information lines) 2411 if (IsLineType(line, kLineTypeSessionBandwidth)) { 2412 std::string bandwidth; 2413 if (HasAttribute(line, kApplicationSpecificMaximum)) { 2414 if (!GetValue(line, kApplicationSpecificMaximum, &bandwidth, error)) { 2415 return false; 2416 } else { 2417 media_desc->set_bandwidth( 2418 talk_base::FromString<int>(bandwidth) * 1000); 2419 } 2420 } 2421 continue; 2422 } 2423 2424 if (!IsLineType(line, kLineTypeAttributes)) { 2425 // TODO: Handle other lines if needed. 2426 LOG(LS_INFO) << "Ignored line: " << line; 2427 continue; 2428 } 2429 2430 // Handle attributes common to SCTP and RTP. 2431 if (HasAttribute(line, kAttributeMid)) { 2432 // RFC 3388 2433 // mid-attribute = "a=mid:" identification-tag 2434 // identification-tag = token 2435 // Use the mid identification-tag as the content name. 2436 if (!GetValue(line, kAttributeMid, &mline_id, error)) { 2437 return false; 2438 } 2439 *content_name = mline_id; 2440 } else if (HasAttribute(line, kAttributeCandidate)) { 2441 Candidate candidate; 2442 if (!ParseCandidate(line, &candidate, error, false)) { 2443 return false; 2444 } 2445 candidates_orig.push_back(candidate); 2446 } else if (HasAttribute(line, kAttributeIceUfrag)) { 2447 if (!GetValue(line, kAttributeIceUfrag, &transport->ice_ufrag, error)) { 2448 return false; 2449 } 2450 } else if (HasAttribute(line, kAttributeIcePwd)) { 2451 if (!GetValue(line, kAttributeIcePwd, &transport->ice_pwd, error)) { 2452 return false; 2453 } 2454 } else if (HasAttribute(line, kAttributeIceOption)) { 2455 if (!ParseIceOptions(line, &transport->transport_options, error)) { 2456 return false; 2457 } 2458 } else if (HasAttribute(line, kAttributeFmtp)) { 2459 if (!ParseFmtpAttributes(line, media_type, media_desc, error)) { 2460 return false; 2461 } 2462 } else if (HasAttribute(line, kAttributeFingerprint)) { 2463 talk_base::SSLFingerprint* fingerprint = NULL; 2464 2465 if (!ParseFingerprintAttribute(line, &fingerprint, error)) { 2466 return false; 2467 } 2468 transport->identity_fingerprint.reset(fingerprint); 2469 } else if (HasAttribute(line, kAttributeSetup)) { 2470 if (!ParseDtlsSetup(line, &(transport->connection_role), error)) { 2471 return false; 2472 } 2473 } else if (is_rtp) { 2474 // 2475 // RTP specific attrubtes 2476 // 2477 if (HasAttribute(line, kAttributeRtcpMux)) { 2478 media_desc->set_rtcp_mux(true); 2479 } else if (HasAttribute(line, kAttributeSsrcGroup)) { 2480 if (!ParseSsrcGroupAttribute(line, &ssrc_groups, error)) { 2481 return false; 2482 } 2483 } else if (HasAttribute(line, kAttributeSsrc)) { 2484 if (!ParseSsrcAttribute(line, &ssrc_infos, error)) { 2485 return false; 2486 } 2487 } else if (HasAttribute(line, kAttributeCrypto)) { 2488 if (!ParseCryptoAttribute(line, media_desc, error)) { 2489 return false; 2490 } 2491 } else if (HasAttribute(line, kAttributeRtpmap)) { 2492 if (!ParseRtpmapAttribute(line, media_type, codec_preference, 2493 media_desc, error)) { 2494 return false; 2495 } 2496 } else if (HasAttribute(line, kCodecParamMaxPTime)) { 2497 if (!GetValue(line, kCodecParamMaxPTime, &maxptime_as_string, error)) { 2498 return false; 2499 } 2500 } else if (HasAttribute(line, kAttributeRtcpFb)) { 2501 if (!ParseRtcpFbAttribute(line, media_type, media_desc, error)) { 2502 return false; 2503 } 2504 } else if (HasAttribute(line, kCodecParamPTime)) { 2505 if (!GetValue(line, kCodecParamPTime, &ptime_as_string, error)) { 2506 return false; 2507 } 2508 } else if (HasAttribute(line, kAttributeSendOnly)) { 2509 media_desc->set_direction(cricket::MD_SENDONLY); 2510 } else if (HasAttribute(line, kAttributeRecvOnly)) { 2511 media_desc->set_direction(cricket::MD_RECVONLY); 2512 } else if (HasAttribute(line, kAttributeInactive)) { 2513 media_desc->set_direction(cricket::MD_INACTIVE); 2514 } else if (HasAttribute(line, kAttributeSendRecv)) { 2515 media_desc->set_direction(cricket::MD_SENDRECV); 2516 } else if (HasAttribute(line, kAttributeExtmap)) { 2517 RtpHeaderExtension extmap; 2518 if (!ParseExtmap(line, &extmap, error)) { 2519 return false; 2520 } 2521 media_desc->AddRtpHeaderExtension(extmap); 2522 } else if (HasAttribute(line, kAttributeXGoogleFlag)) { 2523 // Experimental attribute. Conference mode activates more aggressive 2524 // AEC and NS settings. 2525 // TODO: expose API to set these directly. 2526 std::string flag_value; 2527 if (!GetValue(line, kAttributeXGoogleFlag, &flag_value, error)) { 2528 return false; 2529 } 2530 if (flag_value.compare(kValueConference) == 0) 2531 media_desc->set_conference_mode(true); 2532 } else if (HasAttribute(line, kAttributeXGoogleBufferLatency)) { 2533 // Experimental attribute. 2534 // TODO: expose API to set this directly. 2535 std::string flag_value; 2536 if (!GetValue(line, kAttributeXGoogleBufferLatency, &flag_value, 2537 error)) { 2538 return false; 2539 } 2540 int buffer_latency = 0; 2541 if (!talk_base::FromString(flag_value, &buffer_latency) || 2542 buffer_latency < 0) { 2543 return ParseFailed(message, "Invalid buffer latency.", error); 2544 } 2545 media_desc->set_buffered_mode_latency(buffer_latency); 2546 } 2547 } else { 2548 // Only parse lines that we are interested of. 2549 LOG(LS_INFO) << "Ignored line: " << line; 2550 continue; 2551 } 2552 } 2553 2554 // Create tracks from the |ssrc_infos|. 2555 CreateTracksFromSsrcInfos(ssrc_infos, &tracks); 2556 2557 // Add the ssrc group to the track. 2558 for (SsrcGroupVec::iterator ssrc_group = ssrc_groups.begin(); 2559 ssrc_group != ssrc_groups.end(); ++ssrc_group) { 2560 if (ssrc_group->ssrcs.empty()) { 2561 continue; 2562 } 2563 uint32 ssrc = ssrc_group->ssrcs.front(); 2564 for (StreamParamsVec::iterator track = tracks.begin(); 2565 track != tracks.end(); ++track) { 2566 if (track->has_ssrc(ssrc)) { 2567 track->ssrc_groups.push_back(*ssrc_group); 2568 } 2569 } 2570 } 2571 2572 // Add the new tracks to the |media_desc|. 2573 for (StreamParamsVec::iterator track = tracks.begin(); 2574 track != tracks.end(); ++track) { 2575 media_desc->AddStream(*track); 2576 } 2577 2578 if (media_type == cricket::MEDIA_TYPE_AUDIO) { 2579 AudioContentDescription* audio_desc = 2580 static_cast<AudioContentDescription*>(media_desc); 2581 // Verify audio codec ensures that no audio codec has been populated with 2582 // only fmtp. 2583 if (!VerifyAudioCodecs(audio_desc)) { 2584 return ParseFailed("Failed to parse audio codecs correctly.", error); 2585 } 2586 AddAudioAttribute(kCodecParamMaxPTime, maxptime_as_string, audio_desc); 2587 AddAudioAttribute(kCodecParamPTime, ptime_as_string, audio_desc); 2588 } 2589 2590 if (media_type == cricket::MEDIA_TYPE_VIDEO) { 2591 VideoContentDescription* video_desc = 2592 static_cast<VideoContentDescription*>(media_desc); 2593 UpdateFromWildcardVideoCodecs(video_desc); 2594 // Verify video codec ensures that no video codec has been populated with 2595 // only rtcp-fb. 2596 if (!VerifyVideoCodecs(video_desc)) { 2597 return ParseFailed("Failed to parse video codecs correctly.", error); 2598 } 2599 } 2600 2601 // RFC 5245 2602 // Update the candidates with the media level "ice-pwd" and "ice-ufrag". 2603 for (Candidates::iterator it = candidates_orig.begin(); 2604 it != candidates_orig.end(); ++it) { 2605 ASSERT((*it).username().empty()); 2606 (*it).set_username(transport->ice_ufrag); 2607 ASSERT((*it).password().empty()); 2608 (*it).set_password(transport->ice_pwd); 2609 candidates->push_back( 2610 new JsepIceCandidate(mline_id, mline_index, *it)); 2611 } 2612 return true; 2613 } 2614 2615 bool ParseSsrcAttribute(const std::string& line, SsrcInfoVec* ssrc_infos, 2616 SdpParseError* error) { 2617 ASSERT(ssrc_infos != NULL); 2618 // RFC 5576 2619 // a=ssrc:<ssrc-id> <attribute> 2620 // a=ssrc:<ssrc-id> <attribute>:<value> 2621 std::string field1, field2; 2622 if (!SplitByDelimiter(line.substr(kLinePrefixLength), 2623 kSdpDelimiterSpace, 2624 &field1, 2625 &field2)) { 2626 const size_t expected_fields = 2; 2627 return ParseFailedExpectFieldNum(line, expected_fields, error); 2628 } 2629 2630 // ssrc:<ssrc-id> 2631 std::string ssrc_id_s; 2632 if (!GetValue(field1, kAttributeSsrc, &ssrc_id_s, error)) { 2633 return false; 2634 } 2635 uint32 ssrc_id = talk_base::FromString<uint32>(ssrc_id_s); 2636 2637 std::string attribute; 2638 std::string value; 2639 if (!SplitByDelimiter(field2, kSdpDelimiterColon, 2640 &attribute, &value)) { 2641 std::ostringstream description; 2642 description << "Failed to get the ssrc attribute value from " << field2 2643 << ". Expected format <attribute>:<value>."; 2644 return ParseFailed(line, description.str(), error); 2645 } 2646 2647 // Check if there's already an item for this |ssrc_id|. Create a new one if 2648 // there isn't. 2649 SsrcInfoVec::iterator ssrc_info = ssrc_infos->begin(); 2650 for (; ssrc_info != ssrc_infos->end(); ++ssrc_info) { 2651 if (ssrc_info->ssrc_id == ssrc_id) { 2652 break; 2653 } 2654 } 2655 if (ssrc_info == ssrc_infos->end()) { 2656 SsrcInfo info; 2657 info.ssrc_id = ssrc_id; 2658 ssrc_infos->push_back(info); 2659 ssrc_info = ssrc_infos->end() - 1; 2660 } 2661 2662 // Store the info to the |ssrc_info|. 2663 if (attribute == kSsrcAttributeCname) { 2664 // RFC 5576 2665 // cname:<value> 2666 ssrc_info->cname = value; 2667 } else if (attribute == kSsrcAttributeMsid) { 2668 // draft-alvestrand-mmusic-msid-00 2669 // "msid:" identifier [ " " appdata ] 2670 std::vector<std::string> fields; 2671 talk_base::split(value, kSdpDelimiterSpace, &fields); 2672 if (fields.size() < 1 || fields.size() > 2) { 2673 return ParseFailed(line, 2674 "Expected format \"msid:<identifier>[ <appdata>]\".", 2675 error); 2676 } 2677 ssrc_info->msid_identifier = fields[0]; 2678 if (fields.size() == 2) { 2679 ssrc_info->msid_appdata = fields[1]; 2680 } 2681 } else if (attribute == kSsrcAttributeMslabel) { 2682 // draft-alvestrand-rtcweb-mid-01 2683 // mslabel:<value> 2684 ssrc_info->mslabel = value; 2685 } else if (attribute == kSSrcAttributeLabel) { 2686 // The label isn't defined. 2687 // label:<value> 2688 ssrc_info->label = value; 2689 } 2690 return true; 2691 } 2692 2693 bool ParseSsrcGroupAttribute(const std::string& line, 2694 SsrcGroupVec* ssrc_groups, 2695 SdpParseError* error) { 2696 ASSERT(ssrc_groups != NULL); 2697 // RFC 5576 2698 // a=ssrc-group:<semantics> <ssrc-id> ... 2699 std::vector<std::string> fields; 2700 talk_base::split(line.substr(kLinePrefixLength), 2701 kSdpDelimiterSpace, &fields); 2702 const size_t expected_min_fields = 2; 2703 if (fields.size() < expected_min_fields) { 2704 return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); 2705 } 2706 std::string semantics; 2707 if (!GetValue(fields[0], kAttributeSsrcGroup, &semantics, error)) { 2708 return false; 2709 } 2710 std::vector<uint32> ssrcs; 2711 for (size_t i = 1; i < fields.size(); ++i) { 2712 uint32 ssrc = talk_base::FromString<uint32>(fields[i]); 2713 ssrcs.push_back(ssrc); 2714 } 2715 ssrc_groups->push_back(SsrcGroup(semantics, ssrcs)); 2716 return true; 2717 } 2718 2719 bool ParseCryptoAttribute(const std::string& line, 2720 MediaContentDescription* media_desc, 2721 SdpParseError* error) { 2722 std::vector<std::string> fields; 2723 talk_base::split(line.substr(kLinePrefixLength), 2724 kSdpDelimiterSpace, &fields); 2725 // RFC 4568 2726 // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] 2727 const size_t expected_min_fields = 3; 2728 if (fields.size() < expected_min_fields) { 2729 return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); 2730 } 2731 std::string tag_value; 2732 if (!GetValue(fields[0], kAttributeCrypto, &tag_value, error)) { 2733 return false; 2734 } 2735 int tag = talk_base::FromString<int>(tag_value); 2736 const std::string crypto_suite = fields[1]; 2737 const std::string key_params = fields[2]; 2738 std::string session_params; 2739 if (fields.size() > 3) { 2740 session_params = fields[3]; 2741 } 2742 media_desc->AddCrypto(CryptoParams(tag, crypto_suite, key_params, 2743 session_params)); 2744 return true; 2745 } 2746 2747 // Updates or creates a new codec entry in the audio description with according 2748 // to |name|, |clockrate|, |bitrate|, |channels| and |preference|. 2749 void UpdateCodec(int payload_type, const std::string& name, int clockrate, 2750 int bitrate, int channels, int preference, 2751 AudioContentDescription* audio_desc) { 2752 // Codec may already be populated with (only) optional parameters 2753 // (from an fmtp). 2754 cricket::AudioCodec codec = GetCodec(audio_desc->codecs(), payload_type); 2755 codec.name = name; 2756 codec.clockrate = clockrate; 2757 codec.bitrate = bitrate; 2758 codec.channels = channels; 2759 codec.preference = preference; 2760 AddOrReplaceCodec<AudioContentDescription, cricket::AudioCodec>(audio_desc, 2761 codec); 2762 } 2763 2764 // Updates or creates a new codec entry in the video description according to 2765 // |name|, |width|, |height|, |framerate| and |preference|. 2766 void UpdateCodec(int payload_type, const std::string& name, int width, 2767 int height, int framerate, int preference, 2768 VideoContentDescription* video_desc) { 2769 // Codec may already be populated with (only) optional parameters 2770 // (from an fmtp). 2771 cricket::VideoCodec codec = GetCodec(video_desc->codecs(), payload_type); 2772 codec.name = name; 2773 codec.width = width; 2774 codec.height = height; 2775 codec.framerate = framerate; 2776 codec.preference = preference; 2777 AddOrReplaceCodec<VideoContentDescription, cricket::VideoCodec>(video_desc, 2778 codec); 2779 } 2780 2781 bool ParseRtpmapAttribute(const std::string& line, 2782 const MediaType media_type, 2783 const std::vector<int>& codec_preference, 2784 MediaContentDescription* media_desc, 2785 SdpParseError* error) { 2786 std::vector<std::string> fields; 2787 talk_base::split(line.substr(kLinePrefixLength), 2788 kSdpDelimiterSpace, &fields); 2789 // RFC 4566 2790 // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encodingparameters>] 2791 const size_t expected_min_fields = 2; 2792 if (fields.size() < expected_min_fields) { 2793 return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); 2794 } 2795 std::string payload_type_value; 2796 if (!GetValue(fields[0], kAttributeRtpmap, &payload_type_value, error)) { 2797 return false; 2798 } 2799 const int payload_type = talk_base::FromString<int>(payload_type_value); 2800 2801 // Set the preference order depending on the order of the pl type in the 2802 // <fmt> of the m-line. 2803 const int preference = codec_preference.end() - 2804 std::find(codec_preference.begin(), codec_preference.end(), 2805 payload_type); 2806 if (preference == 0) { 2807 LOG(LS_WARNING) << "Ignore rtpmap line that did not appear in the " 2808 << "<fmt> of the m-line: " << line; 2809 return true; 2810 } 2811 const std::string encoder = fields[1]; 2812 std::vector<std::string> codec_params; 2813 talk_base::split(encoder, '/', &codec_params); 2814 // <encoding name>/<clock rate>[/<encodingparameters>] 2815 // 2 mandatory fields 2816 if (codec_params.size() < 2 || codec_params.size() > 3) { 2817 return ParseFailed(line, 2818 "Expected format \"<encoding name>/<clock rate>" 2819 "[/<encodingparameters>]\".", 2820 error); 2821 } 2822 const std::string encoding_name = codec_params[0]; 2823 const int clock_rate = talk_base::FromString<int>(codec_params[1]); 2824 if (media_type == cricket::MEDIA_TYPE_VIDEO) { 2825 VideoContentDescription* video_desc = 2826 static_cast<VideoContentDescription*>(media_desc); 2827 // TODO: We will send resolution in SDP. For now use 2828 // JsepSessionDescription::kMaxVideoCodecWidth and kMaxVideoCodecHeight. 2829 UpdateCodec(payload_type, encoding_name, 2830 JsepSessionDescription::kMaxVideoCodecWidth, 2831 JsepSessionDescription::kMaxVideoCodecHeight, 2832 JsepSessionDescription::kDefaultVideoCodecFramerate, 2833 preference, video_desc); 2834 } else if (media_type == cricket::MEDIA_TYPE_AUDIO) { 2835 // RFC 4566 2836 // For audio streams, <encoding parameters> indicates the number 2837 // of audio channels. This parameter is OPTIONAL and may be 2838 // omitted if the number of channels is one, provided that no 2839 // additional parameters are needed. 2840 int channels = 1; 2841 if (codec_params.size() == 3) { 2842 channels = talk_base::FromString<int>(codec_params[2]); 2843 } 2844 int bitrate = 0; 2845 // The default behavior for ISAC (bitrate == 0) in webrtcvoiceengine.cc 2846 // (specifically FindWebRtcCodec) is bandwidth-adaptive variable bitrate. 2847 // The bandwidth adaptation doesn't always work well, so this code 2848 // sets a fixed target bitrate instead. 2849 if (_stricmp(encoding_name.c_str(), kIsacCodecName) == 0) { 2850 if (clock_rate <= 16000) { 2851 bitrate = kIsacWbDefaultRate; 2852 } else { 2853 bitrate = kIsacSwbDefaultRate; 2854 } 2855 } 2856 AudioContentDescription* audio_desc = 2857 static_cast<AudioContentDescription*>(media_desc); 2858 UpdateCodec(payload_type, encoding_name, clock_rate, bitrate, channels, 2859 preference, audio_desc); 2860 } else if (media_type == cricket::MEDIA_TYPE_DATA) { 2861 DataContentDescription* data_desc = 2862 static_cast<DataContentDescription*>(media_desc); 2863 data_desc->AddCodec(cricket::DataCodec(payload_type, encoding_name, 2864 preference)); 2865 } 2866 return true; 2867 } 2868 2869 void PruneRight(const char delimiter, std::string* message) { 2870 size_t trailing = message->find(delimiter); 2871 if (trailing != std::string::npos) { 2872 *message = message->substr(0, trailing); 2873 } 2874 } 2875 2876 bool ParseFmtpParam(const std::string& line, std::string* parameter, 2877 std::string* value, SdpParseError* error) { 2878 if (!SplitByDelimiter(line, kSdpDelimiterEqual, parameter, value)) { 2879 ParseFailed(line, "Unable to parse fmtp parameter. \'=\' missing.", error); 2880 return false; 2881 } 2882 // a=fmtp:<payload_type> <param1>=<value1>; <param2>=<value2>; ... 2883 // When parsing the values the trailing ";" gets picked up. Remove them. 2884 PruneRight(kSdpDelimiterSemicolon, value); 2885 return true; 2886 } 2887 2888 bool ParseFmtpAttributes(const std::string& line, const MediaType media_type, 2889 MediaContentDescription* media_desc, 2890 SdpParseError* error) { 2891 if (media_type != cricket::MEDIA_TYPE_AUDIO && 2892 media_type != cricket::MEDIA_TYPE_VIDEO) { 2893 return true; 2894 } 2895 std::vector<std::string> fields; 2896 talk_base::split(line.substr(kLinePrefixLength), 2897 kSdpDelimiterSpace, &fields); 2898 2899 // RFC 5576 2900 // a=fmtp:<format> <format specific parameters> 2901 // At least two fields, whereas the second one is any of the optional 2902 // parameters. 2903 if (fields.size() < 2) { 2904 ParseFailedExpectMinFieldNum(line, 2, error); 2905 return false; 2906 } 2907 2908 std::string payload_type; 2909 if (!GetValue(fields[0], kAttributeFmtp, &payload_type, error)) { 2910 return false; 2911 } 2912 2913 cricket::CodecParameterMap codec_params; 2914 for (std::vector<std::string>::const_iterator iter = fields.begin() + 1; 2915 iter != fields.end(); ++iter) { 2916 std::string name; 2917 std::string value; 2918 if (iter->find(kSdpDelimiterEqual) == std::string::npos) { 2919 // Only fmtps with equals are currently supported. Other fmtp types 2920 // should be ignored. Unknown fmtps do not constitute an error. 2921 continue; 2922 } 2923 if (!ParseFmtpParam(*iter, &name, &value, error)) { 2924 return false; 2925 } 2926 codec_params[name] = value; 2927 } 2928 2929 int int_payload_type = talk_base::FromString<int>(payload_type); 2930 if (media_type == cricket::MEDIA_TYPE_AUDIO) { 2931 UpdateCodec<AudioContentDescription, cricket::AudioCodec>( 2932 media_desc, int_payload_type, codec_params); 2933 } else if (media_type == cricket::MEDIA_TYPE_VIDEO) { 2934 UpdateCodec<VideoContentDescription, cricket::VideoCodec>( 2935 media_desc, int_payload_type, codec_params); 2936 } 2937 return true; 2938 } 2939 2940 bool ParseRtcpFbAttribute(const std::string& line, const MediaType media_type, 2941 MediaContentDescription* media_desc, 2942 SdpParseError* error) { 2943 if (media_type != cricket::MEDIA_TYPE_AUDIO && 2944 media_type != cricket::MEDIA_TYPE_VIDEO) { 2945 return true; 2946 } 2947 std::vector<std::string> rtcp_fb_fields; 2948 talk_base::split(line.c_str(), kSdpDelimiterSpace, &rtcp_fb_fields); 2949 if (rtcp_fb_fields.size() < 2) { 2950 return ParseFailedGetValue(line, kAttributeRtcpFb, error); 2951 } 2952 std::string payload_type_string; 2953 if (!GetValue(rtcp_fb_fields[0], kAttributeRtcpFb, &payload_type_string, 2954 error)) { 2955 return false; 2956 } 2957 int payload_type = (payload_type_string == "*") ? 2958 kWildcardPayloadType : talk_base::FromString<int>(payload_type_string); 2959 std::string id = rtcp_fb_fields[1]; 2960 std::string param = ""; 2961 for (std::vector<std::string>::iterator iter = rtcp_fb_fields.begin() + 2; 2962 iter != rtcp_fb_fields.end(); ++iter) { 2963 param.append(*iter); 2964 } 2965 const cricket::FeedbackParam feedback_param(id, param); 2966 2967 if (media_type == cricket::MEDIA_TYPE_AUDIO) { 2968 UpdateCodec<AudioContentDescription, cricket::AudioCodec>(media_desc, 2969 payload_type, 2970 feedback_param); 2971 } else if (media_type == cricket::MEDIA_TYPE_VIDEO) { 2972 UpdateCodec<VideoContentDescription, cricket::VideoCodec>(media_desc, 2973 payload_type, 2974 feedback_param); 2975 } 2976 return true; 2977 } 2978 2979 } // namespace webrtc 2980