前言

代码分析
Conductor::OnSuccess
void Conductor::OnSuccess(webrtc::SessionDescriptionInterface* desc) {peer_connection_->SetLocalDescription(DummySetSessionDescriptionObserver::Create(), desc);std::string sdp;desc->ToString(&sdp);***}
JsepSessionDescription::ToString
bool JsepSessionDescription::ToString(std::string* out) const {if (!description_ || !out) {return false;}*out = SdpSerialize(*this);return !out->empty();}
SdpSerialize
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>rtc::StringBuilder 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);// Groupif (desc->HasGroup(cricket::GROUP_TYPE_BUNDLE)) {std::string group_line = kAttrGroup;const cricket::ContentGroup* group =desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);RTC_DCHECK(group != NULL);for (const std::string& content_name : group->content_names()) {group_line.append(" ");group_line.append(content_name);}AddLine(group_line, &message);}// Mixed one- and two-byte header extension.if (desc->extmap_allow_mixed()) {InitAttrLine(kAttributeExtmapAllowMixed, &os);AddLine(os.str(), &message);}// MediaStream semanticsInitAttrLine(kAttributeMsidSemantics, &os);os << kSdpDelimiterColon << " " << kMediaStreamSemantic;std::set<std::string> media_stream_ids;const ContentInfo* audio_content = GetFirstAudioContent(desc);if (audio_content)GetMediaStreamIds(audio_content, &media_stream_ids);const ContentInfo* video_content = GetFirstVideoContent(desc);if (video_content)GetMediaStreamIds(video_content, &media_stream_ids);for (const std::string& id : media_stream_ids) {os << " " << id;}AddLine(os.str(), &message);// a=ice-lite//// TODO(deadbeef): It's weird that we need to iterate TransportInfos for// this, when it's a session-level attribute. It really should be moved to a// session-level structure like SessionDescription.for (const cricket::TransportInfo& transport : desc->transport_infos()) {if (transport.description.ice_mode == cricket::ICEMODE_LITE) {InitAttrLine(kAttributeIceLite, &os);AddLine(os.str(), &message);break;}}// Preserve the order of the media contents.// 重点***int mline_index = -1;for (const ContentInfo& content : desc->contents()) {std::vector<Candidate> candidates;GetCandidatesByMindex(jdesc, ++mline_index, &candidates);// 构建每一个媒体行和解释BuildMediaDescription(&content, desc->GetTransportInfoByName(content.name),content.media_description()->type(), candidates,desc->msid_signaling(), &message);}return message;}
AddLine的作用是将信息添加到message中,可以算是sdp文本中的一行。
InitLine 一行信息的初始化
BuildMediaDescription
void BuildMediaDescription(const ContentInfo* content_info,const TransportInfo* transport_info,const cricket::MediaType media_type,const std::vector<Candidate>& candidates,int msid_signaling,std::string* message) {RTC_DCHECK(message != NULL);if (content_info == NULL || message == NULL) {return;}// 创建一个buffer,最后将这个buffer输出到message中。rtc::StringBuilder os;// 取出media_desc,然后下面根据类型来做相应的处理const MediaContentDescription* media_desc = content_info->media_description();RTC_DCHECK(media_desc);// RFC 4566// m=<media> <port> <proto> <fmt>// fmt is a list of payload type numbers that MAY be used in the session.std::string type;std::string fmt;if (media_type == cricket::MEDIA_TYPE_VIDEO) { //视频类型type = kMediaTypeVideo;const VideoContentDescription* video_desc = media_desc->as_video();for (const cricket::VideoCodec& codec : video_desc->codecs()) {fmt.append(" ");fmt.append(rtc::ToString(codec.id));}} else if (media_type == cricket::MEDIA_TYPE_AUDIO) { // 音频类型type = kMediaTypeAudio;const AudioContentDescription* audio_desc = media_desc->as_audio();for (const cricket::AudioCodec& codec : audio_desc->codecs()) {fmt.append(" ");fmt.append(rtc::ToString(codec.id));}} else if (media_type == cricket::MEDIA_TYPE_DATA) { // 数据类型type = kMediaTypeData;const cricket::SctpDataContentDescription* sctp_data_desc =media_desc->as_sctp();if (sctp_data_desc) {fmt.append(" ");if (sctp_data_desc->use_sctpmap()) {fmt.append(rtc::ToString(sctp_data_desc->port()));} else {fmt.append(kDefaultSctpmapProtocol);}} else {const RtpDataContentDescription* rtp_data_desc =media_desc->as_rtp_data();for (const cricket::RtpDataCodec& codec : rtp_data_desc->codecs()) {fmt.append(" ");fmt.append(rtc::ToString(codec.id));}}} else if (media_type == cricket::MEDIA_TYPE_UNSUPPORTED) { // 不支持的类型const UnsupportedContentDescription* unsupported_desc =media_desc->as_unsupported();type = unsupported_desc->media_type();} else {RTC_NOTREACHED();}// 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 associated with// the candidates.//// A port value of 0 indicates that the m= section is rejected.// RFC 3264// To reject an offered stream, the port number in the corresponding stream in// the answer MUST be set to zero.//// However, the BUNDLE draft adds a new meaning to port zero, when used along// with a=bundle-only.std::string port = kDummyPort; //定义端口,默认为9if (content_info->rejected || content_info->bundle_only) {port = kMediaPortRejected;} else if (!media_desc->connection_address().IsNil()) {port = rtc::ToString(media_desc->connection_address().port());}// 获取指纹对象rtc::SSLFingerprint* fp =(transport_info) ? transport_info->description.identity_fingerprint.get(): NULL;// Add the m and c lines.// 添加m行和c行InitLine(kLineTypeMedia, type, &os);os << " " << port << " " << media_desc->protocol() << fmt;AddLine(os.str(), message);InitLine(kLineTypeConnection, kConnectionNettype, &os);if (media_desc->connection_address().IsNil()) {os << " " << kConnectionIpv4Addrtype << " " << kDummyAddress;} else if (media_desc->connection_address().family() == AF_INET) {os << " " << kConnectionIpv4Addrtype << " "<< media_desc->connection_address().ipaddr().ToString();} else if (media_desc->connection_address().family() == AF_INET6) {os << " " << kConnectionIpv6Addrtype << " "<< media_desc->connection_address().ipaddr().ToString();} else {os << " " << kConnectionIpv4Addrtype << " " << kDummyAddress;}AddLine(os.str(), message);// 添加带宽的行内容// RFC 4566// b=AS:<bandwidth> or// b=TIAS:<bandwidth>int bandwidth = media_desc->bandwidth();std::string bandwidth_type = media_desc->bandwidth_type();if (bandwidth_type == kApplicationSpecificBandwidth && bandwidth >= 1000) {InitLine(kLineTypeSessionBandwidth, bandwidth_type, &os);bandwidth /= 1000;os << kSdpDelimiterColon << bandwidth;AddLine(os.str(), message);} else if (bandwidth_type == kTransportSpecificBandwidth && bandwidth > 0) {InitLine(kLineTypeSessionBandwidth, bandwidth_type, &os);os << kSdpDelimiterColon << bandwidth;AddLine(os.str(), message);}// Add the a=bundle-only line.if (content_info->bundle_only) {InitAttrLine(kAttributeBundleOnly, &os);AddLine(os.str(), message);}// Add the a=rtcp line.if (cricket::IsRtpProtocol(media_desc->protocol())) {std::string rtcp_line = GetRtcpLine(candidates);if (!rtcp_line.empty()) {AddLine(rtcp_line, message);}}// 老的方式,现在已经不使用了。用的是读取配置方式// Build the a=candidate lines. We don't include ufrag and pwd in the// candidates in the SDP to avoid redundancy.BuildCandidate(candidates, false, 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-ufragif (!transport_info->description.ice_ufrag.empty()) {InitAttrLine(kAttributeIceUfrag, &os);os << kSdpDelimiterColon << transport_info->description.ice_ufrag;AddLine(os.str(), message);}// ice-pwdif (!transport_info->description.ice_pwd.empty()) {InitAttrLine(kAttributeIcePwd, &os);os << kSdpDelimiterColon << transport_info->description.ice_pwd;AddLine(os.str(), message);}// draft-petithuguenin-mmusic-ice-attributes-level-03BuildIceOptions(transport_info->description.transport_options, message);// RFC 4572// fingerprint-attribute =// "fingerprint" ":" hash-func SP fingerprintif (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;const bool success =cricket::ConnectionRoleToString(role, &dtls_role_str);RTC_DCHECK(success);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 (cricket::IsDtlsSctp(media_desc->protocol())) {const cricket::SctpDataContentDescription* data_desc =media_desc->as_sctp();BuildSctpContentAttributes(message, data_desc);} else if (cricket::IsRtpProtocol(media_desc->protocol())) {BuildRtpContentAttributes(media_desc, media_type, msid_signaling, message);}}
BuildSctpContentAttributes
BuildRtpContentAttributes
BuildRtpContentAttributes
void BuildRtpContentAttributes(const MediaContentDescription* media_desc,const cricket::MediaType media_type,int msid_signaling,std::string* message) {SdpSerializer serializer;rtc::StringBuilder os;// RFC 8285// a=extmap-allow-mixed// The attribute MUST be either on session level or media level. We support// responding on both levels, however, we don't respond on media level if it's// set on session level.if (media_desc->extmap_allow_mixed_enum() ==MediaContentDescription::kMedia) {InitAttrLine(kAttributeExtmapAllowMixed, &os);AddLine(os.str(), message);}// RFC 8285// 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) {const RtpExtension& extension = media_desc->rtp_header_extensions()[i];InitAttrLine(kAttributeExtmap, &os);os << kSdpDelimiterColon << extension.id;if (extension.encrypt) {os << kSdpDelimiterSpace << RtpExtension::kEncryptHeaderExtensionsUri;}os << kSdpDelimiterSpace << extension.uri;AddLine(os.str(), message);}// RFC 3264// a=sendrecv || a=sendonly || a=sendrecv || a=inactiveswitch (media_desc->direction()) {// Special case that for sdp purposes should be treated same as inactive.case RtpTransceiverDirection::kStopped:case RtpTransceiverDirection::kInactive:InitAttrLine(kAttributeInactive, &os);break;case RtpTransceiverDirection::kSendOnly:InitAttrLine(kAttributeSendOnly, &os);break;case RtpTransceiverDirection::kRecvOnly:InitAttrLine(kAttributeRecvOnly, &os);break;case RtpTransceiverDirection::kSendRecv:InitAttrLine(kAttributeSendRecv, &os);break;default:RTC_NOTREACHED();InitAttrLine(kAttributeSendRecv, &os);break;}// 默认是sendrecv方式AddLine(os.str(), message);// Specified in https://datatracker.ietf.org/doc/draft-ietf-mmusic-msid/16/// a=msid:<msid-id> <msid-appdata>// The msid-id is a 1*64 token char representing the media stream id, and the// msid-appdata is a 1*64 token char representing the track id. There is a// line for every media stream, with a special msid-id value of "-"// representing no streams. The value of "msid-appdata" MUST be identical for// all lines.if (msid_signaling & cricket::kMsidSignalingMediaSection) {const StreamParamsVec& streams = media_desc->streams();if (streams.size() == 1u) {const StreamParams& track = streams[0];std::vector<std::string> stream_ids = track.stream_ids();if (stream_ids.empty()) {stream_ids.push_back(kNoStreamMsid);}for (const std::string& stream_id : stream_ids) {InitAttrLine(kAttributeMsid, &os);os << kSdpDelimiterColon << stream_id << kSdpDelimiterSpace << track.id;AddLine(os.str(), message);}} else if (streams.size() > 1u) {RTC_LOG(LS_WARNING)<< "Trying to serialize Unified Plan SDP with more than ""one track in a media section. Omitting 'a=msid'.";}}// RFC 5761// a=rtcp-muxif (media_desc->rtcp_mux()) {InitAttrLine(kAttributeRtcpMux, &os);AddLine(os.str(), message);}// RFC 5506// a=rtcp-rsizeif (media_desc->rtcp_reduced_size()) {InitAttrLine(kAttributeRtcpReducedSize, &os);AddLine(os.str(), message);}if (media_desc->conference_mode()) {InitAttrLine(kAttributeXGoogleFlag, &os);os << kSdpDelimiterColon << kValueConference;AddLine(os.str(), message);}if (media_desc->remote_estimate()) {InitAttrLine(kAttributeRtcpRemoteEstimate, &os);AddLine(os.str(), message);}// RFC 4568// a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]for (const CryptoParams& crypto_params : media_desc->cryptos()) {InitAttrLine(kAttributeCrypto, &os);os << kSdpDelimiterColon << crypto_params.tag << " "<< crypto_params.cipher_suite << " " << crypto_params.key_params;if (!crypto_params.session_params.empty()) {os << " " << crypto_params.session_params;}AddLine(os.str(), message);}// RFC 4566// a=rtpmap:<payload type> <encoding name>/<clock rate>// [/<encodingparameters>]BuildRtpMap(media_desc, media_type, message);for (const StreamParams& track : media_desc->streams()) {// Build the ssrc-group lines.for (const SsrcGroup& ssrc_group : track.ssrc_groups) {// RFC 5576// a=ssrc-group:<semantics> <ssrc-id> ...if (ssrc_group.ssrcs.empty()) {continue;}InitAttrLine(kAttributeSsrcGroup, &os);os << kSdpDelimiterColon << ssrc_group.semantics;for (uint32_t ssrc : ssrc_group.ssrcs) {os << kSdpDelimiterSpace << rtc::ToString(ssrc);}AddLine(os.str(), message);}// Build the ssrc lines for each ssrc.for (uint32_t ssrc : track.ssrcs) {// RFC 5576// a=ssrc:<ssrc-id> cname:<value>AddSsrcLine(ssrc, kSsrcAttributeCname, track.cname, message);if (msid_signaling & cricket::kMsidSignalingSsrcAttribute) {// draft-alvestrand-mmusic-msid-00// a=ssrc:<ssrc-id> msid:identifier [appdata]// The appdata consists of the "id" attribute of a MediaStreamTrack,// which corresponds to the "id" attribute of StreamParams.// Since a=ssrc msid signaling is used in Plan B SDP semantics, and// multiple stream ids are not supported for Plan B, we are only adding// a line for the first media stream id here.const std::string& track_stream_id = track.first_stream_id();// We use a special msid-id value of "-" to represent no streams,// for Unified Plan compatibility. Plan B will always have a// track_stream_id.const std::string& stream_id =track_stream_id.empty() ? kNoStreamMsid : track_stream_id;InitAttrLine(kAttributeSsrc, &os);os << kSdpDelimiterColon << ssrc << kSdpDelimiterSpace<< kSsrcAttributeMsid << kSdpDelimiterColon << stream_id<< kSdpDelimiterSpace << track.id;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, stream_id, message);AddSsrcLine(ssrc, kSSrcAttributeLabel, track.id, message);}}// Build the rid lines for each layer of the trackfor (const RidDescription& rid_description : track.rids()) {InitAttrLine(kAttributeRid, &os);os << kSdpDelimiterColon<< serializer.SerializeRidDescription(rid_description);AddLine(os.str(), message);}}for (const RidDescription& rid_description : media_desc->receive_rids()) {InitAttrLine(kAttributeRid, &os);os << kSdpDelimiterColon<< serializer.SerializeRidDescription(rid_description);AddLine(os.str(), message);}// Simulcast (a=simulcast)// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1if (media_desc->HasSimulcast()) {const auto& simulcast = media_desc->simulcast_description();InitAttrLine(kAttributeSimulcast, &os);os << kSdpDelimiterColon<< serializer.SerializeSimulcastDescription(simulcast);AddLine(os.str(), message);}}

默认是sendrecv。
