概述

WebRtc信令交换的过程实际上是基于JSEP01(Javascript Session Establishment Protocol)。
在上一篇[WebRtc建立P2P链接的总体流程]中只是描述了一种简单的形式,建立链接主要需要经过如下过程:

以此为基础总结下p2p建立的过程!

CreatOffer

peerconnection.cc

void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,const MediaConstraintsInterface* constraints) {if (!VERIFY(observer != NULL)) {LOG(LS_ERROR) << "CreateOffer - observer is NULL.";return;}RTCOfferAnswerOptions options;bool value;size_t mandatory_constraints = 0;//根据应用层传入的MediaConstraints设置RTCOfferAnswerOptionsif (FindConstraint(constraints,MediaConstraintsInterface::kOfferToReceiveAudio,&value,&mandatory_constraints)) {options.offer_to_receive_audio =value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0;}......CreateOffer(observer, options);
}void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,const RTCOfferAnswerOptions& options) {if (!VERIFY(observer != NULL)) {LOG(LS_ERROR) << "CreateOffer - observer is NULL.";return;}//此处的session为PeerConnection初始化时创建的WebRtcSession对象session_->CreateOffer(observer, options);}

接下来看WebRtcSession中的CreateOffer是如何实现的,见webrtcsession.cc中:

void WebRtcSession::CreateOffer(CreateSessionDescriptionObserver* observer,const PeerConnectionInterface::RTCOfferAnswerOptions& options) {//webrtc_session_desc_factory_是WebRtcSession在Initialize创建//根据需求创建是否DTLS加密的WebRtcSessionDescriptionFactorywebrtc_session_desc_factory_->CreateOffer(observer, options);
}
void WebRtcSessionDescriptionFactory::CreateOffer(CreateSessionDescriptionObserver* observer,const PeerConnectionInterface::RTCOfferAnswerOptions& options) {cricket::MediaSessionOptions session_options;std::string error = "CreateOffer";if (certificate_request_state_ == CERTIFICATE_FAILED) {error += kFailedDueToIdentityFailed;LOG(LS_ERROR) << error;PostCreateSessionDescriptionFailed(observer, error);return;}
/*
在Java层org/appspot/apprtc/PeerConnectionClient.java中private void createPeerConnectionInternal(EGLContext renderEGLContext){.....peerConnection = factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver);isInitiator = false;
.....mediaStream.addTrack(createVideoTrack(videoCapturer));....mediaStream.addTrack(factory.createAudioTrack(AUDIO_TRACK_ID,factory.createAudioSource(audioConstraints)));peerConnection.addStream(mediaStream); .....}最终是将会话中需要的MediaStream传入mediastream_signaling_进行管理!所以在mediastream_signaling_中可以获取MediaSessionOptions*/if (!mediastream_signaling_->GetOptionsForOffer(options,&session_options)) {error += " called with invalid options.";LOG(LS_ERROR) << error;PostCreateSessionDescriptionFailed(observer, error);return;}
//检测提供的audio video流是否合法即不能有相同的idif (!ValidStreams(session_options.streams)) {error += " called with invalid media streams.";LOG(LS_ERROR) << error;PostCreateSessionDescriptionFailed(observer, error);return;}if (data_channel_type_ == cricket::DCT_SCTP &&mediastream_signaling_->HasDataChannels()) { //若在应用层建立了datachannel传输用户数据,设置成SCTP协议传输session_options.data_channel_type = cricket::DCT_SCTP;}CreateSessionDescriptionRequest request(CreateSessionDescriptionRequest::kOffer, observer, session_options);if (certificate_request_state_ == CERTIFICATE_WAITING) {create_session_description_requests_.push(request);} else {ASSERT(certificate_request_state_ == CERTIFICATE_SUCCEEDED ||certificate_request_state_ == CERTIFICATE_NOT_NEEDED);InternalCreateOffer(request); //根据request创建SDP}
}
//从上可以看出mediastreamsignaling.cc实际上是peerconnection.cc和webrtcsession.cc沟通的桥梁mediastreamsignaling.cc负责多媒体相关的管理!//根据request创建SDP
void WebRtcSessionDescriptionFactory::InternalCreateOffer(CreateSessionDescriptionRequest request) {cricket::SessionDescription* desc(session_desc_factory_.CreateOffer(request.options,static_cast<cricket::BaseSession*>(session_)->local_description()));// RFC 3264// When issuing an offer that modifies the session,// the "o=" line of the new SDP MUST be identical to that in the// previous SDP, except that the version in the origin field MUST// increment by one from the previous SDP.// Just increase the version number by one each time when a new offer// is created regardless if it's identical to the previous one or not.// The |session_version_| is a uint64, the wrap around should not happen.ASSERT(session_version_ + 1 > session_version_);//根据JSEP规范创建JsepSessionDescriptionJsepSessionDescription* offer(new JsepSessionDescription(JsepSessionDescription::kOffer));if (!offer->Initialize(desc, session_id_,rtc::ToString(session_version_++))) {delete offer;PostCreateSessionDescriptionFailed(request.observer,"Failed to initialize the offer.");return;}if (session_->local_description() &&!request.options.transport_options.ice_restart) {// Include all local ice candidates in the SessionDescription unless// the an ice restart has been requested.CopyCandidatesFromSessionDescription(session_->local_description(), offer);}//将创建的JsepSessionDescription回调给上层的应用,应用层可以结合自己的情况做相应更改PostCreateSessionDescriptionSucceeded(request.observer, offer);
}/*
onCreateSuccess为org/appspot/apprtc/PeerConnectionClient.java
中offer创建成功后调用的回调,在此会调用根据实际情况修改了音视频编解码相关的信息
private class SDPObserver implements SdpObserver {@Overridepublic void onCreateSuccess(final SessionDescription origSdp) {if (localSdp != null) {reportError("Multiple SDP create.");return;}String sdpDescription = origSdp.description;if (preferIsac) {sdpDescription = preferCodec(sdpDescription, AUDIO_CODEC_ISAC, true);}if (videoCallEnabled && preferH264) {sdpDescription = preferCodec(sdpDescription, VIDEO_CODEC_H264, false);}final SessionDescription sdp = new SessionDescription(origSdp.type, sdpDescription);localSdp = sdp;executor.execute(new Runnable() {@Overridepublic void run() {if (peerConnection != null && !isError) {Log.d(TAG, "Set local SDP from " + sdp.type);peerConnection.setLocalDescription(sdpObserver, sdp);}}});}*/

SetlocalSDP

下面我们看看PeerConnectionClient.java中回调的实现

private class SDPObserver implements SdpObserver {@Overridepublic void onCreateSuccess(final SessionDescription origSdp) {if (localSdp != null) {reportError("Multiple SDP create.");return;}String sdpDescription = origSdp.description;//根据平台,更改音视频的编码方式if (preferIsac) {sdpDescription = preferCodec(sdpDescription, AUDIO_CODEC_ISAC, true);}if (videoCallEnabled && preferH264) {sdpDescription = preferCodec(sdpDescription, VIDEO_CODEC_H264, false);}//创建最终的SessionDescriptionfinal SessionDescription sdp = new SessionDescription(origSdp.type, sdpDescription);localSdp = sdp;executor.execute(new Runnable() {@Overridepublic void run() {if (peerConnection != null && !isError) {Log.d(TAG, "Set local SDP from " + sdp.type);//更新本地的SessionDescription,若设置成功将回调SDPObserver的onSetSuccess方法peerConnection.setLocalDescription(sdpObserver, sdp);}}});}@Overridepublic void onSetSuccess() {executor.execute(new Runnable() {@Overridepublic void run() {if (peerConnection == null || isError) {return;}if (isInitiator) {//发起呼叫端逻辑// For offering peer connection we first create offer and set// local SDP, then after receiving answer set remote SDP.if (peerConnection.getRemoteDescription() == null) {// We've just set our local SDP so time to send it.Log.d(TAG, "Local SDP set succesfully");//将local SDP发送到服务器events.onLocalDescription(localSdp);} else {// We've just set remote description, so drain remote// and send local ICE candidates.Log.d(TAG, "Remote SDP set succesfully");drainCandidates();}} else {//被动应答端逻辑// For answering peer connection we set remote SDP and then// create answer and set local SDP.if (peerConnection.getLocalDescription() != null) {// We've just set our local SDP so time to send it, drain// remote and send local ICE candidates.Log.d(TAG, "Local SDP set succesfully");events.onLocalDescription(localSdp);drainCandidates();} else {// We've just set remote SDP - do nothing for now -// answer will be created soon.Log.d(TAG, "Remote SDP set succesfully");}}}});}.....
}

peerConnection.setLocalDescription(sdpObserver, sdp);的实现如下:

void PeerConnection::SetLocalDescription(SetSessionDescriptionObserver* observer,SessionDescriptionInterface* desc) {......//检测传入的desc是否合法更新状态后,设置到if (!session_->SetLocalDescription(desc, &error)) {PostSetSessionDescriptionFailure(observer, error);return;}SetSessionDescriptionMsg* msg =  new SetSessionDescriptionMsg(observer);//回调sdpObserver.onSetSuccess将local SDP发送给服务器signaling_thread()->Post(this, MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg);// MaybeStartGathering needs to be called after posting// MSG_SET_SESSIONDESCRIPTION_SUCCESS, so that we don't signal any candidates// before signaling that SetLocalDescription completed.//设置LocalDescription后向ICE服务器发出请求StartGatheringsession_->MaybeStartGathering();
}bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc,std::string* err_desc) {ASSERT(signaling_thread()->IsCurrent());// Takes the ownership of |desc| regardless of the result.rtc::scoped_ptr<SessionDescriptionInterface> desc_temp(desc);// Validate SDP.if (!ValidateSessionDescription(desc, cricket::CS_LOCAL, err_desc)) {return false;}// Update the initiator flag if this session is the initiator.Action action = GetAction(desc->type());if (state() == STATE_INIT && action == kOffer) {set_initiator(true);}.....//设置本地descriptionset_local_description(desc->description()->Copy());local_desc_.reset(desc_temp.release());// Transport and Media channels will be created only when offer is set.//CreateChannels根据本地的description创建audiochannel videochannel datachannelif (action == kOffer && !CreateChannels(local_desc_->description())) {// TODO(mallinath) - Handle CreateChannel failure, as new local description// is applied. Restore back to old description.return BadLocalSdp(desc->type(), kCreateChannelFailed, err_desc);}......if (remote_description()) {//如果会话已经设置了远程description// Now that we have a local description, we can push down remote candidates// that we stored, and those from the remote description.if (!saved_candidates_.empty()) {// If there are saved candidates which arrived before the local// description was set, copy those to the remote description.CopySavedCandidates(remote_desc_.get());}// Push remote candidates in remote description to transport channels.//从远程SDP中获取condidate并通过//TransportController::AddRemoteCandidates-->P2PTransportChannel::AddRemoteCandidate--->bool P2PTransportChannel::CreateConnections//最终会建立P2P的链接,前面文章提到在ClientB链接到服务器时,服务器会将ClientA的SDP返回给B端,B端首先会设置remote_description//所以在ClientB设置local description时开始与ClientA建立p2p链接UseCandidatesInSessionDescription(remote_desc_.get());}// Update state and SSRC of local MediaStreams and DataChannels based on the// local session description.mediastream_signaling_->OnLocalDescriptionChanged(local_desc_.get());rtc::SSLRole role;if (data_channel_type_ == cricket::DCT_SCTP && GetSslRole(&role)) {mediastream_signaling_->OnDtlsRoleReadyForSctp(role);}if (error() != cricket::BaseSession::ERROR_NONE) {return BadLocalSdp(desc->type(), GetSessionErrorMsg(), err_desc);}return true;
}

GatheringICECondidate

上面的分析提到,在void PeerConnection::SetLocalDescription中会调用 session_->MaybeStartGathering()开始访问iceserver获取本地的condidate

void BaseSession::MaybeStartGathering() {
//直接调用TransportController类中的MaybeStartGathering方法transport_controller_->MaybeStartGathering();
}void TransportController::MaybeStartGathering() {//在工作线程worker_thread_中调用MaybeStartGathering_w方法!worker_thread_->Invoke<void>(rtc::Bind(&TransportController::MaybeStartGathering_w, this));
}void TransportController::MaybeStartGathering_w() {
//transports_为map<std::string, Transport*> TransportMap,最终会调用P2PTransportChannel::MaybeStartGathering()for (const auto& kv : transports_) {kv.second->MaybeStartGathering();}
}void P2PTransportChannel::MaybeStartGathering() {// Start gathering if we never started before, or if an ICE restart occurred.if (allocator_sessions_.empty() ||IceCredentialsChanged(allocator_sessions_.back()->ice_ufrag(),allocator_sessions_.back()->ice_pwd(), ice_ufrag_,ice_pwd_)) {if (gathering_state_ != kIceGatheringGathering) {gathering_state_ = kIceGatheringGathering;SignalGatheringState(this);}// Time for a new allocatorAddAllocatorSession(allocator_->CreateSession(SessionId(), transport_name(), component(), ice_ufrag_, ice_pwd_));}
}void P2PTransportChannel::AddAllocatorSession(PortAllocatorSession* session) {session->set_generation(static_cast<uint32>(allocator_sessions_.size()));allocator_sessions_.push_back(session);// We now only want to apply new candidates that we receive to the ports// created by this new session because these are replacing those of the// previous sessions.ports_.clear();
//通过信号与槽的方式获取底层的通知事件,此处的port不仅仅是传统意义上的端口
//实际代表的是一种通信协议,eg:TCPPort ,UDPPort,StunPort,RelayPortsession->SignalPortReady.connect(this, &P2PTransportChannel::OnPortReady);//底层每发现一个condiatate都会通知到P2PTransportChannel::OnCandidatesReadysession->SignalCandidatesReady.connect(this, &P2PTransportChannel::OnCandidatesReady);session->SignalCandidatesAllocationDone.connect(this, &P2PTransportChannel::OnCandidatesAllocationDone);// session实际为BasicPortAllocatorSessionsession->StartGettingPorts();
}void BasicPortAllocatorSession::StartGettingPorts() {network_thread_ = rtc::Thread::Current();if (!socket_factory_) {owned_socket_factory_.reset(new rtc::BasicPacketSocketFactory(network_thread_));socket_factory_ = owned_socket_factory_.get();}running_ = true;network_thread_->Post(this, MSG_CONFIG_START);if (flags() & PORTALLOCATOR_ENABLE_SHAKER)network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE);
}//经过一次处理MSG_CONFIG_START ==>MSG_CONFIG_READY==>MSG_ALLOCATE 消息==>OnAllocate==>DoAllocate()
//为本机每一个物理网络分配ports
void BasicPortAllocatorSession::DoAllocate() {bool done_signal_needed = false;std::vector<rtc::Network*> networks;//获取本机的网络信息eg:ip 。。。GetNetworks(&networks);if (networks.empty()) {LOG(LS_WARNING) << "Machine has no networks; no ports will be allocated";done_signal_needed = true;} else {for (uint32 i = 0; i < networks.size(); ++i) {PortConfiguration* config = NULL;if (configs_.size() > 0)config = configs_.back();uint32 sequence_flags = flags();if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {// If all the ports are disabled we should just fire the allocation// done event and return.done_signal_needed = true;break;}if (!config || config->relays.empty()) {// No relay ports specified in this config.sequence_flags |= PORTALLOCATOR_DISABLE_RELAY;}if (!(sequence_flags & PORTALLOCATOR_ENABLE_IPV6) &&networks[i]->GetBestIP().family() == AF_INET6) {// Skip IPv6 networks unless the flag's been set.continue;}// Disable phases that would only create ports equivalent to// ones that we have already made.DisableEquivalentPhases(networks[i], config, &sequence_flags);if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {// New AllocationSequence would have nothing to do, so don't make it.continue;}AllocationSequence* sequence =new AllocationSequence(this, networks[i], config, sequence_flags);if (!sequence->Init()) {//初始化udp_socket_:AsyncPacketSocket delete sequence;continue;}done_signal_needed = true;//ports分配完毕后会触发BasicPortAllocatorSession::OnPortAllocationComplete 将事件抛给上层sequence->SignalPortAllocationComplete.connect(this, &BasicPortAllocatorSession::OnPortAllocationComplete);if (running_)//开始分配各种ports PHASE_UDP ->PHASE_RELAY->PHASE_TCP ->PHASE_SSLTCPsequence->Start();sequences_.push_back(sequence);}}if (done_signal_needed) {network_thread_->Post(this, MSG_SEQUENCEOBJECTS_CREATED);}
}
//每种端口在分配时,会根据相应的协议获取Condidate将会通过SignalCandidatesReady信号通知到上层!
//BasicPortAllocatorSession::SignalCandidatesReady==>
//P2PTransportChannel::OnCandidatesReady==>P2PTransportChannel::SignalCandidateGathered==>
//Transport::OnChannelCandidateGathered==>Transport::SignalCandidatesGathered==>
//TransportController::OnTransportCandidatesGathered_w==>TransportController::SignalCandidatesGathered==>
//WebRtcSession::OnTransportControllerCandidatesGathered==> ice_observer_->OnIceCandidate(&candidate);
//最终会调用应用层实现的IceObserver.OnIceCandidate

