
Normally the RTP data channel is capped at 30kbps, but by mangling the SDP string, one could get around this limitation. With this fix, SdpDeserialize will return an error if it detects this condition. BUG=280726 R=pthatcher@webrtc.org Review URL: https://codereview.webrtc.org/1196403004. Cr-Commit-Position: refs/heads/master@{#9499}
3108 lines
112 KiB
C++
3108 lines
112 KiB
C++
/*
|
|
* libjingle
|
|
* Copyright 2011 Google Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "talk/app/webrtc/webrtcsdp.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <ctype.h>
|
|
|
|
#include "talk/app/webrtc/jsepicecandidate.h"
|
|
#include "talk/app/webrtc/jsepsessiondescription.h"
|
|
#include "talk/media/base/codec.h"
|
|
#include "talk/media/base/constants.h"
|
|
#include "talk/media/base/cryptoparams.h"
|
|
#include "talk/media/base/rtputils.h"
|
|
#include "talk/media/sctp/sctpdataengine.h"
|
|
#include "webrtc/p2p/base/candidate.h"
|
|
#include "webrtc/p2p/base/constants.h"
|
|
#include "webrtc/p2p/base/port.h"
|
|
#include "talk/session/media/mediasession.h"
|
|
#include "webrtc/base/common.h"
|
|
#include "webrtc/base/logging.h"
|
|
#include "webrtc/base/messagedigest.h"
|
|
#include "webrtc/base/stringutils.h"
|
|
|
|
using cricket::AudioContentDescription;
|
|
using cricket::Candidate;
|
|
using cricket::Candidates;
|
|
using cricket::ContentDescription;
|
|
using cricket::ContentInfo;
|
|
using cricket::CryptoParams;
|
|
using cricket::DataContentDescription;
|
|
using cricket::ICE_CANDIDATE_COMPONENT_RTP;
|
|
using cricket::ICE_CANDIDATE_COMPONENT_RTCP;
|
|
using cricket::kCodecParamMaxBitrate;
|
|
using cricket::kCodecParamMaxPTime;
|
|
using cricket::kCodecParamMaxQuantization;
|
|
using cricket::kCodecParamMinBitrate;
|
|
using cricket::kCodecParamMinPTime;
|
|
using cricket::kCodecParamPTime;
|
|
using cricket::kCodecParamSPropStereo;
|
|
using cricket::kCodecParamStartBitrate;
|
|
using cricket::kCodecParamStereo;
|
|
using cricket::kCodecParamUseInbandFec;
|
|
using cricket::kCodecParamUseDtx;
|
|
using cricket::kCodecParamSctpProtocol;
|
|
using cricket::kCodecParamSctpStreams;
|
|
using cricket::kCodecParamMaxAverageBitrate;
|
|
using cricket::kCodecParamMaxPlaybackRate;
|
|
using cricket::kCodecParamAssociatedPayloadType;
|
|
using cricket::MediaContentDescription;
|
|
using cricket::MediaType;
|
|
using cricket::NS_JINGLE_ICE_UDP;
|
|
using cricket::RtpHeaderExtension;
|
|
using cricket::SsrcGroup;
|
|
using cricket::StreamParams;
|
|
using cricket::StreamParamsVec;
|
|
using cricket::TransportDescription;
|
|
using cricket::TransportInfo;
|
|
using cricket::VideoContentDescription;
|
|
using rtc::SocketAddress;
|
|
|
|
typedef std::vector<RtpHeaderExtension> RtpHeaderExtensions;
|
|
|
|
namespace cricket {
|
|
class SessionDescription;
|
|
}
|
|
|
|
namespace webrtc {
|
|
|
|
// Line type
|
|
// RFC 4566
|
|
// An SDP session description consists of a number of lines of text of
|
|
// the form:
|
|
// <type>=<value>
|
|
// where <type> MUST be exactly one case-significant character.
|
|
static const int kLinePrefixLength = 2; // Lenght of <type>=
|
|
static const char kLineTypeVersion = 'v';
|
|
static const char kLineTypeOrigin = 'o';
|
|
static const char kLineTypeSessionName = 's';
|
|
static const char kLineTypeSessionInfo = 'i';
|
|
static const char kLineTypeSessionUri = 'u';
|
|
static const char kLineTypeSessionEmail = 'e';
|
|
static const char kLineTypeSessionPhone = 'p';
|
|
static const char kLineTypeSessionBandwidth = 'b';
|
|
static const char kLineTypeTiming = 't';
|
|
static const char kLineTypeRepeatTimes = 'r';
|
|
static const char kLineTypeTimeZone = 'z';
|
|
static const char kLineTypeEncryptionKey = 'k';
|
|
static const char kLineTypeMedia = 'm';
|
|
static const char kLineTypeConnection = 'c';
|
|
static const char kLineTypeAttributes = 'a';
|
|
|
|
// Attributes
|
|
static const char kAttributeGroup[] = "group";
|
|
static const char kAttributeMid[] = "mid";
|
|
static const char kAttributeRtcpMux[] = "rtcp-mux";
|
|
static const char kAttributeSsrc[] = "ssrc";
|
|
static const char kSsrcAttributeCname[] = "cname";
|
|
static const char kAttributeExtmap[] = "extmap";
|
|
// draft-alvestrand-mmusic-msid-01
|
|
// a=msid-semantic: WMS
|
|
static const char kAttributeMsidSemantics[] = "msid-semantic";
|
|
static const char kMediaStreamSemantic[] = "WMS";
|
|
static const char kSsrcAttributeMsid[] = "msid";
|
|
static const char kDefaultMsid[] = "default";
|
|
static const char kSsrcAttributeMslabel[] = "mslabel";
|
|
static const char kSSrcAttributeLabel[] = "label";
|
|
static const char kAttributeSsrcGroup[] = "ssrc-group";
|
|
static const char kAttributeCrypto[] = "crypto";
|
|
static const char kAttributeCandidate[] = "candidate";
|
|
static const char kAttributeCandidateTyp[] = "typ";
|
|
static const char kAttributeCandidateRaddr[] = "raddr";
|
|
static const char kAttributeCandidateRport[] = "rport";
|
|
static const char kAttributeCandidateUsername[] = "username";
|
|
static const char kAttributeCandidatePassword[] = "password";
|
|
static const char kAttributeCandidateGeneration[] = "generation";
|
|
static const char kAttributeFingerprint[] = "fingerprint";
|
|
static const char kAttributeSetup[] = "setup";
|
|
static const char kAttributeFmtp[] = "fmtp";
|
|
static const char kAttributeRtpmap[] = "rtpmap";
|
|
static const char kAttributeSctpmap[] = "sctpmap";
|
|
static const char kAttributeRtcp[] = "rtcp";
|
|
static const char kAttributeIceUfrag[] = "ice-ufrag";
|
|
static const char kAttributeIcePwd[] = "ice-pwd";
|
|
static const char kAttributeIceLite[] = "ice-lite";
|
|
static const char kAttributeIceOption[] = "ice-options";
|
|
static const char kAttributeSendOnly[] = "sendonly";
|
|
static const char kAttributeRecvOnly[] = "recvonly";
|
|
static const char kAttributeRtcpFb[] = "rtcp-fb";
|
|
static const char kAttributeSendRecv[] = "sendrecv";
|
|
static const char kAttributeInactive[] = "inactive";
|
|
// draft-ietf-mmusic-sctp-sdp-07
|
|
// a=sctp-port
|
|
static const char kAttributeSctpPort[] = "sctp-port";
|
|
|
|
// Experimental flags
|
|
static const char kAttributeXGoogleFlag[] = "x-google-flag";
|
|
static const char kValueConference[] = "conference";
|
|
static const char kAttributeXGoogleBufferLatency[] =
|
|
"x-google-buffer-latency";
|
|
|
|
// Candidate
|
|
static const char kCandidateHost[] = "host";
|
|
static const char kCandidateSrflx[] = "srflx";
|
|
// TODO: How to map the prflx with circket candidate type
|
|
// static const char kCandidatePrflx[] = "prflx";
|
|
static const char kCandidateRelay[] = "relay";
|
|
static const char kTcpCandidateType[] = "tcptype";
|
|
|
|
static const char kSdpDelimiterEqual = '=';
|
|
static const char kSdpDelimiterSpace = ' ';
|
|
static const char kSdpDelimiterColon = ':';
|
|
static const char kSdpDelimiterSemicolon = ';';
|
|
static const char kSdpDelimiterSlash = '/';
|
|
static const char kNewLine = '\n';
|
|
static const char kReturn = '\r';
|
|
static const char kLineBreak[] = "\r\n";
|
|
|
|
// TODO: Generate the Session and Time description
|
|
// instead of hardcoding.
|
|
static const char kSessionVersion[] = "v=0";
|
|
// RFC 4566
|
|
static const char kSessionOriginUsername[] = "-";
|
|
static const char kSessionOriginSessionId[] = "0";
|
|
static const char kSessionOriginSessionVersion[] = "0";
|
|
static const char kSessionOriginNettype[] = "IN";
|
|
static const char kSessionOriginAddrtype[] = "IP4";
|
|
static const char kSessionOriginAddress[] = "127.0.0.1";
|
|
static const char kSessionName[] = "s=-";
|
|
static const char kTimeDescription[] = "t=0 0";
|
|
static const char kAttrGroup[] = "a=group:BUNDLE";
|
|
static const char kConnectionNettype[] = "IN";
|
|
static const char kConnectionIpv4Addrtype[] = "IP4";
|
|
static const char kConnectionIpv6Addrtype[] = "IP6";
|
|
static const char kMediaTypeVideo[] = "video";
|
|
static const char kMediaTypeAudio[] = "audio";
|
|
static const char kMediaTypeData[] = "application";
|
|
static const char kMediaPortRejected[] = "0";
|
|
// draft-ietf-mmusic-trickle-ice-01
|
|
// When no candidates have been gathered, set the connection
|
|
// address to IP6 ::.
|
|
// TODO(perkj): FF can not parse IP6 ::. See http://crbug/430333
|
|
// Use IPV4 per default.
|
|
static const char kDummyAddress[] = "0.0.0.0";
|
|
static const char kDummyPort[] = "9";
|
|
// RFC 3556
|
|
static const char kApplicationSpecificMaximum[] = "AS";
|
|
|
|
static const int kDefaultVideoClockrate = 90000;
|
|
|
|
// ISAC special-case.
|
|
static const char kIsacCodecName[] = "ISAC"; // From webrtcvoiceengine.cc
|
|
static const int kIsacWbDefaultRate = 32000; // From acm_common_defs.h
|
|
static const int kIsacSwbDefaultRate = 56000; // From acm_common_defs.h
|
|
|
|
static const char kDefaultSctpmapProtocol[] = "webrtc-datachannel";
|
|
|
|
// RTP payload type is in the 0-127 range. Use -1 to indicate "all" payload
|
|
// types.
|
|
const int kWildcardPayloadType = -1;
|
|
|
|
struct SsrcInfo {
|
|
SsrcInfo()
|
|
: msid_identifier(kDefaultMsid),
|
|
// TODO(ronghuawu): What should we do if the appdata doesn't appear?
|
|
// Create random string (which will be used as track label later)?
|
|
msid_appdata(rtc::CreateRandomString(8)) {
|
|
}
|
|
uint32 ssrc_id;
|
|
std::string cname;
|
|
std::string msid_identifier;
|
|
std::string msid_appdata;
|
|
|
|
// For backward compatibility.
|
|
// TODO(ronghuawu): Remove below 2 fields once all the clients support msid.
|
|
std::string label;
|
|
std::string mslabel;
|
|
};
|
|
typedef std::vector<SsrcInfo> SsrcInfoVec;
|
|
typedef std::vector<SsrcGroup> SsrcGroupVec;
|
|
|
|
template <class T>
|
|
static void AddFmtpLine(const T& codec, std::string* message);
|
|
static void BuildMediaDescription(const ContentInfo* content_info,
|
|
const TransportInfo* transport_info,
|
|
const MediaType media_type,
|
|
const std::vector<Candidate>& candidates,
|
|
std::string* message);
|
|
static void BuildSctpContentAttributes(std::string* message, int sctp_port);
|
|
static void BuildRtpContentAttributes(
|
|
const MediaContentDescription* media_desc,
|
|
const MediaType media_type,
|
|
std::string* message);
|
|
static void BuildRtpMap(const MediaContentDescription* media_desc,
|
|
const MediaType media_type,
|
|
std::string* message);
|
|
static void BuildCandidate(const std::vector<Candidate>& candidates,
|
|
std::string* message);
|
|
static void BuildIceOptions(const std::vector<std::string>& transport_options,
|
|
std::string* message);
|
|
static bool IsRtp(const std::string& protocol);
|
|
static bool IsDtlsSctp(const std::string& protocol);
|
|
static bool ParseSessionDescription(const std::string& message, size_t* pos,
|
|
std::string* session_id,
|
|
std::string* session_version,
|
|
bool* supports_msid,
|
|
TransportDescription* session_td,
|
|
RtpHeaderExtensions* session_extmaps,
|
|
cricket::SessionDescription* desc,
|
|
SdpParseError* error);
|
|
static bool ParseGroupAttribute(const std::string& line,
|
|
cricket::SessionDescription* desc,
|
|
SdpParseError* error);
|
|
static bool ParseMediaDescription(
|
|
const std::string& message,
|
|
const TransportDescription& session_td,
|
|
const RtpHeaderExtensions& session_extmaps,
|
|
bool supports_msid,
|
|
size_t* pos, cricket::SessionDescription* desc,
|
|
std::vector<JsepIceCandidate*>* candidates,
|
|
SdpParseError* error);
|
|
static bool ParseContent(const std::string& message,
|
|
const MediaType media_type,
|
|
int mline_index,
|
|
const std::string& protocol,
|
|
const std::vector<int>& codec_preference,
|
|
size_t* pos,
|
|
std::string* content_name,
|
|
MediaContentDescription* media_desc,
|
|
TransportDescription* transport,
|
|
std::vector<JsepIceCandidate*>* candidates,
|
|
SdpParseError* error);
|
|
static bool ParseSsrcAttribute(const std::string& line,
|
|
SsrcInfoVec* ssrc_infos,
|
|
SdpParseError* error);
|
|
static bool ParseSsrcGroupAttribute(const std::string& line,
|
|
SsrcGroupVec* ssrc_groups,
|
|
SdpParseError* error);
|
|
static bool ParseCryptoAttribute(const std::string& line,
|
|
MediaContentDescription* media_desc,
|
|
SdpParseError* error);
|
|
static bool ParseRtpmapAttribute(const std::string& line,
|
|
const MediaType media_type,
|
|
const std::vector<int>& codec_preference,
|
|
MediaContentDescription* media_desc,
|
|
SdpParseError* error);
|
|
static bool ParseFmtpAttributes(const std::string& line,
|
|
const MediaType media_type,
|
|
MediaContentDescription* media_desc,
|
|
SdpParseError* error);
|
|
static bool ParseFmtpParam(const std::string& line, std::string* parameter,
|
|
std::string* value, SdpParseError* error);
|
|
static bool ParseCandidate(const std::string& message, Candidate* candidate,
|
|
SdpParseError* error, bool is_raw);
|
|
static bool ParseRtcpFbAttribute(const std::string& line,
|
|
const MediaType media_type,
|
|
MediaContentDescription* media_desc,
|
|
SdpParseError* error);
|
|
static bool ParseIceOptions(const std::string& line,
|
|
std::vector<std::string>* transport_options,
|
|
SdpParseError* error);
|
|
static bool ParseExtmap(const std::string& line,
|
|
RtpHeaderExtension* extmap,
|
|
SdpParseError* error);
|
|
static bool ParseFingerprintAttribute(const std::string& line,
|
|
rtc::SSLFingerprint** fingerprint,
|
|
SdpParseError* error);
|
|
static bool ParseDtlsSetup(const std::string& line,
|
|
cricket::ConnectionRole* role,
|
|
SdpParseError* error);
|
|
|
|
// Helper functions
|
|
|
|
// Below ParseFailed*** functions output the line that caused the parsing
|
|
// failure and the detailed reason (|description|) of the failure to |error|.
|
|
// The functions always return false so that they can be used directly in the
|
|
// following way when error happens:
|
|
// "return ParseFailed***(...);"
|
|
|
|
// The line starting at |line_start| of |message| is the failing line.
|
|
// The reason for the failure should be provided in the |description|.
|
|
// An example of a description could be "unknown character".
|
|
static bool ParseFailed(const std::string& message,
|
|
size_t line_start,
|
|
const std::string& description,
|
|
SdpParseError* error) {
|
|
// Get the first line of |message| from |line_start|.
|
|
std::string first_line;
|
|
size_t line_end = message.find(kNewLine, line_start);
|
|
if (line_end != std::string::npos) {
|
|
if (line_end > 0 && (message.at(line_end - 1) == kReturn)) {
|
|
--line_end;
|
|
}
|
|
first_line = message.substr(line_start, (line_end - line_start));
|
|
} else {
|
|
first_line = message.substr(line_start);
|
|
}
|
|
|
|
if (error) {
|
|
error->line = first_line;
|
|
error->description = description;
|
|
}
|
|
LOG(LS_ERROR) << "Failed to parse: \"" << first_line
|
|
<< "\". Reason: " << description;
|
|
return false;
|
|
}
|
|
|
|
// |line| is the failing line. The reason for the failure should be
|
|
// provided in the |description|.
|
|
static bool ParseFailed(const std::string& line,
|
|
const std::string& description,
|
|
SdpParseError* error) {
|
|
return ParseFailed(line, 0, description, error);
|
|
}
|
|
|
|
// Parses failure where the failing SDP line isn't know or there are multiple
|
|
// failing lines.
|
|
static bool ParseFailed(const std::string& description,
|
|
SdpParseError* error) {
|
|
return ParseFailed("", description, error);
|
|
}
|
|
|
|
// |line| is the failing line. The failure is due to the fact that |line|
|
|
// doesn't have |expected_fields| fields.
|
|
static bool ParseFailedExpectFieldNum(const std::string& line,
|
|
int expected_fields,
|
|
SdpParseError* error) {
|
|
std::ostringstream description;
|
|
description << "Expects " << expected_fields << " fields.";
|
|
return ParseFailed(line, description.str(), error);
|
|
}
|
|
|
|
// |line| is the failing line. The failure is due to the fact that |line| has
|
|
// less than |expected_min_fields| fields.
|
|
static bool ParseFailedExpectMinFieldNum(const std::string& line,
|
|
int expected_min_fields,
|
|
SdpParseError* error) {
|
|
std::ostringstream description;
|
|
description << "Expects at least " << expected_min_fields << " fields.";
|
|
return ParseFailed(line, description.str(), error);
|
|
}
|
|
|
|
// |line| is the failing line. The failure is due to the fact that it failed to
|
|
// get the value of |attribute|.
|
|
static bool ParseFailedGetValue(const std::string& line,
|
|
const std::string& attribute,
|
|
SdpParseError* error) {
|
|
std::ostringstream description;
|
|
description << "Failed to get the value of attribute: " << attribute;
|
|
return ParseFailed(line, description.str(), error);
|
|
}
|
|
|
|
// The line starting at |line_start| of |message| is the failing line. The
|
|
// failure is due to the line type (e.g. the "m" part of the "m-line")
|
|
// not matching what is expected. The expected line type should be
|
|
// provided as |line_type|.
|
|
static bool ParseFailedExpectLine(const std::string& message,
|
|
size_t line_start,
|
|
const char line_type,
|
|
const std::string& line_value,
|
|
SdpParseError* error) {
|
|
std::ostringstream description;
|
|
description << "Expect line: " << line_type << "=" << line_value;
|
|
return ParseFailed(message, line_start, description.str(), error);
|
|
}
|
|
|
|
static bool AddLine(const std::string& line, std::string* message) {
|
|
if (!message)
|
|
return false;
|
|
|
|
message->append(line);
|
|
message->append(kLineBreak);
|
|
return true;
|
|
}
|
|
|
|
static bool GetLine(const std::string& message,
|
|
size_t* pos,
|
|
std::string* line) {
|
|
size_t line_begin = *pos;
|
|
size_t line_end = message.find(kNewLine, line_begin);
|
|
if (line_end == std::string::npos) {
|
|
return false;
|
|
}
|
|
// Update the new start position
|
|
*pos = line_end + 1;
|
|
if (line_end > 0 && (message.at(line_end - 1) == kReturn)) {
|
|
--line_end;
|
|
}
|
|
*line = message.substr(line_begin, (line_end - line_begin));
|
|
const char* cline = line->c_str();
|
|
// RFC 4566
|
|
// An SDP session description consists of a number of lines of text of
|
|
// the form:
|
|
// <type>=<value>
|
|
// where <type> MUST be exactly one case-significant character and
|
|
// <value> is structured text whose format depends on <type>.
|
|
// Whitespace MUST NOT be used on either side of the "=" sign.
|
|
if (line->length() < 3 ||
|
|
!islower(cline[0]) ||
|
|
cline[1] != kSdpDelimiterEqual ||
|
|
cline[2] == kSdpDelimiterSpace) {
|
|
*pos = line_begin;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Init |os| to "|type|=|value|".
|
|
static void InitLine(const char type,
|
|
const std::string& value,
|
|
std::ostringstream* os) {
|
|
os->str("");
|
|
*os << type << kSdpDelimiterEqual << value;
|
|
}
|
|
|
|
// Init |os| to "a=|attribute|".
|
|
static void InitAttrLine(const std::string& attribute, std::ostringstream* os) {
|
|
InitLine(kLineTypeAttributes, attribute, os);
|
|
}
|
|
|
|
// Writes a SDP attribute line based on |attribute| and |value| to |message|.
|
|
static void AddAttributeLine(const std::string& attribute, int value,
|
|
std::string* message) {
|
|
std::ostringstream os;
|
|
InitAttrLine(attribute, &os);
|
|
os << kSdpDelimiterColon << value;
|
|
AddLine(os.str(), message);
|
|
}
|
|
|
|
static bool IsLineType(const std::string& message,
|
|
const char type,
|
|
size_t line_start) {
|
|
if (message.size() < line_start + kLinePrefixLength) {
|
|
return false;
|
|
}
|
|
const char* cmessage = message.c_str();
|
|
return (cmessage[line_start] == type &&
|
|
cmessage[line_start + 1] == kSdpDelimiterEqual);
|
|
}
|
|
|
|
static bool IsLineType(const std::string& line,
|
|
const char type) {
|
|
return IsLineType(line, type, 0);
|
|
}
|
|
|
|
static bool GetLineWithType(const std::string& message, size_t* pos,
|
|
std::string* line, const char type) {
|
|
if (!IsLineType(message, type, *pos)) {
|
|
return false;
|
|
}
|
|
|
|
if (!GetLine(message, pos, line))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool HasAttribute(const std::string& line,
|
|
const std::string& attribute) {
|
|
return (line.compare(kLinePrefixLength, attribute.size(), attribute) == 0);
|
|
}
|
|
|
|
static bool AddSsrcLine(uint32 ssrc_id, const std::string& attribute,
|
|
const std::string& value, std::string* message) {
|
|
// RFC 5576
|
|
// a=ssrc:<ssrc-id> <attribute>:<value>
|
|
std::ostringstream os;
|
|
InitAttrLine(kAttributeSsrc, &os);
|
|
os << kSdpDelimiterColon << ssrc_id << kSdpDelimiterSpace
|
|
<< attribute << kSdpDelimiterColon << value;
|
|
return AddLine(os.str(), message);
|
|
}
|
|
|
|
// Get value only from <attribute>:<value>.
|
|
static bool GetValue(const std::string& message, const std::string& attribute,
|
|
std::string* value, SdpParseError* error) {
|
|
std::string leftpart;
|
|
if (!rtc::tokenize_first(message, kSdpDelimiterColon, &leftpart, value)) {
|
|
return ParseFailedGetValue(message, attribute, error);
|
|
}
|
|
// The left part should end with the expected attribute.
|
|
if (leftpart.length() < attribute.length() ||
|
|
leftpart.compare(leftpart.length() - attribute.length(),
|
|
attribute.length(), attribute) != 0) {
|
|
return ParseFailedGetValue(message, attribute, error);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool CaseInsensitiveFind(std::string str1, std::string str2) {
|
|
std::transform(str1.begin(), str1.end(), str1.begin(),
|
|
::tolower);
|
|
std::transform(str2.begin(), str2.end(), str2.begin(),
|
|
::tolower);
|
|
return str1.find(str2) != std::string::npos;
|
|
}
|
|
|
|
template <class T>
|
|
static bool GetValueFromString(const std::string& line,
|
|
const std::string& s,
|
|
T* t,
|
|
SdpParseError* error) {
|
|
if (!rtc::FromString(s, t)) {
|
|
std::ostringstream description;
|
|
description << "Invalid value: " << s << ".";
|
|
return ParseFailed(line, description.str(), error);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool GetPayloadTypeFromString(const std::string& line,
|
|
const std::string& s,
|
|
int* payload_type,
|
|
SdpParseError* error) {
|
|
return GetValueFromString(line, s, payload_type, error) &&
|
|
cricket::IsValidRtpPayloadType(*payload_type);
|
|
}
|
|
|
|
void CreateTracksFromSsrcInfos(const SsrcInfoVec& ssrc_infos,
|
|
StreamParamsVec* tracks) {
|
|
ASSERT(tracks != NULL);
|
|
for (SsrcInfoVec::const_iterator ssrc_info = ssrc_infos.begin();
|
|
ssrc_info != ssrc_infos.end(); ++ssrc_info) {
|
|
if (ssrc_info->cname.empty()) {
|
|
continue;
|
|
}
|
|
|
|
std::string sync_label;
|
|
std::string track_id;
|
|
if (ssrc_info->msid_identifier == kDefaultMsid &&
|
|
!ssrc_info->mslabel.empty()) {
|
|
// If there's no msid and there's mslabel, we consider this is a sdp from
|
|
// a older version of client that doesn't support msid.
|
|
// In that case, we use the mslabel and label to construct the track.
|
|
sync_label = ssrc_info->mslabel;
|
|
track_id = ssrc_info->label;
|
|
} else {
|
|
sync_label = ssrc_info->msid_identifier;
|
|
// The appdata consists of the "id" attribute of a MediaStreamTrack, which
|
|
// is corresponding to the "id" attribute of StreamParams.
|
|
track_id = ssrc_info->msid_appdata;
|
|
}
|
|
if (sync_label.empty() || track_id.empty()) {
|
|
ASSERT(false);
|
|
continue;
|
|
}
|
|
|
|
StreamParamsVec::iterator track = tracks->begin();
|
|
for (; track != tracks->end(); ++track) {
|
|
if (track->id == track_id) {
|
|
break;
|
|
}
|
|
}
|
|
if (track == tracks->end()) {
|
|
// If we don't find an existing track, create a new one.
|
|
tracks->push_back(StreamParams());
|
|
track = tracks->end() - 1;
|
|
}
|
|
track->add_ssrc(ssrc_info->ssrc_id);
|
|
track->cname = ssrc_info->cname;
|
|
track->sync_label = sync_label;
|
|
track->id = track_id;
|
|
}
|
|
}
|
|
|
|
void GetMediaStreamLabels(const ContentInfo* content,
|
|
std::set<std::string>* labels) {
|
|
const MediaContentDescription* media_desc =
|
|
static_cast<const MediaContentDescription*>(
|
|
content->description);
|
|
const cricket::StreamParamsVec& streams = media_desc->streams();
|
|
for (cricket::StreamParamsVec::const_iterator it = streams.begin();
|
|
it != streams.end(); ++it) {
|
|
labels->insert(it->sync_label);
|
|
}
|
|
}
|
|
|
|
// RFC 5245
|
|
// It is RECOMMENDED that default candidates be chosen based on the
|
|
// likelihood of those candidates to work with the peer that is being
|
|
// contacted. It is RECOMMENDED that relayed > reflexive > host.
|
|
static const int kPreferenceUnknown = 0;
|
|
static const int kPreferenceHost = 1;
|
|
static const int kPreferenceReflexive = 2;
|
|
static const int kPreferenceRelayed = 3;
|
|
|
|
static int GetCandidatePreferenceFromType(const std::string& type) {
|
|
int preference = kPreferenceUnknown;
|
|
if (type == cricket::LOCAL_PORT_TYPE) {
|
|
preference = kPreferenceHost;
|
|
} else if (type == cricket::STUN_PORT_TYPE) {
|
|
preference = kPreferenceReflexive;
|
|
} else if (type == cricket::RELAY_PORT_TYPE) {
|
|
preference = kPreferenceRelayed;
|
|
} else {
|
|
ASSERT(false);
|
|
}
|
|
return preference;
|
|
}
|
|
|
|
// Get ip and port of the default destination from the |candidates| with the
|
|
// given value of |component_id|. The default candidate should be the one most
|
|
// likely to work, typically IPv4 relay.
|
|
// RFC 5245
|
|
// The value of |component_id| currently supported are 1 (RTP) and 2 (RTCP).
|
|
// TODO: Decide the default destination in webrtcsession and
|
|
// pass it down via SessionDescription.
|
|
static void GetDefaultDestination(
|
|
const std::vector<Candidate>& candidates,
|
|
int component_id, std::string* port,
|
|
std::string* ip, std::string* addr_type) {
|
|
*addr_type = kConnectionIpv4Addrtype;
|
|
*port = kDummyPort;
|
|
*ip = kDummyAddress;
|
|
int current_preference = kPreferenceUnknown;
|
|
int current_family = AF_UNSPEC;
|
|
for (std::vector<Candidate>::const_iterator it = candidates.begin();
|
|
it != candidates.end(); ++it) {
|
|
if (it->component() != component_id) {
|
|
continue;
|
|
}
|
|
// Default destination should be UDP only.
|
|
if (it->protocol() != cricket::UDP_PROTOCOL_NAME) {
|
|
continue;
|
|
}
|
|
const int preference = GetCandidatePreferenceFromType(it->type());
|
|
const int family = it->address().ipaddr().family();
|
|
// See if this candidate is more preferable then the current one if it's the
|
|
// same family. Or if the current family is IPv4 already so we could safely
|
|
// ignore all IPv6 ones. WebRTC bug 4269.
|
|
// http://code.google.com/p/webrtc/issues/detail?id=4269
|
|
if ((preference <= current_preference && current_family == family) ||
|
|
(current_family == AF_INET && family == AF_INET6)) {
|
|
continue;
|
|
}
|
|
if (family == AF_INET) {
|
|
addr_type->assign(kConnectionIpv4Addrtype);
|
|
} else if (family == AF_INET6) {
|
|
addr_type->assign(kConnectionIpv6Addrtype);
|
|
}
|
|
current_preference = preference;
|
|
current_family = family;
|
|
*port = it->address().PortAsString();
|
|
*ip = it->address().ipaddr().ToString();
|
|
}
|
|
}
|
|
|
|
// Update |mline|'s default destination and append a c line after it.
|
|
static void UpdateMediaDefaultDestination(
|
|
const std::vector<Candidate>& candidates,
|
|
const std::string mline,
|
|
std::string* message) {
|
|
std::string new_lines;
|
|
AddLine(mline, &new_lines);
|
|
// RFC 4566
|
|
// m=<media> <port> <proto> <fmt> ...
|
|
std::vector<std::string> fields;
|
|
rtc::split(mline, kSdpDelimiterSpace, &fields);
|
|
if (fields.size() < 3) {
|
|
return;
|
|
}
|
|
|
|
std::ostringstream os;
|
|
std::string rtp_port, rtp_ip, addr_type;
|
|
GetDefaultDestination(candidates, ICE_CANDIDATE_COMPONENT_RTP,
|
|
&rtp_port, &rtp_ip, &addr_type);
|
|
// Found default RTP candidate.
|
|
// RFC 5245
|
|
// The default candidates are added to the SDP as the default
|
|
// destination for media. For streams based on RTP, this is done by
|
|
// placing the IP address and port of the RTP candidate into the c and m
|
|
// lines, respectively.
|
|
// Update the port in the m line.
|
|
// If this is a m-line with port equal to 0, we don't change it.
|
|
if (fields[1] != kMediaPortRejected) {
|
|
new_lines.replace(fields[0].size() + 1,
|
|
fields[1].size(),
|
|
rtp_port);
|
|
}
|
|
// Add the c line.
|
|
// RFC 4566
|
|
// c=<nettype> <addrtype> <connection-address>
|
|
InitLine(kLineTypeConnection, kConnectionNettype, &os);
|
|
os << " " << addr_type << " " << rtp_ip;
|
|
AddLine(os.str(), &new_lines);
|
|
message->append(new_lines);
|
|
}
|
|
|
|
// Gets "a=rtcp" line if found default RTCP candidate from |candidates|.
|
|
static std::string GetRtcpLine(const std::vector<Candidate>& candidates) {
|
|
std::string rtcp_line, rtcp_port, rtcp_ip, addr_type;
|
|
GetDefaultDestination(candidates, ICE_CANDIDATE_COMPONENT_RTCP,
|
|
&rtcp_port, &rtcp_ip, &addr_type);
|
|
// Found default RTCP candidate.
|
|
// RFC 5245
|
|
// If the agent is utilizing RTCP, it MUST encode the RTCP candidate
|
|
// using the a=rtcp attribute as defined in RFC 3605.
|
|
|
|
// RFC 3605
|
|
// rtcp-attribute = "a=rtcp:" port [nettype space addrtype space
|
|
// connection-address] CRLF
|
|
std::ostringstream os;
|
|
InitAttrLine(kAttributeRtcp, &os);
|
|
os << kSdpDelimiterColon
|
|
<< rtcp_port << " "
|
|
<< kConnectionNettype << " "
|
|
<< addr_type << " "
|
|
<< rtcp_ip;
|
|
rtcp_line = os.str();
|
|
return rtcp_line;
|
|
}
|
|
|
|
// Get candidates according to the mline index from SessionDescriptionInterface.
|
|
static void GetCandidatesByMindex(const SessionDescriptionInterface& desci,
|
|
int mline_index,
|
|
std::vector<Candidate>* candidates) {
|
|
if (!candidates) {
|
|
return;
|
|
}
|
|
const IceCandidateCollection* cc = desci.candidates(mline_index);
|
|
for (size_t i = 0; i < cc->count(); ++i) {
|
|
const IceCandidateInterface* candidate = cc->at(i);
|
|
candidates->push_back(candidate->candidate());
|
|
}
|
|
}
|
|
|
|
std::string SdpSerialize(const JsepSessionDescription& jdesc) {
|
|
const cricket::SessionDescription* desc = jdesc.description();
|
|
if (!desc) {
|
|
return "";
|
|
}
|
|
|
|
std::string message;
|
|
|
|
// Session Description.
|
|
AddLine(kSessionVersion, &message);
|
|
// Session Origin
|
|
// RFC 4566
|
|
// o=<username> <sess-id> <sess-version> <nettype> <addrtype>
|
|
// <unicast-address>
|
|
std::ostringstream os;
|
|
InitLine(kLineTypeOrigin, kSessionOriginUsername, &os);
|
|
const std::string session_id = jdesc.session_id().empty() ?
|
|
kSessionOriginSessionId : jdesc.session_id();
|
|
const std::string session_version = jdesc.session_version().empty() ?
|
|
kSessionOriginSessionVersion : jdesc.session_version();
|
|
os << " " << session_id << " " << session_version << " "
|
|
<< kSessionOriginNettype << " " << kSessionOriginAddrtype << " "
|
|
<< kSessionOriginAddress;
|
|
AddLine(os.str(), &message);
|
|
AddLine(kSessionName, &message);
|
|
|
|
// Time Description.
|
|
AddLine(kTimeDescription, &message);
|
|
|
|
// Group
|
|
if (desc->HasGroup(cricket::GROUP_TYPE_BUNDLE)) {
|
|
std::string group_line = kAttrGroup;
|
|
const cricket::ContentGroup* group =
|
|
desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
|
|
ASSERT(group != NULL);
|
|
const cricket::ContentNames& content_names = group->content_names();
|
|
for (cricket::ContentNames::const_iterator it = content_names.begin();
|
|
it != content_names.end(); ++it) {
|
|
group_line.append(" ");
|
|
group_line.append(*it);
|
|
}
|
|
AddLine(group_line, &message);
|
|
}
|
|
|
|
// MediaStream semantics
|
|
InitAttrLine(kAttributeMsidSemantics, &os);
|
|
os << kSdpDelimiterColon << " " << kMediaStreamSemantic;
|
|
|
|
std::set<std::string> media_stream_labels;
|
|
const ContentInfo* audio_content = GetFirstAudioContent(desc);
|
|
if (audio_content)
|
|
GetMediaStreamLabels(audio_content, &media_stream_labels);
|
|
|
|
const ContentInfo* video_content = GetFirstVideoContent(desc);
|
|
if (video_content)
|
|
GetMediaStreamLabels(video_content, &media_stream_labels);
|
|
|
|
for (std::set<std::string>::const_iterator it =
|
|
media_stream_labels.begin(); it != media_stream_labels.end(); ++it) {
|
|
os << " " << *it;
|
|
}
|
|
AddLine(os.str(), &message);
|
|
|
|
// Preserve the order of the media contents.
|
|
int mline_index = -1;
|
|
for (cricket::ContentInfos::const_iterator it = desc->contents().begin();
|
|
it != desc->contents().end(); ++it) {
|
|
const MediaContentDescription* mdesc =
|
|
static_cast<const MediaContentDescription*>(it->description);
|
|
std::vector<Candidate> candidates;
|
|
GetCandidatesByMindex(jdesc, ++mline_index, &candidates);
|
|
BuildMediaDescription(&*it,
|
|
desc->GetTransportInfoByName(it->name),
|
|
mdesc->type(),
|
|
candidates,
|
|
&message);
|
|
}
|
|
return message;
|
|
}
|
|
|
|
// Serializes the passed in IceCandidateInterface to a SDP string.
|
|
// candidate - The candidate to be serialized.
|
|
std::string SdpSerializeCandidate(
|
|
const IceCandidateInterface& candidate) {
|
|
std::string message;
|
|
std::vector<cricket::Candidate> candidates;
|
|
candidates.push_back(candidate.candidate());
|
|
BuildCandidate(candidates, &message);
|
|
// From WebRTC draft section 4.8.1.1 candidate-attribute will be
|
|
// just candidate:<candidate> not a=candidate:<blah>CRLF
|
|
ASSERT(message.find("a=") == 0);
|
|
message.erase(0, 2);
|
|
ASSERT(message.find(kLineBreak) == message.size() - 2);
|
|
message.resize(message.size() - 2);
|
|
return message;
|
|
}
|
|
|
|
bool SdpDeserialize(const std::string& message,
|
|
JsepSessionDescription* jdesc,
|
|
SdpParseError* error) {
|
|
std::string session_id;
|
|
std::string session_version;
|
|
TransportDescription session_td(NS_JINGLE_ICE_UDP,
|
|
std::string(), std::string());
|
|
RtpHeaderExtensions session_extmaps;
|
|
cricket::SessionDescription* desc = new cricket::SessionDescription();
|
|
std::vector<JsepIceCandidate*> candidates;
|
|
size_t current_pos = 0;
|
|
bool supports_msid = false;
|
|
|
|
// Session Description
|
|
if (!ParseSessionDescription(message, ¤t_pos, &session_id,
|
|
&session_version, &supports_msid, &session_td,
|
|
&session_extmaps, desc, error)) {
|
|
delete desc;
|
|
return false;
|
|
}
|
|
|
|
// Media Description
|
|
if (!ParseMediaDescription(message, session_td, session_extmaps,
|
|
supports_msid, ¤t_pos, desc, &candidates,
|
|
error)) {
|
|
delete desc;
|
|
for (std::vector<JsepIceCandidate*>::const_iterator
|
|
it = candidates.begin(); it != candidates.end(); ++it) {
|
|
delete *it;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
jdesc->Initialize(desc, session_id, session_version);
|
|
|
|
for (std::vector<JsepIceCandidate*>::const_iterator
|
|
it = candidates.begin(); it != candidates.end(); ++it) {
|
|
jdesc->AddCandidate(*it);
|
|
delete *it;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SdpDeserializeCandidate(const std::string& message,
|
|
JsepIceCandidate* jcandidate,
|
|
SdpParseError* error) {
|
|
ASSERT(jcandidate != NULL);
|
|
Candidate candidate;
|
|
if (!ParseCandidate(message, &candidate, error, true)) {
|
|
return false;
|
|
}
|
|
jcandidate->SetCandidate(candidate);
|
|
return true;
|
|
}
|
|
|
|
bool ParseCandidate(const std::string& message, Candidate* candidate,
|
|
SdpParseError* error, bool is_raw) {
|
|
ASSERT(candidate != NULL);
|
|
|
|
// Get the first line from |message|.
|
|
std::string first_line = message;
|
|
size_t pos = 0;
|
|
GetLine(message, &pos, &first_line);
|
|
|
|
// Makes sure |message| contains only one line.
|
|
if (message.size() > first_line.size()) {
|
|
std::string left, right;
|
|
if (rtc::tokenize_first(message, kNewLine, &left, &right) &&
|
|
!right.empty()) {
|
|
return ParseFailed(message, 0, "Expect one line only", error);
|
|
}
|
|
}
|
|
|
|
// From WebRTC draft section 4.8.1.1 candidate-attribute should be
|
|
// candidate:<candidate> when trickled, but we still support
|
|
// a=candidate:<blah>CRLF for backward compatibility and for parsing a line
|
|
// from the SDP.
|
|
if (IsLineType(first_line, kLineTypeAttributes)) {
|
|
first_line = first_line.substr(kLinePrefixLength);
|
|
}
|
|
|
|
std::string attribute_candidate;
|
|
std::string candidate_value;
|
|
|
|
// |first_line| must be in the form of "candidate:<value>".
|
|
if (!rtc::tokenize_first(first_line, kSdpDelimiterColon, &attribute_candidate,
|
|
&candidate_value) ||
|
|
attribute_candidate != kAttributeCandidate) {
|
|
if (is_raw) {
|
|
std::ostringstream description;
|
|
description << "Expect line: " << kAttributeCandidate
|
|
<< ":" << "<candidate-str>";
|
|
return ParseFailed(first_line, 0, description.str(), error);
|
|
} else {
|
|
return ParseFailedExpectLine(first_line, 0, kLineTypeAttributes,
|
|
kAttributeCandidate, error);
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> fields;
|
|
rtc::split(candidate_value, kSdpDelimiterSpace, &fields);
|
|
|
|
// RFC 5245
|
|
// a=candidate:<foundation> <component-id> <transport> <priority>
|
|
// <connection-address> <port> typ <candidate-types>
|
|
// [raddr <connection-address>] [rport <port>]
|
|
// *(SP extension-att-name SP extension-att-value)
|
|
const size_t expected_min_fields = 8;
|
|
if (fields.size() < expected_min_fields ||
|
|
(fields[6] != kAttributeCandidateTyp)) {
|
|
return ParseFailedExpectMinFieldNum(first_line, expected_min_fields, error);
|
|
}
|
|
std::string foundation = fields[0];
|
|
|
|
int component_id = 0;
|
|
if (!GetValueFromString(first_line, fields[1], &component_id, error)) {
|
|
return false;
|
|
}
|
|
const std::string transport = fields[2];
|
|
uint32 priority = 0;
|
|
if (!GetValueFromString(first_line, fields[3], &priority, error)) {
|
|
return false;
|
|
}
|
|
const std::string connection_address = fields[4];
|
|
int port = 0;
|
|
if (!GetValueFromString(first_line, fields[5], &port, error)) {
|
|
return false;
|
|
}
|
|
SocketAddress address(connection_address, port);
|
|
|
|
cricket::ProtocolType protocol;
|
|
if (!StringToProto(transport.c_str(), &protocol)) {
|
|
return ParseFailed(first_line, "Unsupported transport type.", error);
|
|
}
|
|
|
|
std::string candidate_type;
|
|
const std::string type = fields[7];
|
|
if (type == kCandidateHost) {
|
|
candidate_type = cricket::LOCAL_PORT_TYPE;
|
|
} else if (type == kCandidateSrflx) {
|
|
candidate_type = cricket::STUN_PORT_TYPE;
|
|
} else if (type == kCandidateRelay) {
|
|
candidate_type = cricket::RELAY_PORT_TYPE;
|
|
} else {
|
|
return ParseFailed(first_line, "Unsupported candidate type.", error);
|
|
}
|
|
|
|
size_t current_position = expected_min_fields;
|
|
SocketAddress related_address;
|
|
// The 2 optional fields for related address
|
|
// [raddr <connection-address>] [rport <port>]
|
|
if (fields.size() >= (current_position + 2) &&
|
|
fields[current_position] == kAttributeCandidateRaddr) {
|
|
related_address.SetIP(fields[++current_position]);
|
|
++current_position;
|
|
}
|
|
if (fields.size() >= (current_position + 2) &&
|
|
fields[current_position] == kAttributeCandidateRport) {
|
|
int port = 0;
|
|
if (!GetValueFromString(
|
|
first_line, fields[++current_position], &port, error)) {
|
|
return false;
|
|
}
|
|
related_address.SetPort(port);
|
|
++current_position;
|
|
}
|
|
|
|
// If this is a TCP candidate, it has additional extension as defined in
|
|
// RFC 6544.
|
|
std::string tcptype;
|
|
if (fields.size() >= (current_position + 2) &&
|
|
fields[current_position] == kTcpCandidateType) {
|
|
tcptype = fields[++current_position];
|
|
++current_position;
|
|
|
|
if (tcptype != cricket::TCPTYPE_ACTIVE_STR &&
|
|
tcptype != cricket::TCPTYPE_PASSIVE_STR &&
|
|
tcptype != cricket::TCPTYPE_SIMOPEN_STR) {
|
|
return ParseFailed(first_line, "Invalid TCP candidate type.", error);
|
|
}
|
|
|
|
if (protocol != cricket::PROTO_TCP) {
|
|
return ParseFailed(first_line, "Invalid non-TCP candidate", error);
|
|
}
|
|
}
|
|
|
|
// Extension
|
|
// Empty string as the candidate username and password.
|
|
// Will be updated later with the ice-ufrag and ice-pwd.
|
|
// TODO: Remove the username/password extension, which is currently
|
|
// kept for backwards compatibility.
|
|
std::string username;
|
|
std::string password;
|
|
uint32 generation = 0;
|
|
for (size_t i = current_position; i + 1 < fields.size(); ++i) {
|
|
// RFC 5245
|
|
// *(SP extension-att-name SP extension-att-value)
|
|
if (fields[i] == kAttributeCandidateGeneration) {
|
|
if (!GetValueFromString(first_line, fields[++i], &generation, error)) {
|
|
return false;
|
|
}
|
|
} else if (fields[i] == kAttributeCandidateUsername) {
|
|
username = fields[++i];
|
|
} else if (fields[i] == kAttributeCandidatePassword) {
|
|
password = fields[++i];
|
|
} else {
|
|
// Skip the unknown extension.
|
|
++i;
|
|
}
|
|
}
|
|
|
|
*candidate = Candidate(component_id, cricket::ProtoToString(protocol),
|
|
address, priority, username, password, candidate_type,
|
|
generation, foundation);
|
|
candidate->set_related_address(related_address);
|
|
candidate->set_tcptype(tcptype);
|
|
return true;
|
|
}
|
|
|
|
bool ParseIceOptions(const std::string& line,
|
|
std::vector<std::string>* transport_options,
|
|
SdpParseError* error) {
|
|
std::string ice_options;
|
|
if (!GetValue(line, kAttributeIceOption, &ice_options, error)) {
|
|
return false;
|
|
}
|
|
std::vector<std::string> fields;
|
|
rtc::split(ice_options, kSdpDelimiterSpace, &fields);
|
|
for (size_t i = 0; i < fields.size(); ++i) {
|
|
transport_options->push_back(fields[i]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ParseSctpPort(const std::string& line,
|
|
int* sctp_port,
|
|
SdpParseError* error) {
|
|
// draft-ietf-mmusic-sctp-sdp-07
|
|
// a=sctp-port
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength),
|
|
kSdpDelimiterSpace, &fields);
|
|
const size_t expected_min_fields = 2;
|
|
if (fields.size() < expected_min_fields) {
|
|
return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
|
|
}
|
|
if (!rtc::FromString(fields[1], sctp_port)) {
|
|
return ParseFailed(line,
|
|
"Invalid sctp port value.",
|
|
error);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ParseExtmap(const std::string& line, RtpHeaderExtension* extmap,
|
|
SdpParseError* error) {
|
|
// RFC 5285
|
|
// a=extmap:<value>["/"<direction>] <URI> <extensionattributes>
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength),
|
|
kSdpDelimiterSpace, &fields);
|
|
const size_t expected_min_fields = 2;
|
|
if (fields.size() < expected_min_fields) {
|
|
return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
|
|
}
|
|
std::string uri = fields[1];
|
|
|
|
std::string value_direction;
|
|
if (!GetValue(fields[0], kAttributeExtmap, &value_direction, error)) {
|
|
return false;
|
|
}
|
|
std::vector<std::string> sub_fields;
|
|
rtc::split(value_direction, kSdpDelimiterSlash, &sub_fields);
|
|
int value = 0;
|
|
if (!GetValueFromString(line, sub_fields[0], &value, error)) {
|
|
return false;
|
|
}
|
|
|
|
*extmap = RtpHeaderExtension(uri, value);
|
|
return true;
|
|
}
|
|
|
|
void BuildMediaDescription(const ContentInfo* content_info,
|
|
const TransportInfo* transport_info,
|
|
const MediaType media_type,
|
|
const std::vector<Candidate>& candidates,
|
|
std::string* message) {
|
|
ASSERT(message != NULL);
|
|
if (content_info == NULL || message == NULL) {
|
|
return;
|
|
}
|
|
// TODO: Rethink if we should use sprintfn instead of stringstream.
|
|
// According to the style guide, streams should only be used for logging.
|
|
// http://google-styleguide.googlecode.com/svn/
|
|
// trunk/cppguide.xml?showone=Streams#Streams
|
|
std::ostringstream os;
|
|
const MediaContentDescription* media_desc =
|
|
static_cast<const MediaContentDescription*>(
|
|
content_info->description);
|
|
ASSERT(media_desc != NULL);
|
|
|
|
int sctp_port = cricket::kSctpDefaultPort;
|
|
|
|
// RFC 4566
|
|
// m=<media> <port> <proto> <fmt>
|
|
// fmt is a list of payload type numbers that MAY be used in the session.
|
|
const char* type = NULL;
|
|
if (media_type == cricket::MEDIA_TYPE_AUDIO)
|
|
type = kMediaTypeAudio;
|
|
else if (media_type == cricket::MEDIA_TYPE_VIDEO)
|
|
type = kMediaTypeVideo;
|
|
else if (media_type == cricket::MEDIA_TYPE_DATA)
|
|
type = kMediaTypeData;
|
|
else
|
|
ASSERT(false);
|
|
|
|
std::string fmt;
|
|
if (media_type == cricket::MEDIA_TYPE_VIDEO) {
|
|
const VideoContentDescription* video_desc =
|
|
static_cast<const VideoContentDescription*>(media_desc);
|
|
for (std::vector<cricket::VideoCodec>::const_iterator it =
|
|
video_desc->codecs().begin();
|
|
it != video_desc->codecs().end(); ++it) {
|
|
fmt.append(" ");
|
|
fmt.append(rtc::ToString<int>(it->id));
|
|
}
|
|
} else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
|
|
const AudioContentDescription* audio_desc =
|
|
static_cast<const AudioContentDescription*>(media_desc);
|
|
for (std::vector<cricket::AudioCodec>::const_iterator it =
|
|
audio_desc->codecs().begin();
|
|
it != audio_desc->codecs().end(); ++it) {
|
|
fmt.append(" ");
|
|
fmt.append(rtc::ToString<int>(it->id));
|
|
}
|
|
} else if (media_type == cricket::MEDIA_TYPE_DATA) {
|
|
const DataContentDescription* data_desc =
|
|
static_cast<const DataContentDescription*>(media_desc);
|
|
if (IsDtlsSctp(media_desc->protocol())) {
|
|
fmt.append(" ");
|
|
|
|
for (std::vector<cricket::DataCodec>::const_iterator it =
|
|
data_desc->codecs().begin();
|
|
it != data_desc->codecs().end(); ++it) {
|
|
if (it->id == cricket::kGoogleSctpDataCodecId &&
|
|
it->GetParam(cricket::kCodecParamPort, &sctp_port)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
fmt.append(rtc::ToString<int>(sctp_port));
|
|
} else {
|
|
for (std::vector<cricket::DataCodec>::const_iterator it =
|
|
data_desc->codecs().begin();
|
|
it != data_desc->codecs().end(); ++it) {
|
|
fmt.append(" ");
|
|
fmt.append(rtc::ToString<int>(it->id));
|
|
}
|
|
}
|
|
}
|
|
// The fmt must never be empty. If no codecs are found, set the fmt attribute
|
|
// to 0.
|
|
if (fmt.empty()) {
|
|
fmt = " 0";
|
|
}
|
|
|
|
// The port number in the m line will be updated later when associate with
|
|
// the candidates.
|
|
// RFC 3264
|
|
// To reject an offered stream, the port number in the corresponding stream in
|
|
// the answer MUST be set to zero.
|
|
const std::string port = content_info->rejected ?
|
|
kMediaPortRejected : kDummyPort;
|
|
|
|
rtc::SSLFingerprint* fp = (transport_info) ?
|
|
transport_info->description.identity_fingerprint.get() : NULL;
|
|
|
|
// Add the m and c lines.
|
|
InitLine(kLineTypeMedia, type, &os);
|
|
os << " " << port << " " << media_desc->protocol() << fmt;
|
|
std::string mline = os.str();
|
|
UpdateMediaDefaultDestination(candidates, mline, message);
|
|
|
|
// RFC 4566
|
|
// b=AS:<bandwidth>
|
|
if (media_desc->bandwidth() >= 1000) {
|
|
InitLine(kLineTypeSessionBandwidth, kApplicationSpecificMaximum, &os);
|
|
os << kSdpDelimiterColon << (media_desc->bandwidth() / 1000);
|
|
AddLine(os.str(), message);
|
|
}
|
|
|
|
// Add the a=rtcp line.
|
|
if (IsRtp(media_desc->protocol())) {
|
|
std::string rtcp_line = GetRtcpLine(candidates);
|
|
if (!rtcp_line.empty()) {
|
|
AddLine(rtcp_line, message);
|
|
}
|
|
}
|
|
|
|
// Build the a=candidate lines.
|
|
BuildCandidate(candidates, message);
|
|
|
|
// Use the transport_info to build the media level ice-ufrag and ice-pwd.
|
|
if (transport_info) {
|
|
// RFC 5245
|
|
// ice-pwd-att = "ice-pwd" ":" password
|
|
// ice-ufrag-att = "ice-ufrag" ":" ufrag
|
|
// ice-ufrag
|
|
InitAttrLine(kAttributeIceUfrag, &os);
|
|
os << kSdpDelimiterColon << transport_info->description.ice_ufrag;
|
|
AddLine(os.str(), message);
|
|
// ice-pwd
|
|
InitAttrLine(kAttributeIcePwd, &os);
|
|
os << kSdpDelimiterColon << transport_info->description.ice_pwd;
|
|
AddLine(os.str(), message);
|
|
|
|
// draft-petithuguenin-mmusic-ice-attributes-level-03
|
|
BuildIceOptions(transport_info->description.transport_options, message);
|
|
|
|
// RFC 4572
|
|
// fingerprint-attribute =
|
|
// "fingerprint" ":" hash-func SP fingerprint
|
|
if (fp) {
|
|
// Insert the fingerprint attribute.
|
|
InitAttrLine(kAttributeFingerprint, &os);
|
|
os << kSdpDelimiterColon
|
|
<< fp->algorithm << kSdpDelimiterSpace
|
|
<< fp->GetRfc4572Fingerprint();
|
|
AddLine(os.str(), message);
|
|
|
|
// Inserting setup attribute.
|
|
if (transport_info->description.connection_role !=
|
|
cricket::CONNECTIONROLE_NONE) {
|
|
// Making sure we are not using "passive" mode.
|
|
cricket::ConnectionRole role =
|
|
transport_info->description.connection_role;
|
|
std::string dtls_role_str;
|
|
VERIFY(cricket::ConnectionRoleToString(role, &dtls_role_str));
|
|
InitAttrLine(kAttributeSetup, &os);
|
|
os << kSdpDelimiterColon << dtls_role_str;
|
|
AddLine(os.str(), message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// RFC 3388
|
|
// mid-attribute = "a=mid:" identification-tag
|
|
// identification-tag = token
|
|
// Use the content name as the mid identification-tag.
|
|
InitAttrLine(kAttributeMid, &os);
|
|
os << kSdpDelimiterColon << content_info->name;
|
|
AddLine(os.str(), message);
|
|
|
|
if (IsDtlsSctp(media_desc->protocol())) {
|
|
BuildSctpContentAttributes(message, sctp_port);
|
|
} else if (IsRtp(media_desc->protocol())) {
|
|
BuildRtpContentAttributes(media_desc, media_type, message);
|
|
}
|
|
}
|
|
|
|
void BuildSctpContentAttributes(std::string* message, int sctp_port) {
|
|
// draft-ietf-mmusic-sctp-sdp-04
|
|
// a=sctpmap:sctpmap-number protocol [streams]
|
|
std::ostringstream os;
|
|
InitAttrLine(kAttributeSctpmap, &os);
|
|
os << kSdpDelimiterColon << sctp_port << kSdpDelimiterSpace
|
|
<< kDefaultSctpmapProtocol << kSdpDelimiterSpace
|
|
<< (cricket::kMaxSctpSid + 1);
|
|
AddLine(os.str(), message);
|
|
}
|
|
|
|
void BuildRtpContentAttributes(
|
|
const MediaContentDescription* media_desc,
|
|
const MediaType media_type,
|
|
std::string* message) {
|
|
std::ostringstream os;
|
|
// RFC 5285
|
|
// a=extmap:<value>["/"<direction>] <URI> <extensionattributes>
|
|
// The definitions MUST be either all session level or all media level. This
|
|
// implementation uses all media level.
|
|
for (size_t i = 0; i < media_desc->rtp_header_extensions().size(); ++i) {
|
|
InitAttrLine(kAttributeExtmap, &os);
|
|
os << kSdpDelimiterColon << media_desc->rtp_header_extensions()[i].id
|
|
<< kSdpDelimiterSpace << media_desc->rtp_header_extensions()[i].uri;
|
|
AddLine(os.str(), message);
|
|
}
|
|
|
|
// RFC 3264
|
|
// a=sendrecv || a=sendonly || a=sendrecv || a=inactive
|
|
|
|
cricket::MediaContentDirection direction = media_desc->direction();
|
|
if (media_desc->streams().empty() && direction == cricket::MD_SENDRECV) {
|
|
direction = cricket::MD_RECVONLY;
|
|
}
|
|
|
|
switch (direction) {
|
|
case cricket::MD_INACTIVE:
|
|
InitAttrLine(kAttributeInactive, &os);
|
|
break;
|
|
case cricket::MD_SENDONLY:
|
|
InitAttrLine(kAttributeSendOnly, &os);
|
|
break;
|
|
case cricket::MD_RECVONLY:
|
|
InitAttrLine(kAttributeRecvOnly, &os);
|
|
break;
|
|
case cricket::MD_SENDRECV:
|
|
default:
|
|
InitAttrLine(kAttributeSendRecv, &os);
|
|
break;
|
|
}
|
|
AddLine(os.str(), message);
|
|
|
|
// RFC 5761
|
|
// a=rtcp-mux
|
|
if (media_desc->rtcp_mux()) {
|
|
InitAttrLine(kAttributeRtcpMux, &os);
|
|
AddLine(os.str(), message);
|
|
}
|
|
|
|
// RFC 4568
|
|
// a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
|
|
for (std::vector<CryptoParams>::const_iterator it =
|
|
media_desc->cryptos().begin();
|
|
it != media_desc->cryptos().end(); ++it) {
|
|
InitAttrLine(kAttributeCrypto, &os);
|
|
os << kSdpDelimiterColon << it->tag << " " << it->cipher_suite << " "
|
|
<< it->key_params;
|
|
if (!it->session_params.empty()) {
|
|
os << " " << it->session_params;
|
|
}
|
|
AddLine(os.str(), message);
|
|
}
|
|
|
|
// RFC 4566
|
|
// a=rtpmap:<payload type> <encoding name>/<clock rate>
|
|
// [/<encodingparameters>]
|
|
BuildRtpMap(media_desc, media_type, message);
|
|
|
|
// Specify latency for buffered mode.
|
|
// a=x-google-buffer-latency:<value>
|
|
if (media_desc->buffered_mode_latency() != cricket::kBufferedModeDisabled) {
|
|
std::ostringstream os;
|
|
InitAttrLine(kAttributeXGoogleBufferLatency, &os);
|
|
os << kSdpDelimiterColon << media_desc->buffered_mode_latency();
|
|
AddLine(os.str(), message);
|
|
}
|
|
|
|
for (StreamParamsVec::const_iterator track = media_desc->streams().begin();
|
|
track != media_desc->streams().end(); ++track) {
|
|
// Require that the track belongs to a media stream,
|
|
// ie the sync_label is set. This extra check is necessary since the
|
|
// MediaContentDescription always contains a streamparam with an ssrc even
|
|
// if no track or media stream have been created.
|
|
if (track->sync_label.empty()) continue;
|
|
|
|
// Build the ssrc-group lines.
|
|
for (size_t i = 0; i < track->ssrc_groups.size(); ++i) {
|
|
// RFC 5576
|
|
// a=ssrc-group:<semantics> <ssrc-id> ...
|
|
if (track->ssrc_groups[i].ssrcs.empty()) {
|
|
continue;
|
|
}
|
|
std::ostringstream os;
|
|
InitAttrLine(kAttributeSsrcGroup, &os);
|
|
os << kSdpDelimiterColon << track->ssrc_groups[i].semantics;
|
|
std::vector<uint32>::const_iterator ssrc =
|
|
track->ssrc_groups[i].ssrcs.begin();
|
|
for (; ssrc != track->ssrc_groups[i].ssrcs.end(); ++ssrc) {
|
|
os << kSdpDelimiterSpace << rtc::ToString<uint32>(*ssrc);
|
|
}
|
|
AddLine(os.str(), message);
|
|
}
|
|
// Build the ssrc lines for each ssrc.
|
|
for (size_t i = 0; i < track->ssrcs.size(); ++i) {
|
|
uint32 ssrc = track->ssrcs[i];
|
|
// RFC 5576
|
|
// a=ssrc:<ssrc-id> cname:<value>
|
|
AddSsrcLine(ssrc, kSsrcAttributeCname,
|
|
track->cname, message);
|
|
|
|
// draft-alvestrand-mmusic-msid-00
|
|
// a=ssrc:<ssrc-id> msid:identifier [appdata]
|
|
// The appdata consists of the "id" attribute of a MediaStreamTrack, which
|
|
// is corresponding to the "name" attribute of StreamParams.
|
|
std::string appdata = track->id;
|
|
std::ostringstream os;
|
|
InitAttrLine(kAttributeSsrc, &os);
|
|
os << kSdpDelimiterColon << ssrc << kSdpDelimiterSpace
|
|
<< kSsrcAttributeMsid << kSdpDelimiterColon << track->sync_label
|
|
<< kSdpDelimiterSpace << appdata;
|
|
AddLine(os.str(), message);
|
|
|
|
// TODO(ronghuawu): Remove below code which is for backward compatibility.
|
|
// draft-alvestrand-rtcweb-mid-01
|
|
// a=ssrc:<ssrc-id> mslabel:<value>
|
|
// The label isn't yet defined.
|
|
// a=ssrc:<ssrc-id> label:<value>
|
|
AddSsrcLine(ssrc, kSsrcAttributeMslabel, track->sync_label, message);
|
|
AddSsrcLine(ssrc, kSSrcAttributeLabel, track->id, message);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WriteFmtpHeader(int payload_type, std::ostringstream* os) {
|
|
// fmtp header: a=fmtp:|payload_type| <parameters>
|
|
// Add a=fmtp
|
|
InitAttrLine(kAttributeFmtp, os);
|
|
// Add :|payload_type|
|
|
*os << kSdpDelimiterColon << payload_type;
|
|
}
|
|
|
|
void WriteRtcpFbHeader(int payload_type, std::ostringstream* os) {
|
|
// rtcp-fb header: a=rtcp-fb:|payload_type|
|
|
// <parameters>/<ccm <ccm_parameters>>
|
|
// Add a=rtcp-fb
|
|
InitAttrLine(kAttributeRtcpFb, os);
|
|
// Add :
|
|
*os << kSdpDelimiterColon;
|
|
if (payload_type == kWildcardPayloadType) {
|
|
*os << "*";
|
|
} else {
|
|
*os << payload_type;
|
|
}
|
|
}
|
|
|
|
void WriteFmtpParameter(const std::string& parameter_name,
|
|
const std::string& parameter_value,
|
|
std::ostringstream* os) {
|
|
// fmtp parameters: |parameter_name|=|parameter_value|
|
|
*os << parameter_name << kSdpDelimiterEqual << parameter_value;
|
|
}
|
|
|
|
void WriteFmtpParameters(const cricket::CodecParameterMap& parameters,
|
|
std::ostringstream* os) {
|
|
for (cricket::CodecParameterMap::const_iterator fmtp = parameters.begin();
|
|
fmtp != parameters.end(); ++fmtp) {
|
|
// Each new parameter, except the first one starts with ";" and " ".
|
|
if (fmtp != parameters.begin()) {
|
|
*os << kSdpDelimiterSemicolon;
|
|
}
|
|
*os << kSdpDelimiterSpace;
|
|
WriteFmtpParameter(fmtp->first, fmtp->second, os);
|
|
}
|
|
}
|
|
|
|
bool IsFmtpParam(const std::string& name) {
|
|
const char* kFmtpParams[] = {
|
|
kCodecParamMinPTime, kCodecParamSPropStereo,
|
|
kCodecParamStereo, kCodecParamUseInbandFec, kCodecParamUseDtx,
|
|
kCodecParamStartBitrate, kCodecParamMaxBitrate, kCodecParamMinBitrate,
|
|
kCodecParamMaxQuantization, kCodecParamSctpProtocol, kCodecParamSctpStreams,
|
|
kCodecParamMaxAverageBitrate, kCodecParamMaxPlaybackRate,
|
|
kCodecParamAssociatedPayloadType
|
|
};
|
|
for (size_t i = 0; i < ARRAY_SIZE(kFmtpParams); ++i) {
|
|
if (_stricmp(name.c_str(), kFmtpParams[i]) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Retreives fmtp parameters from |params|, which may contain other parameters
|
|
// as well, and puts them in |fmtp_parameters|.
|
|
void GetFmtpParams(const cricket::CodecParameterMap& params,
|
|
cricket::CodecParameterMap* fmtp_parameters) {
|
|
for (cricket::CodecParameterMap::const_iterator iter = params.begin();
|
|
iter != params.end(); ++iter) {
|
|
if (IsFmtpParam(iter->first)) {
|
|
(*fmtp_parameters)[iter->first] = iter->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
void AddFmtpLine(const T& codec, std::string* message) {
|
|
cricket::CodecParameterMap fmtp_parameters;
|
|
GetFmtpParams(codec.params, &fmtp_parameters);
|
|
if (fmtp_parameters.empty()) {
|
|
// No need to add an fmtp if it will have no (optional) parameters.
|
|
return;
|
|
}
|
|
std::ostringstream os;
|
|
WriteFmtpHeader(codec.id, &os);
|
|
WriteFmtpParameters(fmtp_parameters, &os);
|
|
AddLine(os.str(), message);
|
|
return;
|
|
}
|
|
|
|
template <class T>
|
|
void AddRtcpFbLines(const T& codec, std::string* message) {
|
|
for (std::vector<cricket::FeedbackParam>::const_iterator iter =
|
|
codec.feedback_params.params().begin();
|
|
iter != codec.feedback_params.params().end(); ++iter) {
|
|
std::ostringstream os;
|
|
WriteRtcpFbHeader(codec.id, &os);
|
|
os << " " << iter->id();
|
|
if (!iter->param().empty()) {
|
|
os << " " << iter->param();
|
|
}
|
|
AddLine(os.str(), message);
|
|
}
|
|
}
|
|
|
|
bool AddSctpDataCodec(DataContentDescription* media_desc,
|
|
int sctp_port) {
|
|
if (media_desc->HasCodec(cricket::kGoogleSctpDataCodecId)) {
|
|
return ParseFailed("",
|
|
"Can't have multiple sctp port attributes.",
|
|
NULL);
|
|
}
|
|
// Add the SCTP Port number as a pseudo-codec "port" parameter
|
|
cricket::DataCodec codec_port(
|
|
cricket::kGoogleSctpDataCodecId, cricket::kGoogleSctpDataCodecName,
|
|
0);
|
|
codec_port.SetParam(cricket::kCodecParamPort, sctp_port);
|
|
LOG(INFO) << "AddSctpDataCodec: Got SCTP Port Number "
|
|
<< sctp_port;
|
|
media_desc->AddCodec(codec_port);
|
|
return true;
|
|
}
|
|
|
|
bool GetMinValue(const std::vector<int>& values, int* value) {
|
|
if (values.empty()) {
|
|
return false;
|
|
}
|
|
std::vector<int>::const_iterator found =
|
|
std::min_element(values.begin(), values.end());
|
|
*value = *found;
|
|
return true;
|
|
}
|
|
|
|
bool GetParameter(const std::string& name,
|
|
const cricket::CodecParameterMap& params, int* value) {
|
|
std::map<std::string, std::string>::const_iterator found =
|
|
params.find(name);
|
|
if (found == params.end()) {
|
|
return false;
|
|
}
|
|
if (!rtc::FromString(found->second, value)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void BuildRtpMap(const MediaContentDescription* media_desc,
|
|
const MediaType media_type,
|
|
std::string* message) {
|
|
ASSERT(message != NULL);
|
|
ASSERT(media_desc != NULL);
|
|
std::ostringstream os;
|
|
if (media_type == cricket::MEDIA_TYPE_VIDEO) {
|
|
const VideoContentDescription* video_desc =
|
|
static_cast<const VideoContentDescription*>(media_desc);
|
|
for (std::vector<cricket::VideoCodec>::const_iterator it =
|
|
video_desc->codecs().begin();
|
|
it != video_desc->codecs().end(); ++it) {
|
|
// RFC 4566
|
|
// a=rtpmap:<payload type> <encoding name>/<clock rate>
|
|
// [/<encodingparameters>]
|
|
if (it->id != kWildcardPayloadType) {
|
|
InitAttrLine(kAttributeRtpmap, &os);
|
|
os << kSdpDelimiterColon << it->id << " " << it->name
|
|
<< "/" << kDefaultVideoClockrate;
|
|
AddLine(os.str(), message);
|
|
}
|
|
AddRtcpFbLines(*it, message);
|
|
AddFmtpLine(*it, message);
|
|
}
|
|
} else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
|
|
const AudioContentDescription* audio_desc =
|
|
static_cast<const AudioContentDescription*>(media_desc);
|
|
std::vector<int> ptimes;
|
|
std::vector<int> maxptimes;
|
|
int max_minptime = 0;
|
|
for (std::vector<cricket::AudioCodec>::const_iterator it =
|
|
audio_desc->codecs().begin();
|
|
it != audio_desc->codecs().end(); ++it) {
|
|
ASSERT(!it->name.empty());
|
|
// RFC 4566
|
|
// a=rtpmap:<payload type> <encoding name>/<clock rate>
|
|
// [/<encodingparameters>]
|
|
InitAttrLine(kAttributeRtpmap, &os);
|
|
os << kSdpDelimiterColon << it->id << " ";
|
|
os << it->name << "/" << it->clockrate;
|
|
if (it->channels != 1) {
|
|
os << "/" << it->channels;
|
|
}
|
|
AddLine(os.str(), message);
|
|
AddRtcpFbLines(*it, message);
|
|
AddFmtpLine(*it, message);
|
|
int minptime = 0;
|
|
if (GetParameter(kCodecParamMinPTime, it->params, &minptime)) {
|
|
max_minptime = std::max(minptime, max_minptime);
|
|
}
|
|
int ptime;
|
|
if (GetParameter(kCodecParamPTime, it->params, &ptime)) {
|
|
ptimes.push_back(ptime);
|
|
}
|
|
int maxptime;
|
|
if (GetParameter(kCodecParamMaxPTime, it->params, &maxptime)) {
|
|
maxptimes.push_back(maxptime);
|
|
}
|
|
}
|
|
// Populate the maxptime attribute with the smallest maxptime of all codecs
|
|
// under the same m-line.
|
|
int min_maxptime = INT_MAX;
|
|
if (GetMinValue(maxptimes, &min_maxptime)) {
|
|
AddAttributeLine(kCodecParamMaxPTime, min_maxptime, message);
|
|
}
|
|
ASSERT(min_maxptime > max_minptime);
|
|
// Populate the ptime attribute with the smallest ptime or the largest
|
|
// minptime, whichever is the largest, for all codecs under the same m-line.
|
|
int ptime = INT_MAX;
|
|
if (GetMinValue(ptimes, &ptime)) {
|
|
ptime = std::min(ptime, min_maxptime);
|
|
ptime = std::max(ptime, max_minptime);
|
|
AddAttributeLine(kCodecParamPTime, ptime, message);
|
|
}
|
|
} else if (media_type == cricket::MEDIA_TYPE_DATA) {
|
|
const DataContentDescription* data_desc =
|
|
static_cast<const DataContentDescription*>(media_desc);
|
|
for (std::vector<cricket::DataCodec>::const_iterator it =
|
|
data_desc->codecs().begin();
|
|
it != data_desc->codecs().end(); ++it) {
|
|
// RFC 4566
|
|
// a=rtpmap:<payload type> <encoding name>/<clock rate>
|
|
// [/<encodingparameters>]
|
|
InitAttrLine(kAttributeRtpmap, &os);
|
|
os << kSdpDelimiterColon << it->id << " "
|
|
<< it->name << "/" << it->clockrate;
|
|
AddLine(os.str(), message);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BuildCandidate(const std::vector<Candidate>& candidates,
|
|
std::string* message) {
|
|
std::ostringstream os;
|
|
|
|
for (std::vector<Candidate>::const_iterator it = candidates.begin();
|
|
it != candidates.end(); ++it) {
|
|
// RFC 5245
|
|
// a=candidate:<foundation> <component-id> <transport> <priority>
|
|
// <connection-address> <port> typ <candidate-types>
|
|
// [raddr <connection-address>] [rport <port>]
|
|
// *(SP extension-att-name SP extension-att-value)
|
|
std::string type;
|
|
// Map the cricket candidate type to "host" / "srflx" / "prflx" / "relay"
|
|
if (it->type() == cricket::LOCAL_PORT_TYPE) {
|
|
type = kCandidateHost;
|
|
} else if (it->type() == cricket::STUN_PORT_TYPE) {
|
|
type = kCandidateSrflx;
|
|
} else if (it->type() == cricket::RELAY_PORT_TYPE) {
|
|
type = kCandidateRelay;
|
|
} else {
|
|
ASSERT(false);
|
|
// Never write out candidates if we don't know the type.
|
|
continue;
|
|
}
|
|
|
|
InitAttrLine(kAttributeCandidate, &os);
|
|
os << kSdpDelimiterColon
|
|
<< it->foundation() << " "
|
|
<< it->component() << " "
|
|
<< it->protocol() << " "
|
|
<< it->priority() << " "
|
|
<< it->address().ipaddr().ToString() << " "
|
|
<< it->address().PortAsString() << " "
|
|
<< kAttributeCandidateTyp << " "
|
|
<< type << " ";
|
|
|
|
// Related address
|
|
if (!it->related_address().IsNil()) {
|
|
os << kAttributeCandidateRaddr << " "
|
|
<< it->related_address().ipaddr().ToString() << " "
|
|
<< kAttributeCandidateRport << " "
|
|
<< it->related_address().PortAsString() << " ";
|
|
}
|
|
|
|
if (it->protocol() == cricket::TCP_PROTOCOL_NAME) {
|
|
os << kTcpCandidateType << " " << it->tcptype() << " ";
|
|
}
|
|
|
|
// Extensions
|
|
os << kAttributeCandidateGeneration << " " << it->generation();
|
|
|
|
AddLine(os.str(), message);
|
|
}
|
|
}
|
|
|
|
void BuildIceOptions(const std::vector<std::string>& transport_options,
|
|
std::string* message) {
|
|
if (!transport_options.empty()) {
|
|
std::ostringstream os;
|
|
InitAttrLine(kAttributeIceOption, &os);
|
|
os << kSdpDelimiterColon << transport_options[0];
|
|
for (size_t i = 1; i < transport_options.size(); ++i) {
|
|
os << kSdpDelimiterSpace << transport_options[i];
|
|
}
|
|
AddLine(os.str(), message);
|
|
}
|
|
}
|
|
|
|
bool IsRtp(const std::string& protocol) {
|
|
return protocol.empty() ||
|
|
(protocol.find(cricket::kMediaProtocolRtpPrefix) != std::string::npos);
|
|
}
|
|
|
|
bool IsDtlsSctp(const std::string& protocol) {
|
|
// This intentionally excludes "SCTP" and "SCTP/DTLS".
|
|
return protocol.find(cricket::kMediaProtocolDtlsSctp) != std::string::npos;
|
|
}
|
|
|
|
bool ParseSessionDescription(const std::string& message, size_t* pos,
|
|
std::string* session_id,
|
|
std::string* session_version,
|
|
bool* supports_msid,
|
|
TransportDescription* session_td,
|
|
RtpHeaderExtensions* session_extmaps,
|
|
cricket::SessionDescription* desc,
|
|
SdpParseError* error) {
|
|
std::string line;
|
|
|
|
// RFC 4566
|
|
// v= (protocol version)
|
|
if (!GetLineWithType(message, pos, &line, kLineTypeVersion)) {
|
|
return ParseFailedExpectLine(message, *pos, kLineTypeVersion,
|
|
std::string(), error);
|
|
}
|
|
// RFC 4566
|
|
// o=<username> <sess-id> <sess-version> <nettype> <addrtype>
|
|
// <unicast-address>
|
|
if (!GetLineWithType(message, pos, &line, kLineTypeOrigin)) {
|
|
return ParseFailedExpectLine(message, *pos, kLineTypeOrigin,
|
|
std::string(), error);
|
|
}
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength),
|
|
kSdpDelimiterSpace, &fields);
|
|
const size_t expected_fields = 6;
|
|
if (fields.size() != expected_fields) {
|
|
return ParseFailedExpectFieldNum(line, expected_fields, error);
|
|
}
|
|
*session_id = fields[1];
|
|
*session_version = fields[2];
|
|
|
|
// RFC 4566
|
|
// s= (session name)
|
|
if (!GetLineWithType(message, pos, &line, kLineTypeSessionName)) {
|
|
return ParseFailedExpectLine(message, *pos, kLineTypeSessionName,
|
|
std::string(), error);
|
|
}
|
|
|
|
// Optional lines
|
|
// Those are the optional lines, so shouldn't return false if not present.
|
|
// RFC 4566
|
|
// i=* (session information)
|
|
GetLineWithType(message, pos, &line, kLineTypeSessionInfo);
|
|
|
|
// RFC 4566
|
|
// u=* (URI of description)
|
|
GetLineWithType(message, pos, &line, kLineTypeSessionUri);
|
|
|
|
// RFC 4566
|
|
// e=* (email address)
|
|
GetLineWithType(message, pos, &line, kLineTypeSessionEmail);
|
|
|
|
// RFC 4566
|
|
// p=* (phone number)
|
|
GetLineWithType(message, pos, &line, kLineTypeSessionPhone);
|
|
|
|
// RFC 4566
|
|
// c=* (connection information -- not required if included in
|
|
// all media)
|
|
GetLineWithType(message, pos, &line, kLineTypeConnection);
|
|
|
|
// RFC 4566
|
|
// b=* (zero or more bandwidth information lines)
|
|
while (GetLineWithType(message, pos, &line, kLineTypeSessionBandwidth)) {
|
|
// By pass zero or more b lines.
|
|
}
|
|
|
|
// RFC 4566
|
|
// One or more time descriptions ("t=" and "r=" lines; see below)
|
|
// t= (time the session is active)
|
|
// r=* (zero or more repeat times)
|
|
// Ensure there's at least one time description
|
|
if (!GetLineWithType(message, pos, &line, kLineTypeTiming)) {
|
|
return ParseFailedExpectLine(message, *pos, kLineTypeTiming, std::string(),
|
|
error);
|
|
}
|
|
|
|
while (GetLineWithType(message, pos, &line, kLineTypeRepeatTimes)) {
|
|
// By pass zero or more r lines.
|
|
}
|
|
|
|
// Go through the rest of the time descriptions
|
|
while (GetLineWithType(message, pos, &line, kLineTypeTiming)) {
|
|
while (GetLineWithType(message, pos, &line, kLineTypeRepeatTimes)) {
|
|
// By pass zero or more r lines.
|
|
}
|
|
}
|
|
|
|
// RFC 4566
|
|
// z=* (time zone adjustments)
|
|
GetLineWithType(message, pos, &line, kLineTypeTimeZone);
|
|
|
|
// RFC 4566
|
|
// k=* (encryption key)
|
|
GetLineWithType(message, pos, &line, kLineTypeEncryptionKey);
|
|
|
|
// RFC 4566
|
|
// a=* (zero or more session attribute lines)
|
|
while (GetLineWithType(message, pos, &line, kLineTypeAttributes)) {
|
|
if (HasAttribute(line, kAttributeGroup)) {
|
|
if (!ParseGroupAttribute(line, desc, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeIceUfrag)) {
|
|
if (!GetValue(line, kAttributeIceUfrag,
|
|
&(session_td->ice_ufrag), error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeIcePwd)) {
|
|
if (!GetValue(line, kAttributeIcePwd, &(session_td->ice_pwd), error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeIceLite)) {
|
|
session_td->ice_mode = cricket::ICEMODE_LITE;
|
|
} else if (HasAttribute(line, kAttributeIceOption)) {
|
|
if (!ParseIceOptions(line, &(session_td->transport_options), error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeFingerprint)) {
|
|
if (session_td->identity_fingerprint.get()) {
|
|
return ParseFailed(
|
|
line,
|
|
"Can't have multiple fingerprint attributes at the same level.",
|
|
error);
|
|
}
|
|
rtc::SSLFingerprint* fingerprint = NULL;
|
|
if (!ParseFingerprintAttribute(line, &fingerprint, error)) {
|
|
return false;
|
|
}
|
|
session_td->identity_fingerprint.reset(fingerprint);
|
|
} else if (HasAttribute(line, kAttributeSetup)) {
|
|
if (!ParseDtlsSetup(line, &(session_td->connection_role), error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeMsidSemantics)) {
|
|
std::string semantics;
|
|
if (!GetValue(line, kAttributeMsidSemantics, &semantics, error)) {
|
|
return false;
|
|
}
|
|
*supports_msid = CaseInsensitiveFind(semantics, kMediaStreamSemantic);
|
|
} else if (HasAttribute(line, kAttributeExtmap)) {
|
|
RtpHeaderExtension extmap;
|
|
if (!ParseExtmap(line, &extmap, error)) {
|
|
return false;
|
|
}
|
|
session_extmaps->push_back(extmap);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ParseGroupAttribute(const std::string& line,
|
|
cricket::SessionDescription* desc,
|
|
SdpParseError* error) {
|
|
ASSERT(desc != NULL);
|
|
|
|
// RFC 5888 and draft-holmberg-mmusic-sdp-bundle-negotiation-00
|
|
// a=group:BUNDLE video voice
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength),
|
|
kSdpDelimiterSpace, &fields);
|
|
std::string semantics;
|
|
if (!GetValue(fields[0], kAttributeGroup, &semantics, error)) {
|
|
return false;
|
|
}
|
|
cricket::ContentGroup group(semantics);
|
|
for (size_t i = 1; i < fields.size(); ++i) {
|
|
group.AddContentName(fields[i]);
|
|
}
|
|
desc->AddGroup(group);
|
|
return true;
|
|
}
|
|
|
|
static bool ParseFingerprintAttribute(const std::string& line,
|
|
rtc::SSLFingerprint** fingerprint,
|
|
SdpParseError* error) {
|
|
if (!IsLineType(line, kLineTypeAttributes) ||
|
|
!HasAttribute(line, kAttributeFingerprint)) {
|
|
return ParseFailedExpectLine(line, 0, kLineTypeAttributes,
|
|
kAttributeFingerprint, error);
|
|
}
|
|
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength),
|
|
kSdpDelimiterSpace, &fields);
|
|
const size_t expected_fields = 2;
|
|
if (fields.size() != expected_fields) {
|
|
return ParseFailedExpectFieldNum(line, expected_fields, error);
|
|
}
|
|
|
|
// The first field here is "fingerprint:<hash>.
|
|
std::string algorithm;
|
|
if (!GetValue(fields[0], kAttributeFingerprint, &algorithm, error)) {
|
|
return false;
|
|
}
|
|
|
|
// Downcase the algorithm. Note that we don't need to downcase the
|
|
// fingerprint because hex_decode can handle upper-case.
|
|
std::transform(algorithm.begin(), algorithm.end(), algorithm.begin(),
|
|
::tolower);
|
|
|
|
// The second field is the digest value. De-hexify it.
|
|
*fingerprint = rtc::SSLFingerprint::CreateFromRfc4572(
|
|
algorithm, fields[1]);
|
|
if (!*fingerprint) {
|
|
return ParseFailed(line,
|
|
"Failed to create fingerprint from the digest.",
|
|
error);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ParseDtlsSetup(const std::string& line,
|
|
cricket::ConnectionRole* role,
|
|
SdpParseError* error) {
|
|
// setup-attr = "a=setup:" role
|
|
// role = "active" / "passive" / "actpass" / "holdconn"
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterColon, &fields);
|
|
const size_t expected_fields = 2;
|
|
if (fields.size() != expected_fields) {
|
|
return ParseFailedExpectFieldNum(line, expected_fields, error);
|
|
}
|
|
std::string role_str = fields[1];
|
|
if (!cricket::StringToConnectionRole(role_str, role)) {
|
|
return ParseFailed(line, "Invalid attribute value.", error);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// RFC 3551
|
|
// PT encoding media type clock rate channels
|
|
// name (Hz)
|
|
// 0 PCMU A 8,000 1
|
|
// 1 reserved A
|
|
// 2 reserved A
|
|
// 3 GSM A 8,000 1
|
|
// 4 G723 A 8,000 1
|
|
// 5 DVI4 A 8,000 1
|
|
// 6 DVI4 A 16,000 1
|
|
// 7 LPC A 8,000 1
|
|
// 8 PCMA A 8,000 1
|
|
// 9 G722 A 8,000 1
|
|
// 10 L16 A 44,100 2
|
|
// 11 L16 A 44,100 1
|
|
// 12 QCELP A 8,000 1
|
|
// 13 CN A 8,000 1
|
|
// 14 MPA A 90,000 (see text)
|
|
// 15 G728 A 8,000 1
|
|
// 16 DVI4 A 11,025 1
|
|
// 17 DVI4 A 22,050 1
|
|
// 18 G729 A 8,000 1
|
|
struct StaticPayloadAudioCodec {
|
|
const char* name;
|
|
int clockrate;
|
|
int channels;
|
|
};
|
|
static const StaticPayloadAudioCodec kStaticPayloadAudioCodecs[] = {
|
|
{ "PCMU", 8000, 1 },
|
|
{ "reserved", 0, 0 },
|
|
{ "reserved", 0, 0 },
|
|
{ "GSM", 8000, 1 },
|
|
{ "G723", 8000, 1 },
|
|
{ "DVI4", 8000, 1 },
|
|
{ "DVI4", 16000, 1 },
|
|
{ "LPC", 8000, 1 },
|
|
{ "PCMA", 8000, 1 },
|
|
{ "G722", 8000, 1 },
|
|
{ "L16", 44100, 2 },
|
|
{ "L16", 44100, 1 },
|
|
{ "QCELP", 8000, 1 },
|
|
{ "CN", 8000, 1 },
|
|
{ "MPA", 90000, 1 },
|
|
{ "G728", 8000, 1 },
|
|
{ "DVI4", 11025, 1 },
|
|
{ "DVI4", 22050, 1 },
|
|
{ "G729", 8000, 1 },
|
|
};
|
|
|
|
void MaybeCreateStaticPayloadAudioCodecs(
|
|
const std::vector<int>& fmts, AudioContentDescription* media_desc) {
|
|
if (!media_desc) {
|
|
return;
|
|
}
|
|
int preference = static_cast<int>(fmts.size());
|
|
std::vector<int>::const_iterator it = fmts.begin();
|
|
bool add_new_codec = false;
|
|
for (; it != fmts.end(); ++it) {
|
|
int payload_type = *it;
|
|
if (!media_desc->HasCodec(payload_type) &&
|
|
payload_type >= 0 &&
|
|
payload_type < ARRAY_SIZE(kStaticPayloadAudioCodecs)) {
|
|
std::string encoding_name = kStaticPayloadAudioCodecs[payload_type].name;
|
|
int clock_rate = kStaticPayloadAudioCodecs[payload_type].clockrate;
|
|
int channels = kStaticPayloadAudioCodecs[payload_type].channels;
|
|
media_desc->AddCodec(cricket::AudioCodec(payload_type, encoding_name,
|
|
clock_rate, 0, channels,
|
|
preference));
|
|
add_new_codec = true;
|
|
}
|
|
--preference;
|
|
}
|
|
if (add_new_codec) {
|
|
media_desc->SortCodecs();
|
|
}
|
|
}
|
|
|
|
template <class C>
|
|
static C* ParseContentDescription(const std::string& message,
|
|
const MediaType media_type,
|
|
int mline_index,
|
|
const std::string& protocol,
|
|
const std::vector<int>& codec_preference,
|
|
size_t* pos,
|
|
std::string* content_name,
|
|
TransportDescription* transport,
|
|
std::vector<JsepIceCandidate*>* candidates,
|
|
webrtc::SdpParseError* error) {
|
|
C* media_desc = new C();
|
|
switch (media_type) {
|
|
case cricket::MEDIA_TYPE_AUDIO:
|
|
*content_name = cricket::CN_AUDIO;
|
|
break;
|
|
case cricket::MEDIA_TYPE_VIDEO:
|
|
*content_name = cricket::CN_VIDEO;
|
|
break;
|
|
case cricket::MEDIA_TYPE_DATA:
|
|
*content_name = cricket::CN_DATA;
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
if (!ParseContent(message, media_type, mline_index, protocol,
|
|
codec_preference, pos, content_name,
|
|
media_desc, transport, candidates, error)) {
|
|
delete media_desc;
|
|
return NULL;
|
|
}
|
|
// Sort the codecs according to the m-line fmt list.
|
|
media_desc->SortCodecs();
|
|
return media_desc;
|
|
}
|
|
|
|
bool ParseMediaDescription(const std::string& message,
|
|
const TransportDescription& session_td,
|
|
const RtpHeaderExtensions& session_extmaps,
|
|
bool supports_msid,
|
|
size_t* pos,
|
|
cricket::SessionDescription* desc,
|
|
std::vector<JsepIceCandidate*>* candidates,
|
|
SdpParseError* error) {
|
|
ASSERT(desc != NULL);
|
|
std::string line;
|
|
int mline_index = -1;
|
|
|
|
// Zero or more media descriptions
|
|
// RFC 4566
|
|
// m=<media> <port> <proto> <fmt>
|
|
while (GetLineWithType(message, pos, &line, kLineTypeMedia)) {
|
|
++mline_index;
|
|
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength),
|
|
kSdpDelimiterSpace, &fields);
|
|
const size_t expected_min_fields = 4;
|
|
if (fields.size() < expected_min_fields) {
|
|
return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
|
|
}
|
|
bool rejected = false;
|
|
// RFC 3264
|
|
// To reject an offered stream, the port number in the corresponding stream
|
|
// in the answer MUST be set to zero.
|
|
if (fields[1] == kMediaPortRejected) {
|
|
rejected = true;
|
|
}
|
|
|
|
std::string protocol = fields[2];
|
|
|
|
// <fmt>
|
|
std::vector<int> codec_preference;
|
|
if (IsRtp(protocol)) {
|
|
for (size_t j = 3 ; j < fields.size(); ++j) {
|
|
// TODO(wu): Remove when below bug is fixed.
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=996329
|
|
if (fields[j] == "" && j == fields.size() - 1) {
|
|
continue;
|
|
}
|
|
|
|
int pl = 0;
|
|
if (!GetPayloadTypeFromString(line, fields[j], &pl, error)) {
|
|
return false;
|
|
}
|
|
codec_preference.push_back(pl);
|
|
}
|
|
}
|
|
|
|
// Make a temporary TransportDescription based on |session_td|.
|
|
// Some of this gets overwritten by ParseContent.
|
|
TransportDescription transport(NS_JINGLE_ICE_UDP,
|
|
session_td.transport_options,
|
|
session_td.ice_ufrag,
|
|
session_td.ice_pwd,
|
|
session_td.ice_mode,
|
|
session_td.connection_role,
|
|
session_td.identity_fingerprint.get(),
|
|
Candidates());
|
|
|
|
rtc::scoped_ptr<MediaContentDescription> content;
|
|
std::string content_name;
|
|
if (HasAttribute(line, kMediaTypeVideo)) {
|
|
content.reset(ParseContentDescription<VideoContentDescription>(
|
|
message, cricket::MEDIA_TYPE_VIDEO, mline_index, protocol,
|
|
codec_preference, pos, &content_name,
|
|
&transport, candidates, error));
|
|
} else if (HasAttribute(line, kMediaTypeAudio)) {
|
|
content.reset(ParseContentDescription<AudioContentDescription>(
|
|
message, cricket::MEDIA_TYPE_AUDIO, mline_index, protocol,
|
|
codec_preference, pos, &content_name,
|
|
&transport, candidates, error));
|
|
} else if (HasAttribute(line, kMediaTypeData)) {
|
|
DataContentDescription* data_desc =
|
|
ParseContentDescription<DataContentDescription>(
|
|
message, cricket::MEDIA_TYPE_DATA, mline_index, protocol,
|
|
codec_preference, pos, &content_name,
|
|
&transport, candidates, error);
|
|
content.reset(data_desc);
|
|
|
|
int p;
|
|
if (data_desc && IsDtlsSctp(protocol) && rtc::FromString(fields[3], &p)) {
|
|
if (!AddSctpDataCodec(data_desc, p))
|
|
return false;
|
|
}
|
|
} else {
|
|
LOG(LS_WARNING) << "Unsupported media type: " << line;
|
|
continue;
|
|
}
|
|
if (!content.get()) {
|
|
// ParseContentDescription returns NULL if failed.
|
|
return false;
|
|
}
|
|
|
|
if (IsRtp(protocol)) {
|
|
// Make sure to set the media direction correctly. If the direction is not
|
|
// MD_RECVONLY or Inactive and no streams are parsed,
|
|
// a default MediaStream will be created to prepare for receiving media.
|
|
if (supports_msid && content->streams().empty() &&
|
|
content->direction() == cricket::MD_SENDRECV) {
|
|
content->set_direction(cricket::MD_RECVONLY);
|
|
}
|
|
|
|
// Set the extmap.
|
|
if (!session_extmaps.empty() &&
|
|
!content->rtp_header_extensions().empty()) {
|
|
return ParseFailed("",
|
|
"The a=extmap MUST be either all session level or "
|
|
"all media level.",
|
|
error);
|
|
}
|
|
for (size_t i = 0; i < session_extmaps.size(); ++i) {
|
|
content->AddRtpHeaderExtension(session_extmaps[i]);
|
|
}
|
|
}
|
|
content->set_protocol(protocol);
|
|
desc->AddContent(content_name,
|
|
IsDtlsSctp(protocol) ? cricket::NS_JINGLE_DRAFT_SCTP :
|
|
cricket::NS_JINGLE_RTP,
|
|
rejected,
|
|
content.release());
|
|
// Create TransportInfo with the media level "ice-pwd" and "ice-ufrag".
|
|
TransportInfo transport_info(content_name, transport);
|
|
|
|
if (!desc->AddTransportInfo(transport_info)) {
|
|
std::ostringstream description;
|
|
description << "Failed to AddTransportInfo with content name: "
|
|
<< content_name;
|
|
return ParseFailed("", description.str(), error);
|
|
}
|
|
}
|
|
|
|
size_t end_of_message = message.size();
|
|
if (mline_index == -1 && *pos != end_of_message) {
|
|
ParseFailed(message, *pos, "Expects m line.", error);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool VerifyCodec(const cricket::Codec& codec) {
|
|
// Codec has not been populated correctly unless the name has been set. This
|
|
// can happen if an SDP has an fmtp or rtcp-fb with a payload type but doesn't
|
|
// have a corresponding "rtpmap" line.
|
|
cricket::Codec default_codec;
|
|
return default_codec.name != codec.name;
|
|
}
|
|
|
|
bool VerifyAudioCodecs(const AudioContentDescription* audio_desc) {
|
|
const std::vector<cricket::AudioCodec>& codecs = audio_desc->codecs();
|
|
for (std::vector<cricket::AudioCodec>::const_iterator iter = codecs.begin();
|
|
iter != codecs.end(); ++iter) {
|
|
if (!VerifyCodec(*iter)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool VerifyVideoCodecs(const VideoContentDescription* video_desc) {
|
|
const std::vector<cricket::VideoCodec>& codecs = video_desc->codecs();
|
|
for (std::vector<cricket::VideoCodec>::const_iterator iter = codecs.begin();
|
|
iter != codecs.end(); ++iter) {
|
|
if (!VerifyCodec(*iter)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AddParameters(const cricket::CodecParameterMap& parameters,
|
|
cricket::Codec* codec) {
|
|
for (cricket::CodecParameterMap::const_iterator iter =
|
|
parameters.begin(); iter != parameters.end(); ++iter) {
|
|
codec->SetParam(iter->first, iter->second);
|
|
}
|
|
}
|
|
|
|
void AddFeedbackParameter(const cricket::FeedbackParam& feedback_param,
|
|
cricket::Codec* codec) {
|
|
codec->AddFeedbackParam(feedback_param);
|
|
}
|
|
|
|
void AddFeedbackParameters(const cricket::FeedbackParams& feedback_params,
|
|
cricket::Codec* codec) {
|
|
for (std::vector<cricket::FeedbackParam>::const_iterator iter =
|
|
feedback_params.params().begin();
|
|
iter != feedback_params.params().end(); ++iter) {
|
|
codec->AddFeedbackParam(*iter);
|
|
}
|
|
}
|
|
|
|
// Gets the current codec setting associated with |payload_type|. If there
|
|
// is no Codec associated with that payload type it returns an empty codec
|
|
// with that payload type.
|
|
template <class T>
|
|
T GetCodecWithPayloadType(const std::vector<T>& codecs, int payload_type) {
|
|
T ret_val;
|
|
if (!FindCodecById(codecs, payload_type, &ret_val)) {
|
|
ret_val.id = payload_type;
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
// Updates or creates a new codec entry in the audio description.
|
|
template <class T, class U>
|
|
void AddOrReplaceCodec(MediaContentDescription* content_desc, const U& codec) {
|
|
T* desc = static_cast<T*>(content_desc);
|
|
std::vector<U> codecs = desc->codecs();
|
|
bool found = false;
|
|
|
|
typename std::vector<U>::iterator iter;
|
|
for (iter = codecs.begin(); iter != codecs.end(); ++iter) {
|
|
if (iter->id == codec.id) {
|
|
*iter = codec;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
desc->AddCodec(codec);
|
|
return;
|
|
}
|
|
desc->set_codecs(codecs);
|
|
}
|
|
|
|
// Adds or updates existing codec corresponding to |payload_type| according
|
|
// to |parameters|.
|
|
template <class T, class U>
|
|
void UpdateCodec(MediaContentDescription* content_desc, int payload_type,
|
|
const cricket::CodecParameterMap& parameters) {
|
|
// Codec might already have been populated (from rtpmap).
|
|
U new_codec = GetCodecWithPayloadType(static_cast<T*>(content_desc)->codecs(),
|
|
payload_type);
|
|
AddParameters(parameters, &new_codec);
|
|
AddOrReplaceCodec<T, U>(content_desc, new_codec);
|
|
}
|
|
|
|
// Adds or updates existing codec corresponding to |payload_type| according
|
|
// to |feedback_param|.
|
|
template <class T, class U>
|
|
void UpdateCodec(MediaContentDescription* content_desc, int payload_type,
|
|
const cricket::FeedbackParam& feedback_param) {
|
|
// Codec might already have been populated (from rtpmap).
|
|
U new_codec = GetCodecWithPayloadType(static_cast<T*>(content_desc)->codecs(),
|
|
payload_type);
|
|
AddFeedbackParameter(feedback_param, &new_codec);
|
|
AddOrReplaceCodec<T, U>(content_desc, new_codec);
|
|
}
|
|
|
|
template <class T>
|
|
bool PopWildcardCodec(std::vector<T>* codecs, T* wildcard_codec) {
|
|
for (auto iter = codecs->begin(); iter != codecs->end(); ++iter) {
|
|
if (iter->id == kWildcardPayloadType) {
|
|
*wildcard_codec = *iter;
|
|
codecs->erase(iter);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template<class T>
|
|
void UpdateFromWildcardCodecs(cricket::MediaContentDescriptionImpl<T>* desc) {
|
|
auto codecs = desc->codecs();
|
|
T wildcard_codec;
|
|
if (!PopWildcardCodec(&codecs, &wildcard_codec)) {
|
|
return;
|
|
}
|
|
for (auto& codec : codecs) {
|
|
AddFeedbackParameters(wildcard_codec.feedback_params, &codec);
|
|
}
|
|
desc->set_codecs(codecs);
|
|
}
|
|
|
|
void AddAudioAttribute(const std::string& name, const std::string& value,
|
|
AudioContentDescription* audio_desc) {
|
|
if (value.empty()) {
|
|
return;
|
|
}
|
|
std::vector<cricket::AudioCodec> codecs = audio_desc->codecs();
|
|
for (std::vector<cricket::AudioCodec>::iterator iter = codecs.begin();
|
|
iter != codecs.end(); ++iter) {
|
|
iter->params[name] = value;
|
|
}
|
|
audio_desc->set_codecs(codecs);
|
|
}
|
|
|
|
bool ParseContent(const std::string& message,
|
|
const MediaType media_type,
|
|
int mline_index,
|
|
const std::string& protocol,
|
|
const std::vector<int>& codec_preference,
|
|
size_t* pos,
|
|
std::string* content_name,
|
|
MediaContentDescription* media_desc,
|
|
TransportDescription* transport,
|
|
std::vector<JsepIceCandidate*>* candidates,
|
|
SdpParseError* error) {
|
|
ASSERT(media_desc != NULL);
|
|
ASSERT(content_name != NULL);
|
|
ASSERT(transport != NULL);
|
|
|
|
if (media_type == cricket::MEDIA_TYPE_AUDIO) {
|
|
MaybeCreateStaticPayloadAudioCodecs(
|
|
codec_preference, static_cast<AudioContentDescription*>(media_desc));
|
|
}
|
|
|
|
// The media level "ice-ufrag" and "ice-pwd".
|
|
// The candidates before update the media level "ice-pwd" and "ice-ufrag".
|
|
Candidates candidates_orig;
|
|
std::string line;
|
|
std::string mline_id;
|
|
// Tracks created out of the ssrc attributes.
|
|
StreamParamsVec tracks;
|
|
SsrcInfoVec ssrc_infos;
|
|
SsrcGroupVec ssrc_groups;
|
|
std::string maxptime_as_string;
|
|
std::string ptime_as_string;
|
|
|
|
// Loop until the next m line
|
|
while (!IsLineType(message, kLineTypeMedia, *pos)) {
|
|
if (!GetLine(message, pos, &line)) {
|
|
if (*pos >= message.size()) {
|
|
break; // Done parsing
|
|
} else {
|
|
return ParseFailed(message, *pos, "Invalid SDP line.", error);
|
|
}
|
|
}
|
|
|
|
// RFC 4566
|
|
// b=* (zero or more bandwidth information lines)
|
|
if (IsLineType(line, kLineTypeSessionBandwidth)) {
|
|
std::string bandwidth;
|
|
if (HasAttribute(line, kApplicationSpecificMaximum)) {
|
|
if (!GetValue(line, kApplicationSpecificMaximum, &bandwidth, error)) {
|
|
return false;
|
|
} else {
|
|
int b = 0;
|
|
if (!GetValueFromString(line, bandwidth, &b, error)) {
|
|
return false;
|
|
}
|
|
// We should never use more than the default bandwidth for RTP-based
|
|
// data channels. Don't allow SDP to set the bandwidth, because
|
|
// that would give JS the opportunity to "break the Internet".
|
|
// See: https://code.google.com/p/chromium/issues/detail?id=280726
|
|
if (media_type == cricket::MEDIA_TYPE_DATA && IsRtp(protocol) &&
|
|
b > cricket::kDataMaxBandwidth / 1000) {
|
|
std::ostringstream description;
|
|
description << "RTP-based data channels may not send more than "
|
|
<< cricket::kDataMaxBandwidth / 1000 << "kbps.";
|
|
return ParseFailed(line, description.str(), error);
|
|
}
|
|
media_desc->set_bandwidth(b * 1000);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!IsLineType(line, kLineTypeAttributes)) {
|
|
// TODO: Handle other lines if needed.
|
|
LOG(LS_INFO) << "Ignored line: " << line;
|
|
continue;
|
|
}
|
|
|
|
// Handle attributes common to SCTP and RTP.
|
|
if (HasAttribute(line, kAttributeMid)) {
|
|
// RFC 3388
|
|
// mid-attribute = "a=mid:" identification-tag
|
|
// identification-tag = token
|
|
// Use the mid identification-tag as the content name.
|
|
if (!GetValue(line, kAttributeMid, &mline_id, error)) {
|
|
return false;
|
|
}
|
|
*content_name = mline_id;
|
|
} else if (HasAttribute(line, kAttributeCandidate)) {
|
|
Candidate candidate;
|
|
if (!ParseCandidate(line, &candidate, error, false)) {
|
|
return false;
|
|
}
|
|
candidates_orig.push_back(candidate);
|
|
} else if (HasAttribute(line, kAttributeIceUfrag)) {
|
|
if (!GetValue(line, kAttributeIceUfrag, &transport->ice_ufrag, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeIcePwd)) {
|
|
if (!GetValue(line, kAttributeIcePwd, &transport->ice_pwd, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeIceOption)) {
|
|
if (!ParseIceOptions(line, &transport->transport_options, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeFmtp)) {
|
|
if (!ParseFmtpAttributes(line, media_type, media_desc, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeFingerprint)) {
|
|
rtc::SSLFingerprint* fingerprint = NULL;
|
|
|
|
if (!ParseFingerprintAttribute(line, &fingerprint, error)) {
|
|
return false;
|
|
}
|
|
transport->identity_fingerprint.reset(fingerprint);
|
|
} else if (HasAttribute(line, kAttributeSetup)) {
|
|
if (!ParseDtlsSetup(line, &(transport->connection_role), error)) {
|
|
return false;
|
|
}
|
|
} else if (IsDtlsSctp(protocol) && HasAttribute(line, kAttributeSctpPort)) {
|
|
int sctp_port;
|
|
if (!ParseSctpPort(line, &sctp_port, error)) {
|
|
return false;
|
|
}
|
|
if (!AddSctpDataCodec(static_cast<DataContentDescription*>(media_desc),
|
|
sctp_port)) {
|
|
return false;
|
|
}
|
|
} else if (IsRtp(protocol)) {
|
|
//
|
|
// RTP specific attrubtes
|
|
//
|
|
if (HasAttribute(line, kAttributeRtcpMux)) {
|
|
media_desc->set_rtcp_mux(true);
|
|
} else if (HasAttribute(line, kAttributeSsrcGroup)) {
|
|
if (!ParseSsrcGroupAttribute(line, &ssrc_groups, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeSsrc)) {
|
|
if (!ParseSsrcAttribute(line, &ssrc_infos, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeCrypto)) {
|
|
if (!ParseCryptoAttribute(line, media_desc, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeRtpmap)) {
|
|
if (!ParseRtpmapAttribute(line, media_type, codec_preference,
|
|
media_desc, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kCodecParamMaxPTime)) {
|
|
if (!GetValue(line, kCodecParamMaxPTime, &maxptime_as_string, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeRtcpFb)) {
|
|
if (!ParseRtcpFbAttribute(line, media_type, media_desc, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kCodecParamPTime)) {
|
|
if (!GetValue(line, kCodecParamPTime, &ptime_as_string, error)) {
|
|
return false;
|
|
}
|
|
} else if (HasAttribute(line, kAttributeSendOnly)) {
|
|
media_desc->set_direction(cricket::MD_SENDONLY);
|
|
} else if (HasAttribute(line, kAttributeRecvOnly)) {
|
|
media_desc->set_direction(cricket::MD_RECVONLY);
|
|
} else if (HasAttribute(line, kAttributeInactive)) {
|
|
media_desc->set_direction(cricket::MD_INACTIVE);
|
|
} else if (HasAttribute(line, kAttributeSendRecv)) {
|
|
media_desc->set_direction(cricket::MD_SENDRECV);
|
|
} else if (HasAttribute(line, kAttributeExtmap)) {
|
|
RtpHeaderExtension extmap;
|
|
if (!ParseExtmap(line, &extmap, error)) {
|
|
return false;
|
|
}
|
|
media_desc->AddRtpHeaderExtension(extmap);
|
|
} else if (HasAttribute(line, kAttributeXGoogleFlag)) {
|
|
// Experimental attribute. Conference mode activates more aggressive
|
|
// AEC and NS settings.
|
|
// TODO: expose API to set these directly.
|
|
std::string flag_value;
|
|
if (!GetValue(line, kAttributeXGoogleFlag, &flag_value, error)) {
|
|
return false;
|
|
}
|
|
if (flag_value.compare(kValueConference) == 0)
|
|
media_desc->set_conference_mode(true);
|
|
} else if (HasAttribute(line, kAttributeXGoogleBufferLatency)) {
|
|
// Experimental attribute.
|
|
// TODO: expose API to set this directly.
|
|
std::string flag_value;
|
|
if (!GetValue(line, kAttributeXGoogleBufferLatency, &flag_value,
|
|
error)) {
|
|
return false;
|
|
}
|
|
int buffer_latency = 0;
|
|
if (!GetValueFromString(line, flag_value, &buffer_latency, error)) {
|
|
return false;
|
|
}
|
|
if (buffer_latency < 0) {
|
|
return ParseFailed(line, "Buffer latency less than 0.", error);
|
|
}
|
|
media_desc->set_buffered_mode_latency(buffer_latency);
|
|
}
|
|
} else {
|
|
// Only parse lines that we are interested of.
|
|
LOG(LS_INFO) << "Ignored line: " << line;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Create tracks from the |ssrc_infos|.
|
|
CreateTracksFromSsrcInfos(ssrc_infos, &tracks);
|
|
|
|
// Add the ssrc group to the track.
|
|
for (SsrcGroupVec::iterator ssrc_group = ssrc_groups.begin();
|
|
ssrc_group != ssrc_groups.end(); ++ssrc_group) {
|
|
if (ssrc_group->ssrcs.empty()) {
|
|
continue;
|
|
}
|
|
uint32 ssrc = ssrc_group->ssrcs.front();
|
|
for (StreamParamsVec::iterator track = tracks.begin();
|
|
track != tracks.end(); ++track) {
|
|
if (track->has_ssrc(ssrc)) {
|
|
track->ssrc_groups.push_back(*ssrc_group);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the new tracks to the |media_desc|.
|
|
for (StreamParamsVec::iterator track = tracks.begin();
|
|
track != tracks.end(); ++track) {
|
|
media_desc->AddStream(*track);
|
|
}
|
|
|
|
if (media_type == cricket::MEDIA_TYPE_AUDIO) {
|
|
AudioContentDescription* audio_desc =
|
|
static_cast<AudioContentDescription*>(media_desc);
|
|
UpdateFromWildcardCodecs(audio_desc);
|
|
|
|
// Verify audio codec ensures that no audio codec has been populated with
|
|
// only fmtp.
|
|
if (!VerifyAudioCodecs(audio_desc)) {
|
|
return ParseFailed("Failed to parse audio codecs correctly.", error);
|
|
}
|
|
AddAudioAttribute(kCodecParamMaxPTime, maxptime_as_string, audio_desc);
|
|
AddAudioAttribute(kCodecParamPTime, ptime_as_string, audio_desc);
|
|
}
|
|
|
|
if (media_type == cricket::MEDIA_TYPE_VIDEO) {
|
|
VideoContentDescription* video_desc =
|
|
static_cast<VideoContentDescription*>(media_desc);
|
|
UpdateFromWildcardCodecs(video_desc);
|
|
// Verify video codec ensures that no video codec has been populated with
|
|
// only rtcp-fb.
|
|
if (!VerifyVideoCodecs(video_desc)) {
|
|
return ParseFailed("Failed to parse video codecs correctly.", error);
|
|
}
|
|
}
|
|
|
|
// RFC 5245
|
|
// Update the candidates with the media level "ice-pwd" and "ice-ufrag".
|
|
for (Candidates::iterator it = candidates_orig.begin();
|
|
it != candidates_orig.end(); ++it) {
|
|
ASSERT((*it).username().empty());
|
|
(*it).set_username(transport->ice_ufrag);
|
|
ASSERT((*it).password().empty());
|
|
(*it).set_password(transport->ice_pwd);
|
|
candidates->push_back(
|
|
new JsepIceCandidate(mline_id, mline_index, *it));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ParseSsrcAttribute(const std::string& line, SsrcInfoVec* ssrc_infos,
|
|
SdpParseError* error) {
|
|
ASSERT(ssrc_infos != NULL);
|
|
// RFC 5576
|
|
// a=ssrc:<ssrc-id> <attribute>
|
|
// a=ssrc:<ssrc-id> <attribute>:<value>
|
|
std::string field1, field2;
|
|
if (!rtc::tokenize_first(line.substr(kLinePrefixLength), kSdpDelimiterSpace,
|
|
&field1, &field2)) {
|
|
const size_t expected_fields = 2;
|
|
return ParseFailedExpectFieldNum(line, expected_fields, error);
|
|
}
|
|
|
|
// ssrc:<ssrc-id>
|
|
std::string ssrc_id_s;
|
|
if (!GetValue(field1, kAttributeSsrc, &ssrc_id_s, error)) {
|
|
return false;
|
|
}
|
|
uint32 ssrc_id = 0;
|
|
if (!GetValueFromString(line, ssrc_id_s, &ssrc_id, error)) {
|
|
return false;
|
|
}
|
|
|
|
std::string attribute;
|
|
std::string value;
|
|
if (!rtc::tokenize_first(field2, kSdpDelimiterColon, &attribute, &value)) {
|
|
std::ostringstream description;
|
|
description << "Failed to get the ssrc attribute value from " << field2
|
|
<< ". Expected format <attribute>:<value>.";
|
|
return ParseFailed(line, description.str(), error);
|
|
}
|
|
|
|
// Check if there's already an item for this |ssrc_id|. Create a new one if
|
|
// there isn't.
|
|
SsrcInfoVec::iterator ssrc_info = ssrc_infos->begin();
|
|
for (; ssrc_info != ssrc_infos->end(); ++ssrc_info) {
|
|
if (ssrc_info->ssrc_id == ssrc_id) {
|
|
break;
|
|
}
|
|
}
|
|
if (ssrc_info == ssrc_infos->end()) {
|
|
SsrcInfo info;
|
|
info.ssrc_id = ssrc_id;
|
|
ssrc_infos->push_back(info);
|
|
ssrc_info = ssrc_infos->end() - 1;
|
|
}
|
|
|
|
// Store the info to the |ssrc_info|.
|
|
if (attribute == kSsrcAttributeCname) {
|
|
// RFC 5576
|
|
// cname:<value>
|
|
ssrc_info->cname = value;
|
|
} else if (attribute == kSsrcAttributeMsid) {
|
|
// draft-alvestrand-mmusic-msid-00
|
|
// "msid:" identifier [ " " appdata ]
|
|
std::vector<std::string> fields;
|
|
rtc::split(value, kSdpDelimiterSpace, &fields);
|
|
if (fields.size() < 1 || fields.size() > 2) {
|
|
return ParseFailed(line,
|
|
"Expected format \"msid:<identifier>[ <appdata>]\".",
|
|
error);
|
|
}
|
|
ssrc_info->msid_identifier = fields[0];
|
|
if (fields.size() == 2) {
|
|
ssrc_info->msid_appdata = fields[1];
|
|
}
|
|
} else if (attribute == kSsrcAttributeMslabel) {
|
|
// draft-alvestrand-rtcweb-mid-01
|
|
// mslabel:<value>
|
|
ssrc_info->mslabel = value;
|
|
} else if (attribute == kSSrcAttributeLabel) {
|
|
// The label isn't defined.
|
|
// label:<value>
|
|
ssrc_info->label = value;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ParseSsrcGroupAttribute(const std::string& line,
|
|
SsrcGroupVec* ssrc_groups,
|
|
SdpParseError* error) {
|
|
ASSERT(ssrc_groups != NULL);
|
|
// RFC 5576
|
|
// a=ssrc-group:<semantics> <ssrc-id> ...
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength),
|
|
kSdpDelimiterSpace, &fields);
|
|
const size_t expected_min_fields = 2;
|
|
if (fields.size() < expected_min_fields) {
|
|
return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
|
|
}
|
|
std::string semantics;
|
|
if (!GetValue(fields[0], kAttributeSsrcGroup, &semantics, error)) {
|
|
return false;
|
|
}
|
|
std::vector<uint32> ssrcs;
|
|
for (size_t i = 1; i < fields.size(); ++i) {
|
|
uint32 ssrc = 0;
|
|
if (!GetValueFromString(line, fields[i], &ssrc, error)) {
|
|
return false;
|
|
}
|
|
ssrcs.push_back(ssrc);
|
|
}
|
|
ssrc_groups->push_back(SsrcGroup(semantics, ssrcs));
|
|
return true;
|
|
}
|
|
|
|
bool ParseCryptoAttribute(const std::string& line,
|
|
MediaContentDescription* media_desc,
|
|
SdpParseError* error) {
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength),
|
|
kSdpDelimiterSpace, &fields);
|
|
// RFC 4568
|
|
// a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
|
|
const size_t expected_min_fields = 3;
|
|
if (fields.size() < expected_min_fields) {
|
|
return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
|
|
}
|
|
std::string tag_value;
|
|
if (!GetValue(fields[0], kAttributeCrypto, &tag_value, error)) {
|
|
return false;
|
|
}
|
|
int tag = 0;
|
|
if (!GetValueFromString(line, tag_value, &tag, error)) {
|
|
return false;
|
|
}
|
|
const std::string crypto_suite = fields[1];
|
|
const std::string key_params = fields[2];
|
|
std::string session_params;
|
|
if (fields.size() > 3) {
|
|
session_params = fields[3];
|
|
}
|
|
media_desc->AddCrypto(CryptoParams(tag, crypto_suite, key_params,
|
|
session_params));
|
|
return true;
|
|
}
|
|
|
|
// Updates or creates a new codec entry in the audio description with according
|
|
// to |name|, |clockrate|, |bitrate|, |channels| and |preference|.
|
|
void UpdateCodec(int payload_type, const std::string& name, int clockrate,
|
|
int bitrate, int channels, int preference,
|
|
AudioContentDescription* audio_desc) {
|
|
// Codec may already be populated with (only) optional parameters
|
|
// (from an fmtp).
|
|
cricket::AudioCodec codec =
|
|
GetCodecWithPayloadType(audio_desc->codecs(), payload_type);
|
|
codec.name = name;
|
|
codec.clockrate = clockrate;
|
|
codec.bitrate = bitrate;
|
|
codec.channels = channels;
|
|
codec.preference = preference;
|
|
AddOrReplaceCodec<AudioContentDescription, cricket::AudioCodec>(audio_desc,
|
|
codec);
|
|
}
|
|
|
|
// Updates or creates a new codec entry in the video description according to
|
|
// |name|, |width|, |height|, |framerate| and |preference|.
|
|
void UpdateCodec(int payload_type, const std::string& name, int width,
|
|
int height, int framerate, int preference,
|
|
VideoContentDescription* video_desc) {
|
|
// Codec may already be populated with (only) optional parameters
|
|
// (from an fmtp).
|
|
cricket::VideoCodec codec =
|
|
GetCodecWithPayloadType(video_desc->codecs(), payload_type);
|
|
codec.name = name;
|
|
codec.width = width;
|
|
codec.height = height;
|
|
codec.framerate = framerate;
|
|
codec.preference = preference;
|
|
AddOrReplaceCodec<VideoContentDescription, cricket::VideoCodec>(video_desc,
|
|
codec);
|
|
}
|
|
|
|
bool ParseRtpmapAttribute(const std::string& line,
|
|
const MediaType media_type,
|
|
const std::vector<int>& codec_preference,
|
|
MediaContentDescription* media_desc,
|
|
SdpParseError* error) {
|
|
std::vector<std::string> fields;
|
|
rtc::split(line.substr(kLinePrefixLength),
|
|
kSdpDelimiterSpace, &fields);
|
|
// RFC 4566
|
|
// a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encodingparameters>]
|
|
const size_t expected_min_fields = 2;
|
|
if (fields.size() < expected_min_fields) {
|
|
return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
|
|
}
|
|
std::string payload_type_value;
|
|
if (!GetValue(fields[0], kAttributeRtpmap, &payload_type_value, error)) {
|
|
return false;
|
|
}
|
|
int payload_type = 0;
|
|
if (!GetPayloadTypeFromString(line, payload_type_value, &payload_type,
|
|
error)) {
|
|
return false;
|
|
}
|
|
|
|
// Set the preference order depending on the order of the pl type in the
|
|
// <fmt> of the m-line.
|
|
const int preference = codec_preference.end() -
|
|
std::find(codec_preference.begin(), codec_preference.end(),
|
|
payload_type);
|
|
if (preference == 0) {
|
|
LOG(LS_WARNING) << "Ignore rtpmap line that did not appear in the "
|
|
<< "<fmt> of the m-line: " << line;
|
|
return true;
|
|
}
|
|
const std::string encoder = fields[1];
|
|
std::vector<std::string> codec_params;
|
|
rtc::split(encoder, '/', &codec_params);
|
|
// <encoding name>/<clock rate>[/<encodingparameters>]
|
|
// 2 mandatory fields
|
|
if (codec_params.size() < 2 || codec_params.size() > 3) {
|
|
return ParseFailed(line,
|
|
"Expected format \"<encoding name>/<clock rate>"
|
|
"[/<encodingparameters>]\".",
|
|
error);
|
|
}
|
|
const std::string encoding_name = codec_params[0];
|
|
int clock_rate = 0;
|
|
if (!GetValueFromString(line, codec_params[1], &clock_rate, error)) {
|
|
return false;
|
|
}
|
|
if (media_type == cricket::MEDIA_TYPE_VIDEO) {
|
|
VideoContentDescription* video_desc =
|
|
static_cast<VideoContentDescription*>(media_desc);
|
|
// TODO: We will send resolution in SDP. For now use
|
|
// JsepSessionDescription::kMaxVideoCodecWidth and kMaxVideoCodecHeight.
|
|
UpdateCodec(payload_type, encoding_name,
|
|
JsepSessionDescription::kMaxVideoCodecWidth,
|
|
JsepSessionDescription::kMaxVideoCodecHeight,
|
|
JsepSessionDescription::kDefaultVideoCodecFramerate,
|
|
preference, video_desc);
|
|
} else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
|
|
// RFC 4566
|
|
// For audio streams, <encoding parameters> indicates the number
|
|
// of audio channels. This parameter is OPTIONAL and may be
|
|
// omitted if the number of channels is one, provided that no
|
|
// additional parameters are needed.
|
|
int channels = 1;
|
|
if (codec_params.size() == 3) {
|
|
if (!GetValueFromString(line, codec_params[2], &channels, error)) {
|
|
return false;
|
|
}
|
|
}
|
|
int bitrate = 0;
|
|
// The default behavior for ISAC (bitrate == 0) in webrtcvoiceengine.cc
|
|
// (specifically FindWebRtcCodec) is bandwidth-adaptive variable bitrate.
|
|
// The bandwidth adaptation doesn't always work well, so this code
|
|
// sets a fixed target bitrate instead.
|
|
if (_stricmp(encoding_name.c_str(), kIsacCodecName) == 0) {
|
|
if (clock_rate <= 16000) {
|
|
bitrate = kIsacWbDefaultRate;
|
|
} else {
|
|
bitrate = kIsacSwbDefaultRate;
|
|
}
|
|
}
|
|
AudioContentDescription* audio_desc =
|
|
static_cast<AudioContentDescription*>(media_desc);
|
|
UpdateCodec(payload_type, encoding_name, clock_rate, bitrate, channels,
|
|
preference, audio_desc);
|
|
} else if (media_type == cricket::MEDIA_TYPE_DATA) {
|
|
DataContentDescription* data_desc =
|
|
static_cast<DataContentDescription*>(media_desc);
|
|
data_desc->AddCodec(cricket::DataCodec(payload_type, encoding_name,
|
|
preference));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ParseFmtpParam(const std::string& line, std::string* parameter,
|
|
std::string* value, SdpParseError* error) {
|
|
if (!rtc::tokenize_first(line, kSdpDelimiterEqual, parameter, value)) {
|
|
ParseFailed(line, "Unable to parse fmtp parameter. \'=\' missing.", error);
|
|
return false;
|
|
}
|
|
// a=fmtp:<payload_type> <param1>=<value1>; <param2>=<value2>; ...
|
|
return true;
|
|
}
|
|
|
|
bool ParseFmtpAttributes(const std::string& line, const MediaType media_type,
|
|
MediaContentDescription* media_desc,
|
|
SdpParseError* error) {
|
|
if (media_type != cricket::MEDIA_TYPE_AUDIO &&
|
|
media_type != cricket::MEDIA_TYPE_VIDEO) {
|
|
return true;
|
|
}
|
|
|
|
std::string line_payload;
|
|
std::string line_params;
|
|
|
|
// RFC 5576
|
|
// a=fmtp:<format> <format specific parameters>
|
|
// At least two fields, whereas the second one is any of the optional
|
|
// parameters.
|
|
if (!rtc::tokenize_first(line.substr(kLinePrefixLength), kSdpDelimiterSpace,
|
|
&line_payload, &line_params)) {
|
|
ParseFailedExpectMinFieldNum(line, 2, error);
|
|
return false;
|
|
}
|
|
|
|
// Parse out the payload information.
|
|
std::string payload_type_str;
|
|
if (!GetValue(line_payload, kAttributeFmtp, &payload_type_str, error)) {
|
|
return false;
|
|
}
|
|
|
|
int payload_type = 0;
|
|
if (!GetPayloadTypeFromString(line_payload, payload_type_str, &payload_type,
|
|
error)) {
|
|
return false;
|
|
}
|
|
|
|
// Parse out format specific parameters.
|
|
std::vector<std::string> fields;
|
|
rtc::split(line_params, kSdpDelimiterSemicolon, &fields);
|
|
|
|
cricket::CodecParameterMap codec_params;
|
|
for (auto& iter : fields) {
|
|
if (iter.find(kSdpDelimiterEqual) == std::string::npos) {
|
|
// Only fmtps with equals are currently supported. Other fmtp types
|
|
// should be ignored. Unknown fmtps do not constitute an error.
|
|
continue;
|
|
}
|
|
|
|
std::string name;
|
|
std::string value;
|
|
if (!ParseFmtpParam(rtc::string_trim(iter), &name, &value, error)) {
|
|
return false;
|
|
}
|
|
codec_params[name] = value;
|
|
}
|
|
|
|
if (media_type == cricket::MEDIA_TYPE_AUDIO) {
|
|
UpdateCodec<AudioContentDescription, cricket::AudioCodec>(
|
|
media_desc, payload_type, codec_params);
|
|
} else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
|
|
UpdateCodec<VideoContentDescription, cricket::VideoCodec>(
|
|
media_desc, payload_type, codec_params);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ParseRtcpFbAttribute(const std::string& line, const MediaType media_type,
|
|
MediaContentDescription* media_desc,
|
|
SdpParseError* error) {
|
|
if (media_type != cricket::MEDIA_TYPE_AUDIO &&
|
|
media_type != cricket::MEDIA_TYPE_VIDEO) {
|
|
return true;
|
|
}
|
|
std::vector<std::string> rtcp_fb_fields;
|
|
rtc::split(line.c_str(), kSdpDelimiterSpace, &rtcp_fb_fields);
|
|
if (rtcp_fb_fields.size() < 2) {
|
|
return ParseFailedGetValue(line, kAttributeRtcpFb, error);
|
|
}
|
|
std::string payload_type_string;
|
|
if (!GetValue(rtcp_fb_fields[0], kAttributeRtcpFb, &payload_type_string,
|
|
error)) {
|
|
return false;
|
|
}
|
|
int payload_type = kWildcardPayloadType;
|
|
if (payload_type_string != "*") {
|
|
if (!GetPayloadTypeFromString(line, payload_type_string, &payload_type,
|
|
error)) {
|
|
return false;
|
|
}
|
|
}
|
|
std::string id = rtcp_fb_fields[1];
|
|
std::string param = "";
|
|
for (std::vector<std::string>::iterator iter = rtcp_fb_fields.begin() + 2;
|
|
iter != rtcp_fb_fields.end(); ++iter) {
|
|
param.append(*iter);
|
|
}
|
|
const cricket::FeedbackParam feedback_param(id, param);
|
|
|
|
if (media_type == cricket::MEDIA_TYPE_AUDIO) {
|
|
UpdateCodec<AudioContentDescription, cricket::AudioCodec>(
|
|
media_desc, payload_type, feedback_param);
|
|
} else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
|
|
UpdateCodec<VideoContentDescription, cricket::VideoCodec>(
|
|
media_desc, payload_type, feedback_param);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace webrtc
|