前言
createOffer之前,必须生成证书。。
代码分析
Conductor::ConnectToPeer->if (InitializePeerConnection()) {peer_id_ = peer_id;peer_connection_->CreateOffer(this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());} else {main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);}->PeerConnection::CreateOffer->SdpOfferAnswerHandler::CreateOffer(CreateSessionDescriptionObserver* observer,const PeerConnectionInterface::RTCOfferAnswerOptions& options)->SdpOfferAnswerHandler::DoCreateOffer->WebRtcSessionDescriptionFactory::CreateOffer->WebRtcSessionDescriptionFactory::InternalCreateOffer
SdpOfferAnswerHandler::DoCreateOffer
void SdpOfferAnswerHandler::DoCreateOffer(const PeerConnectionInterface::RTCOfferAnswerOptions& options,rtc::scoped_refptr<CreateSessionDescriptionObserver> observer) {******获取会话选项参数,并创建offercricket::MediaSessionOptions session_options;GetOptionsForOffer(options, &session_options);webrtc_session_desc_factory_->CreateOffer(observer, options, session_options);}

WebRtcSessionDescriptionFactory::CreateOffer
void WebRtcSessionDescriptionFactory::CreateOffer(CreateSessionDescriptionObserver* observer,const PeerConnectionInterface::RTCOfferAnswerOptions& options,const cricket::MediaSessionOptions& session_options) {******// 创建申请,然后设置observer回调CreateSessionDescriptionRequest request(CreateSessionDescriptionRequest::kOffer, observer, session_options);if (certificate_request_state_ == CERTIFICATE_WAITING) {create_session_description_requests_.push(request);} else {RTC_DCHECK(certificate_request_state_ == CERTIFICATE_SUCCEEDED ||certificate_request_state_ == CERTIFICATE_NOT_NEEDED);InternalCreateOffer(request);}}

工作线程创建好证书后,就会调用WebRtcCertificateGeneratorCallback::OnSuccess ,后续给信令线程继续操作
WebRtcCertificateGeneratorCallback::OnSuccess
void WebRtcCertificateGeneratorCallback::OnSuccess(const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {SignalCertificateReady(certificate);}

后面是在void RTCCertificateGenerator::GenerateCertificateAsync调用的OnSuccess
void RTCCertificateGenerator::GenerateCertificateAsync(const KeyParams& key_params,const absl::optional<uint64_t>& expires_ms,const scoped_refptr<RTCCertificateGeneratorCallback>& callback) {RTC_DCHECK(signaling_thread_->IsCurrent());RTC_DCHECK(callback);// Create a new |RTCCertificateGenerationTask| for this generation request. It// is reference counted and referenced by the message data, ensuring it lives// until the task has completed (independent of |RTCCertificateGenerator|).worker_thread_->PostTask(RTC_FROM_HERE, [key_params, expires_ms,signaling_thread = signaling_thread_,cb = callback]() {scoped_refptr<RTCCertificate> certificate =RTCCertificateGenerator::GenerateCertificate(key_params, expires_ms);signaling_thread->PostTask(RTC_FROM_HERE, [cert = std::move(certificate), cb = std::move(cb)]() {cert ? cb->OnSuccess(cert) : cb->OnFailure();});});
WebRtcCertificateGeneratorCallback::OnSuccess函数里面的SignalCertificateReady(certificate);函数实际上就是调用WebRtcSessionDescriptionFactory::SetCertificate 。
WebRtcSessionDescriptionFactory::SetCertificate
void WebRtcSessionDescriptionFactory::SetCertificate(const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {RTC_DCHECK(certificate);RTC_LOG(LS_VERBOSE) << "Setting new certificate.";certificate_request_state_ = CERTIFICATE_SUCCEEDED;on_certificate_ready_(certificate);transport_desc_factory_.set_certificate(certificate);transport_desc_factory_.set_secure(cricket::SEC_ENABLED);// 判断请求是否为空while (!create_session_description_requests_.empty()) {if (create_session_description_requests_.front().type ==CreateSessionDescriptionRequest::kOffer) {InternalCreateOffer(create_session_description_requests_.front());} else {InternalCreateAnswer(create_session_description_requests_.front());}create_session_description_requests_.pop();}}

当前创建的是offer,所以进去InternalCreateOffer
WebRtcSessionDescriptionFactory::InternalCreateOffer
void WebRtcSessionDescriptionFactory::InternalCreateOffer(CreateSessionDescriptionRequest request) {// 如果是本地if (sdp_info_->local_description()) {// If the needs-ice-restart flag is set as described by JSEP, we should// generate an offer with a new ufrag/password to trigger an ICE restart.for (cricket::MediaDescriptionOptions& options :request.options.media_description_options) {if (sdp_info_->NeedsIceRestart(options.mid)) {options.transport_options.ice_restart = true;}}}// 创建offerstd::unique_ptr<cricket::SessionDescription> desc =session_desc_factory_.CreateOffer(request.options, sdp_info_->local_description()? sdp_info_->local_description()->description(): nullptr);if (!desc) {PostCreateSessionDescriptionFailed(request.observer,"Failed to initialize the offer.");return;}
MediaSessionDescriptionFactory::CreateOffer
std::unique_ptr<SessionDescription> MediaSessionDescriptionFactory::CreateOffer(*****AudioCodecs offer_audio_codecs;VideoCodecs offer_video_codecs;RtpDataCodecs offer_rtp_data_codecs;// 获取编码信息GetCodecsForOffer(current_active_contents, &offer_audio_codecs, &offer_video_codecs,session_options.data_channel_type == DataChannelType::DCT_SCTP? nullptr: &offer_rtp_data_codecs);if (!session_options.vad_enabled) {// If application doesn't want CN codecs in offer.StripCNCodecs(&offer_audio_codecs);}//获取rtp扩展头AudioVideoRtpHeaderExtensions extensions_with_ids =GetOfferedRtpHeaderExtensionsWithIds(current_active_contents, session_options.offer_extmap_allow_mixed,session_options.media_description_options);auto offer = std::make_unique<SessionDescription>();// Iterate through the media description options, matching with existing media// descriptions in |current_description|.size_t msection_index = 0;// 循环处理属性选项for (const MediaDescriptionOptions& media_description_options :session_options.media_description_options) {const ContentInfo* current_content = nullptr;if (current_description &&msection_index < current_description->contents().size()) {current_content = ¤t_description->contents()[msection_index];// Media type must match unless this media section is being recycled.RTC_DCHECK(current_content->name != media_description_options.mid ||IsMediaContentOfType(current_content,media_description_options.type));}switch (media_description_options.type) {case MEDIA_TYPE_AUDIO:// 添加音频媒体数据if (!AddAudioContentForOffer(media_description_options, session_options,current_content, current_description,extensions_with_ids.audio,offer_audio_codecs, ¤t_streams,offer.get(), &ice_credentials)) {***break;case MEDIA_TYPE_VIDEO:if (!AddVideoContentForOffer(media_description_options, session_options,current_content, current_description,extensions_with_ids.video,offer_video_codecs, ¤t_streams,offer.get(), &ice_credentials)) {***case MEDIA_TYPE_DATA:if (!AddDataContentForOffer(media_description_options, session_options,current_content, current_description,offer_rtp_data_codecs, ¤t_streams,offer.get(), &ice_credentials)) {***case MEDIA_TYPE_UNSUPPORTED:if (!AddUnsupportedContentForOffer(media_description_options, session_options, current_content,current_description, offer.get(), &ice_credentials)) {***}++msection_index;}// Bundle the contents together, if we've been asked to do so, and update any// parameters that need to be tweaked for BUNDLE.if (session_options.bundle_enabled) {ContentGroup offer_bundle(GROUP_TYPE_BUNDLE);for (const ContentInfo& content : offer->contents()) {if (content.rejected) {continue;}// TODO(deadbeef): There are conditions that make bundling two media// descriptions together illegal. For example, they use the same payload// type to represent different codecs, or same IDs for different header// extensions. We need to detect this and not try to bundle those media// descriptions together.offer_bundle.AddContentName(content.name);}if (!offer_bundle.content_names().empty()) {offer->AddGroup(offer_bundle);if (!UpdateTransportInfoForBundle(offer_bundle, offer.get())) {RTC_LOG(LS_ERROR)<< "CreateOffer failed to UpdateTransportInfoForBundle.";return nullptr;}if (!UpdateCryptoParamsForBundle(offer_bundle, offer.get())) {RTC_LOG(LS_ERROR)<< "CreateOffer failed to UpdateCryptoParamsForBundle.";return nullptr;}}}// The following determines how to signal MSIDs to ensure compatibility with// older endpoints (in particular, older Plan B endpoints).if (is_unified_plan_) {// Be conservative and signal using both a=msid and a=ssrc lines. Unified// Plan answerers will look at a=msid and Plan B answerers will look at the// a=ssrc MSID line.offer->set_msid_signaling(cricket::kMsidSignalingMediaSection |cricket::kMsidSignalingSsrcAttribute);} else {// Plan B always signals MSID using a=ssrc lines.offer->set_msid_signaling(cricket::kMsidSignalingSsrcAttribute);}offer->set_extmap_allow_mixed(session_options.offer_extmap_allow_mixed);return offer;}
AddAudioContentForOffer
bool MediaSessionDescriptionFactory::AddAudioContentForOffer(const MediaDescriptionOptions& media_description_options,const MediaSessionOptions& session_options,const ContentInfo* current_content,const SessionDescription* current_description,const RtpHeaderExtensions& audio_rtp_extensions,const AudioCodecs& audio_codecs,StreamParamsVec* current_streams,SessionDescription* desc,IceCredentialsIterator* ice_credentials) const {*****if (!CreateMediaContentOffer(media_description_options, session_options,filtered_codecs, sdes_policy,GetCryptos(current_content), crypto_suites,audio_rtp_extensions, ssrc_generator_,current_streams, audio.get())) {return false;}// 设置媒体协议bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);SetMediaProtocol(secure_transport, audio.get());audio->set_direction(media_description_options.direction);// 添加内容desc->AddContent(media_description_options.mid, MediaProtocolType::kRtp,media_description_options.stopped, std::move(audio));if (!AddTransportOffer(media_description_options.mid,media_description_options.transport_options,current_description, desc, ice_credentials)) {return false;}return true;}
CreateMediaContentOffer
template <class C>static bool CreateMediaContentOffer(const MediaDescriptionOptions& media_description_options,const MediaSessionOptions& session_options,const std::vector<C>& codecs,const SecurePolicy& secure_policy,const CryptoParamsVec* current_cryptos,const std::vector<std::string>& crypto_suites,const RtpHeaderExtensions& rtp_extensions,UniqueRandomIdGenerator* ssrc_generator,StreamParamsVec* current_streams,MediaContentDescriptionImpl<C>* offer) {offer->AddCodecs(codecs);if (!AddStreamParams(media_description_options.sender_options,session_options.rtcp_cname, ssrc_generator,current_streams, offer)) {return false;}return CreateContentOffer(media_description_options, session_options,secure_policy, current_cryptos, crypto_suites,rtp_extensions, ssrc_generator, current_streams,offer);}

AddStreamParams-》
CreateStreamParamsForNewSenderWithSsrcs 每一路流ssrc的创建都调用它
CreateContentOffer
// Create a media content to be offered for the given |sender_options|,// according to the given options.rtcp_mux, session_options.is_muc, codecs,// secure_transport, crypto, and current_streams. If we don't currently have// crypto (in current_cryptos) and it is enabled (in secure_policy), crypto is// created (according to crypto_suites). The created content is added to the// offer.static bool CreateContentOffer(const MediaDescriptionOptions& media_description_options,const MediaSessionOptions& session_options,const SecurePolicy& secure_policy,const CryptoParamsVec* current_cryptos,const std::vector<std::string>& crypto_suites,const RtpHeaderExtensions& rtp_extensions,UniqueRandomIdGenerator* ssrc_generator,StreamParamsVec* current_streams,MediaContentDescription* offer) {offer->set_rtcp_mux(session_options.rtcp_mux_enabled);if (offer->type() == cricket::MEDIA_TYPE_VIDEO) {offer->set_rtcp_reduced_size(true);}// Build the vector of header extensions with directions for this// media_description's options.RtpHeaderExtensions extensions;for (auto extension_with_id : rtp_extensions) {for (const auto& extension : media_description_options.header_extensions) {if (extension_with_id.uri == extension.uri) {// TODO(crbug.com/1051821): Configure the extension direction from// the information in the media_description_options extension// capability.extensions.push_back(extension_with_id);}}}offer->set_rtp_header_extensions(extensions);AddSimulcastToMediaDescription(media_description_options, offer);// sdes 安全策略,我们用的是dtls,暂时不会进来这里if (secure_policy != SEC_DISABLED) {if (current_cryptos) {AddMediaCryptos(*current_cryptos, offer);}if (offer->cryptos().empty()) {if (!CreateMediaCryptos(crypto_suites, offer)) {return false;}}}if (secure_policy == SEC_REQUIRED && offer->cryptos().empty()) {return false;}return true;}
offer->set_rtp_header_extensions(extensions); 设置拓展头
当该函数返回时,已经创建好offer了,可以查看
打开第一个标签的sendstreams 属性,可以看到是音频标签。
还可以看codecs
SetMediaProtocol
static void SetMediaProtocol(bool secure_transport,MediaContentDescription* desc) {if (!desc->cryptos().empty()) //sdes协议desc->set_protocol(kMediaProtocolSavpf);else if (secure_transport) //dtls协议desc->set_protocol(kMediaProtocolDtlsSavpf);elsedesc->set_protocol(kMediaProtocolAvpf);}