在谷歌的WebRtc的demo中由PeerConnectionClient.java PCObserver实现,如下:

 private class PCObserver implements PeerConnection.Observer {@Overridepublic void onIceCandidate(final IceCandidate candidate){executor.execute(new Runnable() {@Overridepublic void run() {events.onIceCandidate(candidate);}});}@Overridepublic void onSignalingChange(PeerConnection.SignalingState newState) {Log.d(TAG, "SignalingState: " + newState);}......}
//events为PeerConnectionEvents 由CallActivity实现
public class CallActivity extends Activityimplements AppRTCClient.SignalingEvents,PeerConnectionClient.PeerConnectionEvents,CallFragment.OnCallEvents{......@Overridepublic void onIceCandidate(final IceCandidate candidate) {runOnUiThread(new Runnable() {@Overridepublic void run() {if (appRtcClient != null) {appRtcClient.sendLocalIceCandidate(candidate);}}});}......
}
public class WebSocketRTCClient implements AppRTCClient,WebSocketChannelEvents{.....// Send Ice candidate to the other participant.@Overridepublic void sendLocalIceCandidate(final IceCandidate candidate) {executor.execute(new Runnable() {@Overridepublic void run() {JSONObject json = new JSONObject();jsonPut(json, "type", "candidate");jsonPut(json, "label", candidate.sdpMLineIndex);jsonPut(json, "id", candidate.sdpMid);jsonPut(json, "candidate", candidate.sdp);if (initiator) {// Call initiator sends ice candidates to GAE server.if (roomState != ConnectionState.CONNECTED) {reportError("Sending ICE candidate in non connected state.");return;}//offer端通过http先将本地candidate发送到远程服务器,再由远程服务器发送到响应端!sendPostMessage(MessageType.MESSAGE, messageUrl, json.toString());if (connectionParameters.loopback) {events.onRemoteIceCandidate(candidate);}} else {// Call receiver sends ice candidates to websocket server.wsClient.send(json.toString());}}});}.....}

到目前位置ClientA端的工作告一段落,假如服务器服务器接受到响应端的SDP并转发给ClientA后,ClientA接下来会发生些什么呢?

SetRemoteSDP

服务器通过WebSocket将SDP发送到ClientA端后,最终会触发WebSocketChannelEvents.onWebSocketMessage方法,实现如下:

/*WebSocketRTCClient.java*/public class WebSocketRTCClient implements AppRTCClient,WebSocketChannelEvents {......@Overridepublic void onWebSocketMessage(final String msg) {if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {Log.e(TAG, "Got WebSocket message in non registered state.");return;}try {JSONObject json = new JSONObject(msg);String msgText = json.getString("msg");String errorText = json.optString("error");if (msgText.length() > 0) {json = new JSONObject(msgText);String type = json.optString("type");//从服务器消息中获取到candidateif (type.equals("candidate")) {IceCandidate candidate = new IceCandidate(json.getString("id"),json.getInt("label"),json.getString("candidate"));events.onRemoteIceCandidate(candidate);} else if (type.equals("answer")) {if (initiator) {//Offer端收到了响应端的SDPSessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type),json.getString("sdp"));events.onRemoteDescription(sdp);} else {reportError("Received answer for call initiator: " + msg);}} else if (type.equals("offer")) {if (!initiator) {//响应端收到了Offer端的SDPSessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type),json.getString("sdp"));events.onRemoteDescription(sdp);} else {reportError("Received offer for call receiver: " + msg);}} else if (type.equals("bye")) {events.onChannelClose();} else {reportError("Unexpected WebSocket message: " + msg);}} else {if (errorText != null && errorText.length() > 0) {reportError("WebSocket error message: " + errorText);} else {reportError("Unexpected WebSocket message: " + msg);}}} catch (JSONException e) {reportError("WebSocket message JSON parsing error: " + e.toString());}}......}

我们先看events.onRemoteDescription(sdp)的实现,后面再看 events.onRemoteIceCandidate(candidate)

events.onRemoteDescription(sdp)--> peerConnectionClient.setRemoteDescription(sdp);public void setRemoteDescription(final SessionDescription sdp) {executor.execute(new Runnable() {@Overridepublic void run() {......SessionDescription sdpRemote = new SessionDescription(sdp.type, sdpDescription);peerConnection.setRemoteDescription(sdpObserver, sdpRemote);......}});}void PeerConnection::SetRemoteDescription(SetSessionDescriptionObserver* observer,SessionDescriptionInterface* desc) {if (!VERIFY(observer != NULL)) {LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL.";return;}if (!desc) {PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL.");return;}// Update stats here so that we have the most recent stats for tracks and// streams that might be removed by updating the session description.stats_->UpdateStats(kStatsOutputLevelStandard);std::string error;//设置会话中的RemoteDescription,会根据RemoteDescription创建响应的channelif (!session_->SetRemoteDescription(desc, &error)) {PostSetSessionDescriptionFailure(observer, error);return;}SetSessionDescriptionMsg* msg  = new SetSessionDescriptionMsg(observer);//设置成功后调用相应的回调通知应用signaling_thread()->Post(this, MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg);
}bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc,std::string* err_desc) {ASSERT(signaling_thread()->IsCurrent());// Takes the ownership of |desc| regardless of the result.rtc::scoped_ptr<SessionDescriptionInterface> desc_temp(desc);// Validate SDP.if (!ValidateSessionDescription(desc, cricket::CS_REMOTE, err_desc)) {return false;}// Transport and Media channels will be created only when offer is set.Action action = GetAction(desc->type());//根据远程的SDP 创建会话需要的channelif (action == kOffer && !CreateChannels(desc->description())) {// TODO(mallinath) - Handle CreateChannel failure, as new local description// is applied. Restore back to old description.return BadRemoteSdp(desc->type(), kCreateChannelFailed, err_desc);}// Remove unused channels if MediaContentDescription is rejected.RemoveUnusedChannels(desc->description());// NOTE: Candidates allocation will be initiated only when SetLocalDescription// is called.//设置远程的descriptionset_remote_description(desc->description()->Copy());if (!UpdateSessionState(action, cricket::CS_REMOTE, err_desc)) {return false;}......}

SetRemoteCondidate

 events.onRemoteIceCandidate(candidate)-->peerConnectionClient.addRemoteIceCandidate(candidate);-->peerConnection.addIceCandidate(candidate);bool PeerConnection::AddIceCandidate(const IceCandidateInterface* ice_candidate) {return session_->ProcessIceMessage(ice_candidate);
}bool WebRtcSession::ProcessIceMessage(const IceCandidateInterface* candidate) {if (state() == STATE_INIT) {LOG(LS_ERROR) << "ProcessIceMessage: ICE candidates can't be added "<< "without any offer (local or remote) "<< "session description.";return false;}if (!candidate) {LOG(LS_ERROR) << "ProcessIceMessage: Candidate is NULL";return false;}bool valid = false;if (!ReadyToUseRemoteCandidate(candidate, NULL, &valid)) {if (valid) {LOG(LS_INFO) << "ProcessIceMessage: Candidate saved";saved_candidates_.push_back(new JsepIceCandidate(candidate->sdp_mid(),candidate->sdp_mline_index(),candidate->candidate()));}return valid;}// Add this candidate to the remote session description.if (!remote_desc_->AddCandidate(candidate)) {LOG(LS_ERROR) << "ProcessIceMessage: Candidate cannot be used";return false;}return UseCandidate(candidate);
}UseCandidate--->transport_controller()->AddRemoteCandidates(content.name, candidates,&error)--->TransportController::AddRemoteCandidates_w--->
Transport::AddRemoteCandidates--->void P2PTransportChannel::AddRemoteCandidate(const Candidate& candidate) {ASSERT(worker_thread_ == rtc::Thread::Current());uint32 generation = candidate.generation();// Network may not guarantee the order of the candidate delivery. If a// remote candidate with an older generation arrives, drop it.if (generation != 0 && generation < remote_candidate_generation_) {LOG(LS_WARNING) << "Dropping a remote candidate because its generation "<< generation<< " is lower than the current remote generation "<< remote_candidate_generation_;return;}// Create connections to this remote candidate.//创建connectionsCreateConnections(candidate, NULL); // Resort the connections list, which may have new elements.SortConnections();
}

响应端的各个过程与此类似,只是相应的顺序不一样,结合Offer端不难明白!

WebRtc 之P2C的建立相关推荐

  1. WebRTC 学习资料整理一

    官网永远是最重要,但同时也是最容易忽略的学习途径.So you should look official websites firtsly.. 先看一看基础概念的解释 WebRTC 相關縮寫名詞簡介 ...

  2. WebRtc学习资料整理

    很久没有写了,尤其是技术文章,总感觉很难受.这里总结一下最近的学习内容. 官网永远是最重要,但同时也是最容易忽略的学习途径.So you should look official websites f ...

  3. kurento教程_如何使用WebRTC和Kurento媒体服务器,来建立视频会议App(一)

    原文标题:[TUTORIAL] ​How ​to ​Build ​a ​Video Conference ​Application ​with WebRTC ​& ​Kurento ​Medi ...

  4. WebRTC详解-zz

    1.WebRTC目的 WebRTC(Web Real-Time Communication)项目的最终目的主要是让Web开发者能够基于浏览器(Chrome\FireFox\...) 轻易快捷开发出丰富 ...

  5. WebRTC之linux ARM64交叉编译(七)

    WebRTC实现了基于网页的视频会议,标准是WHATWG 协议,目的是通过浏览器提供简单的javascript就可以达到实时通讯(Real-Time Communications (RTC))能力. ...

  6. WebRTC[7]-Failed to set remote offer sdp: Called with SDP without DTLS fingerprint

    目录 问题 解决 Java: OC: C++: JS: 问题 WebRTC音视频通道建立的前提是完成SDP信息的交换,前端时间遇到了一个SDP信息交换失败的问题,非常具有代表性,今天周末特意整理了这篇 ...

  7. WebRtc以Trickle ICE形式去进行pair

    文章目录 简介 时序图 伪代码 主动方 被动方 简介 Trickle ICE(Interactive Connectivity Establishment)是WebRTC的一种流程,它允许WebRTC ...

  8. 流媒体协议之WebRTC实现p2p视频通话(二)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 简介 目的 帮助自己了解webrtc 实现端对端通信 # 使用流 ...

  9. WebRTC 入门教程(二)| WebRTC信令控制与STUN/TURN服务器搭建

    本文将向大家介绍两个方面的知识: WebRTC信令控制 STUN/TURN服务器的搭建 在https://mp.csdn.net/postedit/92436226已经向大家介绍了如何构建信令服务器. ...

最新文章

  1. pytorch 安装方法
  2. dyld: Library not loaded: @rpath/Alamofire.framework/Alamofire
  3. 水平输送水汽通量matlab,分享:水汽通量散度
  4. kubectl 安装
  5. Apache Tomcat目录下各个文件夹的作用
  6. shell之文本过滤(grep)
  7. 拓展名php,取扩展名_php
  8. DIV+CSS:页脚永远保持在页面底部
  9. 数据科学包11-数据可视化
  10. 二叉搜索树前序序列转中序和后序
  11. Only老K说-Java设计模式之原型模式(Prototype)
  12. uni-app 使用API中的uni.chooseImage 上传照片以及uni.previewImage图片预览(身份证照片为例)
  13. Vue实战中的一些小魔法
  14. WebRTC::FEC
  15. MySQL 安装流程 常见安装失败问题汇总!
  16. 洛谷P1010 [NOIP1998 普及组] 幂次方 C语言/C++
  17. 什么是软件生命周期模型?试比较瀑布模型、快速原型模型、增量模型和螺旋模型的优缺点,说明每种模型的使用范围。
  18. 数据结构中的算法,算法的定义与特征
  19. java流行框架有哪些?
  20. 策略模式、模板模式实战

热门文章

  1. 1.当推荐系统邂逅深度学习
  2. 学习笔记-BloodHound
  3. 2019,海尔真的慌了
  4. ios NSTimeInterval获取时间间隔
  5. Python 大作业 网易云歌单数据分析及可视化(参考多位博主文章)
  6. printf格式字符串和输出列表个数及类型不匹配案例
  7. Centos7 64位 -- glibc-2.29 编译升级方法(已成功)
  8. 学习记录651@python之收益率与对数收益率案例实战
  9. python爬取图片零基础
  10. NOIP2016模拟赛 序 (LIS)