当前位置:网站首页>webrtc 有关 SDP 部分的解析流程分析
webrtc 有关 SDP 部分的解析流程分析
2022-08-02 14:16:00 【freeabc】
----------------------------------------------------------------------------------------------------------------------------------------
一分钟快速搭建 rtmpd 服务器: https://blog.csdn.net/freeabc/article/details/102880984
软件下载地址: http://www.qiyicc.com/download/rtmpd.rar
github 地址:https://github.com/superconvert/smart_rtmpd
-----------------------------------------------------------------------------------------------------------------------------------------
webrtc 有关 SDP 部分的解析流程分析
在这个例子里,我们以 android 端为例进行说明整个流程,但整个流程都发生在 JNI 层,别的基本一样。
1.
./sdk/android/src/jni/pc/session_description.cc
std::unique_ptr<SessionDescriptionInterface> JavaToNativeSessionDescription(
JNIEnv* jni,
const JavaRef<jobject>& j_sdp) {
std::string std_type = JavaToStdString(
jni, Java_SessionDescription_getTypeInCanonicalForm(jni, j_sdp));
std::string std_description =
JavaToStdString(jni, Java_SessionDescription_getDescription(jni, j_sdp));
absl::optional<SdpType> sdp_type_maybe = SdpTypeFromString(std_type);
if (!sdp_type_maybe) {
RTC_LOG(LS_ERROR) << "Unexpected SDP type: " << std_type;
return nullptr;
}
// 需要进一步分析这个函数
return CreateSessionDescription(*sdp_type_maybe, std_description);
}
需要进一步分析这个函数 CreateSessionDescription
2.
./pc/jsep_session_description.cc
std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
SdpType type,
const std::string& sdp) {
return CreateSessionDescription(type, sdp, nullptr);
}
std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
SdpType type,
const std::string& sdp,
SdpParseError* error_out) {
auto jsep_desc = std::make_unique<JsepSessionDescription>(type);
if (type != SdpType::kRollback) {
// 需要进一步分析这个函数
if (!SdpDeserialize(sdp, jsep_desc.get(), error_out)) {
return nullptr;
}
}
return std::move(jsep_desc);
}
需要进一步分析这个函数 SdpDeserialize
3.
./pc/webrtc_sdp.cc
bool SdpDeserialize(const std::string& message,
JsepSessionDescription* jdesc,
SdpParseError* error) {
std::string session_id;
std::string session_version;
TransportDescription session_td("", "");
RtpHeaderExtensions session_extmaps;
rtc::SocketAddress session_connection_addr;
auto desc = std::make_unique<cricket::SessionDescription>();
size_t current_pos = 0;
// Session Description
if (!ParseSessionDescription(message, ¤t_pos, &session_id,
&session_version, &session_td, &session_extmaps,
&session_connection_addr, desc.get(), error)) {
return false;
}
// Media Description
std::vector<std::unique_ptr<JsepIceCandidate>> candidates;
if (!ParseMediaDescription(message, session_td, session_extmaps, ¤t_pos,
session_connection_addr, desc.get(), &candidates,
error)) {
return false;
}
jdesc->Initialize(std::move(desc), session_id, session_version);
for (const auto& candidate : candidates) {
jdesc->AddCandidate(candidate.get());
}
return true;
}
看上述代码,我们知道 SDP 解析分为这几个部分,会话层解析 ParseSessionDescription, 媒体层解析 ParseMediaDescription, 还有 Candidate 的解析(如果存在)
会话层解析
bool ParseSessionDescription(const std::string& message,
size_t* pos,
std::string* session_id,
std::string* session_version,
TransportDescription* session_td,
RtpHeaderExtensions* session_extmaps,
rtc::SocketAddress* connection_addr,
cricket::SessionDescription* desc,
SdpParseError* error) {
std::string line;
desc->set_msid_supported(false);
desc->set_extmap_allow_mixed(false);
// 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), kSdpDelimiterSpaceChar, &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);
}
// absl::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)
if (GetLineWithType(message, pos, &line, kLineTypeConnection)) {
if (!ParseConnectionData(line, connection_addr, error)) {
return false;
}
}
// 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);
}
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (!ParseFingerprintAttribute(line, &fingerprint, error)) {
return false;
}
session_td->identity_fingerprint = std::move(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;
}
desc->set_msid_supported(
CaseInsensitiveFind(semantics, kMediaStreamSemantic));
} else if (HasAttribute(line, kAttributeExtmapAllowMixed)) {
desc->set_extmap_allow_mixed(true);
} else if (HasAttribute(line, kAttributeExtmap)) {
RtpExtension extmap;
if (!ParseExtmap(line, &extmap, error)) {
return false;
}
session_extmaps->push_back(extmap);
}
}
return true;
}
媒体层解析
bool ParseMediaDescription(
const std::string& message,
const TransportDescription& session_td,
const RtpHeaderExtensions& session_extmaps,
size_t* pos,
const rtc::SocketAddress& session_connection_addr,
cricket::SessionDescription* desc,
std::vector<std::unique_ptr<JsepIceCandidate>>* candidates,
SdpParseError* error) {
RTC_DCHECK(desc != NULL);
std::string line;
int mline_index = -1;
int msid_signaling = 0;
// 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), kSdpDelimiterSpaceChar, &fields);
const size_t expected_min_fields = 4;
if (fields.size() < expected_min_fields) {
return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
}
bool port_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) {
port_rejected = true;
}
int port = 0;
if (!rtc::FromString<int>(fields[1], &port) || !IsValidPort(port)) {
return ParseFailed(line, "The port number is invalid", error);
}
std::string protocol = fields[2];
// <fmt>
std::vector<int> payload_types;
if (cricket::IsRtpProtocol(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].empty() && j == fields.size() - 1) {
continue;
}
int pl = 0;
if (!GetPayloadTypeFromString(line, fields[j], &pl, error)) {
return false;
}
payload_types.push_back(pl);
}
}
// Make a temporary TransportDescription based on |session_td|.
// Some of this gets overwritten by ParseContent.
TransportDescription transport(
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());
std::unique_ptr<MediaContentDescription> content;
std::string content_name;
bool bundle_only = false;
int section_msid_signaling = 0;
if (HasAttribute(line, kMediaTypeVideo)) {
content = ParseContentDescription<VideoContentDescription>(
message, cricket::MEDIA_TYPE_VIDEO, mline_index, protocol,
payload_types, pos, &content_name, &bundle_only,
§ion_msid_signaling, &transport, candidates, error);
} else if (HasAttribute(line, kMediaTypeAudio)) {
content = ParseContentDescription<AudioContentDescription>(
message, cricket::MEDIA_TYPE_AUDIO, mline_index, protocol,
payload_types, pos, &content_name, &bundle_only,
§ion_msid_signaling, &transport, candidates, error);
} else if (HasAttribute(line, kMediaTypeData)) {
if (cricket::IsDtlsSctp(protocol)) {
// The draft-03 format is:
// m=application <port> DTLS/SCTP <sctp-port>...
// use_sctpmap should be false.
// The draft-26 format is:
// m=application <port> UDP/DTLS/SCTP webrtc-datachannel
// use_sctpmap should be false.
auto data_desc = std::make_unique<SctpDataContentDescription>();
// Default max message size is 64K
// according to draft-ietf-mmusic-sctp-sdp-26
data_desc->set_max_message_size(kDefaultSctpMaxMessageSize);
int p;
if (rtc::FromString(fields[3], &p)) {
data_desc->set_port(p);
} else if (fields[3] == kDefaultSctpmapProtocol) {
data_desc->set_use_sctpmap(false);
}
if (!ParseContent(message, cricket::MEDIA_TYPE_DATA, mline_index,
protocol, payload_types, pos, &content_name,
&bundle_only, §ion_msid_signaling,
data_desc.get(), &transport, candidates, error)) {
return false;
}
data_desc->set_protocol(protocol);
content = std::move(data_desc);
} else {
// RTP
std::unique_ptr<RtpDataContentDescription> data_desc =
ParseContentDescription<RtpDataContentDescription>(
message, cricket::MEDIA_TYPE_DATA, mline_index, protocol,
payload_types, pos, &content_name, &bundle_only,
§ion_msid_signaling, &transport, candidates, error);
content = std::move(data_desc);
}
} else {
RTC_LOG(LS_WARNING) << "Unsupported media type: " << line;
continue;
}
if (!content.get()) {
// ParseContentDescription returns NULL if failed.
return false;
}
msid_signaling |= section_msid_signaling;
bool content_rejected = false;
// A port of 0 is not interpreted as a rejected m= section when it's
// used along with a=bundle-only.
if (bundle_only) {
if (!port_rejected) {
// Usage of bundle-only with a nonzero port is unspecified. So just
// ignore bundle-only if we see this.
bundle_only = false;
RTC_LOG(LS_WARNING)
<< "a=bundle-only attribute observed with a nonzero "
"port; this usage is unspecified so the attribute is being "
"ignored.";
}
} else {
// If not using bundle-only, interpret port 0 in the normal way; the m=
// section is being rejected.
content_rejected = port_rejected;
}
if (cricket::IsRtpProtocol(protocol) && !content->as_sctp()) {
content->set_protocol(protocol);
// 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]);
}
} else if (content->as_sctp()) {
// Do nothing, it's OK
} else {
RTC_LOG(LS_WARNING) << "Parse failed with unknown protocol " << protocol;
return false;
}
// Use the session level connection address if the media level addresses are
// not specified.
rtc::SocketAddress address;
address = content->connection_address().IsNil()
? session_connection_addr
: content->connection_address();
address.SetPort(port);
content->set_connection_address(address);
desc->AddContent(content_name,
cricket::IsDtlsSctp(protocol) ? MediaProtocolType::kSctp
: MediaProtocolType::kRtp,
content_rejected, bundle_only, std::move(content));
// Create TransportInfo with the media level "ice-pwd" and "ice-ufrag".
desc->AddTransportInfo(TransportInfo(content_name, transport));
}
desc->set_msid_signaling(msid_signaling);
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;
}
我们继续分析一下 ParseContentDescription 函数 ./pc/webrtc_sdp.cc
template <class C>
static std::unique_ptr<C> ParseContentDescription(
const std::string& message,
const cricket::MediaType media_type,
int mline_index,
const std::string& protocol,
const std::vector<int>& payload_types,
size_t* pos,
std::string* content_name,
bool* bundle_only,
int* msid_signaling,
TransportDescription* transport,
std::vector<std::unique_ptr<JsepIceCandidate>>* candidates,
webrtc::SdpParseError* error) {
auto media_desc = std::make_unique<C>();
if (!ParseContent(message, media_type, mline_index, protocol, payload_types,
pos, content_name, bundle_only, msid_signaling,
media_desc.get(), transport, candidates, error)) {
return nullptr;
}
// Sort the codecs according to the m-line fmt list.
std::unordered_map<int, int> payload_type_preferences;
// "size + 1" so that the lowest preference payload type has a preference of
// 1, which is greater than the default (0) for payload types not in the fmt
// list.
int preference = static_cast<int>(payload_types.size() + 1);
for (int pt : payload_types) {
payload_type_preferences[pt] = preference--;
}
std::vector<typename C::CodecType> codecs = media_desc->codecs();
absl::c_sort(
codecs, [&payload_type_preferences](const typename C::CodecType& a,
const typename C::CodecType& b) {
return payload_type_preferences[a.id] > payload_type_preferences[b.id];
});
media_desc->set_codecs(codecs);
return media_desc;
}
ParseContent 函数 ./pc/webrtc_sdp.cc
bool ParseContent(const std::string& message,
const cricket::MediaType media_type,
int mline_index,
const std::string& protocol,
const std::vector<int>& payload_types,
size_t* pos,
std::string* content_name,
bool* bundle_only,
int* msid_signaling,
MediaContentDescription* media_desc,
TransportDescription* transport,
std::vector<std::unique_ptr<JsepIceCandidate>>* candidates,
SdpParseError* error) {
RTC_DCHECK(media_desc != NULL);
RTC_DCHECK(content_name != NULL);
RTC_DCHECK(transport != NULL);
if (media_type == cricket::MEDIA_TYPE_AUDIO) {
MaybeCreateStaticPayloadAudioCodecs(payload_types, media_desc->as_audio());
}
// 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;
std::vector<std::string> stream_ids;
std::string track_id;
SdpSerializer deserializer;
std::vector<RidDescription> rids;
SimulcastDescription simulcast;
// 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;
}
// TODO(deadbeef): Historically, applications may be setting a value
// of -1 to mean "unset any previously set bandwidth limit", even
// though ommitting the "b=AS" entirely will do just that. Once we've
// transitioned applications to doing the right thing, it would be
// better to treat this as a hard error instead of just ignoring it.
if (b == -1) {
RTC_LOG(LS_WARNING)
<< "Ignoring \"b=AS:-1\"; will be treated as \"no "
"bandwidth limit\".";
continue;
}
if (b < 0) {
return ParseFailed(line, "b=AS value can't be negative.", error);
}
// 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 &&
cricket::IsRtpProtocol(protocol) &&
b > cricket::kDataMaxBandwidth / 1000) {
rtc::StringBuilder description;
description << "RTP-based data channels may not send more than "
<< cricket::kDataMaxBandwidth / 1000 << "kbps.";
return ParseFailed(line, description.str(), error);
}
// Prevent integer overflow.
b = std::min(b, INT_MAX / 1000);
media_desc->set_bandwidth(b * 1000);
}
}
continue;
}
// Parse the media level connection data.
if (IsLineType(line, kLineTypeConnection)) {
rtc::SocketAddress addr;
if (!ParseConnectionData(line, &addr, error)) {
return false;
}
media_desc->set_connection_address(addr);
continue;
}
if (!IsLineType(line, kLineTypeAttributes)) {
// TODO(deadbeef): Handle other lines if needed.
RTC_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, kAttributeBundleOnly)) {
*bundle_only = true;
} else if (HasAttribute(line, kAttributeCandidate)) {
Candidate candidate;
if (!ParseCandidate(line, &candidate, error, false)) {
return false;
}
// ParseCandidate will parse non-standard ufrag and password attributes,
// since it's used for candidate trickling, but we only want to process
// the "a=ice-ufrag"/"a=ice-pwd" values in a session description, so
// strip them off at this point.
candidate.set_username(std::string());
candidate.set_password(std::string());
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, kOpaqueTransportParametersLine)) {
transport->opaque_parameters = cricket::OpaqueTransportParameters();
if (!ParseOpaqueTransportLine(
line, &transport->opaque_parameters->protocol,
&transport->opaque_parameters->parameters, error)) {
return false;
}
} else if (HasAttribute(line, kAltProtocolLine)) {
std::string alt_protocol;
if (!ParseAltProtocolLine(line, &alt_protocol, error)) {
return false;
}
media_desc->set_alt_protocol(alt_protocol);
} else if (HasAttribute(line, kAttributeFmtp)) {
if (!ParseFmtpAttributes(line, media_type, media_desc, error)) {
return false;
}
} else if (HasAttribute(line, kAttributeFingerprint)) {
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (!ParseFingerprintAttribute(line, &fingerprint, error)) {
return false;
}
transport->identity_fingerprint = std::move(fingerprint);
} else if (HasAttribute(line, kAttributeSetup)) {
if (!ParseDtlsSetup(line, &(transport->connection_role), error)) {
return false;
}
} else if (cricket::IsDtlsSctp(protocol) &&
HasAttribute(line, kAttributeSctpPort)) {
if (media_type != cricket::MEDIA_TYPE_DATA) {
return ParseFailed(
line, "sctp-port attribute found in non-data media description.",
error);
}
if (media_desc->as_sctp()->use_sctpmap()) {
return ParseFailed(
line, "sctp-port attribute can't be used with sctpmap.", error);
}
int sctp_port;
if (!ParseSctpPort(line, &sctp_port, error)) {
return false;
}
media_desc->as_sctp()->set_port(sctp_port);
} else if (cricket::IsDtlsSctp(protocol) &&
HasAttribute(line, kAttributeMaxMessageSize)) {
if (media_type != cricket::MEDIA_TYPE_DATA) {
return ParseFailed(
line,
"max-message-size attribute found in non-data media description.",
error);
}
int max_message_size;
if (!ParseSctpMaxMessageSize(line, &max_message_size, error)) {
return false;
}
media_desc->as_sctp()->set_max_message_size(max_message_size);
} else if (cricket::IsRtpProtocol(protocol)) {
//
// RTP specific attrubtes
//
if (HasAttribute(line, kAttributeRtcpMux)) {
media_desc->set_rtcp_mux(true);
} else if (HasAttribute(line, kAttributeRtcpReducedSize)) {
media_desc->set_rtcp_reduced_size(true);
} else if (HasAttribute(line, kAttributeRtcpRemoteEstimate)) {
media_desc->set_remote_estimate(true);
} else if (HasAttribute(line, kAttributeSsrcGroup)) {
if (!ParseSsrcGroupAttribute(line, &ssrc_groups, error)) {
return false;
}
} else if (HasAttribute(line, kAttributeSsrc)) {
if (!ParseSsrcAttribute(line, &ssrc_infos, msid_signaling, 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, payload_types, media_desc,
error)) {
return false;
}
} else if (HasAttribute(line, kCodecParamMaxPTime)) {
if (!GetValue(line, kCodecParamMaxPTime, &maxptime_as_string, error)) {
return false;
}
} else if (HasAttribute(line, kAttributePacketization)) {
if (!ParsePacketizationAttribute(line, media_type, media_desc, 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(RtpTransceiverDirection::kSendOnly);
} else if (HasAttribute(line, kAttributeRecvOnly)) {
media_desc->set_direction(RtpTransceiverDirection::kRecvOnly);
} else if (HasAttribute(line, kAttributeInactive)) {
media_desc->set_direction(RtpTransceiverDirection::kInactive);
} else if (HasAttribute(line, kAttributeSendRecv)) {
media_desc->set_direction(RtpTransceiverDirection::kSendRecv);
} else if (HasAttribute(line, kAttributeExtmapAllowMixed)) {
media_desc->set_extmap_allow_mixed_enum(
MediaContentDescription::kMedia);
} else if (HasAttribute(line, kAttributeExtmap)) {
RtpExtension 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(deadbeef): 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, kAttributeMsid)) {
if (!ParseMsidAttribute(line, &stream_ids, &track_id, error)) {
return false;
}
*msid_signaling |= cricket::kMsidSignalingMediaSection;
} else if (HasAttribute(line, kAttributeRid)) {
const size_t kRidPrefixLength =
kLinePrefixLength + arraysize(kAttributeRid);
if (line.size() <= kRidPrefixLength) {
RTC_LOG(LS_INFO) << "Ignoring empty RID attribute: " << line;
continue;
}
RTCErrorOr<RidDescription> error_or_rid_description =
deserializer.DeserializeRidDescription(
line.substr(kRidPrefixLength));
// Malformed a=rid lines are discarded.
if (!error_or_rid_description.ok()) {
RTC_LOG(LS_INFO) << "Ignoring malformed RID line: '" << line
<< "'. Error: "
<< error_or_rid_description.error().message();
continue;
}
rids.push_back(error_or_rid_description.MoveValue());
} else if (HasAttribute(line, kAttributeSimulcast)) {
const size_t kSimulcastPrefixLength =
kLinePrefixLength + arraysize(kAttributeSimulcast);
if (line.size() <= kSimulcastPrefixLength) {
return ParseFailed(line, "Simulcast attribute is empty.", error);
}
if (!simulcast.empty()) {
return ParseFailed(line, "Multiple Simulcast attributes specified.",
error);
}
RTCErrorOr<SimulcastDescription> error_or_simulcast =
deserializer.DeserializeSimulcastDescription(
line.substr(kSimulcastPrefixLength));
if (!error_or_simulcast.ok()) {
return ParseFailed(line,
std::string("Malformed simulcast line: ") +
error_or_simulcast.error().message(),
error);
}
simulcast = error_or_simulcast.value();
} else {
// Unrecognized attribute in RTP protocol.
RTC_LOG(LS_INFO) << "Ignored line: " << line;
continue;
}
} else {
// Only parse lines that we are interested of.
RTC_LOG(LS_INFO) << "Ignored line: " << line;
continue;
}
}
// Remove duplicate or inconsistent rids.
RemoveInvalidRidDescriptions(payload_types, &rids);
// If simulcast is specifed, split the rids into send and receive.
// Rids that do not appear in simulcast attribute will be removed.
// If it is not specified, we assume that all rids are for send layers.
std::vector<RidDescription> send_rids;
std::vector<RidDescription> receive_rids;
if (!simulcast.empty()) {
// Verify that the rids in simulcast match rids in sdp.
RemoveInvalidRidsFromSimulcast(rids, &simulcast);
// Use simulcast description to figure out Send / Receive RIDs.
std::map<std::string, RidDescription> rid_map;
for (const RidDescription& rid : rids) {
rid_map[rid.rid] = rid;
}
for (const auto& layer : simulcast.send_layers().GetAllLayers()) {
auto iter = rid_map.find(layer.rid);
RTC_DCHECK(iter != rid_map.end());
send_rids.push_back(iter->second);
}
for (const auto& layer : simulcast.receive_layers().GetAllLayers()) {
auto iter = rid_map.find(layer.rid);
RTC_DCHECK(iter != rid_map.end());
receive_rids.push_back(iter->second);
}
media_desc->set_simulcast_description(simulcast);
} else {
send_rids = rids;
}
media_desc->set_receive_rids(receive_rids);
// Create tracks from the |ssrc_infos|.
// If the stream_id/track_id for all SSRCS are identical, one StreamParams
// will be created in CreateTracksFromSsrcInfos, containing all the SSRCs from
// the m= section.
if (!ssrc_infos.empty()) {
CreateTracksFromSsrcInfos(ssrc_infos, stream_ids, track_id, &tracks,
*msid_signaling);
} else if (media_type != cricket::MEDIA_TYPE_DATA &&
(*msid_signaling & cricket::kMsidSignalingMediaSection)) {
// If the stream_ids/track_id was signaled but SSRCs were unsignaled we
// still create a track. This isn't done for data media types because
// StreamParams aren't used for SCTP streams, and RTP data channels don't
// support unsignaled SSRCs.
CreateTrackWithNoSsrcs(stream_ids, track_id, send_rids, &tracks);
}
// Add the ssrc group to the track.
for (const SsrcGroup& ssrc_group : ssrc_groups) {
if (ssrc_group.ssrcs.empty()) {
continue;
}
uint32_t ssrc = ssrc_group.ssrcs.front();
for (StreamParams& track : tracks) {
if (track.has_ssrc(ssrc)) {
track.ssrc_groups.push_back(ssrc_group);
}
}
}
// Add the new tracks to the |media_desc|.
for (StreamParams& track : tracks) {
media_desc->AddStream(track);
}
if (media_type == cricket::MEDIA_TYPE_AUDIO) {
AudioContentDescription* audio_desc = media_desc->as_audio();
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 = media_desc->as_video();
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 (Candidate& candidate : candidates_orig) {
RTC_DCHECK(candidate.username().empty() ||
candidate.username() == transport->ice_ufrag);
candidate.set_username(transport->ice_ufrag);
RTC_DCHECK(candidate.password().empty());
candidate.set_password(transport->ice_pwd);
candidates->push_back(
std::make_unique<JsepIceCandidate>(mline_id, mline_index, candidate));
}
return true;
}
经过上述流程我们知道底层的 SDP 对象其实就是一个 JsepSessionDescription 对象,而 JsepSessionDescription 又包含这个对象 SessionDescription 作为属性,其实核心数据都在 SessionDescription
底层对象 PeerConnection 的属性
./pc/peer_connection.h
std::unique_ptr<SessionDescriptionInterface> current_local_description_
std::unique_ptr<SessionDescriptionInterface> pending_local_description_
std::unique_ptr<SessionDescriptionInterface> current_remote_description_
std::unique_ptr<SessionDescriptionInterface> pending_remote_description_
其实也就是 JsepSessionDescription 对象,有关 SDP 的具体数据就是 SessionDescription
我们所有的 SDP 信息都在 current_local_description_, current_remote_description_ 而它的 contents_ 包含了每一个 SDP 的 m 段,代表一路音频或视频的具体信息。
边栏推荐
猜你喜欢
随机推荐
使用三个线程,按顺序打印X,Y,Z,连续打印10次
Mysql开启日志并按天进行分割
光波导应用中的真实光栅效应
WEB自动化之多窗口操作、切换frame、弹窗处理
Oauth2.0 认证服务器添加验证码登陆方式
C语言函数调用过程-汇编分析
一线大厂研发流程(转载自鱼皮)
JMM&synchronized&volatile详解
大厂年薪50w+招聘具有测试平台开发能力的测试工程师
在mininet中测试arp欺骗
代码细节带来的极致体验,ShardingSphere 5.1.0 性能提升密钥
Zabbix: PHP option“date.timezone” Fail
How to tick the word box?
Apache ShardingSphere 5.1.1 正式发布
内存和硬盘、磁盘的区别
How does ns3 solve cross reference issue
smart rtmpd web 接口说明
Technical Selection of Message Queuing
mininet hosts talk to real internet
华为Vlan创建及原理简单说明