前言
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) {
******
获取会话选项参数,并创建offer
cricket::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;
}
}
}
// 创建offer
std::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);
else
desc->set_protocol(kMediaProtocolAvpf);
}