2 * Copyright 2011 The WebRTC project authors. All Rights Reserved.
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
11 #include "pc/webrtc_sdp.h"
23 #include <type_traits>
24 #include <unordered_map>
28 #include "absl/algorithm/container.h"
29 #include "absl/strings/ascii.h"
30 #include "absl/strings/match.h"
31 #include "api/candidate.h"
32 #include "api/crypto_params.h"
33 #include "api/jsep_ice_candidate.h"
34 #include "api/jsep_session_description.h"
35 #include "api/media_types.h"
37 #include "absl/strings/string_view.h"
38 #include "absl/types/optional.h"
39 #include "api/rtc_error.h"
40 #include "api/rtp_parameters.h"
41 #include "api/rtp_transceiver_direction.h"
42 #include "media/base/codec.h"
43 #include "media/base/media_constants.h"
44 #include "media/base/rid_description.h"
45 #include "media/base/rtp_utils.h"
46 #include "media/base/stream_params.h"
47 #include "media/sctp/sctp_transport_internal.h"
48 #include "p2p/base/candidate_pair_interface.h"
49 #include "p2p/base/ice_transport_internal.h"
50 #include "p2p/base/p2p_constants.h"
51 #include "p2p/base/port.h"
52 #include "p2p/base/port_interface.h"
53 #include "p2p/base/transport_description.h"
54 #include "p2p/base/transport_info.h"
55 #include "pc/media_protocol_names.h"
56 #include "pc/media_session.h"
57 #include "pc/sdp_serializer.h"
58 #include "pc/session_description.h"
59 #include "pc/simulcast_description.h"
60 #include "rtc_base/arraysize.h"
61 #include "rtc_base/checks.h"
62 #include "rtc_base/helpers.h"
63 #include "rtc_base/ip_address.h"
64 #include "rtc_base/logging.h"
65 #include "rtc_base/net_helper.h"
66 #include "rtc_base/network_constants.h"
67 #include "rtc_base/socket_address.h"
68 #include "rtc_base/ssl_fingerprint.h"
69 #include "rtc_base/string_encode.h"
70 #include "rtc_base/string_utils.h"
71 #include "rtc_base/strings/string_builder.h"
73 using cricket::AudioContentDescription
;
74 using cricket::Candidate
;
75 using cricket::Candidates
;
76 using cricket::ContentInfo
;
77 using cricket::CryptoParams
;
78 using cricket::ICE_CANDIDATE_COMPONENT_RTCP
;
79 using cricket::ICE_CANDIDATE_COMPONENT_RTP
;
80 using cricket::kApplicationSpecificBandwidth
;
81 using cricket::kCodecParamMaxPTime
;
82 using cricket::kCodecParamMinPTime
;
83 using cricket::kCodecParamPTime
;
84 using cricket::kTransportSpecificBandwidth
;
85 using cricket::MediaContentDescription
;
86 using cricket::MediaProtocolType
;
87 using cricket::MediaType
;
88 using cricket::RidDescription
;
89 using cricket::RtpHeaderExtensions
;
90 using cricket::SctpDataContentDescription
;
91 using cricket::SimulcastDescription
;
92 using cricket::SimulcastLayer
;
93 using cricket::SimulcastLayerList
;
94 using cricket::SsrcGroup
;
95 using cricket::StreamParams
;
96 using cricket::StreamParamsVec
;
97 using cricket::TransportDescription
;
98 using cricket::TransportInfo
;
99 using cricket::UnsupportedContentDescription
;
100 using cricket::VideoContentDescription
;
101 using rtc::SocketAddress
;
103 // TODO(deadbeef): Switch to using anonymous namespace rather than declaring
104 // everything "static".
109 // An SDP session description consists of a number of lines of text of
112 // where <type> MUST be exactly one case-significant character.
114 // Check if passed character is a "token-char" from RFC 4566.
115 // https://datatracker.ietf.org/doc/html/rfc4566#section-9
116 // token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39
117 // / %x41-5A / %x5E-7E
118 bool IsTokenChar(char ch
) {
119 return ch
== 0x21 || (ch
>= 0x23 && ch
<= 0x27) || ch
== 0x2a || ch
== 0x2b ||
120 ch
== 0x2d || ch
== 0x2e || (ch
>= 0x30 && ch
<= 0x39) ||
121 (ch
>= 0x41 && ch
<= 0x5a) || (ch
>= 0x5e && ch
<= 0x7e);
123 static const int kLinePrefixLength
= 2; // Length of <type>=
124 static const char kLineTypeVersion
= 'v';
125 static const char kLineTypeOrigin
= 'o';
126 static const char kLineTypeSessionName
= 's';
127 static const char kLineTypeSessionInfo
= 'i';
128 static const char kLineTypeSessionUri
= 'u';
129 static const char kLineTypeSessionEmail
= 'e';
130 static const char kLineTypeSessionPhone
= 'p';
131 static const char kLineTypeSessionBandwidth
= 'b';
132 static const char kLineTypeTiming
= 't';
133 static const char kLineTypeRepeatTimes
= 'r';
134 static const char kLineTypeTimeZone
= 'z';
135 static const char kLineTypeEncryptionKey
= 'k';
136 static const char kLineTypeMedia
= 'm';
137 static const char kLineTypeConnection
= 'c';
138 static const char kLineTypeAttributes
= 'a';
141 static const char kAttributeGroup
[] = "group";
142 static const char kAttributeMid
[] = "mid";
143 static const char kAttributeMsid
[] = "msid";
144 static const char kAttributeBundleOnly
[] = "bundle-only";
145 static const char kAttributeRtcpMux
[] = "rtcp-mux";
146 static const char kAttributeRtcpReducedSize
[] = "rtcp-rsize";
147 static const char kAttributeSsrc
[] = "ssrc";
148 static const char kSsrcAttributeCname
[] = "cname";
149 static const char kAttributeExtmapAllowMixed
[] = "extmap-allow-mixed";
150 static const char kAttributeExtmap
[] = "extmap";
151 // draft-alvestrand-mmusic-msid-01
152 // a=msid-semantic: WMS
153 // This is a legacy field supported only for Plan B semantics.
154 static const char kAttributeMsidSemantics
[] = "msid-semantic";
155 static const char kMediaStreamSemantic
[] = "WMS";
156 static const char kSsrcAttributeMsid
[] = "msid";
157 static const char kDefaultMsid
[] = "default";
158 static const char kNoStreamMsid
[] = "-";
159 static const char kAttributeSsrcGroup
[] = "ssrc-group";
160 static const char kAttributeCrypto
[] = "crypto";
161 static const char kAttributeCandidate
[] = "candidate";
162 static const char kAttributeCandidateTyp
[] = "typ";
163 static const char kAttributeCandidateRaddr
[] = "raddr";
164 static const char kAttributeCandidateRport
[] = "rport";
165 static const char kAttributeCandidateUfrag
[] = "ufrag";
166 static const char kAttributeCandidatePwd
[] = "pwd";
167 static const char kAttributeCandidateGeneration
[] = "generation";
168 static const char kAttributeCandidateNetworkId
[] = "network-id";
169 static const char kAttributeCandidateNetworkCost
[] = "network-cost";
170 static const char kAttributeFingerprint
[] = "fingerprint";
171 static const char kAttributeSetup
[] = "setup";
172 static const char kAttributeFmtp
[] = "fmtp";
173 static const char kAttributeRtpmap
[] = "rtpmap";
174 static const char kAttributeSctpmap
[] = "sctpmap";
175 static const char kAttributeRtcp
[] = "rtcp";
176 static const char kAttributeIceUfrag
[] = "ice-ufrag";
177 static const char kAttributeIcePwd
[] = "ice-pwd";
178 static const char kAttributeIceLite
[] = "ice-lite";
179 static const char kAttributeIceOption
[] = "ice-options";
180 static const char kAttributeSendOnly
[] = "sendonly";
181 static const char kAttributeRecvOnly
[] = "recvonly";
182 static const char kAttributeRtcpFb
[] = "rtcp-fb";
183 static const char kAttributeSendRecv
[] = "sendrecv";
184 static const char kAttributeInactive
[] = "inactive";
185 // draft-ietf-mmusic-sctp-sdp-26
186 // a=sctp-port, a=max-message-size
187 static const char kAttributeSctpPort
[] = "sctp-port";
188 static const char kAttributeMaxMessageSize
[] = "max-message-size";
189 static const int kDefaultSctpMaxMessageSize
= 65536;
190 // draft-ietf-mmusic-sdp-simulcast-13
192 static const char kAttributeSimulcast
[] = "simulcast";
193 // draft-ietf-mmusic-rid-15
195 static const char kAttributeRid
[] = "rid";
196 static const char kAttributePacketization
[] = "packetization";
198 // Experimental flags
199 static const char kAttributeXGoogleFlag
[] = "x-google-flag";
200 static const char kValueConference
[] = "conference";
202 static const char kAttributeRtcpRemoteEstimate
[] = "remote-net-estimate";
205 static const char kCandidateHost
[] = "host";
206 static const char kCandidateSrflx
[] = "srflx";
207 static const char kCandidatePrflx
[] = "prflx";
208 static const char kCandidateRelay
[] = "relay";
209 static const char kTcpCandidateType
[] = "tcptype";
211 // rtc::StringBuilder doesn't have a << overload for chars, while rtc::split and
212 // rtc::tokenize_first both take a char delimiter. To handle both cases these
213 // constants come in pairs of a chars and length-one strings.
214 static const char kSdpDelimiterEqual
[] = "=";
215 static const char kSdpDelimiterEqualChar
= '=';
216 static const char kSdpDelimiterSpace
[] = " ";
217 static const char kSdpDelimiterSpaceChar
= ' ';
218 static const char kSdpDelimiterColon
[] = ":";
219 static const char kSdpDelimiterColonChar
= ':';
220 static const char kSdpDelimiterSemicolon
[] = ";";
221 static const char kSdpDelimiterSemicolonChar
= ';';
222 static const char kSdpDelimiterSlashChar
= '/';
223 static const char kNewLineChar
= '\n';
224 static const char kReturnChar
= '\r';
225 static const char kLineBreak
[] = "\r\n";
227 // TODO(deadbeef): Generate the Session and Time description
228 // instead of hardcoding.
229 static const char kSessionVersion
[] = "v=0";
231 static const char kSessionOriginUsername
[] = "-";
232 static const char kSessionOriginSessionId
[] = "0";
233 static const char kSessionOriginSessionVersion
[] = "0";
234 static const char kSessionOriginNettype
[] = "IN";
235 static const char kSessionOriginAddrtype
[] = "IP4";
236 static const char kSessionOriginAddress
[] = "127.0.0.1";
237 static const char kSessionName
[] = "s=-";
238 static const char kTimeDescription
[] = "t=0 0";
239 static const char kAttrGroup
[] = "a=group:BUNDLE";
240 static const char kConnectionNettype
[] = "IN";
241 static const char kConnectionIpv4Addrtype
[] = "IP4";
242 static const char kConnectionIpv6Addrtype
[] = "IP6";
243 static const char kMediaTypeVideo
[] = "video";
244 static const char kMediaTypeAudio
[] = "audio";
245 static const char kMediaTypeData
[] = "application";
246 static const char kMediaPortRejected
[] = "0";
247 // draft-ietf-mmusic-trickle-ice-01
248 // When no candidates have been gathered, set the connection
249 // address to IP6 ::.
250 // TODO(perkj): FF can not parse IP6 ::. See http://crbug/430333
251 // Use IPV4 per default.
252 static const char kDummyAddress
[] = "0.0.0.0";
253 static const char kDummyPort
[] = "9";
255 static const char kDefaultSctpmapProtocol
[] = "webrtc-datachannel";
257 // RTP payload type is in the 0-127 range. Use -1 to indicate "all" payload
259 const int kWildcardPayloadType
= -1;
261 // Maximum number of channels allowed.
262 static const size_t kMaxNumberOfChannels
= 24;
267 std::string stream_id
;
268 std::string track_id
;
270 typedef std::vector
<SsrcInfo
> SsrcInfoVec
;
271 typedef std::vector
<SsrcGroup
> SsrcGroupVec
;
274 static void AddFmtpLine(const T
& codec
, std::string
* message
);
275 static void BuildMediaDescription(const ContentInfo
* content_info
,
276 const TransportInfo
* transport_info
,
277 const cricket::MediaType media_type
,
278 const std::vector
<Candidate
>& candidates
,
280 std::string
* message
);
281 static void BuildMediaLine(const cricket::MediaType media_type
,
282 const ContentInfo
* content_info
,
283 const MediaContentDescription
* media_desc
,
284 std::string
* message
);
285 static void BuildRtpContentAttributes(const MediaContentDescription
* media_desc
,
286 const cricket::MediaType media_type
,
288 std::string
* message
);
289 static void BuildRtpHeaderExtensions(const RtpHeaderExtensions
& extensions
,
290 std::string
* message
);
291 static void BuildRtpmap(const MediaContentDescription
* media_desc
,
292 const cricket::MediaType media_type
,
293 std::string
* message
);
294 static void BuildCandidate(const std::vector
<Candidate
>& candidates
,
296 std::string
* message
);
297 static void BuildIceUfragPwd(const TransportInfo
* transport_info
,
298 std::string
* message
);
299 static void BuildDtlsFingerprintSetup(const TransportInfo
* transport_info
,
300 std::string
* message
);
301 static void BuildIceOptions(const std::vector
<std::string
>& transport_options
,
302 std::string
* message
);
303 static bool ParseSessionDescription(absl::string_view message
,
305 std::string
* session_id
,
306 std::string
* session_version
,
307 TransportDescription
* session_td
,
308 RtpHeaderExtensions
* session_extmaps
,
309 rtc::SocketAddress
* connection_addr
,
310 cricket::SessionDescription
* desc
,
311 SdpParseError
* error
);
312 static bool ParseMediaDescription(
313 absl::string_view message
,
314 const TransportDescription
& session_td
,
315 const RtpHeaderExtensions
& session_extmaps
,
317 const rtc::SocketAddress
& session_connection_addr
,
318 cricket::SessionDescription
* desc
,
319 std::vector
<std::unique_ptr
<JsepIceCandidate
>>* candidates
,
320 SdpParseError
* error
);
321 static bool ParseContent(
322 absl::string_view message
,
323 const cricket::MediaType media_type
,
325 absl::string_view protocol
,
326 const std::vector
<int>& payload_types
,
328 std::string
* content_name
,
331 MediaContentDescription
* media_desc
,
332 TransportDescription
* transport
,
333 std::vector
<std::unique_ptr
<JsepIceCandidate
>>* candidates
,
334 SdpParseError
* error
);
335 static bool ParseGroupAttribute(absl::string_view line
,
336 cricket::SessionDescription
* desc
,
337 SdpParseError
* error
);
338 static bool ParseSsrcAttribute(absl::string_view line
,
339 SsrcInfoVec
* ssrc_infos
,
341 SdpParseError
* error
);
342 static bool ParseSsrcGroupAttribute(absl::string_view line
,
343 SsrcGroupVec
* ssrc_groups
,
344 SdpParseError
* error
);
345 static bool ParseCryptoAttribute(absl::string_view line
,
346 MediaContentDescription
* media_desc
,
347 SdpParseError
* error
);
348 static bool ParseRtpmapAttribute(absl::string_view line
,
349 const cricket::MediaType media_type
,
350 const std::vector
<int>& payload_types
,
351 MediaContentDescription
* media_desc
,
352 SdpParseError
* error
);
353 static bool ParseFmtpAttributes(absl::string_view line
,
354 const cricket::MediaType media_type
,
355 MediaContentDescription
* media_desc
,
356 SdpParseError
* error
);
357 static bool ParseFmtpParam(absl::string_view line
,
358 std::string
* parameter
,
360 SdpParseError
* error
);
361 static bool ParsePacketizationAttribute(absl::string_view line
,
362 const cricket::MediaType media_type
,
363 MediaContentDescription
* media_desc
,
364 SdpParseError
* error
);
365 static bool ParseRtcpFbAttribute(absl::string_view line
,
366 const cricket::MediaType media_type
,
367 MediaContentDescription
* media_desc
,
368 SdpParseError
* error
);
369 static bool ParseIceOptions(absl::string_view line
,
370 std::vector
<std::string
>* transport_options
,
371 SdpParseError
* error
);
372 static bool ParseExtmap(absl::string_view line
,
373 RtpExtension
* extmap
,
374 SdpParseError
* error
);
375 static bool ParseFingerprintAttribute(
376 absl::string_view line
,
377 std::unique_ptr
<rtc::SSLFingerprint
>* fingerprint
,
378 SdpParseError
* error
);
379 static bool ParseDtlsSetup(absl::string_view line
,
380 cricket::ConnectionRole
* role
,
381 SdpParseError
* error
);
382 static bool ParseMsidAttribute(absl::string_view line
,
383 std::vector
<std::string
>* stream_ids
,
384 std::string
* track_id
,
385 SdpParseError
* error
);
387 static void RemoveInvalidRidDescriptions(const std::vector
<int>& payload_types
,
388 std::vector
<RidDescription
>* rids
);
390 static SimulcastLayerList
RemoveRidsFromSimulcastLayerList(
391 const std::set
<std::string
>& to_remove
,
392 const SimulcastLayerList
& layers
);
394 static void RemoveInvalidRidsFromSimulcast(
395 const std::vector
<RidDescription
>& rids
,
396 SimulcastDescription
* simulcast
);
400 // Below ParseFailed*** functions output the line that caused the parsing
401 // failure and the detailed reason (`description`) of the failure to `error`.
402 // The functions always return false so that they can be used directly in the
403 // following way when error happens:
404 // "return ParseFailed***(...);"
406 // The line starting at `line_start` of `message` is the failing line.
407 // The reason for the failure should be provided in the `description`.
408 // An example of a description could be "unknown character".
409 static bool ParseFailed(absl::string_view message
,
411 std::string description
,
412 SdpParseError
* error
) {
413 // Get the first line of `message` from `line_start`.
414 absl::string_view first_line
;
415 size_t line_end
= message
.find(kNewLineChar
, line_start
);
416 if (line_end
!= std::string::npos
) {
417 if (line_end
> 0 && (message
.at(line_end
- 1) == kReturnChar
)) {
420 first_line
= message
.substr(line_start
, (line_end
- line_start
));
422 first_line
= message
.substr(line_start
);
425 RTC_LOG(LS_ERROR
) << "Failed to parse: \"" << first_line
426 << "\". Reason: " << description
;
428 // TODO(bugs.webrtc.org/13220): In C++17, we can use plain assignment, with
429 // a string_view on the right hand side.
430 error
->line
.assign(first_line
.data(), first_line
.size());
431 error
->description
= std::move(description
);
436 // `line` is the failing line. The reason for the failure should be
437 // provided in the `description`.
438 static bool ParseFailed(absl::string_view line
,
439 std::string description
,
440 SdpParseError
* error
) {
441 return ParseFailed(line
, 0, std::move(description
), error
);
444 // Parses failure where the failing SDP line isn't know or there are multiple
446 static bool ParseFailed(std::string description
, SdpParseError
* error
) {
447 return ParseFailed("", std::move(description
), error
);
450 // `line` is the failing line. The failure is due to the fact that `line`
451 // doesn't have `expected_fields` fields.
452 static bool ParseFailedExpectFieldNum(absl::string_view line
,
454 SdpParseError
* error
) {
455 rtc::StringBuilder description
;
456 description
<< "Expects " << expected_fields
<< " fields.";
457 return ParseFailed(line
, description
.Release(), error
);
460 // `line` is the failing line. The failure is due to the fact that `line` has
461 // less than `expected_min_fields` fields.
462 static bool ParseFailedExpectMinFieldNum(absl::string_view line
,
463 int expected_min_fields
,
464 SdpParseError
* error
) {
465 rtc::StringBuilder description
;
466 description
<< "Expects at least " << expected_min_fields
<< " fields.";
467 return ParseFailed(line
, description
.Release(), error
);
470 // `line` is the failing line. The failure is due to the fact that it failed to
471 // get the value of `attribute`.
472 static bool ParseFailedGetValue(absl::string_view line
,
473 absl::string_view attribute
,
474 SdpParseError
* error
) {
475 rtc::StringBuilder description
;
476 description
<< "Failed to get the value of attribute: " << attribute
;
477 return ParseFailed(line
, description
.Release(), error
);
480 // The line starting at `line_start` of `message` is the failing line. The
481 // failure is due to the line type (e.g. the "m" part of the "m-line")
482 // not matching what is expected. The expected line type should be
483 // provided as `line_type`.
484 static bool ParseFailedExpectLine(absl::string_view message
,
486 const char line_type
,
487 absl::string_view line_value
,
488 SdpParseError
* error
) {
489 rtc::StringBuilder description
;
490 description
<< "Expect line: " << std::string(1, line_type
) << "="
492 return ParseFailed(message
, line_start
, description
.Release(), error
);
495 static bool AddLine(absl::string_view line
, std::string
* message
) {
499 message
->append(line
.data(), line
.size());
500 message
->append(kLineBreak
);
504 // Trim return character, if any.
505 static absl::string_view
TrimReturnChar(absl::string_view line
) {
506 if (!line
.empty() && line
.back() == kReturnChar
) {
507 line
.remove_suffix(1);
512 // Gets line of `message` starting at `pos`, and checks overall SDP syntax. On
513 // success, advances `pos` to the next line.
514 static absl::optional
<absl::string_view
> GetLine(absl::string_view message
,
516 size_t line_end
= message
.find(kNewLineChar
, *pos
);
517 if (line_end
== absl::string_view::npos
) {
518 return absl::nullopt
;
520 absl::string_view line
=
521 TrimReturnChar(message
.substr(*pos
, line_end
- *pos
));
524 // An SDP session description consists of a number of lines of text of
527 // where <type> MUST be exactly one case-significant character and
528 // <value> is structured text whose format depends on <type>.
529 // Whitespace MUST NOT be used on either side of the "=" sign.
531 // However, an exception to the whitespace rule is made for "s=", since
532 // RFC4566 also says:
534 // If a session has no meaningful name, the value "s= " SHOULD be used
535 // (i.e., a single space as the session name).
536 if (line
.length() < 3 || !islower(static_cast<unsigned char>(line
[0])) ||
537 line
[1] != kSdpDelimiterEqualChar
||
538 (line
[0] != kLineTypeSessionName
&& line
[2] == kSdpDelimiterSpaceChar
)) {
539 return absl::nullopt
;
545 // Init `os` to "`type`=`value`".
546 static void InitLine(const char type
,
547 absl::string_view value
,
548 rtc::StringBuilder
* os
) {
550 *os
<< std::string(1, type
) << kSdpDelimiterEqual
<< value
;
553 // Init `os` to "a=`attribute`".
554 static void InitAttrLine(absl::string_view attribute
, rtc::StringBuilder
* os
) {
555 InitLine(kLineTypeAttributes
, attribute
, os
);
558 // Writes a SDP attribute line based on `attribute` and `value` to `message`.
559 static void AddAttributeLine(absl::string_view attribute
,
561 std::string
* message
) {
562 rtc::StringBuilder os
;
563 InitAttrLine(attribute
, &os
);
564 os
<< kSdpDelimiterColon
<< value
;
565 AddLine(os
.str(), message
);
568 static bool IsLineType(absl::string_view message
,
571 if (message
.size() < line_start
+ kLinePrefixLength
) {
574 return (message
[line_start
] == type
&&
575 message
[line_start
+ 1] == kSdpDelimiterEqualChar
);
578 static bool IsLineType(absl::string_view line
, const char type
) {
579 return IsLineType(line
, type
, 0);
582 static absl::optional
<absl::string_view
>
583 GetLineWithType(absl::string_view message
, size_t* pos
, const char type
) {
584 if (IsLineType(message
, type
, *pos
)) {
585 return GetLine(message
, pos
);
587 return absl::nullopt
;
590 static bool HasAttribute(absl::string_view line
, absl::string_view attribute
) {
591 if (line
.compare(kLinePrefixLength
, attribute
.size(), attribute
) == 0) {
592 // Make sure that the match is not only a partial match. If length of
593 // strings doesn't match, the next character of the line must be ':' or ' '.
594 // This function is also used for media descriptions (e.g., "m=audio 9..."),
595 // hence the need to also allow space in the end.
596 RTC_CHECK_LE(kLinePrefixLength
+ attribute
.size(), line
.size());
597 if ((kLinePrefixLength
+ attribute
.size()) == line
.size() ||
598 line
[kLinePrefixLength
+ attribute
.size()] == kSdpDelimiterColonChar
||
599 line
[kLinePrefixLength
+ attribute
.size()] == kSdpDelimiterSpaceChar
) {
606 static bool AddSsrcLine(uint32_t ssrc_id
,
607 absl::string_view attribute
,
608 absl::string_view value
,
609 std::string
* message
) {
611 // a=ssrc:<ssrc-id> <attribute>:<value>
612 rtc::StringBuilder os
;
613 InitAttrLine(kAttributeSsrc
, &os
);
614 os
<< kSdpDelimiterColon
<< ssrc_id
<< kSdpDelimiterSpace
<< attribute
615 << kSdpDelimiterColon
<< value
;
616 return AddLine(os
.str(), message
);
619 // Get value only from <attribute>:<value>.
620 static bool GetValue(absl::string_view message
,
621 absl::string_view attribute
,
623 SdpParseError
* error
) {
624 std::string leftpart
;
625 if (!rtc::tokenize_first(message
, kSdpDelimiterColonChar
, &leftpart
, value
)) {
626 return ParseFailedGetValue(message
, attribute
, error
);
628 // The left part should end with the expected attribute.
629 if (leftpart
.length() < attribute
.length() ||
630 absl::string_view(leftpart
).compare(
631 leftpart
.length() - attribute
.length(), attribute
.length(),
633 return ParseFailedGetValue(message
, attribute
, error
);
638 // Get a single [token] from <attribute>:<token>
639 static bool GetSingleTokenValue(absl::string_view message
,
640 absl::string_view attribute
,
642 SdpParseError
* error
) {
643 if (!GetValue(message
, attribute
, value
, error
)) {
646 if (!absl::c_all_of(absl::string_view(*value
), IsTokenChar
)) {
647 rtc::StringBuilder description
;
648 description
<< "Illegal character found in the value of " << attribute
;
649 return ParseFailed(message
, description
.Release(), error
);
654 static bool CaseInsensitiveFind(std::string str1
, std::string str2
) {
655 absl::c_transform(str1
, str1
.begin(), ::tolower
);
656 absl::c_transform(str2
, str2
.begin(), ::tolower
);
657 return str1
.find(str2
) != std::string::npos
;
661 static bool GetValueFromString(absl::string_view line
,
664 SdpParseError
* error
) {
665 if (!rtc::FromString(s
, t
)) {
666 rtc::StringBuilder description
;
667 description
<< "Invalid value: " << s
<< ".";
668 return ParseFailed(line
, description
.Release(), error
);
673 static bool GetPayloadTypeFromString(absl::string_view line
,
676 SdpParseError
* error
) {
677 return GetValueFromString(line
, s
, payload_type
, error
) &&
678 cricket::IsValidRtpPayloadType(*payload_type
);
681 // Creates a StreamParams track in the case when no SSRC lines are signaled.
682 // This is a track that does not contain SSRCs and only contains
683 // stream_ids/track_id if it's signaled with a=msid lines.
684 void CreateTrackWithNoSsrcs(const std::vector
<std::string
>& msid_stream_ids
,
685 absl::string_view msid_track_id
,
686 const std::vector
<RidDescription
>& rids
,
687 StreamParamsVec
* tracks
) {
689 if (msid_track_id
.empty() && rids
.empty()) {
690 // We only create an unsignaled track if a=msid lines were signaled.
691 RTC_LOG(LS_INFO
) << "MSID not signaled, skipping creation of StreamParams";
694 track
.set_stream_ids(msid_stream_ids
);
695 track
.id
= std::string(msid_track_id
);
696 track
.set_rids(rids
);
697 tracks
->push_back(track
);
700 // Creates the StreamParams tracks, for the case when SSRC lines are signaled.
701 // `msid_stream_ids` and `msid_track_id` represent the stream/track ID from the
702 // "a=msid" attribute, if it exists. They are empty if the attribute does not
703 // exist. We prioritize getting stream_ids/track_ids signaled in a=msid lines.
704 void CreateTracksFromSsrcInfos(const SsrcInfoVec
& ssrc_infos
,
705 const std::vector
<std::string
>& msid_stream_ids
,
706 absl::string_view msid_track_id
,
707 StreamParamsVec
* tracks
,
708 int msid_signaling
) {
710 for (const SsrcInfo
& ssrc_info
: ssrc_infos
) {
711 // According to https://tools.ietf.org/html/rfc5576#section-6.1, the CNAME
712 // attribute is mandatory, but we relax that restriction.
713 if (ssrc_info
.cname
.empty()) {
714 RTC_LOG(LS_WARNING
) << "CNAME attribute missing for SSRC "
715 << ssrc_info
.ssrc_id
;
717 std::vector
<std::string
> stream_ids
;
718 std::string track_id
;
719 if (msid_signaling
& cricket::kMsidSignalingMediaSection
) {
720 // This is the case with Unified Plan SDP msid signaling.
721 stream_ids
= msid_stream_ids
;
722 track_id
= std::string(msid_track_id
);
723 } else if (msid_signaling
& cricket::kMsidSignalingSsrcAttribute
) {
724 // This is the case with Plan B SDP msid signaling.
725 stream_ids
.push_back(ssrc_info
.stream_id
);
726 track_id
= ssrc_info
.track_id
;
728 // Since no media streams isn't supported with older SDP signaling, we
729 // use a default stream id.
730 stream_ids
.push_back(kDefaultMsid
);
733 auto track_it
= absl::c_find_if(
735 [track_id
](const StreamParams
& track
) { return track
.id
== track_id
; });
736 if (track_it
== tracks
->end()) {
737 // If we don't find an existing track, create a new one.
738 tracks
->push_back(StreamParams());
739 track_it
= tracks
->end() - 1;
741 StreamParams
& track
= *track_it
;
742 track
.add_ssrc(ssrc_info
.ssrc_id
);
743 track
.cname
= ssrc_info
.cname
;
744 track
.set_stream_ids(stream_ids
);
747 for (StreamParams
& stream
: *tracks
) {
748 // If a track ID wasn't populated from the SSRC attributes OR the
749 // msid attribute, use default/random values. This happens after
751 if (stream
.id
.empty()) {
752 stream
.id
= rtc::CreateRandomString(8);
757 void GetMediaStreamIds(const ContentInfo
* content
,
758 std::set
<std::string
>* labels
) {
759 for (const StreamParams
& stream_params
:
760 content
->media_description()->streams()) {
761 for (const std::string
& stream_id
: stream_params
.stream_ids()) {
762 labels
->insert(stream_id
);
768 // It is RECOMMENDED that default candidates be chosen based on the
769 // likelihood of those candidates to work with the peer that is being
770 // contacted. It is RECOMMENDED that relayed > reflexive > host.
771 static const int kPreferenceUnknown
= 0;
772 static const int kPreferenceHost
= 1;
773 static const int kPreferenceReflexive
= 2;
774 static const int kPreferenceRelayed
= 3;
776 static int GetCandidatePreferenceFromType(absl::string_view type
) {
777 int preference
= kPreferenceUnknown
;
778 if (type
== cricket::LOCAL_PORT_TYPE
) {
779 preference
= kPreferenceHost
;
780 } else if (type
== cricket::STUN_PORT_TYPE
) {
781 preference
= kPreferenceReflexive
;
782 } else if (type
== cricket::RELAY_PORT_TYPE
) {
783 preference
= kPreferenceRelayed
;
785 RTC_DCHECK_NOTREACHED();
790 // Get ip and port of the default destination from the `candidates` with the
791 // given value of `component_id`. The default candidate should be the one most
792 // likely to work, typically IPv4 relay.
794 // The value of `component_id` currently supported are 1 (RTP) and 2 (RTCP).
795 // TODO(deadbeef): Decide the default destination in webrtcsession and
796 // pass it down via SessionDescription.
797 static void GetDefaultDestination(const std::vector
<Candidate
>& candidates
,
801 std::string
* addr_type
) {
802 *addr_type
= kConnectionIpv4Addrtype
;
805 int current_preference
= kPreferenceUnknown
;
806 int current_family
= AF_UNSPEC
;
807 for (const Candidate
& candidate
: candidates
) {
808 if (candidate
.component() != component_id
) {
811 // Default destination should be UDP only.
812 if (candidate
.protocol() != cricket::UDP_PROTOCOL_NAME
) {
815 const int preference
= GetCandidatePreferenceFromType(candidate
.type());
816 const int family
= candidate
.address().ipaddr().family();
817 // See if this candidate is more preferable then the current one if it's the
818 // same family. Or if the current family is IPv4 already so we could safely
819 // ignore all IPv6 ones. WebRTC bug 4269.
820 // http://code.google.com/p/webrtc/issues/detail?id=4269
821 if ((preference
<= current_preference
&& current_family
== family
) ||
822 (current_family
== AF_INET
&& family
== AF_INET6
)) {
825 if (family
== AF_INET
) {
826 addr_type
->assign(kConnectionIpv4Addrtype
);
827 } else if (family
== AF_INET6
) {
828 addr_type
->assign(kConnectionIpv6Addrtype
);
830 current_preference
= preference
;
831 current_family
= family
;
832 *port
= candidate
.address().PortAsString();
833 *ip
= candidate
.address().ipaddr().ToString();
837 // Gets "a=rtcp" line if found default RTCP candidate from `candidates`.
838 static std::string
GetRtcpLine(const std::vector
<Candidate
>& candidates
) {
839 std::string rtcp_line
, rtcp_port
, rtcp_ip
, addr_type
;
840 GetDefaultDestination(candidates
, ICE_CANDIDATE_COMPONENT_RTCP
, &rtcp_port
,
841 &rtcp_ip
, &addr_type
);
842 // Found default RTCP candidate.
844 // If the agent is utilizing RTCP, it MUST encode the RTCP candidate
845 // using the a=rtcp attribute as defined in RFC 3605.
848 // rtcp-attribute = "a=rtcp:" port [nettype space addrtype space
849 // connection-address] CRLF
850 rtc::StringBuilder os
;
851 InitAttrLine(kAttributeRtcp
, &os
);
852 os
<< kSdpDelimiterColon
<< rtcp_port
<< " " << kConnectionNettype
<< " "
853 << addr_type
<< " " << rtcp_ip
;
854 rtcp_line
= os
.str();
858 // Get candidates according to the mline index from SessionDescriptionInterface.
859 static void GetCandidatesByMindex(const SessionDescriptionInterface
& desci
,
861 std::vector
<Candidate
>* candidates
) {
865 const IceCandidateCollection
* cc
= desci
.candidates(mline_index
);
866 for (size_t i
= 0; i
< cc
->count(); ++i
) {
867 const IceCandidateInterface
* candidate
= cc
->at(i
);
868 candidates
->push_back(candidate
->candidate());
872 static bool IsValidPort(int port
) {
873 return port
>= 0 && port
<= 65535;
876 std::string
SdpSerialize(const JsepSessionDescription
& jdesc
) {
877 const cricket::SessionDescription
* desc
= jdesc
.description();
884 // Session Description.
885 AddLine(kSessionVersion
, &message
);
888 // o=<username> <sess-id> <sess-version> <nettype> <addrtype>
890 rtc::StringBuilder os
;
891 InitLine(kLineTypeOrigin
, kSessionOriginUsername
, &os
);
892 const std::string
& session_id
=
893 jdesc
.session_id().empty() ? kSessionOriginSessionId
: jdesc
.session_id();
894 const std::string
& session_version
= jdesc
.session_version().empty()
895 ? kSessionOriginSessionVersion
896 : jdesc
.session_version();
897 os
<< " " << session_id
<< " " << session_version
<< " "
898 << kSessionOriginNettype
<< " " << kSessionOriginAddrtype
<< " "
899 << kSessionOriginAddress
;
900 AddLine(os
.str(), &message
);
901 AddLine(kSessionName
, &message
);
904 AddLine(kTimeDescription
, &message
);
907 std::vector
<const cricket::ContentGroup
*> groups
=
908 desc
->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE
);
909 for (const cricket::ContentGroup
* group
: groups
) {
910 std::string group_line
= kAttrGroup
;
911 RTC_DCHECK(group
!= NULL
);
912 for (const std::string
& content_name
: group
->content_names()) {
913 group_line
.append(" ");
914 group_line
.append(content_name
);
916 AddLine(group_line
, &message
);
919 // Mixed one- and two-byte header extension.
920 if (desc
->extmap_allow_mixed()) {
921 InitAttrLine(kAttributeExtmapAllowMixed
, &os
);
922 AddLine(os
.str(), &message
);
925 // MediaStream semantics
926 InitAttrLine(kAttributeMsidSemantics
, &os
);
927 os
<< kSdpDelimiterColon
<< " " << kMediaStreamSemantic
;
929 std::set
<std::string
> media_stream_ids
;
930 const ContentInfo
* audio_content
= GetFirstAudioContent(desc
);
932 GetMediaStreamIds(audio_content
, &media_stream_ids
);
934 const ContentInfo
* video_content
= GetFirstVideoContent(desc
);
936 GetMediaStreamIds(video_content
, &media_stream_ids
);
938 for (const std::string
& id
: media_stream_ids
) {
941 AddLine(os
.str(), &message
);
945 // TODO(deadbeef): It's weird that we need to iterate TransportInfos for
946 // this, when it's a session-level attribute. It really should be moved to a
947 // session-level structure like SessionDescription.
948 for (const cricket::TransportInfo
& transport
: desc
->transport_infos()) {
949 if (transport
.description
.ice_mode
== cricket::ICEMODE_LITE
) {
950 InitAttrLine(kAttributeIceLite
, &os
);
951 AddLine(os
.str(), &message
);
956 // Preserve the order of the media contents.
957 int mline_index
= -1;
958 for (const ContentInfo
& content
: desc
->contents()) {
959 std::vector
<Candidate
> candidates
;
960 GetCandidatesByMindex(jdesc
, ++mline_index
, &candidates
);
961 BuildMediaDescription(&content
, desc
->GetTransportInfoByName(content
.name
),
962 content
.media_description()->type(), candidates
,
963 desc
->msid_signaling(), &message
);
968 // Serializes the passed in IceCandidateInterface to a SDP string.
969 // candidate - The candidate to be serialized.
970 std::string
SdpSerializeCandidate(const IceCandidateInterface
& candidate
) {
971 return SdpSerializeCandidate(candidate
.candidate());
974 // Serializes a cricket Candidate.
975 std::string
SdpSerializeCandidate(const cricket::Candidate
& candidate
) {
977 std::vector
<cricket::Candidate
> candidates(1, candidate
);
978 BuildCandidate(candidates
, true, &message
);
979 // From WebRTC draft section 4.8.1.1 candidate-attribute will be
980 // just candidate:<candidate> not a=candidate:<blah>CRLF
981 RTC_DCHECK(message
.find("a=") == 0);
983 RTC_DCHECK(message
.find(kLineBreak
) == message
.size() - 2);
984 message
.resize(message
.size() - 2);
988 bool SdpDeserialize(absl::string_view message
,
989 JsepSessionDescription
* jdesc
,
990 SdpParseError
* error
) {
991 std::string session_id
;
992 std::string session_version
;
993 TransportDescription
session_td("", "");
994 RtpHeaderExtensions session_extmaps
;
995 rtc::SocketAddress session_connection_addr
;
996 auto desc
= std::make_unique
<cricket::SessionDescription
>();
997 size_t current_pos
= 0;
999 // Session Description
1000 if (!ParseSessionDescription(message
, ¤t_pos
, &session_id
,
1001 &session_version
, &session_td
, &session_extmaps
,
1002 &session_connection_addr
, desc
.get(), error
)) {
1006 // Media Description
1007 std::vector
<std::unique_ptr
<JsepIceCandidate
>> candidates
;
1008 if (!ParseMediaDescription(message
, session_td
, session_extmaps
, ¤t_pos
,
1009 session_connection_addr
, desc
.get(), &candidates
,
1014 jdesc
->Initialize(std::move(desc
), session_id
, session_version
);
1016 for (const auto& candidate
: candidates
) {
1017 jdesc
->AddCandidate(candidate
.get());
1022 bool SdpDeserializeCandidate(absl::string_view message
,
1023 JsepIceCandidate
* jcandidate
,
1024 SdpParseError
* error
) {
1025 RTC_DCHECK(jcandidate
!= NULL
);
1026 Candidate candidate
;
1027 if (!ParseCandidate(message
, &candidate
, error
, true)) {
1030 jcandidate
->SetCandidate(candidate
);
1034 bool SdpDeserializeCandidate(absl::string_view transport_name
,
1035 absl::string_view message
,
1036 cricket::Candidate
* candidate
,
1037 SdpParseError
* error
) {
1038 RTC_DCHECK(candidate
!= nullptr);
1039 if (!ParseCandidate(message
, candidate
, error
, true)) {
1042 candidate
->set_transport_name(transport_name
);
1046 bool ParseCandidate(absl::string_view message
,
1047 Candidate
* candidate
,
1048 SdpParseError
* error
,
1050 RTC_DCHECK(candidate
!= NULL
);
1052 // Makes sure `message` contains only one line.
1053 absl::string_view first_line
;
1055 size_t line_end
= message
.find(kNewLineChar
);
1056 if (line_end
== absl::string_view::npos
) {
1057 first_line
= message
;
1058 } else if (line_end
+ 1 == message
.size()) {
1059 first_line
= message
.substr(0, line_end
);
1061 return ParseFailed(message
, 0, "Expect one line only", error
);
1064 // Trim return char, if any.
1065 first_line
= TrimReturnChar(first_line
);
1067 // From WebRTC draft section 4.8.1.1 candidate-attribute should be
1068 // candidate:<candidate> when trickled, but we still support
1069 // a=candidate:<blah>CRLF for backward compatibility and for parsing a line
1071 if (IsLineType(first_line
, kLineTypeAttributes
)) {
1072 first_line
= first_line
.substr(kLinePrefixLength
);
1075 std::string attribute_candidate
;
1076 std::string candidate_value
;
1078 // `first_line` must be in the form of "candidate:<value>".
1079 if (!rtc::tokenize_first(first_line
, kSdpDelimiterColonChar
,
1080 &attribute_candidate
, &candidate_value
) ||
1081 attribute_candidate
!= kAttributeCandidate
) {
1083 rtc::StringBuilder description
;
1084 description
<< "Expect line: " << kAttributeCandidate
1087 return ParseFailed(first_line
, 0, description
.Release(), error
);
1089 return ParseFailedExpectLine(first_line
, 0, kLineTypeAttributes
,
1090 kAttributeCandidate
, error
);
1094 std::vector
<absl::string_view
> fields
=
1095 rtc::split(candidate_value
, kSdpDelimiterSpaceChar
);
1098 // a=candidate:<foundation> <component-id> <transport> <priority>
1099 // <connection-address> <port> typ <candidate-types>
1100 // [raddr <connection-address>] [rport <port>]
1101 // *(SP extension-att-name SP extension-att-value)
1102 const size_t expected_min_fields
= 8;
1103 if (fields
.size() < expected_min_fields
||
1104 (fields
[6] != kAttributeCandidateTyp
)) {
1105 return ParseFailedExpectMinFieldNum(first_line
, expected_min_fields
, error
);
1107 const absl::string_view foundation
= fields
[0];
1109 int component_id
= 0;
1110 if (!GetValueFromString(first_line
, fields
[1], &component_id
, error
)) {
1113 const absl::string_view transport
= fields
[2];
1114 uint32_t priority
= 0;
1115 if (!GetValueFromString(first_line
, fields
[3], &priority
, error
)) {
1118 const absl::string_view connection_address
= fields
[4];
1120 if (!GetValueFromString(first_line
, fields
[5], &port
, error
)) {
1123 if (!IsValidPort(port
)) {
1124 return ParseFailed(first_line
, "Invalid port number.", error
);
1126 SocketAddress
address(connection_address
, port
);
1128 absl::optional
<cricket::ProtocolType
> protocol
=
1129 cricket::StringToProto(transport
);
1131 return ParseFailed(first_line
, "Unsupported transport type.", error
);
1133 bool tcp_protocol
= false;
1134 switch (*protocol
) {
1135 // Supported protocols.
1136 case cricket::PROTO_UDP
:
1138 case cricket::PROTO_TCP
:
1139 case cricket::PROTO_SSLTCP
:
1140 tcp_protocol
= true;
1143 return ParseFailed(first_line
, "Unsupported transport type.", error
);
1146 std::string candidate_type
;
1147 const absl::string_view type
= fields
[7];
1148 if (type
== kCandidateHost
) {
1149 candidate_type
= cricket::LOCAL_PORT_TYPE
;
1150 } else if (type
== kCandidateSrflx
) {
1151 candidate_type
= cricket::STUN_PORT_TYPE
;
1152 } else if (type
== kCandidateRelay
) {
1153 candidate_type
= cricket::RELAY_PORT_TYPE
;
1154 } else if (type
== kCandidatePrflx
) {
1155 candidate_type
= cricket::PRFLX_PORT_TYPE
;
1157 return ParseFailed(first_line
, "Unsupported candidate type.", error
);
1160 size_t current_position
= expected_min_fields
;
1161 SocketAddress related_address
;
1162 // The 2 optional fields for related address
1163 // [raddr <connection-address>] [rport <port>]
1164 if (fields
.size() >= (current_position
+ 2) &&
1165 fields
[current_position
] == kAttributeCandidateRaddr
) {
1166 related_address
.SetIP(fields
[++current_position
]);
1169 if (fields
.size() >= (current_position
+ 2) &&
1170 fields
[current_position
] == kAttributeCandidateRport
) {
1172 if (!GetValueFromString(first_line
, fields
[++current_position
], &port
,
1176 if (!IsValidPort(port
)) {
1177 return ParseFailed(first_line
, "Invalid port number.", error
);
1179 related_address
.SetPort(port
);
1183 // If this is a TCP candidate, it has additional extension as defined in
1185 absl::string_view tcptype
;
1186 if (fields
.size() >= (current_position
+ 2) &&
1187 fields
[current_position
] == kTcpCandidateType
) {
1188 tcptype
= fields
[++current_position
];
1191 if (tcptype
!= cricket::TCPTYPE_ACTIVE_STR
&&
1192 tcptype
!= cricket::TCPTYPE_PASSIVE_STR
&&
1193 tcptype
!= cricket::TCPTYPE_SIMOPEN_STR
) {
1194 return ParseFailed(first_line
, "Invalid TCP candidate type.", error
);
1197 if (!tcp_protocol
) {
1198 return ParseFailed(first_line
, "Invalid non-TCP candidate", error
);
1200 } else if (tcp_protocol
) {
1201 // We allow the tcptype to be missing, for backwards compatibility,
1202 // treating it as a passive candidate.
1203 // TODO(bugs.webrtc.org/11466): Treat a missing tcptype as an error?
1204 tcptype
= cricket::TCPTYPE_PASSIVE_STR
;
1208 // Though non-standard, we support the ICE ufrag and pwd being signaled on
1209 // the candidate to avoid issues with confusing which generation a candidate
1210 // belongs to when trickling multiple generations at the same time.
1211 absl::string_view username
;
1212 absl::string_view password
;
1213 uint32_t generation
= 0;
1214 uint16_t network_id
= 0;
1215 uint16_t network_cost
= 0;
1216 for (size_t i
= current_position
; i
+ 1 < fields
.size(); ++i
) {
1218 // *(SP extension-att-name SP extension-att-value)
1219 if (fields
[i
] == kAttributeCandidateGeneration
) {
1220 if (!GetValueFromString(first_line
, fields
[++i
], &generation
, error
)) {
1223 } else if (fields
[i
] == kAttributeCandidateUfrag
) {
1224 username
= fields
[++i
];
1225 } else if (fields
[i
] == kAttributeCandidatePwd
) {
1226 password
= fields
[++i
];
1227 } else if (fields
[i
] == kAttributeCandidateNetworkId
) {
1228 if (!GetValueFromString(first_line
, fields
[++i
], &network_id
, error
)) {
1231 } else if (fields
[i
] == kAttributeCandidateNetworkCost
) {
1232 if (!GetValueFromString(first_line
, fields
[++i
], &network_cost
, error
)) {
1235 network_cost
= std::min(network_cost
, rtc::kNetworkCostMax
);
1237 // Skip the unknown extension.
1242 *candidate
= Candidate(component_id
, cricket::ProtoToString(*protocol
),
1243 address
, priority
, username
, password
, candidate_type
,
1244 generation
, foundation
, network_id
, network_cost
);
1245 candidate
->set_related_address(related_address
);
1246 candidate
->set_tcptype(tcptype
);
1250 bool ParseIceOptions(absl::string_view line
,
1251 std::vector
<std::string
>* transport_options
,
1252 SdpParseError
* error
) {
1253 std::string ice_options
;
1254 if (!GetValue(line
, kAttributeIceOption
, &ice_options
, error
)) {
1257 std::vector
<absl::string_view
> fields
=
1258 rtc::split(ice_options
, kSdpDelimiterSpaceChar
);
1259 for (size_t i
= 0; i
< fields
.size(); ++i
) {
1260 transport_options
->emplace_back(fields
[i
]);
1265 bool ParseSctpPort(absl::string_view line
,
1267 SdpParseError
* error
) {
1268 // draft-ietf-mmusic-sctp-sdp-26
1270 const size_t expected_min_fields
= 2;
1271 std::vector
<absl::string_view
> fields
=
1272 rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterColonChar
);
1273 if (fields
.size() < expected_min_fields
) {
1274 fields
= rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterSpaceChar
);
1276 if (fields
.size() < expected_min_fields
) {
1277 return ParseFailedExpectMinFieldNum(line
, expected_min_fields
, error
);
1279 if (!rtc::FromString(fields
[1], sctp_port
)) {
1280 return ParseFailed(line
, "Invalid sctp port value.", error
);
1285 bool ParseSctpMaxMessageSize(absl::string_view line
,
1286 int* max_message_size
,
1287 SdpParseError
* error
) {
1288 // draft-ietf-mmusic-sctp-sdp-26
1289 // a=max-message-size:199999
1290 const size_t expected_min_fields
= 2;
1291 std::vector
<absl::string_view
> fields
=
1292 rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterColonChar
);
1293 if (fields
.size() < expected_min_fields
) {
1294 return ParseFailedExpectMinFieldNum(line
, expected_min_fields
, error
);
1296 if (!rtc::FromString(fields
[1], max_message_size
)) {
1297 return ParseFailed(line
, "Invalid SCTP max message size.", error
);
1302 bool ParseExtmap(absl::string_view line
,
1303 RtpExtension
* extmap
,
1304 SdpParseError
* error
) {
1306 // a=extmap:<value>["/"<direction>] <URI> <extensionattributes>
1307 std::vector
<absl::string_view
> fields
=
1308 rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterSpaceChar
);
1309 const size_t expected_min_fields
= 2;
1310 if (fields
.size() < expected_min_fields
) {
1311 return ParseFailedExpectMinFieldNum(line
, expected_min_fields
, error
);
1313 absl::string_view uri
= fields
[1];
1315 std::string value_direction
;
1316 if (!GetValue(fields
[0], kAttributeExtmap
, &value_direction
, error
)) {
1319 std::vector
<absl::string_view
> sub_fields
=
1320 rtc::split(value_direction
, kSdpDelimiterSlashChar
);
1322 if (!GetValueFromString(line
, sub_fields
[0], &value
, error
)) {
1326 bool encrypted
= false;
1327 if (uri
== RtpExtension::kEncryptHeaderExtensionsUri
) {
1329 // a=extmap:<value["/"<direction>] urn:ietf:params:rtp-hdrext:encrypt <URI>
1330 // <extensionattributes>
1331 const size_t expected_min_fields_encrypted
= expected_min_fields
+ 1;
1332 if (fields
.size() < expected_min_fields_encrypted
) {
1333 return ParseFailedExpectMinFieldNum(line
, expected_min_fields_encrypted
,
1339 if (uri
== RtpExtension::kEncryptHeaderExtensionsUri
) {
1340 return ParseFailed(line
, "Recursive encrypted header.", error
);
1344 *extmap
= RtpExtension(uri
, value
, encrypted
);
1348 static void BuildSctpContentAttributes(
1349 std::string
* message
,
1350 const cricket::SctpDataContentDescription
* data_desc
) {
1351 rtc::StringBuilder os
;
1352 if (data_desc
->use_sctpmap()) {
1353 // draft-ietf-mmusic-sctp-sdp-04
1354 // a=sctpmap:sctpmap-number protocol [streams]
1355 rtc::StringBuilder os
;
1356 InitAttrLine(kAttributeSctpmap
, &os
);
1357 os
<< kSdpDelimiterColon
<< data_desc
->port() << kSdpDelimiterSpace
1358 << kDefaultSctpmapProtocol
<< kSdpDelimiterSpace
1359 << cricket::kMaxSctpStreams
;
1360 AddLine(os
.str(), message
);
1362 // draft-ietf-mmusic-sctp-sdp-23
1363 // a=sctp-port:<port>
1364 InitAttrLine(kAttributeSctpPort
, &os
);
1365 os
<< kSdpDelimiterColon
<< data_desc
->port();
1366 AddLine(os
.str(), message
);
1367 if (data_desc
->max_message_size() != kDefaultSctpMaxMessageSize
) {
1368 InitAttrLine(kAttributeMaxMessageSize
, &os
);
1369 os
<< kSdpDelimiterColon
<< data_desc
->max_message_size();
1370 AddLine(os
.str(), message
);
1375 void BuildIceUfragPwd(const TransportInfo
* transport_info
,
1376 std::string
* message
) {
1377 RTC_DCHECK(transport_info
);
1379 rtc::StringBuilder os
;
1381 // ice-pwd-att = "ice-pwd" ":" password
1382 // ice-ufrag-att = "ice-ufrag" ":" ufrag
1384 if (!transport_info
->description
.ice_ufrag
.empty()) {
1385 InitAttrLine(kAttributeIceUfrag
, &os
);
1386 os
<< kSdpDelimiterColon
<< transport_info
->description
.ice_ufrag
;
1387 AddLine(os
.str(), message
);
1390 if (!transport_info
->description
.ice_pwd
.empty()) {
1391 InitAttrLine(kAttributeIcePwd
, &os
);
1392 os
<< kSdpDelimiterColon
<< transport_info
->description
.ice_pwd
;
1393 AddLine(os
.str(), message
);
1397 void BuildDtlsFingerprintSetup(const TransportInfo
* transport_info
,
1398 std::string
* message
) {
1399 RTC_DCHECK(transport_info
);
1401 rtc::StringBuilder os
;
1403 // fingerprint-attribute =
1404 // "fingerprint" ":" hash-func SP fingerprint
1405 // When using max-bundle this is already included at session level.
1406 // Insert the fingerprint attribute.
1407 auto fingerprint
= transport_info
->description
.identity_fingerprint
.get();
1411 InitAttrLine(kAttributeFingerprint
, &os
);
1412 os
<< kSdpDelimiterColon
<< fingerprint
->algorithm
<< kSdpDelimiterSpace
1413 << fingerprint
->GetRfc4572Fingerprint();
1414 AddLine(os
.str(), message
);
1416 // Inserting setup attribute.
1417 if (transport_info
->description
.connection_role
!=
1418 cricket::CONNECTIONROLE_NONE
) {
1419 // Making sure we are not using "passive" mode.
1420 cricket::ConnectionRole role
= transport_info
->description
.connection_role
;
1421 std::string dtls_role_str
;
1422 const bool success
= cricket::ConnectionRoleToString(role
, &dtls_role_str
);
1423 RTC_DCHECK(success
);
1424 InitAttrLine(kAttributeSetup
, &os
);
1425 os
<< kSdpDelimiterColon
<< dtls_role_str
;
1426 AddLine(os
.str(), message
);
1430 void BuildMediaLine(const cricket::MediaType media_type
,
1431 const ContentInfo
* content_info
,
1432 const MediaContentDescription
* media_desc
,
1433 std::string
* message
) {
1434 rtc::StringBuilder os
;
1437 // m=<media> <port> <proto> <fmt>
1438 // fmt is a list of payload type numbers that MAY be used in the session.
1441 if (media_type
== cricket::MEDIA_TYPE_VIDEO
) {
1442 type
= kMediaTypeVideo
;
1443 const VideoContentDescription
* video_desc
= media_desc
->as_video();
1444 for (const cricket::VideoCodec
& codec
: video_desc
->codecs()) {
1446 fmt
.append(rtc::ToString(codec
.id
));
1448 } else if (media_type
== cricket::MEDIA_TYPE_AUDIO
) {
1449 type
= kMediaTypeAudio
;
1450 const AudioContentDescription
* audio_desc
= media_desc
->as_audio();
1451 for (const cricket::AudioCodec
& codec
: audio_desc
->codecs()) {
1453 fmt
.append(rtc::ToString(codec
.id
));
1455 } else if (media_type
== cricket::MEDIA_TYPE_DATA
) {
1456 type
= kMediaTypeData
;
1457 const cricket::SctpDataContentDescription
* sctp_data_desc
=
1458 media_desc
->as_sctp();
1459 if (sctp_data_desc
) {
1462 if (sctp_data_desc
->use_sctpmap()) {
1463 fmt
.append(rtc::ToString(sctp_data_desc
->port()));
1465 fmt
.append(kDefaultSctpmapProtocol
);
1468 RTC_DCHECK_NOTREACHED() << "Data description without SCTP";
1470 } else if (media_type
== cricket::MEDIA_TYPE_UNSUPPORTED
) {
1471 const UnsupportedContentDescription
* unsupported_desc
=
1472 media_desc
->as_unsupported();
1473 type
= unsupported_desc
->media_type();
1475 RTC_DCHECK_NOTREACHED();
1477 // The fmt must never be empty. If no codecs are found, set the fmt attribute
1483 // The port number in the m line will be updated later when associated with
1486 // A port value of 0 indicates that the m= section is rejected.
1488 // To reject an offered stream, the port number in the corresponding stream in
1489 // the answer MUST be set to zero.
1491 // However, the BUNDLE draft adds a new meaning to port zero, when used along
1492 // with a=bundle-only.
1493 std::string port
= kDummyPort
;
1494 if (content_info
->rejected
|| content_info
->bundle_only
) {
1495 port
= kMediaPortRejected
;
1496 } else if (!media_desc
->connection_address().IsNil()) {
1497 port
= rtc::ToString(media_desc
->connection_address().port());
1500 // Add the m and c lines.
1501 InitLine(kLineTypeMedia
, type
, &os
);
1502 os
<< " " << port
<< " " << media_desc
->protocol() << fmt
;
1503 AddLine(os
.str(), message
);
1506 void BuildMediaDescription(const ContentInfo
* content_info
,
1507 const TransportInfo
* transport_info
,
1508 const cricket::MediaType media_type
,
1509 const std::vector
<Candidate
>& candidates
,
1511 std::string
* message
) {
1512 RTC_DCHECK(message
);
1513 if (!content_info
) {
1516 rtc::StringBuilder os
;
1517 const MediaContentDescription
* media_desc
= content_info
->media_description();
1518 RTC_DCHECK(media_desc
);
1521 BuildMediaLine(media_type
, content_info
, media_desc
, message
);
1523 InitLine(kLineTypeConnection
, kConnectionNettype
, &os
);
1524 if (media_desc
->connection_address().IsNil()) {
1525 os
<< " " << kConnectionIpv4Addrtype
<< " " << kDummyAddress
;
1526 } else if (media_desc
->connection_address().family() == AF_INET
) {
1527 os
<< " " << kConnectionIpv4Addrtype
<< " "
1528 << media_desc
->connection_address().ipaddr().ToString();
1529 } else if (media_desc
->connection_address().family() == AF_INET6
) {
1530 os
<< " " << kConnectionIpv6Addrtype
<< " "
1531 << media_desc
->connection_address().ipaddr().ToString();
1533 os
<< " " << kConnectionIpv4Addrtype
<< " " << kDummyAddress
;
1535 AddLine(os
.str(), message
);
1538 // b=AS:<bandwidth> or
1539 // b=TIAS:<bandwidth>
1540 int bandwidth
= media_desc
->bandwidth();
1541 std::string bandwidth_type
= media_desc
->bandwidth_type();
1542 if (bandwidth_type
== kApplicationSpecificBandwidth
&& bandwidth
>= 1000) {
1543 InitLine(kLineTypeSessionBandwidth
, bandwidth_type
, &os
);
1545 os
<< kSdpDelimiterColon
<< bandwidth
;
1546 AddLine(os
.str(), message
);
1547 } else if (bandwidth_type
== kTransportSpecificBandwidth
&& bandwidth
> 0) {
1548 InitLine(kLineTypeSessionBandwidth
, bandwidth_type
, &os
);
1549 os
<< kSdpDelimiterColon
<< bandwidth
;
1550 AddLine(os
.str(), message
);
1553 // Add the a=bundle-only line.
1554 if (content_info
->bundle_only
) {
1555 InitAttrLine(kAttributeBundleOnly
, &os
);
1556 AddLine(os
.str(), message
);
1559 // Add the a=rtcp line.
1560 if (cricket::IsRtpProtocol(media_desc
->protocol())) {
1561 std::string rtcp_line
= GetRtcpLine(candidates
);
1562 if (!rtcp_line
.empty()) {
1563 AddLine(rtcp_line
, message
);
1567 // Build the a=candidate lines. We don't include ufrag and pwd in the
1568 // candidates in the SDP to avoid redundancy.
1569 BuildCandidate(candidates
, false, message
);
1571 // Use the transport_info to build the media level ice-ufrag, ice-pwd
1572 // and DTLS fingerprint and setup attributes.
1573 if (transport_info
) {
1574 BuildIceUfragPwd(transport_info
, message
);
1576 // draft-petithuguenin-mmusic-ice-attributes-level-03
1577 BuildIceOptions(transport_info
->description
.transport_options
, message
);
1579 // Also include the DTLS fingerprint and setup attribute if available.
1580 BuildDtlsFingerprintSetup(transport_info
, message
);
1584 // mid-attribute = "a=mid:" identification-tag
1585 // identification-tag = token
1586 // Use the content name as the mid identification-tag.
1587 InitAttrLine(kAttributeMid
, &os
);
1588 os
<< kSdpDelimiterColon
<< content_info
->name
;
1589 AddLine(os
.str(), message
);
1591 if (cricket::IsDtlsSctp(media_desc
->protocol())) {
1592 const cricket::SctpDataContentDescription
* data_desc
=
1593 media_desc
->as_sctp();
1594 BuildSctpContentAttributes(message
, data_desc
);
1595 } else if (cricket::IsRtpProtocol(media_desc
->protocol())) {
1596 BuildRtpContentAttributes(media_desc
, media_type
, msid_signaling
, message
);
1600 void BuildRtpContentAttributes(const MediaContentDescription
* media_desc
,
1601 const cricket::MediaType media_type
,
1603 std::string
* message
) {
1604 SdpSerializer serializer
;
1605 rtc::StringBuilder os
;
1607 // a=extmap-allow-mixed
1608 // The attribute MUST be either on session level or media level. We support
1609 // responding on both levels, however, we don't respond on media level if it's
1610 // set on session level.
1611 if (media_desc
->extmap_allow_mixed_enum() ==
1612 MediaContentDescription::kMedia
) {
1613 InitAttrLine(kAttributeExtmapAllowMixed
, &os
);
1614 AddLine(os
.str(), message
);
1616 BuildRtpHeaderExtensions(media_desc
->rtp_header_extensions(), message
);
1619 // a=sendrecv || a=sendonly || a=sendrecv || a=inactive
1620 switch (media_desc
->direction()) {
1621 // Special case that for sdp purposes should be treated same as inactive.
1622 case RtpTransceiverDirection::kStopped
:
1623 case RtpTransceiverDirection::kInactive
:
1624 InitAttrLine(kAttributeInactive
, &os
);
1626 case RtpTransceiverDirection::kSendOnly
:
1627 InitAttrLine(kAttributeSendOnly
, &os
);
1629 case RtpTransceiverDirection::kRecvOnly
:
1630 InitAttrLine(kAttributeRecvOnly
, &os
);
1632 case RtpTransceiverDirection::kSendRecv
:
1633 InitAttrLine(kAttributeSendRecv
, &os
);
1636 RTC_DCHECK_NOTREACHED();
1637 InitAttrLine(kAttributeSendRecv
, &os
);
1640 AddLine(os
.str(), message
);
1642 // Specified in https://datatracker.ietf.org/doc/draft-ietf-mmusic-msid/16/
1643 // a=msid:<msid-id> <msid-appdata>
1644 // The msid-id is a 1*64 token char representing the media stream id, and the
1645 // msid-appdata is a 1*64 token char representing the track id. There is a
1646 // line for every media stream, with a special msid-id value of "-"
1647 // representing no streams. The value of "msid-appdata" MUST be identical for
1649 if (msid_signaling
& cricket::kMsidSignalingMediaSection
) {
1650 const StreamParamsVec
& streams
= media_desc
->streams();
1651 if (streams
.size() == 1u) {
1652 const StreamParams
& track
= streams
[0];
1653 std::vector
<std::string
> stream_ids
= track
.stream_ids();
1654 if (stream_ids
.empty()) {
1655 stream_ids
.push_back(kNoStreamMsid
);
1657 for (const std::string
& stream_id
: stream_ids
) {
1658 InitAttrLine(kAttributeMsid
, &os
);
1659 os
<< kSdpDelimiterColon
<< stream_id
<< kSdpDelimiterSpace
<< track
.id
;
1660 AddLine(os
.str(), message
);
1662 } else if (streams
.size() > 1u) {
1664 << "Trying to serialize Unified Plan SDP with more than "
1665 "one track in a media section. Omitting 'a=msid'.";
1671 if (media_desc
->rtcp_mux()) {
1672 InitAttrLine(kAttributeRtcpMux
, &os
);
1673 AddLine(os
.str(), message
);
1678 if (media_desc
->rtcp_reduced_size()) {
1679 InitAttrLine(kAttributeRtcpReducedSize
, &os
);
1680 AddLine(os
.str(), message
);
1683 if (media_desc
->conference_mode()) {
1684 InitAttrLine(kAttributeXGoogleFlag
, &os
);
1685 os
<< kSdpDelimiterColon
<< kValueConference
;
1686 AddLine(os
.str(), message
);
1689 if (media_desc
->remote_estimate()) {
1690 InitAttrLine(kAttributeRtcpRemoteEstimate
, &os
);
1691 AddLine(os
.str(), message
);
1695 // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
1696 for (const CryptoParams
& crypto_params
: media_desc
->cryptos()) {
1697 InitAttrLine(kAttributeCrypto
, &os
);
1698 os
<< kSdpDelimiterColon
<< crypto_params
.tag
<< " "
1699 << crypto_params
.crypto_suite
<< " " << crypto_params
.key_params
;
1700 if (!crypto_params
.session_params
.empty()) {
1701 os
<< " " << crypto_params
.session_params
;
1703 AddLine(os
.str(), message
);
1707 // a=rtpmap:<payload type> <encoding name>/<clock rate>
1708 // [/<encodingparameters>]
1709 BuildRtpmap(media_desc
, media_type
, message
);
1711 for (const StreamParams
& track
: media_desc
->streams()) {
1712 // Build the ssrc-group lines.
1713 for (const SsrcGroup
& ssrc_group
: track
.ssrc_groups
) {
1715 // a=ssrc-group:<semantics> <ssrc-id> ...
1716 if (ssrc_group
.ssrcs
.empty()) {
1719 InitAttrLine(kAttributeSsrcGroup
, &os
);
1720 os
<< kSdpDelimiterColon
<< ssrc_group
.semantics
;
1721 for (uint32_t ssrc
: ssrc_group
.ssrcs
) {
1722 os
<< kSdpDelimiterSpace
<< rtc::ToString(ssrc
);
1724 AddLine(os
.str(), message
);
1726 // Build the ssrc lines for each ssrc.
1727 for (uint32_t ssrc
: track
.ssrcs
) {
1729 // a=ssrc:<ssrc-id> cname:<value>
1730 AddSsrcLine(ssrc
, kSsrcAttributeCname
, track
.cname
, message
);
1732 if (msid_signaling
& cricket::kMsidSignalingSsrcAttribute
) {
1733 // draft-alvestrand-mmusic-msid-00
1734 // a=ssrc:<ssrc-id> msid:identifier [appdata]
1735 // The appdata consists of the "id" attribute of a MediaStreamTrack,
1736 // which corresponds to the "id" attribute of StreamParams.
1737 // Since a=ssrc msid signaling is used in Plan B SDP semantics, and
1738 // multiple stream ids are not supported for Plan B, we are only adding
1739 // a line for the first media stream id here.
1740 const std::string
& track_stream_id
= track
.first_stream_id();
1741 // We use a special msid-id value of "-" to represent no streams,
1742 // for Unified Plan compatibility. Plan B will always have a
1744 const std::string
& stream_id
=
1745 track_stream_id
.empty() ? kNoStreamMsid
: track_stream_id
;
1746 InitAttrLine(kAttributeSsrc
, &os
);
1747 os
<< kSdpDelimiterColon
<< ssrc
<< kSdpDelimiterSpace
1748 << kSsrcAttributeMsid
<< kSdpDelimiterColon
<< stream_id
1749 << kSdpDelimiterSpace
<< track
.id
;
1750 AddLine(os
.str(), message
);
1754 // Build the rid lines for each layer of the track
1755 for (const RidDescription
& rid_description
: track
.rids()) {
1756 InitAttrLine(kAttributeRid
, &os
);
1757 os
<< kSdpDelimiterColon
1758 << serializer
.SerializeRidDescription(rid_description
);
1759 AddLine(os
.str(), message
);
1763 for (const RidDescription
& rid_description
: media_desc
->receive_rids()) {
1764 InitAttrLine(kAttributeRid
, &os
);
1765 os
<< kSdpDelimiterColon
1766 << serializer
.SerializeRidDescription(rid_description
);
1767 AddLine(os
.str(), message
);
1770 // Simulcast (a=simulcast)
1771 // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
1772 if (media_desc
->HasSimulcast()) {
1773 const auto& simulcast
= media_desc
->simulcast_description();
1774 InitAttrLine(kAttributeSimulcast
, &os
);
1775 os
<< kSdpDelimiterColon
1776 << serializer
.SerializeSimulcastDescription(simulcast
);
1777 AddLine(os
.str(), message
);
1781 void BuildRtpHeaderExtensions(const RtpHeaderExtensions
& extensions
,
1782 std::string
* message
) {
1783 rtc::StringBuilder os
;
1786 // a=extmap:<value>["/"<direction>] <URI> <extensionattributes>
1787 // The definitions MUST be either all session level or all media level. This
1788 // implementation uses all media level.
1789 for (const RtpExtension
& extension
: extensions
) {
1790 InitAttrLine(kAttributeExtmap
, &os
);
1791 os
<< kSdpDelimiterColon
<< extension
.id
;
1792 if (extension
.encrypt
) {
1793 os
<< kSdpDelimiterSpace
<< RtpExtension::kEncryptHeaderExtensionsUri
;
1795 os
<< kSdpDelimiterSpace
<< extension
.uri
;
1796 AddLine(os
.str(), message
);
1800 void WriteFmtpHeader(int payload_type
, rtc::StringBuilder
* os
) {
1801 // fmtp header: a=fmtp:`payload_type` <parameters>
1803 InitAttrLine(kAttributeFmtp
, os
);
1804 // Add :`payload_type`
1805 *os
<< kSdpDelimiterColon
<< payload_type
;
1808 void WritePacketizationHeader(int payload_type
, rtc::StringBuilder
* os
) {
1809 // packetization header: a=packetization:`payload_type` <packetization_format>
1810 // Add a=packetization
1811 InitAttrLine(kAttributePacketization
, os
);
1812 // Add :`payload_type`
1813 *os
<< kSdpDelimiterColon
<< payload_type
;
1816 void WriteRtcpFbHeader(int payload_type
, rtc::StringBuilder
* os
) {
1817 // rtcp-fb header: a=rtcp-fb:`payload_type`
1818 // <parameters>/<ccm <ccm_parameters>>
1820 InitAttrLine(kAttributeRtcpFb
, os
);
1822 *os
<< kSdpDelimiterColon
;
1823 if (payload_type
== kWildcardPayloadType
) {
1826 *os
<< payload_type
;
1830 void WriteFmtpParameter(absl::string_view parameter_name
,
1831 absl::string_view parameter_value
,
1832 rtc::StringBuilder
* os
) {
1833 if (parameter_name
.empty()) {
1834 // RFC 2198 and RFC 4733 don't use key-value pairs.
1835 *os
<< parameter_value
;
1837 // fmtp parameters: `parameter_name`=`parameter_value`
1838 *os
<< parameter_name
<< kSdpDelimiterEqual
<< parameter_value
;
1842 bool IsFmtpParam(absl::string_view name
) {
1843 // RFC 4855, section 3 specifies the mapping of media format parameters to SDP
1844 // parameters. Only ptime, maxptime, channels and rate are placed outside of
1845 // the fmtp line. In WebRTC, channels and rate are already handled separately
1846 // and thus not included in the CodecParameterMap.
1847 return name
!= kCodecParamPTime
&& name
!= kCodecParamMaxPTime
;
1850 bool WriteFmtpParameters(const cricket::CodecParameterMap
& parameters
,
1851 rtc::StringBuilder
* os
) {
1853 const char* delimiter
= ""; // No delimiter before first parameter.
1854 for (const auto& entry
: parameters
) {
1855 const std::string
& key
= entry
.first
;
1856 const std::string
& value
= entry
.second
;
1858 if (IsFmtpParam(key
)) {
1860 // A semicolon before each subsequent parameter.
1861 delimiter
= kSdpDelimiterSemicolon
;
1862 WriteFmtpParameter(key
, value
, os
);
1871 void AddFmtpLine(const T
& codec
, std::string
* message
) {
1872 rtc::StringBuilder os
;
1873 WriteFmtpHeader(codec
.id
, &os
);
1874 os
<< kSdpDelimiterSpace
;
1875 // Create FMTP line and check that it's nonempty.
1876 if (WriteFmtpParameters(codec
.params
, &os
)) {
1877 AddLine(os
.str(), message
);
1883 void AddPacketizationLine(const T
& codec
, std::string
* message
) {
1884 if (!codec
.packetization
) {
1887 rtc::StringBuilder os
;
1888 WritePacketizationHeader(codec
.id
, &os
);
1889 os
<< " " << *codec
.packetization
;
1890 AddLine(os
.str(), message
);
1894 void AddRtcpFbLines(const T
& codec
, std::string
* message
) {
1895 for (const cricket::FeedbackParam
& param
: codec
.feedback_params
.params()) {
1896 rtc::StringBuilder os
;
1897 WriteRtcpFbHeader(codec
.id
, &os
);
1898 os
<< " " << param
.id();
1899 if (!param
.param().empty()) {
1900 os
<< " " << param
.param();
1902 AddLine(os
.str(), message
);
1906 bool GetMinValue(const std::vector
<int>& values
, int* value
) {
1907 if (values
.empty()) {
1910 auto it
= absl::c_min_element(values
);
1915 bool GetParameter(const std::string
& name
,
1916 const cricket::CodecParameterMap
& params
,
1918 std::map
<std::string
, std::string
>::const_iterator found
= params
.find(name
);
1919 if (found
== params
.end()) {
1922 if (!rtc::FromString(found
->second
, value
)) {
1928 void BuildRtpmap(const MediaContentDescription
* media_desc
,
1929 const cricket::MediaType media_type
,
1930 std::string
* message
) {
1931 RTC_DCHECK(message
!= NULL
);
1932 RTC_DCHECK(media_desc
!= NULL
);
1933 rtc::StringBuilder os
;
1934 if (media_type
== cricket::MEDIA_TYPE_VIDEO
) {
1935 for (const cricket::VideoCodec
& codec
: media_desc
->as_video()->codecs()) {
1937 // a=rtpmap:<payload type> <encoding name>/<clock rate>
1938 // [/<encodingparameters>]
1939 if (codec
.id
!= kWildcardPayloadType
) {
1940 InitAttrLine(kAttributeRtpmap
, &os
);
1941 os
<< kSdpDelimiterColon
<< codec
.id
<< " " << codec
.name
<< "/"
1942 << cricket::kVideoCodecClockrate
;
1943 AddLine(os
.str(), message
);
1945 AddPacketizationLine(codec
, message
);
1946 AddRtcpFbLines(codec
, message
);
1947 AddFmtpLine(codec
, message
);
1949 } else if (media_type
== cricket::MEDIA_TYPE_AUDIO
) {
1950 std::vector
<int> ptimes
;
1951 std::vector
<int> maxptimes
;
1952 int max_minptime
= 0;
1953 for (const cricket::AudioCodec
& codec
: media_desc
->as_audio()->codecs()) {
1954 RTC_DCHECK(!codec
.name
.empty());
1956 // a=rtpmap:<payload type> <encoding name>/<clock rate>
1957 // [/<encodingparameters>]
1958 InitAttrLine(kAttributeRtpmap
, &os
);
1959 os
<< kSdpDelimiterColon
<< codec
.id
<< " ";
1960 os
<< codec
.name
<< "/" << codec
.clockrate
;
1961 if (codec
.channels
!= 1) {
1962 os
<< "/" << codec
.channels
;
1964 AddLine(os
.str(), message
);
1965 AddRtcpFbLines(codec
, message
);
1966 AddFmtpLine(codec
, message
);
1968 if (GetParameter(kCodecParamMinPTime
, codec
.params
, &minptime
)) {
1969 max_minptime
= std::max(minptime
, max_minptime
);
1972 if (GetParameter(kCodecParamPTime
, codec
.params
, &ptime
)) {
1973 ptimes
.push_back(ptime
);
1976 if (GetParameter(kCodecParamMaxPTime
, codec
.params
, &maxptime
)) {
1977 maxptimes
.push_back(maxptime
);
1980 // Populate the maxptime attribute with the smallest maxptime of all codecs
1981 // under the same m-line.
1982 int min_maxptime
= INT_MAX
;
1983 if (GetMinValue(maxptimes
, &min_maxptime
)) {
1984 AddAttributeLine(kCodecParamMaxPTime
, min_maxptime
, message
);
1986 RTC_DCHECK_GE(min_maxptime
, max_minptime
);
1987 // Populate the ptime attribute with the smallest ptime or the largest
1988 // minptime, whichever is the largest, for all codecs under the same m-line.
1989 int ptime
= INT_MAX
;
1990 if (GetMinValue(ptimes
, &ptime
)) {
1991 ptime
= std::min(ptime
, min_maxptime
);
1992 ptime
= std::max(ptime
, max_minptime
);
1993 AddAttributeLine(kCodecParamPTime
, ptime
, message
);
1998 void BuildCandidate(const std::vector
<Candidate
>& candidates
,
2000 std::string
* message
) {
2001 rtc::StringBuilder os
;
2003 for (const Candidate
& candidate
: candidates
) {
2005 // a=candidate:<foundation> <component-id> <transport> <priority>
2006 // <connection-address> <port> typ <candidate-types>
2007 // [raddr <connection-address>] [rport <port>]
2008 // *(SP extension-att-name SP extension-att-value)
2010 // Map the cricket candidate type to "host" / "srflx" / "prflx" / "relay"
2011 if (candidate
.type() == cricket::LOCAL_PORT_TYPE
) {
2012 type
= kCandidateHost
;
2013 } else if (candidate
.type() == cricket::STUN_PORT_TYPE
) {
2014 type
= kCandidateSrflx
;
2015 } else if (candidate
.type() == cricket::RELAY_PORT_TYPE
) {
2016 type
= kCandidateRelay
;
2017 } else if (candidate
.type() == cricket::PRFLX_PORT_TYPE
) {
2018 type
= kCandidatePrflx
;
2019 // Peer reflexive candidate may be signaled for being removed.
2021 RTC_DCHECK_NOTREACHED();
2022 // Never write out candidates if we don't know the type.
2026 InitAttrLine(kAttributeCandidate
, &os
);
2027 os
<< kSdpDelimiterColon
<< candidate
.foundation() << " "
2028 << candidate
.component() << " " << candidate
.protocol() << " "
2029 << candidate
.priority() << " "
2030 << (candidate
.address().ipaddr().IsNil()
2031 ? candidate
.address().hostname()
2032 : candidate
.address().ipaddr().ToString())
2033 << " " << candidate
.address().PortAsString() << " "
2034 << kAttributeCandidateTyp
<< " " << type
<< " ";
2037 if (!candidate
.related_address().IsNil()) {
2038 os
<< kAttributeCandidateRaddr
<< " "
2039 << candidate
.related_address().ipaddr().ToString() << " "
2040 << kAttributeCandidateRport
<< " "
2041 << candidate
.related_address().PortAsString() << " ";
2044 // Note that we allow the tcptype to be missing, for backwards
2045 // compatibility; the implementation treats this as a passive candidate.
2046 // TODO(bugs.webrtc.org/11466): Treat a missing tcptype as an error?
2047 if (candidate
.protocol() == cricket::TCP_PROTOCOL_NAME
&&
2048 !candidate
.tcptype().empty()) {
2049 os
<< kTcpCandidateType
<< " " << candidate
.tcptype() << " ";
2053 os
<< kAttributeCandidateGeneration
<< " " << candidate
.generation();
2054 if (include_ufrag
&& !candidate
.username().empty()) {
2055 os
<< " " << kAttributeCandidateUfrag
<< " " << candidate
.username();
2057 if (candidate
.network_id() > 0) {
2058 os
<< " " << kAttributeCandidateNetworkId
<< " "
2059 << candidate
.network_id();
2061 if (candidate
.network_cost() > 0) {
2062 os
<< " " << kAttributeCandidateNetworkCost
<< " "
2063 << candidate
.network_cost();
2066 AddLine(os
.str(), message
);
2070 void BuildIceOptions(const std::vector
<std::string
>& transport_options
,
2071 std::string
* message
) {
2072 if (!transport_options
.empty()) {
2073 rtc::StringBuilder os
;
2074 InitAttrLine(kAttributeIceOption
, &os
);
2075 os
<< kSdpDelimiterColon
<< transport_options
[0];
2076 for (size_t i
= 1; i
< transport_options
.size(); ++i
) {
2077 os
<< kSdpDelimiterSpace
<< transport_options
[i
];
2079 AddLine(os
.str(), message
);
2083 bool ParseConnectionData(absl::string_view line
,
2084 rtc::SocketAddress
* addr
,
2085 SdpParseError
* error
) {
2086 // Parse the line from left to right.
2088 std::string rightpart
;
2090 // c=<nettype> <addrtype> <connection-address>
2092 if (!rtc::tokenize_first(line
, kSdpDelimiterEqualChar
, &token
, &rightpart
)) {
2093 return ParseFailed(line
, "Failed to parse the network type.", error
);
2096 // Extract and verify the <nettype>
2097 if (!rtc::tokenize_first(rightpart
, kSdpDelimiterSpaceChar
, &token
,
2099 token
!= kConnectionNettype
) {
2100 return ParseFailed(line
,
2101 "Failed to parse the connection data. The network type "
2102 "is not currently supported.",
2106 // Extract the "<addrtype>" and "<connection-address>".
2107 if (!rtc::tokenize_first(rightpart
, kSdpDelimiterSpaceChar
, &token
,
2109 return ParseFailed(line
, "Failed to parse the address type.", error
);
2112 // The rightpart part should be the IP address without the slash which is used
2114 if (rightpart
.find('/') != std::string::npos
) {
2115 return ParseFailed(line
,
2116 "Failed to parse the connection data. Multicast is not "
2117 "currently supported.",
2120 addr
->SetIP(rightpart
);
2122 // Verify that the addrtype matches the type of the parsed address.
2123 if ((addr
->family() == AF_INET
&& token
!= "IP4") ||
2124 (addr
->family() == AF_INET6
&& token
!= "IP6")) {
2128 "Failed to parse the connection data. The address type is mismatching.",
2134 bool ParseSessionDescription(absl::string_view message
,
2136 std::string
* session_id
,
2137 std::string
* session_version
,
2138 TransportDescription
* session_td
,
2139 RtpHeaderExtensions
* session_extmaps
,
2140 rtc::SocketAddress
* connection_addr
,
2141 cricket::SessionDescription
* desc
,
2142 SdpParseError
* error
) {
2143 absl::optional
<absl::string_view
> line
;
2145 desc
->set_msid_supported(false);
2146 desc
->set_extmap_allow_mixed(false);
2148 // v= (protocol version)
2149 line
= GetLineWithType(message
, pos
, kLineTypeVersion
);
2151 return ParseFailedExpectLine(message
, *pos
, kLineTypeVersion
, std::string(),
2155 // o=<username> <sess-id> <sess-version> <nettype> <addrtype>
2156 // <unicast-address>
2157 line
= GetLineWithType(message
, pos
, kLineTypeOrigin
);
2159 return ParseFailedExpectLine(message
, *pos
, kLineTypeOrigin
, std::string(),
2162 std::vector
<absl::string_view
> fields
=
2163 rtc::split(line
->substr(kLinePrefixLength
), kSdpDelimiterSpaceChar
);
2164 const size_t expected_fields
= 6;
2165 if (fields
.size() != expected_fields
) {
2166 return ParseFailedExpectFieldNum(*line
, expected_fields
, error
);
2168 *session_id
= std::string(fields
[1]);
2169 *session_version
= std::string(fields
[2]);
2172 // s= (session name)
2173 line
= GetLineWithType(message
, pos
, kLineTypeSessionName
);
2175 return ParseFailedExpectLine(message
, *pos
, kLineTypeSessionName
,
2176 std::string(), error
);
2180 // Those are the optional lines, so shouldn't return false if not present.
2182 // i=* (session information)
2183 GetLineWithType(message
, pos
, kLineTypeSessionInfo
);
2186 // u=* (URI of description)
2187 GetLineWithType(message
, pos
, kLineTypeSessionUri
);
2190 // e=* (email address)
2191 GetLineWithType(message
, pos
, kLineTypeSessionEmail
);
2194 // p=* (phone number)
2195 GetLineWithType(message
, pos
, kLineTypeSessionPhone
);
2198 // c=* (connection information -- not required if included in
2200 if (absl::optional
<absl::string_view
> cline
=
2201 GetLineWithType(message
, pos
, kLineTypeConnection
);
2202 cline
.has_value()) {
2203 if (!ParseConnectionData(*cline
, connection_addr
, error
)) {
2209 // b=* (zero or more bandwidth information lines)
2210 while (GetLineWithType(message
, pos
, kLineTypeSessionBandwidth
).has_value()) {
2211 // By pass zero or more b lines.
2215 // One or more time descriptions ("t=" and "r=" lines; see below)
2216 // t= (time the session is active)
2217 // r=* (zero or more repeat times)
2218 // Ensure there's at least one time description
2219 if (!GetLineWithType(message
, pos
, kLineTypeTiming
).has_value()) {
2220 return ParseFailedExpectLine(message
, *pos
, kLineTypeTiming
, std::string(),
2224 while (GetLineWithType(message
, pos
, kLineTypeRepeatTimes
).has_value()) {
2225 // By pass zero or more r lines.
2228 // Go through the rest of the time descriptions
2229 while (GetLineWithType(message
, pos
, kLineTypeTiming
).has_value()) {
2230 while (GetLineWithType(message
, pos
, kLineTypeRepeatTimes
).has_value()) {
2231 // By pass zero or more r lines.
2236 // z=* (time zone adjustments)
2237 GetLineWithType(message
, pos
, kLineTypeTimeZone
);
2240 // k=* (encryption key)
2241 GetLineWithType(message
, pos
, kLineTypeEncryptionKey
);
2244 // a=* (zero or more session attribute lines)
2245 while (absl::optional
<absl::string_view
> aline
=
2246 GetLineWithType(message
, pos
, kLineTypeAttributes
)) {
2247 if (HasAttribute(*aline
, kAttributeGroup
)) {
2248 if (!ParseGroupAttribute(*aline
, desc
, error
)) {
2251 } else if (HasAttribute(*aline
, kAttributeIceUfrag
)) {
2252 if (!GetValue(*aline
, kAttributeIceUfrag
, &(session_td
->ice_ufrag
),
2256 } else if (HasAttribute(*aline
, kAttributeIcePwd
)) {
2257 if (!GetValue(*aline
, kAttributeIcePwd
, &(session_td
->ice_pwd
), error
)) {
2260 } else if (HasAttribute(*aline
, kAttributeIceLite
)) {
2261 session_td
->ice_mode
= cricket::ICEMODE_LITE
;
2262 } else if (HasAttribute(*aline
, kAttributeIceOption
)) {
2263 if (!ParseIceOptions(*aline
, &(session_td
->transport_options
), error
)) {
2266 } else if (HasAttribute(*aline
, kAttributeFingerprint
)) {
2267 if (session_td
->identity_fingerprint
.get()) {
2270 "Can't have multiple fingerprint attributes at the same level.",
2273 std::unique_ptr
<rtc::SSLFingerprint
> fingerprint
;
2274 if (!ParseFingerprintAttribute(*aline
, &fingerprint
, error
)) {
2277 session_td
->identity_fingerprint
= std::move(fingerprint
);
2278 } else if (HasAttribute(*aline
, kAttributeSetup
)) {
2279 if (!ParseDtlsSetup(*aline
, &(session_td
->connection_role
), error
)) {
2282 } else if (HasAttribute(*aline
, kAttributeMsidSemantics
)) {
2283 std::string semantics
;
2284 if (!GetValue(*aline
, kAttributeMsidSemantics
, &semantics
, error
)) {
2287 desc
->set_msid_supported(
2288 CaseInsensitiveFind(semantics
, kMediaStreamSemantic
));
2289 } else if (HasAttribute(*aline
, kAttributeExtmapAllowMixed
)) {
2290 desc
->set_extmap_allow_mixed(true);
2291 } else if (HasAttribute(*aline
, kAttributeExtmap
)) {
2292 RtpExtension extmap
;
2293 if (!ParseExtmap(*aline
, &extmap
, error
)) {
2296 session_extmaps
->push_back(extmap
);
2302 bool ParseGroupAttribute(absl::string_view line
,
2303 cricket::SessionDescription
* desc
,
2304 SdpParseError
* error
) {
2305 RTC_DCHECK(desc
!= NULL
);
2307 // RFC 5888 and draft-holmberg-mmusic-sdp-bundle-negotiation-00
2308 // a=group:BUNDLE video voice
2309 std::vector
<absl::string_view
> fields
=
2310 rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterSpaceChar
);
2311 std::string semantics
;
2312 if (!GetValue(fields
[0], kAttributeGroup
, &semantics
, error
)) {
2315 cricket::ContentGroup
group(semantics
);
2316 for (size_t i
= 1; i
< fields
.size(); ++i
) {
2317 group
.AddContentName(fields
[i
]);
2319 desc
->AddGroup(group
);
2323 static bool ParseFingerprintAttribute(
2324 absl::string_view line
,
2325 std::unique_ptr
<rtc::SSLFingerprint
>* fingerprint
,
2326 SdpParseError
* error
) {
2327 std::vector
<absl::string_view
> fields
=
2328 rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterSpaceChar
);
2329 const size_t expected_fields
= 2;
2330 if (fields
.size() != expected_fields
) {
2331 return ParseFailedExpectFieldNum(line
, expected_fields
, error
);
2334 // The first field here is "fingerprint:<hash>.
2335 std::string algorithm
;
2336 if (!GetValue(fields
[0], kAttributeFingerprint
, &algorithm
, error
)) {
2340 // Downcase the algorithm. Note that we don't need to downcase the
2341 // fingerprint because hex_decode can handle upper-case.
2342 absl::c_transform(algorithm
, algorithm
.begin(), ::tolower
);
2344 // The second field is the digest value. De-hexify it.
2346 rtc::SSLFingerprint::CreateUniqueFromRfc4572(algorithm
, fields
[1]);
2347 if (!*fingerprint
) {
2348 return ParseFailed(line
, "Failed to create fingerprint from the digest.",
2355 static bool ParseDtlsSetup(absl::string_view line
,
2356 cricket::ConnectionRole
* role_ptr
,
2357 SdpParseError
* error
) {
2358 // setup-attr = "a=setup:" role
2359 // role = "active" / "passive" / "actpass" / "holdconn"
2360 std::vector
<absl::string_view
> fields
=
2361 rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterColonChar
);
2362 const size_t expected_fields
= 2;
2363 if (fields
.size() != expected_fields
) {
2364 return ParseFailedExpectFieldNum(line
, expected_fields
, error
);
2366 if (absl::optional
<cricket::ConnectionRole
> role
=
2367 cricket::StringToConnectionRole(fields
[1]);
2372 return ParseFailed(line
, "Invalid attribute value.", error
);
2375 static bool ParseMsidAttribute(absl::string_view line
,
2376 std::vector
<std::string
>* stream_ids
,
2377 std::string
* track_id
,
2378 SdpParseError
* error
) {
2379 // https://datatracker.ietf.org/doc/rfc8830/
2380 // a=msid:<msid-value>
2381 // msid-value = msid-id [ SP msid-appdata ]
2382 // msid-id = 1*64token-char ; see RFC 4566
2383 // msid-appdata = 1*64token-char ; see RFC 4566
2384 // Note that JSEP stipulates not sending msid-appdata so
2385 // a=msid:<stream id> <track id>
2386 // is supported for backward compability reasons only.
2387 std::vector
<std::string
> fields
;
2388 size_t num_fields
= rtc::tokenize(line
.substr(kLinePrefixLength
),
2389 kSdpDelimiterSpaceChar
, &fields
);
2390 if (num_fields
< 1 || num_fields
> 2) {
2391 return ParseFailed(line
, "Expected a stream ID and optionally a track ID",
2394 if (num_fields
== 1) {
2395 if (line
.back() == kSdpDelimiterSpaceChar
) {
2396 return ParseFailed(line
, "Missing track ID in msid attribute.", error
);
2398 if (!track_id
->empty()) {
2399 fields
.push_back(*track_id
);
2401 // Ending with an empty string track will cause a random track id
2402 // to be generated later in the process.
2403 fields
.push_back("");
2406 RTC_DCHECK_EQ(fields
.size(), 2);
2408 // All track ids should be the same within an m section in a Unified Plan SDP.
2409 if (!track_id
->empty() && track_id
->compare(fields
[1]) != 0) {
2411 line
, "Two different track IDs in msid attribute in one m= section",
2414 *track_id
= fields
[1];
2417 std::string new_stream_id
;
2418 if (!GetValue(fields
[0], kAttributeMsid
, &new_stream_id
, error
)) {
2421 if (new_stream_id
.empty()) {
2422 return ParseFailed(line
, "Missing stream ID in msid attribute.", error
);
2424 // The special value "-" indicates "no MediaStream".
2425 if (new_stream_id
.compare(kNoStreamMsid
) != 0 &&
2426 !absl::c_any_of(*stream_ids
,
2427 [&new_stream_id
](const std::string
& existing_stream_id
) {
2428 return new_stream_id
== existing_stream_id
;
2430 stream_ids
->push_back(new_stream_id
);
2435 static void RemoveInvalidRidDescriptions(const std::vector
<int>& payload_types
,
2436 std::vector
<RidDescription
>* rids
) {
2438 std::set
<std::string
> to_remove
;
2439 std::set
<std::string
> unique_rids
;
2441 // Check the rids to see which ones should be removed.
2442 for (RidDescription
& rid
: *rids
) {
2443 // In the case of a duplicate, the entire "a=rid" line, and all "a=rid"
2444 // lines with rid-ids that duplicate this line, are discarded and MUST NOT
2445 // be included in the SDP Answer.
2446 auto pair
= unique_rids
.insert(rid
.rid
);
2447 // Insert will "fail" if element already exists.
2449 to_remove
.insert(rid
.rid
);
2453 // If the "a=rid" line contains a "pt=", the list of payload types
2454 // is verified against the list of valid payload types for the media
2455 // section (that is, those listed on the "m=" line). Any PT missing
2456 // from the "m=" line is discarded from the set of values in the
2457 // "pt=". If no values are left in the "pt=" parameter after this
2458 // processing, then the "a=rid" line is discarded.
2459 if (rid
.payload_types
.empty()) {
2460 // If formats were not specified, rid should not be removed.
2464 // Note: Spec does not mention how to handle duplicate formats.
2465 // Media section does not handle duplicates either.
2466 std::set
<int> removed_formats
;
2467 for (int payload_type
: rid
.payload_types
) {
2468 if (!absl::c_linear_search(payload_types
, payload_type
)) {
2469 removed_formats
.insert(payload_type
);
2473 rid
.payload_types
.erase(
2474 std::remove_if(rid
.payload_types
.begin(), rid
.payload_types
.end(),
2475 [&removed_formats
](int format
) {
2476 return removed_formats
.count(format
) > 0;
2478 rid
.payload_types
.end());
2480 // If all formats were removed then remove the rid alogether.
2481 if (rid
.payload_types
.empty()) {
2482 to_remove
.insert(rid
.rid
);
2486 // Remove every rid description that appears in the to_remove list.
2487 if (!to_remove
.empty()) {
2488 rids
->erase(std::remove_if(rids
->begin(), rids
->end(),
2489 [&to_remove
](const RidDescription
& rid
) {
2490 return to_remove
.count(rid
.rid
) > 0;
2496 // Create a new list (because SimulcastLayerList is immutable) without any
2497 // layers that have a rid in the to_remove list.
2498 // If a group of alternatives is empty after removing layers, the group should
2499 // be removed altogether.
2500 static SimulcastLayerList
RemoveRidsFromSimulcastLayerList(
2501 const std::set
<std::string
>& to_remove
,
2502 const SimulcastLayerList
& layers
) {
2503 SimulcastLayerList result
;
2504 for (const std::vector
<SimulcastLayer
>& vector
: layers
) {
2505 std::vector
<SimulcastLayer
> new_layers
;
2506 for (const SimulcastLayer
& layer
: vector
) {
2507 if (to_remove
.find(layer
.rid
) == to_remove
.end()) {
2508 new_layers
.push_back(layer
);
2511 // If all layers were removed, do not add an entry.
2512 if (!new_layers
.empty()) {
2513 result
.AddLayerWithAlternatives(new_layers
);
2520 // Will remove Simulcast Layers if:
2521 // 1. They appear in both send and receive directions.
2522 // 2. They do not appear in the list of `valid_rids`.
2523 static void RemoveInvalidRidsFromSimulcast(
2524 const std::vector
<RidDescription
>& valid_rids
,
2525 SimulcastDescription
* simulcast
) {
2526 RTC_DCHECK(simulcast
);
2527 std::set
<std::string
> to_remove
;
2528 std::vector
<SimulcastLayer
> all_send_layers
=
2529 simulcast
->send_layers().GetAllLayers();
2530 std::vector
<SimulcastLayer
> all_receive_layers
=
2531 simulcast
->receive_layers().GetAllLayers();
2533 // If a rid appears in both send and receive directions, remove it from both.
2534 // This algorithm runs in O(n^2) time, but for small n (as is the case with
2535 // simulcast layers) it should still perform well.
2536 for (const SimulcastLayer
& send_layer
: all_send_layers
) {
2537 if (absl::c_any_of(all_receive_layers
,
2538 [&send_layer
](const SimulcastLayer
& layer
) {
2539 return layer
.rid
== send_layer
.rid
;
2541 to_remove
.insert(send_layer
.rid
);
2545 // Add any rid that is not in the valid list to the remove set.
2546 for (const SimulcastLayer
& send_layer
: all_send_layers
) {
2547 if (absl::c_none_of(valid_rids
, [&send_layer
](const RidDescription
& rid
) {
2548 return send_layer
.rid
== rid
.rid
&&
2549 rid
.direction
== cricket::RidDirection::kSend
;
2551 to_remove
.insert(send_layer
.rid
);
2555 // Add any rid that is not in the valid list to the remove set.
2556 for (const SimulcastLayer
& receive_layer
: all_receive_layers
) {
2557 if (absl::c_none_of(
2558 valid_rids
, [&receive_layer
](const RidDescription
& rid
) {
2559 return receive_layer
.rid
== rid
.rid
&&
2560 rid
.direction
== cricket::RidDirection::kReceive
;
2562 to_remove
.insert(receive_layer
.rid
);
2566 simulcast
->send_layers() =
2567 RemoveRidsFromSimulcastLayerList(to_remove
, simulcast
->send_layers());
2568 simulcast
->receive_layers() =
2569 RemoveRidsFromSimulcastLayerList(to_remove
, simulcast
->receive_layers());
2573 // PT encoding media type clock rate channels
2581 // 6 DVI4 A 16,000 1
2585 // 10 L16 A 44,100 2
2586 // 11 L16 A 44,100 1
2587 // 12 QCELP A 8,000 1
2589 // 14 MPA A 90,000 (see text)
2590 // 15 G728 A 8,000 1
2591 // 16 DVI4 A 11,025 1
2592 // 17 DVI4 A 22,050 1
2593 // 18 G729 A 8,000 1
2594 struct StaticPayloadAudioCodec
{
2599 static const StaticPayloadAudioCodec kStaticPayloadAudioCodecs
[] = {
2600 {"PCMU", 8000, 1}, {"reserved", 0, 0}, {"reserved", 0, 0},
2601 {"GSM", 8000, 1}, {"G723", 8000, 1}, {"DVI4", 8000, 1},
2602 {"DVI4", 16000, 1}, {"LPC", 8000, 1}, {"PCMA", 8000, 1},
2603 {"G722", 8000, 1}, {"L16", 44100, 2}, {"L16", 44100, 1},
2604 {"QCELP", 8000, 1}, {"CN", 8000, 1}, {"MPA", 90000, 1},
2605 {"G728", 8000, 1}, {"DVI4", 11025, 1}, {"DVI4", 22050, 1},
2609 void MaybeCreateStaticPayloadAudioCodecs(const std::vector
<int>& fmts
,
2610 AudioContentDescription
* media_desc
) {
2614 RTC_DCHECK(media_desc
->codecs().empty());
2615 for (int payload_type
: fmts
) {
2616 if (!media_desc
->HasCodec(payload_type
) && payload_type
>= 0 &&
2617 static_cast<uint32_t>(payload_type
) <
2618 arraysize(kStaticPayloadAudioCodecs
)) {
2619 std::string encoding_name
= kStaticPayloadAudioCodecs
[payload_type
].name
;
2620 int clock_rate
= kStaticPayloadAudioCodecs
[payload_type
].clockrate
;
2621 size_t channels
= kStaticPayloadAudioCodecs
[payload_type
].channels
;
2622 media_desc
->AddCodec(cricket::CreateAudioCodec(
2623 payload_type
, encoding_name
, clock_rate
, channels
));
2629 static std::unique_ptr
<C
> ParseContentDescription(
2630 absl::string_view message
,
2631 const cricket::MediaType media_type
,
2633 absl::string_view protocol
,
2634 const std::vector
<int>& payload_types
,
2636 std::string
* content_name
,
2638 int* msid_signaling
,
2639 TransportDescription
* transport
,
2640 std::vector
<std::unique_ptr
<JsepIceCandidate
>>* candidates
,
2641 webrtc::SdpParseError
* error
) {
2642 auto media_desc
= std::make_unique
<C
>();
2643 media_desc
->set_extmap_allow_mixed_enum(MediaContentDescription::kNo
);
2644 if (!ParseContent(message
, media_type
, mline_index
, protocol
, payload_types
,
2645 pos
, content_name
, bundle_only
, msid_signaling
,
2646 media_desc
.get(), transport
, candidates
, error
)) {
2649 // Sort the codecs according to the m-line fmt list.
2650 std::unordered_map
<int, int> payload_type_preferences
;
2651 // "size + 1" so that the lowest preference payload type has a preference of
2652 // 1, which is greater than the default (0) for payload types not in the fmt
2654 int preference
= static_cast<int>(payload_types
.size() + 1);
2655 for (int pt
: payload_types
) {
2656 payload_type_preferences
[pt
] = preference
--;
2658 std::vector
<cricket::Codec
> codecs
= media_desc
->codecs();
2659 absl::c_sort(codecs
, [&payload_type_preferences
](const cricket::Codec
& a
,
2660 const cricket::Codec
& b
) {
2661 return payload_type_preferences
[a
.id
] > payload_type_preferences
[b
.id
];
2663 media_desc
->set_codecs(codecs
);
2667 bool ParseMediaDescription(
2668 absl::string_view message
,
2669 const TransportDescription
& session_td
,
2670 const RtpHeaderExtensions
& session_extmaps
,
2672 const rtc::SocketAddress
& session_connection_addr
,
2673 cricket::SessionDescription
* desc
,
2674 std::vector
<std::unique_ptr
<JsepIceCandidate
>>* candidates
,
2675 SdpParseError
* error
) {
2676 RTC_DCHECK(desc
!= NULL
);
2677 int mline_index
= -1;
2678 int msid_signaling
= 0;
2680 // Zero or more media descriptions
2682 // m=<media> <port> <proto> <fmt>
2683 while (absl::optional
<absl::string_view
> mline
=
2684 GetLineWithType(message
, pos
, kLineTypeMedia
)) {
2687 std::vector
<absl::string_view
> fields
=
2688 rtc::split(mline
->substr(kLinePrefixLength
), kSdpDelimiterSpaceChar
);
2690 const size_t expected_min_fields
= 4;
2691 if (fields
.size() < expected_min_fields
) {
2692 return ParseFailedExpectMinFieldNum(*mline
, expected_min_fields
, error
);
2694 bool port_rejected
= false;
2696 // To reject an offered stream, the port number in the corresponding stream
2697 // in the answer MUST be set to zero.
2698 if (fields
[1] == kMediaPortRejected
) {
2699 port_rejected
= true;
2703 if (!rtc::FromString
<int>(fields
[1], &port
) || !IsValidPort(port
)) {
2704 return ParseFailed(*mline
, "The port number is invalid", error
);
2706 absl::string_view protocol
= fields
[2];
2709 std::vector
<int> payload_types
;
2710 if (cricket::IsRtpProtocol(protocol
)) {
2711 for (size_t j
= 3; j
< fields
.size(); ++j
) {
2713 if (!GetPayloadTypeFromString(*mline
, fields
[j
], &pl
, error
)) {
2716 payload_types
.push_back(pl
);
2720 // Make a temporary TransportDescription based on `session_td`.
2721 // Some of this gets overwritten by ParseContent.
2722 TransportDescription
transport(
2723 session_td
.transport_options
, session_td
.ice_ufrag
, session_td
.ice_pwd
,
2724 session_td
.ice_mode
, session_td
.connection_role
,
2725 session_td
.identity_fingerprint
.get());
2727 std::unique_ptr
<MediaContentDescription
> content
;
2728 std::string content_name
;
2729 bool bundle_only
= false;
2730 int section_msid_signaling
= 0;
2731 absl::string_view media_type
= fields
[0];
2732 if ((media_type
== kMediaTypeVideo
|| media_type
== kMediaTypeAudio
) &&
2733 !cricket::IsRtpProtocol(protocol
)) {
2734 return ParseFailed(*mline
, "Unsupported protocol for media type", error
);
2736 if (media_type
== kMediaTypeVideo
) {
2737 content
= ParseContentDescription
<VideoContentDescription
>(
2738 message
, cricket::MEDIA_TYPE_VIDEO
, mline_index
, protocol
,
2739 payload_types
, pos
, &content_name
, &bundle_only
,
2740 §ion_msid_signaling
, &transport
, candidates
, error
);
2741 } else if (media_type
== kMediaTypeAudio
) {
2742 content
= ParseContentDescription
<AudioContentDescription
>(
2743 message
, cricket::MEDIA_TYPE_AUDIO
, mline_index
, protocol
,
2744 payload_types
, pos
, &content_name
, &bundle_only
,
2745 §ion_msid_signaling
, &transport
, candidates
, error
);
2746 } else if (media_type
== kMediaTypeData
&& cricket::IsDtlsSctp(protocol
)) {
2747 // The draft-03 format is:
2748 // m=application <port> DTLS/SCTP <sctp-port>...
2749 // use_sctpmap should be false.
2750 // The draft-26 format is:
2751 // m=application <port> UDP/DTLS/SCTP webrtc-datachannel
2752 // use_sctpmap should be false.
2753 auto data_desc
= std::make_unique
<SctpDataContentDescription
>();
2754 // Default max message size is 64K
2755 // according to draft-ietf-mmusic-sctp-sdp-26
2756 data_desc
->set_max_message_size(kDefaultSctpMaxMessageSize
);
2758 if (rtc::FromString(fields
[3], &p
)) {
2759 data_desc
->set_port(p
);
2760 } else if (fields
[3] == kDefaultSctpmapProtocol
) {
2761 data_desc
->set_use_sctpmap(false);
2763 if (!ParseContent(message
, cricket::MEDIA_TYPE_DATA
, mline_index
,
2764 protocol
, payload_types
, pos
, &content_name
,
2765 &bundle_only
, §ion_msid_signaling
, data_desc
.get(),
2766 &transport
, candidates
, error
)) {
2769 data_desc
->set_protocol(protocol
);
2770 content
= std::move(data_desc
);
2772 RTC_LOG(LS_WARNING
) << "Unsupported media type: " << *mline
;
2773 auto unsupported_desc
=
2774 std::make_unique
<UnsupportedContentDescription
>(media_type
);
2775 if (!ParseContent(message
, cricket::MEDIA_TYPE_UNSUPPORTED
, mline_index
,
2776 protocol
, payload_types
, pos
, &content_name
,
2777 &bundle_only
, §ion_msid_signaling
,
2778 unsupported_desc
.get(), &transport
, candidates
,
2782 unsupported_desc
->set_protocol(protocol
);
2783 content
= std::move(unsupported_desc
);
2785 if (!content
.get()) {
2786 // ParseContentDescription returns NULL if failed.
2790 msid_signaling
|= section_msid_signaling
;
2792 bool content_rejected
= false;
2793 // A port of 0 is not interpreted as a rejected m= section when it's
2794 // used along with a=bundle-only.
2796 if (!port_rejected
) {
2797 // Usage of bundle-only with a nonzero port is unspecified. So just
2798 // ignore bundle-only if we see this.
2799 bundle_only
= false;
2801 << "a=bundle-only attribute observed with a nonzero "
2802 "port; this usage is unspecified so the attribute is being "
2806 // If not using bundle-only, interpret port 0 in the normal way; the m=
2807 // section is being rejected.
2808 content_rejected
= port_rejected
;
2811 if (content
->as_unsupported()) {
2812 content_rejected
= true;
2813 } else if (cricket::IsRtpProtocol(protocol
) && !content
->as_sctp()) {
2814 content
->set_protocol(std::string(protocol
));
2816 if (!session_extmaps
.empty() &&
2817 !content
->rtp_header_extensions().empty()) {
2818 return ParseFailed("",
2819 "The a=extmap MUST be either all session level or "
2823 for (size_t i
= 0; i
< session_extmaps
.size(); ++i
) {
2824 content
->AddRtpHeaderExtension(session_extmaps
[i
]);
2826 } else if (content
->as_sctp()) {
2827 // Do nothing, it's OK
2829 RTC_LOG(LS_WARNING
) << "Parse failed with unknown protocol " << protocol
;
2833 // Use the session level connection address if the media level addresses are
2835 rtc::SocketAddress address
;
2836 address
= content
->connection_address().IsNil()
2837 ? session_connection_addr
2838 : content
->connection_address();
2839 address
.SetPort(port
);
2840 content
->set_connection_address(address
);
2842 desc
->AddContent(content_name
,
2843 cricket::IsDtlsSctp(protocol
) ? MediaProtocolType::kSctp
2844 : MediaProtocolType::kRtp
,
2845 content_rejected
, bundle_only
, std::move(content
));
2846 // Create TransportInfo with the media level "ice-pwd" and "ice-ufrag".
2847 desc
->AddTransportInfo(TransportInfo(content_name
, transport
));
2850 desc
->set_msid_signaling(msid_signaling
);
2852 size_t end_of_message
= message
.size();
2853 if (mline_index
== -1 && *pos
!= end_of_message
) {
2854 ParseFailed(message
, *pos
, "Expects m line.", error
);
2860 bool VerifyCodec(const cricket::Codec
& codec
) {
2861 // Codec has not been populated correctly unless the name has been set. This
2862 // can happen if an SDP has an fmtp or rtcp-fb with a payload type but doesn't
2863 // have a corresponding "rtpmap" line.
2864 return !codec
.name
.empty();
2867 bool VerifyAudioCodecs(const AudioContentDescription
* audio_desc
) {
2868 return absl::c_all_of(audio_desc
->codecs(), &VerifyCodec
);
2871 bool VerifyVideoCodecs(const VideoContentDescription
* video_desc
) {
2872 return absl::c_all_of(video_desc
->codecs(), &VerifyCodec
);
2875 void AddParameters(const cricket::CodecParameterMap
& parameters
,
2876 cricket::Codec
* codec
) {
2877 for (const auto& entry
: parameters
) {
2878 const std::string
& key
= entry
.first
;
2879 const std::string
& value
= entry
.second
;
2880 codec
->SetParam(key
, value
);
2884 void AddFeedbackParameter(const cricket::FeedbackParam
& feedback_param
,
2885 cricket::Codec
* codec
) {
2886 codec
->AddFeedbackParam(feedback_param
);
2889 void AddFeedbackParameters(const cricket::FeedbackParams
& feedback_params
,
2890 cricket::Codec
* codec
) {
2891 for (const cricket::FeedbackParam
& param
: feedback_params
.params()) {
2892 codec
->AddFeedbackParam(param
);
2896 // Gets the current codec setting associated with `payload_type`. If there
2897 // is no Codec associated with that payload type it returns an empty codec
2898 // with that payload type.
2900 T
GetCodecWithPayloadType(cricket::MediaType type
,
2901 const std::vector
<T
>& codecs
,
2903 const T
* codec
= FindCodecById(codecs
, payload_type
);
2906 // Return empty codec with `payload_type`.
2907 if (type
== cricket::MEDIA_TYPE_AUDIO
) {
2908 return cricket::CreateAudioCodec(payload_type
, "", 0, 0);
2910 return cricket::CreateVideoCodec(payload_type
, "");
2914 // Updates or creates a new codec entry in the media description.
2915 template <class T
, class U
>
2916 void AddOrReplaceCodec(MediaContentDescription
* content_desc
, const U
& codec
) {
2917 T
* desc
= static_cast<T
*>(content_desc
);
2918 std::vector
<U
> codecs
= desc
->codecs();
2920 for (U
& existing_codec
: codecs
) {
2921 if (codec
.id
== existing_codec
.id
) {
2922 // Overwrite existing codec with the new codec.
2923 existing_codec
= codec
;
2929 desc
->AddCodec(codec
);
2932 desc
->set_codecs(codecs
);
2935 // Adds or updates existing codec corresponding to `payload_type` according
2937 template <class T
, class U
>
2938 void UpdateCodec(MediaContentDescription
* content_desc
,
2940 const cricket::CodecParameterMap
& parameters
) {
2941 // Codec might already have been populated (from rtpmap).
2942 U new_codec
= GetCodecWithPayloadType(content_desc
->type(),
2943 static_cast<T
*>(content_desc
)->codecs(),
2945 AddParameters(parameters
, &new_codec
);
2946 AddOrReplaceCodec
<T
, U
>(content_desc
, new_codec
);
2949 // Adds or updates existing codec corresponding to `payload_type` according
2950 // to `feedback_param`.
2951 template <class T
, class U
>
2952 void UpdateCodec(MediaContentDescription
* content_desc
,
2954 const cricket::FeedbackParam
& feedback_param
) {
2955 // Codec might already have been populated (from rtpmap).
2956 U new_codec
= GetCodecWithPayloadType(content_desc
->type(),
2957 static_cast<T
*>(content_desc
)->codecs(),
2959 AddFeedbackParameter(feedback_param
, &new_codec
);
2960 AddOrReplaceCodec
<T
, U
>(content_desc
, new_codec
);
2963 // Adds or updates existing video codec corresponding to `payload_type`
2964 // according to `packetization`.
2965 void UpdateVideoCodecPacketization(VideoContentDescription
* video_desc
,
2967 absl::string_view packetization
) {
2968 if (packetization
!= cricket::kPacketizationParamRaw
) {
2969 // Ignore unsupported packetization attribute.
2973 // Codec might already have been populated (from rtpmap).
2974 cricket::VideoCodec codec
= GetCodecWithPayloadType(
2975 video_desc
->type(), video_desc
->codecs(), payload_type
);
2976 codec
.packetization
= std::string(packetization
);
2977 AddOrReplaceCodec
<VideoContentDescription
, cricket::VideoCodec
>(video_desc
,
2982 absl::optional
<T
> PopWildcardCodec(std::vector
<T
>* codecs
) {
2983 for (auto iter
= codecs
->begin(); iter
!= codecs
->end(); ++iter
) {
2984 if (iter
->id
== kWildcardPayloadType
) {
2985 T wildcard_codec
= *iter
;
2986 codecs
->erase(iter
);
2987 return wildcard_codec
;
2990 return absl::nullopt
;
2994 void UpdateFromWildcardCodecs(cricket::MediaContentDescriptionImpl
<T
>* desc
) {
2995 auto codecs
= desc
->codecs();
2996 absl::optional
<T
> wildcard_codec
= PopWildcardCodec(&codecs
);
2997 if (!wildcard_codec
) {
3000 for (auto& codec
: codecs
) {
3001 AddFeedbackParameters(wildcard_codec
->feedback_params
, &codec
);
3003 desc
->set_codecs(codecs
);
3006 void AddAudioAttribute(const std::string
& name
,
3007 absl::string_view value
,
3008 AudioContentDescription
* audio_desc
) {
3009 if (value
.empty()) {
3012 std::vector
<cricket::AudioCodec
> codecs
= audio_desc
->codecs();
3013 for (cricket::AudioCodec
& codec
: codecs
) {
3014 codec
.params
[name
] = std::string(value
);
3016 audio_desc
->set_codecs(codecs
);
3019 bool ParseContent(absl::string_view message
,
3020 const cricket::MediaType media_type
,
3022 absl::string_view protocol
,
3023 const std::vector
<int>& payload_types
,
3025 std::string
* content_name
,
3027 int* msid_signaling
,
3028 MediaContentDescription
* media_desc
,
3029 TransportDescription
* transport
,
3030 std::vector
<std::unique_ptr
<JsepIceCandidate
>>* candidates
,
3031 SdpParseError
* error
) {
3032 RTC_DCHECK(media_desc
!= NULL
);
3033 RTC_DCHECK(content_name
!= NULL
);
3034 RTC_DCHECK(transport
!= NULL
);
3036 if (media_type
== cricket::MEDIA_TYPE_AUDIO
) {
3037 MaybeCreateStaticPayloadAudioCodecs(payload_types
, media_desc
->as_audio());
3040 // The media level "ice-ufrag" and "ice-pwd".
3041 // The candidates before update the media level "ice-pwd" and "ice-ufrag".
3042 Candidates candidates_orig
;
3043 std::string mline_id
;
3044 // Tracks created out of the ssrc attributes.
3045 StreamParamsVec tracks
;
3046 SsrcInfoVec ssrc_infos
;
3047 SsrcGroupVec ssrc_groups
;
3048 std::string maxptime_as_string
;
3049 std::string ptime_as_string
;
3050 std::vector
<std::string
> stream_ids
;
3051 std::string track_id
;
3052 SdpSerializer deserializer
;
3053 std::vector
<RidDescription
> rids
;
3054 SimulcastDescription simulcast
;
3056 // Loop until the next m line
3057 while (!IsLineType(message
, kLineTypeMedia
, *pos
)) {
3058 absl::optional
<absl::string_view
> line
= GetLine(message
, pos
);
3059 if (!line
.has_value()) {
3060 if (*pos
>= message
.size()) {
3061 break; // Done parsing
3063 return ParseFailed(message
, *pos
, "Invalid SDP line.", error
);
3068 // b=* (zero or more bandwidth information lines)
3069 if (IsLineType(*line
, kLineTypeSessionBandwidth
)) {
3070 std::string bandwidth
;
3071 std::string bandwidth_type
;
3072 if (!rtc::tokenize_first(line
->substr(kLinePrefixLength
),
3073 kSdpDelimiterColonChar
, &bandwidth_type
,
3077 "b= syntax error, does not match b=<modifier>:<bandwidth-value>.",
3080 if (!(bandwidth_type
== kApplicationSpecificBandwidth
||
3081 bandwidth_type
== kTransportSpecificBandwidth
)) {
3082 // Ignore unknown bandwidth types.
3086 if (!GetValueFromString(*line
, bandwidth
, &b
, error
)) {
3089 // TODO(deadbeef): Historically, applications may be setting a value
3090 // of -1 to mean "unset any previously set bandwidth limit", even
3091 // though ommitting the "b=AS" entirely will do just that. Once we've
3092 // transitioned applications to doing the right thing, it would be
3093 // better to treat this as a hard error instead of just ignoring it.
3094 if (bandwidth_type
== kApplicationSpecificBandwidth
&& b
== -1) {
3095 RTC_LOG(LS_WARNING
) << "Ignoring \"b=AS:-1\"; will be treated as \"no "
3096 "bandwidth limit\".";
3101 *line
, "b=" + bandwidth_type
+ " value can't be negative.", error
);
3103 // Convert values. Prevent integer overflow.
3104 if (bandwidth_type
== kApplicationSpecificBandwidth
) {
3105 b
= std::min(b
, INT_MAX
/ 1000) * 1000;
3107 b
= std::min(b
, INT_MAX
);
3109 media_desc
->set_bandwidth(b
);
3110 media_desc
->set_bandwidth_type(bandwidth_type
);
3114 // Parse the media level connection data.
3115 if (IsLineType(*line
, kLineTypeConnection
)) {
3116 rtc::SocketAddress addr
;
3117 if (!ParseConnectionData(*line
, &addr
, error
)) {
3120 media_desc
->set_connection_address(addr
);
3124 if (!IsLineType(*line
, kLineTypeAttributes
)) {
3125 // TODO(deadbeef): Handle other lines if needed.
3126 RTC_LOG(LS_VERBOSE
) << "Ignored line: " << *line
;
3130 // Handle attributes common to SCTP and RTP.
3131 if (HasAttribute(*line
, kAttributeMid
)) {
3133 // mid-attribute = "a=mid:" identification-tag
3134 // identification-tag = token
3135 // Use the mid identification-tag as the content name.
3136 if (!GetSingleTokenValue(*line
, kAttributeMid
, &mline_id
, error
)) {
3139 *content_name
= mline_id
;
3140 } else if (HasAttribute(*line
, kAttributeBundleOnly
)) {
3141 *bundle_only
= true;
3142 } else if (HasAttribute(*line
, kAttributeCandidate
)) {
3143 Candidate candidate
;
3144 if (!ParseCandidate(*line
, &candidate
, error
, false)) {
3147 // ParseCandidate will parse non-standard ufrag and password attributes,
3148 // since it's used for candidate trickling, but we only want to process
3149 // the "a=ice-ufrag"/"a=ice-pwd" values in a session description, so
3150 // strip them off at this point.
3151 candidate
.set_username(std::string());
3152 candidate
.set_password(std::string());
3153 candidates_orig
.push_back(candidate
);
3154 } else if (HasAttribute(*line
, kAttributeIceUfrag
)) {
3155 if (!GetValue(*line
, kAttributeIceUfrag
, &transport
->ice_ufrag
, error
)) {
3158 } else if (HasAttribute(*line
, kAttributeIcePwd
)) {
3159 if (!GetValue(*line
, kAttributeIcePwd
, &transport
->ice_pwd
, error
)) {
3162 } else if (HasAttribute(*line
, kAttributeIceOption
)) {
3163 if (!ParseIceOptions(*line
, &transport
->transport_options
, error
)) {
3166 } else if (HasAttribute(*line
, kAttributeFmtp
)) {
3167 if (!ParseFmtpAttributes(*line
, media_type
, media_desc
, error
)) {
3170 } else if (HasAttribute(*line
, kAttributeFingerprint
)) {
3171 std::unique_ptr
<rtc::SSLFingerprint
> fingerprint
;
3172 if (!ParseFingerprintAttribute(*line
, &fingerprint
, error
)) {
3175 transport
->identity_fingerprint
= std::move(fingerprint
);
3176 } else if (HasAttribute(*line
, kAttributeSetup
)) {
3177 if (!ParseDtlsSetup(*line
, &(transport
->connection_role
), error
)) {
3180 } else if (cricket::IsDtlsSctp(protocol
) &&
3181 media_type
== cricket::MEDIA_TYPE_DATA
) {
3183 // SCTP specific attributes
3185 if (HasAttribute(*line
, kAttributeSctpPort
)) {
3186 if (media_desc
->as_sctp()->use_sctpmap()) {
3188 *line
, "sctp-port attribute can't be used with sctpmap.", error
);
3191 if (!ParseSctpPort(*line
, &sctp_port
, error
)) {
3194 media_desc
->as_sctp()->set_port(sctp_port
);
3195 } else if (HasAttribute(*line
, kAttributeMaxMessageSize
)) {
3196 int max_message_size
;
3197 if (!ParseSctpMaxMessageSize(*line
, &max_message_size
, error
)) {
3200 media_desc
->as_sctp()->set_max_message_size(max_message_size
);
3201 } else if (HasAttribute(*line
, kAttributeSctpmap
)) {
3202 // Ignore a=sctpmap: from early versions of draft-ietf-mmusic-sctp-sdp
3205 } else if (cricket::IsRtpProtocol(protocol
)) {
3207 // RTP specific attributes
3209 if (HasAttribute(*line
, kAttributeRtcpMux
)) {
3210 media_desc
->set_rtcp_mux(true);
3211 } else if (HasAttribute(*line
, kAttributeRtcpReducedSize
)) {
3212 media_desc
->set_rtcp_reduced_size(true);
3213 } else if (HasAttribute(*line
, kAttributeRtcpRemoteEstimate
)) {
3214 media_desc
->set_remote_estimate(true);
3215 } else if (HasAttribute(*line
, kAttributeSsrcGroup
)) {
3216 if (!ParseSsrcGroupAttribute(*line
, &ssrc_groups
, error
)) {
3219 } else if (HasAttribute(*line
, kAttributeSsrc
)) {
3220 if (!ParseSsrcAttribute(*line
, &ssrc_infos
, msid_signaling
, error
)) {
3223 } else if (HasAttribute(*line
, kAttributeCrypto
)) {
3224 if (!ParseCryptoAttribute(*line
, media_desc
, error
)) {
3227 } else if (HasAttribute(*line
, kAttributeRtpmap
)) {
3228 if (!ParseRtpmapAttribute(*line
, media_type
, payload_types
, media_desc
,
3232 } else if (HasAttribute(*line
, kCodecParamMaxPTime
)) {
3233 if (!GetValue(*line
, kCodecParamMaxPTime
, &maxptime_as_string
, error
)) {
3236 } else if (HasAttribute(*line
, kAttributePacketization
)) {
3237 if (!ParsePacketizationAttribute(*line
, media_type
, media_desc
,
3241 } else if (HasAttribute(*line
, kAttributeRtcpFb
)) {
3242 if (!ParseRtcpFbAttribute(*line
, media_type
, media_desc
, error
)) {
3245 } else if (HasAttribute(*line
, kCodecParamPTime
)) {
3246 if (!GetValue(*line
, kCodecParamPTime
, &ptime_as_string
, error
)) {
3249 } else if (HasAttribute(*line
, kAttributeSendOnly
)) {
3250 media_desc
->set_direction(RtpTransceiverDirection::kSendOnly
);
3251 } else if (HasAttribute(*line
, kAttributeRecvOnly
)) {
3252 media_desc
->set_direction(RtpTransceiverDirection::kRecvOnly
);
3253 } else if (HasAttribute(*line
, kAttributeInactive
)) {
3254 media_desc
->set_direction(RtpTransceiverDirection::kInactive
);
3255 } else if (HasAttribute(*line
, kAttributeSendRecv
)) {
3256 media_desc
->set_direction(RtpTransceiverDirection::kSendRecv
);
3257 } else if (HasAttribute(*line
, kAttributeExtmapAllowMixed
)) {
3258 media_desc
->set_extmap_allow_mixed_enum(
3259 MediaContentDescription::kMedia
);
3260 } else if (HasAttribute(*line
, kAttributeExtmap
)) {
3261 RtpExtension extmap
;
3262 if (!ParseExtmap(*line
, &extmap
, error
)) {
3265 media_desc
->AddRtpHeaderExtension(extmap
);
3266 } else if (HasAttribute(*line
, kAttributeXGoogleFlag
)) {
3267 // Experimental attribute. Conference mode activates more aggressive
3268 // AEC and NS settings.
3269 // TODO(deadbeef): expose API to set these directly.
3270 std::string flag_value
;
3271 if (!GetValue(*line
, kAttributeXGoogleFlag
, &flag_value
, error
)) {
3274 if (flag_value
.compare(kValueConference
) == 0)
3275 media_desc
->set_conference_mode(true);
3276 } else if (HasAttribute(*line
, kAttributeMsid
)) {
3277 if (!ParseMsidAttribute(*line
, &stream_ids
, &track_id
, error
)) {
3280 *msid_signaling
|= cricket::kMsidSignalingMediaSection
;
3281 } else if (HasAttribute(*line
, kAttributeRid
)) {
3282 const size_t kRidPrefixLength
=
3283 kLinePrefixLength
+ arraysize(kAttributeRid
);
3284 if (line
->size() <= kRidPrefixLength
) {
3285 RTC_LOG(LS_INFO
) << "Ignoring empty RID attribute: " << *line
;
3288 RTCErrorOr
<RidDescription
> error_or_rid_description
=
3289 deserializer
.DeserializeRidDescription(
3290 line
->substr(kRidPrefixLength
));
3292 // Malformed a=rid lines are discarded.
3293 if (!error_or_rid_description
.ok()) {
3294 RTC_LOG(LS_INFO
) << "Ignoring malformed RID line: '" << *line
3296 << error_or_rid_description
.error().message();
3300 rids
.push_back(error_or_rid_description
.MoveValue());
3301 } else if (HasAttribute(*line
, kAttributeSimulcast
)) {
3302 const size_t kSimulcastPrefixLength
=
3303 kLinePrefixLength
+ arraysize(kAttributeSimulcast
);
3304 if (line
->size() <= kSimulcastPrefixLength
) {
3305 return ParseFailed(*line
, "Simulcast attribute is empty.", error
);
3308 if (!simulcast
.empty()) {
3309 return ParseFailed(*line
, "Multiple Simulcast attributes specified.",
3313 RTCErrorOr
<SimulcastDescription
> error_or_simulcast
=
3314 deserializer
.DeserializeSimulcastDescription(
3315 line
->substr(kSimulcastPrefixLength
));
3316 if (!error_or_simulcast
.ok()) {
3317 return ParseFailed(*line
,
3318 std::string("Malformed simulcast line: ") +
3319 error_or_simulcast
.error().message(),
3323 simulcast
= error_or_simulcast
.value();
3324 } else if (HasAttribute(*line
, kAttributeRtcp
)) {
3325 // Ignore and do not log a=rtcp line.
3326 // JSEP section 5.8.2 (media section parsing) says to ignore it.
3329 // Unrecognized attribute in RTP protocol.
3330 RTC_LOG(LS_VERBOSE
) << "Ignored line: " << *line
;
3334 // Only parse lines that we are interested of.
3335 RTC_LOG(LS_VERBOSE
) << "Ignored line: " << *line
;
3340 // Remove duplicate or inconsistent rids.
3341 RemoveInvalidRidDescriptions(payload_types
, &rids
);
3343 // If simulcast is specifed, split the rids into send and receive.
3344 // Rids that do not appear in simulcast attribute will be removed.
3345 std::vector
<RidDescription
> send_rids
;
3346 std::vector
<RidDescription
> receive_rids
;
3347 if (!simulcast
.empty()) {
3348 // Verify that the rids in simulcast match rids in sdp.
3349 RemoveInvalidRidsFromSimulcast(rids
, &simulcast
);
3351 // Use simulcast description to figure out Send / Receive RIDs.
3352 std::map
<std::string
, RidDescription
> rid_map
;
3353 for (const RidDescription
& rid
: rids
) {
3354 rid_map
[rid
.rid
] = rid
;
3357 for (const auto& layer
: simulcast
.send_layers().GetAllLayers()) {
3358 auto iter
= rid_map
.find(layer
.rid
);
3359 RTC_DCHECK(iter
!= rid_map
.end());
3360 send_rids
.push_back(iter
->second
);
3363 for (const auto& layer
: simulcast
.receive_layers().GetAllLayers()) {
3364 auto iter
= rid_map
.find(layer
.rid
);
3365 RTC_DCHECK(iter
!= rid_map
.end());
3366 receive_rids
.push_back(iter
->second
);
3369 media_desc
->set_simulcast_description(simulcast
);
3371 // RID is specified in RFC 8851, which identifies a lot of usages.
3372 // We only support RFC 8853 usage of RID, not anything else.
3373 // Ignore all RID parameters when a=simulcast is missing.
3374 // In particular do NOT do send_rids = rids;
3375 RTC_LOG(LS_VERBOSE
) << "Ignoring send_rids without simulcast";
3378 media_desc
->set_receive_rids(receive_rids
);
3380 // Create tracks from the `ssrc_infos`.
3381 // If the stream_id/track_id for all SSRCS are identical, one StreamParams
3382 // will be created in CreateTracksFromSsrcInfos, containing all the SSRCs from
3384 if (!ssrc_infos
.empty()) {
3385 CreateTracksFromSsrcInfos(ssrc_infos
, stream_ids
, track_id
, &tracks
,
3387 } else if (media_type
!= cricket::MEDIA_TYPE_DATA
&&
3388 (*msid_signaling
& cricket::kMsidSignalingMediaSection
)) {
3389 // If the stream_ids/track_id was signaled but SSRCs were unsignaled we
3390 // still create a track. This isn't done for data media types because
3391 // StreamParams aren't used for SCTP streams, and RTP data channels don't
3392 // support unsignaled SSRCs.
3393 // If track id was not specified, create a random one.
3394 if (track_id
.empty()) {
3395 track_id
= rtc::CreateRandomString(8);
3397 CreateTrackWithNoSsrcs(stream_ids
, track_id
, send_rids
, &tracks
);
3400 // Add the ssrc group to the track.
3401 for (const SsrcGroup
& ssrc_group
: ssrc_groups
) {
3402 if (ssrc_group
.ssrcs
.empty()) {
3405 uint32_t ssrc
= ssrc_group
.ssrcs
.front();
3406 for (StreamParams
& track
: tracks
) {
3407 if (track
.has_ssrc(ssrc
)) {
3408 track
.ssrc_groups
.push_back(ssrc_group
);
3413 // Add the new tracks to the `media_desc`.
3414 for (StreamParams
& track
: tracks
) {
3415 media_desc
->AddStream(track
);
3418 if (media_type
== cricket::MEDIA_TYPE_AUDIO
) {
3419 AudioContentDescription
* audio_desc
= media_desc
->as_audio();
3420 UpdateFromWildcardCodecs(audio_desc
);
3422 // Verify audio codec ensures that no audio codec has been populated with
3424 if (!VerifyAudioCodecs(audio_desc
)) {
3425 return ParseFailed("Failed to parse audio codecs correctly.", error
);
3427 AddAudioAttribute(kCodecParamMaxPTime
, maxptime_as_string
, audio_desc
);
3428 AddAudioAttribute(kCodecParamPTime
, ptime_as_string
, audio_desc
);
3431 if (media_type
== cricket::MEDIA_TYPE_VIDEO
) {
3432 VideoContentDescription
* video_desc
= media_desc
->as_video();
3433 UpdateFromWildcardCodecs(video_desc
);
3434 // Verify video codec ensures that no video codec has been populated with
3436 if (!VerifyVideoCodecs(video_desc
)) {
3437 return ParseFailed("Failed to parse video codecs correctly.", error
);
3442 // Update the candidates with the media level "ice-pwd" and "ice-ufrag".
3443 for (Candidate
& candidate
: candidates_orig
) {
3444 RTC_DCHECK(candidate
.username().empty() ||
3445 candidate
.username() == transport
->ice_ufrag
);
3446 candidate
.set_username(transport
->ice_ufrag
);
3447 RTC_DCHECK(candidate
.password().empty());
3448 candidate
.set_password(transport
->ice_pwd
);
3449 candidates
->push_back(
3450 std::make_unique
<JsepIceCandidate
>(mline_id
, mline_index
, candidate
));
3456 bool ParseSsrcAttribute(absl::string_view line
,
3457 SsrcInfoVec
* ssrc_infos
,
3458 int* msid_signaling
,
3459 SdpParseError
* error
) {
3460 RTC_DCHECK(ssrc_infos
!= NULL
);
3462 // a=ssrc:<ssrc-id> <attribute>
3463 // a=ssrc:<ssrc-id> <attribute>:<value>
3464 std::string field1
, field2
;
3465 if (!rtc::tokenize_first(line
.substr(kLinePrefixLength
),
3466 kSdpDelimiterSpaceChar
, &field1
, &field2
)) {
3467 const size_t expected_fields
= 2;
3468 return ParseFailedExpectFieldNum(line
, expected_fields
, error
);
3472 std::string ssrc_id_s
;
3473 if (!GetValue(field1
, kAttributeSsrc
, &ssrc_id_s
, error
)) {
3476 uint32_t ssrc_id
= 0;
3477 if (!GetValueFromString(line
, ssrc_id_s
, &ssrc_id
, error
)) {
3481 std::string attribute
;
3483 if (!rtc::tokenize_first(field2
, kSdpDelimiterColonChar
, &attribute
,
3485 rtc::StringBuilder description
;
3486 description
<< "Failed to get the ssrc attribute value from " << field2
3487 << ". Expected format <attribute>:<value>.";
3488 return ParseFailed(line
, description
.Release(), error
);
3491 // Check if there's already an item for this `ssrc_id`. Create a new one if
3494 absl::c_find_if(*ssrc_infos
, [ssrc_id
](const SsrcInfo
& ssrc_info
) {
3495 return ssrc_info
.ssrc_id
== ssrc_id
;
3497 if (ssrc_info_it
== ssrc_infos
->end()) {
3499 info
.ssrc_id
= ssrc_id
;
3500 ssrc_infos
->push_back(info
);
3501 ssrc_info_it
= ssrc_infos
->end() - 1;
3503 SsrcInfo
& ssrc_info
= *ssrc_info_it
;
3505 // Store the info to the `ssrc_info`.
3506 if (attribute
== kSsrcAttributeCname
) {
3509 ssrc_info
.cname
= value
;
3510 } else if (attribute
== kSsrcAttributeMsid
) {
3511 // draft-alvestrand-mmusic-msid-00
3512 // msid:identifier [appdata]
3513 std::vector
<absl::string_view
> fields
=
3514 rtc::split(value
, kSdpDelimiterSpaceChar
);
3515 if (fields
.size() < 1 || fields
.size() > 2) {
3517 line
, "Expected format \"msid:<identifier>[ <appdata>]\".", error
);
3519 ssrc_info
.stream_id
= std::string(fields
[0]);
3520 if (fields
.size() == 2) {
3521 ssrc_info
.track_id
= std::string(fields
[1]);
3523 *msid_signaling
|= cricket::kMsidSignalingSsrcAttribute
;
3525 RTC_LOG(LS_INFO
) << "Ignored unknown ssrc-specific attribute: " << line
;
3530 bool ParseSsrcGroupAttribute(absl::string_view line
,
3531 SsrcGroupVec
* ssrc_groups
,
3532 SdpParseError
* error
) {
3533 RTC_DCHECK(ssrc_groups
!= NULL
);
3535 // a=ssrc-group:<semantics> <ssrc-id> ...
3536 std::vector
<absl::string_view
> fields
=
3537 rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterSpaceChar
);
3538 const size_t expected_min_fields
= 2;
3539 if (fields
.size() < expected_min_fields
) {
3540 return ParseFailedExpectMinFieldNum(line
, expected_min_fields
, error
);
3542 std::string semantics
;
3543 if (!GetValue(fields
[0], kAttributeSsrcGroup
, &semantics
, error
)) {
3546 std::vector
<uint32_t> ssrcs
;
3547 for (size_t i
= 1; i
< fields
.size(); ++i
) {
3549 if (!GetValueFromString(line
, fields
[i
], &ssrc
, error
)) {
3552 // Reject duplicates. While not forbidden by RFC 5576,
3553 // they don't make sense.
3554 if (absl::c_linear_search(ssrcs
, ssrc
)) {
3555 return ParseFailed(line
, "Duplicate SSRC in ssrc-group", error
);
3557 ssrcs
.push_back(ssrc
);
3559 ssrc_groups
->push_back(SsrcGroup(semantics
, ssrcs
));
3563 bool ParseCryptoAttribute(absl::string_view line
,
3564 MediaContentDescription
* media_desc
,
3565 SdpParseError
* error
) {
3566 std::vector
<absl::string_view
> fields
=
3567 rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterSpaceChar
);
3569 // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
3570 const size_t expected_min_fields
= 3;
3571 if (fields
.size() < expected_min_fields
) {
3572 return ParseFailedExpectMinFieldNum(line
, expected_min_fields
, error
);
3574 std::string tag_value
;
3575 if (!GetValue(fields
[0], kAttributeCrypto
, &tag_value
, error
)) {
3579 if (!GetValueFromString(line
, tag_value
, &tag
, error
)) {
3582 const absl::string_view crypto_suite
= fields
[1];
3583 const absl::string_view key_params
= fields
[2];
3584 absl::string_view session_params
;
3585 if (fields
.size() > 3) {
3586 session_params
= fields
[3];
3589 media_desc
->AddCrypto(
3590 CryptoParams(tag
, crypto_suite
, key_params
, session_params
));
3594 // Updates or creates a new codec entry in the audio description with according
3595 // to `name`, `clockrate`, `bitrate`, and `channels`.
3596 void UpdateCodec(int payload_type
,
3597 absl::string_view name
,
3601 AudioContentDescription
* audio_desc
) {
3602 // Codec may already be populated with (only) optional parameters
3604 cricket::AudioCodec codec
= GetCodecWithPayloadType(
3605 audio_desc
->type(), audio_desc
->codecs(), payload_type
);
3606 codec
.name
= std::string(name
);
3607 codec
.clockrate
= clockrate
;
3608 codec
.bitrate
= bitrate
;
3609 codec
.channels
= channels
;
3610 AddOrReplaceCodec
<AudioContentDescription
, cricket::AudioCodec
>(audio_desc
,
3614 // Updates or creates a new codec entry in the video description according to
3615 // `name`, `width`, `height`, and `framerate`.
3616 void UpdateCodec(int payload_type
,
3617 absl::string_view name
,
3618 VideoContentDescription
* video_desc
) {
3619 // Codec may already be populated with (only) optional parameters
3621 cricket::VideoCodec codec
= GetCodecWithPayloadType(
3622 video_desc
->type(), video_desc
->codecs(), payload_type
);
3623 codec
.name
= std::string(name
);
3624 AddOrReplaceCodec
<VideoContentDescription
, cricket::VideoCodec
>(video_desc
,
3628 bool ParseRtpmapAttribute(absl::string_view line
,
3629 const cricket::MediaType media_type
,
3630 const std::vector
<int>& payload_types
,
3631 MediaContentDescription
* media_desc
,
3632 SdpParseError
* error
) {
3633 static const int kFirstDynamicPayloadTypeLowerRange
= 35;
3634 std::vector
<absl::string_view
> fields
=
3635 rtc::split(line
.substr(kLinePrefixLength
), kSdpDelimiterSpaceChar
);
3637 // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encodingparameters>]
3638 const size_t expected_min_fields
= 2;
3639 if (fields
.size() < expected_min_fields
) {
3640 return ParseFailedExpectMinFieldNum(line
, expected_min_fields
, error
);
3642 std::string payload_type_value
;
3643 if (!GetValue(fields
[0], kAttributeRtpmap
, &payload_type_value
, error
)) {
3646 int payload_type
= 0;
3647 if (!GetPayloadTypeFromString(line
, payload_type_value
, &payload_type
,
3652 if (!absl::c_linear_search(payload_types
, payload_type
)) {
3653 RTC_LOG(LS_WARNING
) << "Ignore rtpmap line that did not appear in the "
3654 "<fmt> of the m-line: "
3658 std::vector
<absl::string_view
> codec_params
= rtc::split(fields
[1], '/');
3659 // <encoding name>/<clock rate>[/<encodingparameters>]
3660 // 2 mandatory fields
3661 if (codec_params
.size() < 2 || codec_params
.size() > 3) {
3662 return ParseFailed(line
,
3663 "Expected format \"<encoding name>/<clock rate>"
3664 "[/<encodingparameters>]\".",
3667 const absl::string_view encoding_name
= codec_params
[0];
3669 if (!GetValueFromString(line
, codec_params
[1], &clock_rate
, error
)) {
3673 if (media_type
== cricket::MEDIA_TYPE_VIDEO
) {
3674 VideoContentDescription
* video_desc
= media_desc
->as_video();
3675 for (const cricket::VideoCodec
& existing_codec
: video_desc
->codecs()) {
3676 if (!existing_codec
.name
.empty() && payload_type
== existing_codec
.id
&&
3677 (!absl::EqualsIgnoreCase(encoding_name
, existing_codec
.name
) ||
3678 clock_rate
!= existing_codec
.clockrate
)) {
3679 rtc::StringBuilder description
;
3682 << (payload_type
< kFirstDynamicPayloadTypeLowerRange
3683 ? "statically assigned"
3685 << " payload type with conflicting codec name or clock rate.";
3686 return ParseFailed(line
, description
.Release(), error
);
3689 UpdateCodec(payload_type
, encoding_name
, video_desc
);
3690 } else if (media_type
== cricket::MEDIA_TYPE_AUDIO
) {
3692 // For audio streams, <encoding parameters> indicates the number
3693 // of audio channels. This parameter is OPTIONAL and may be
3694 // omitted if the number of channels is one, provided that no
3695 // additional parameters are needed.
3696 size_t channels
= 1;
3697 if (codec_params
.size() == 3) {
3698 if (!GetValueFromString(line
, codec_params
[2], &channels
, error
)) {
3702 if (channels
> kMaxNumberOfChannels
) {
3703 return ParseFailed(line
, "At most 24 channels are supported.", error
);
3706 AudioContentDescription
* audio_desc
= media_desc
->as_audio();
3707 for (const cricket::AudioCodec
& existing_codec
: audio_desc
->codecs()) {
3708 // TODO(crbug.com/1338902) re-add checks for clockrate and number of
3710 if (!existing_codec
.name
.empty() && payload_type
== existing_codec
.id
&&
3711 (!absl::EqualsIgnoreCase(encoding_name
, existing_codec
.name
))) {
3712 rtc::StringBuilder description
;
3715 << (payload_type
< kFirstDynamicPayloadTypeLowerRange
3716 ? "statically assigned"
3718 << " payload type with conflicting codec name or clock rate.";
3719 return ParseFailed(line
, description
.Release(), error
);
3722 UpdateCodec(payload_type
, encoding_name
, clock_rate
, 0, channels
,
3728 bool ParseFmtpParam(absl::string_view line
,
3729 std::string
* parameter
,
3731 SdpParseError
* error
) {
3732 if (!rtc::tokenize_first(line
, kSdpDelimiterEqualChar
, parameter
, value
)) {
3733 // Support for non-key-value lines like RFC 2198 or RFC 4733.
3735 *value
= std::string(line
);
3738 // a=fmtp:<payload_type> <param1>=<value1>; <param2>=<value2>; ...
3742 bool ParseFmtpAttributes(absl::string_view line
,
3743 const cricket::MediaType media_type
,
3744 MediaContentDescription
* media_desc
,
3745 SdpParseError
* error
) {
3746 if (media_type
!= cricket::MEDIA_TYPE_AUDIO
&&
3747 media_type
!= cricket::MEDIA_TYPE_VIDEO
) {
3751 std::string line_payload
;
3752 std::string line_params
;
3754 // https://tools.ietf.org/html/rfc4566#section-6
3755 // a=fmtp:<format> <format specific parameters>
3756 // At least two fields, whereas the second one is any of the optional
3758 if (!rtc::tokenize_first(line
.substr(kLinePrefixLength
),
3759 kSdpDelimiterSpaceChar
, &line_payload
,
3761 ParseFailedExpectMinFieldNum(line
, 2, error
);
3765 // Parse out the payload information.
3766 std::string payload_type_str
;
3767 if (!GetValue(line_payload
, kAttributeFmtp
, &payload_type_str
, error
)) {
3771 int payload_type
= 0;
3772 if (!GetPayloadTypeFromString(line_payload
, payload_type_str
, &payload_type
,
3777 // Parse out format specific parameters.
3778 cricket::CodecParameterMap codec_params
;
3779 for (absl::string_view param
:
3780 rtc::split(line_params
, kSdpDelimiterSemicolonChar
)) {
3783 if (!ParseFmtpParam(absl::StripAsciiWhitespace(param
), &name
, &value
,
3787 if (codec_params
.find(name
) != codec_params
.end()) {
3788 RTC_LOG(LS_INFO
) << "Overwriting duplicate fmtp parameter with key \""
3791 codec_params
[name
] = value
;
3794 if (media_type
== cricket::MEDIA_TYPE_AUDIO
) {
3795 UpdateCodec
<AudioContentDescription
, cricket::AudioCodec
>(
3796 media_desc
, payload_type
, codec_params
);
3797 } else if (media_type
== cricket::MEDIA_TYPE_VIDEO
) {
3798 UpdateCodec
<VideoContentDescription
, cricket::VideoCodec
>(
3799 media_desc
, payload_type
, codec_params
);
3804 bool ParsePacketizationAttribute(absl::string_view line
,
3805 const cricket::MediaType media_type
,
3806 MediaContentDescription
* media_desc
,
3807 SdpParseError
* error
) {
3808 if (media_type
!= cricket::MEDIA_TYPE_VIDEO
) {
3811 std::vector
<absl::string_view
> packetization_fields
=
3812 rtc::split(line
, kSdpDelimiterSpaceChar
);
3813 if (packetization_fields
.size() < 2) {
3814 return ParseFailedGetValue(line
, kAttributePacketization
, error
);
3816 std::string payload_type_string
;
3817 if (!GetValue(packetization_fields
[0], kAttributePacketization
,
3818 &payload_type_string
, error
)) {
3822 if (!GetPayloadTypeFromString(line
, payload_type_string
, &payload_type
,
3826 absl::string_view packetization
= packetization_fields
[1];
3827 UpdateVideoCodecPacketization(media_desc
->as_video(), payload_type
,
3832 bool ParseRtcpFbAttribute(absl::string_view line
,
3833 const cricket::MediaType media_type
,
3834 MediaContentDescription
* media_desc
,
3835 SdpParseError
* error
) {
3836 if (media_type
!= cricket::MEDIA_TYPE_AUDIO
&&
3837 media_type
!= cricket::MEDIA_TYPE_VIDEO
) {
3840 std::vector
<absl::string_view
> rtcp_fb_fields
=
3841 rtc::split(line
, kSdpDelimiterSpaceChar
);
3842 if (rtcp_fb_fields
.size() < 2) {
3843 return ParseFailedGetValue(line
, kAttributeRtcpFb
, error
);
3845 std::string payload_type_string
;
3846 if (!GetValue(rtcp_fb_fields
[0], kAttributeRtcpFb
, &payload_type_string
,
3850 int payload_type
= kWildcardPayloadType
;
3851 if (payload_type_string
!= "*") {
3852 if (!GetPayloadTypeFromString(line
, payload_type_string
, &payload_type
,
3857 absl::string_view id
= rtcp_fb_fields
[1];
3858 std::string param
= "";
3859 for (auto iter
= rtcp_fb_fields
.begin() + 2; iter
!= rtcp_fb_fields
.end();
3861 param
.append(iter
->data(), iter
->length());
3863 const cricket::FeedbackParam
feedback_param(id
, param
);
3865 if (media_type
== cricket::MEDIA_TYPE_AUDIO
) {
3866 UpdateCodec
<AudioContentDescription
, cricket::AudioCodec
>(
3867 media_desc
, payload_type
, feedback_param
);
3868 } else if (media_type
== cricket::MEDIA_TYPE_VIDEO
) {
3869 UpdateCodec
<VideoContentDescription
, cricket::VideoCodec
>(
3870 media_desc
, payload_type
, feedback_param
);
3875 } // namespace webrtc