WebRtc信令交換的過程實(shí)際上是基于JSEP01(javascript session Establishment PRotocol)。 在上一篇[WebRtc建立P2P鏈接的總體流程]中只是描述了一種簡(jiǎn)單的形式,建立鏈接主要需要經(jīng)過如下過程: 
以此為基礎(chǔ)總結(jié)下p2p建立的過程!
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; //根據(jù)應(yīng)用層傳入的MediaConstraints設(shè)置RTCOfferAnswerOptions if (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初始化時(shí)創(chuàng)建的WebRtcSession對(duì)象 session_->CreateOffer(observer, options);}接下來看WebRtcSession中的CreateOffer是如何實(shí)現(xiàn)的,見webrtcsession.cc中:
void WebRtcSession::CreateOffer( CreateSessionDescriptionObserver* observer, const PeerConnectionInterface::RTCOfferAnswerOptions& options) { //webrtc_session_desc_factory_是WebRtcSession在Initialize創(chuàng)建 //根據(jù)需求創(chuàng)建是否DTLS加密的WebRtcSessionDescriptionFactory webrtc_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); ..... } 最終是將會(huì)話中需要的MediaStream傳入mediastream_signaling_進(jìn)行管理! 所以在mediastream_signaling_中可以獲取MediaSessionOptions*/ if (!mediastream_signaling_->GetOptionsForOffer(options, &session_options)) { error += " called with invalid options."; LOG(LS_ERROR) << error; PostCreateSessionDescriptionFailed(observer, error); return; }//檢測(cè)提供的audio video流是否合法即不能有相同的id if (!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()) { //若在應(yīng)用層建立了datachannel傳輸用戶數(shù)據(jù),設(shè)置成SCTP協(xié)議傳輸 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); //根據(jù)request創(chuàng)建SDP }}//從上可以看出mediastreamsignaling.cc實(shí)際上是peerconnection.cc和webrtcsession.cc溝通的橋梁mediastreamsignaling.cc負(fù)責(zé)多媒體相關(guān)的管理!//根據(jù)request創(chuàng)建SDPvoid 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_); //根據(jù)JSEP規(guī)范創(chuàng)建JsepSessionDescription JsepSessionDescription* 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); } //將創(chuàng)建的JsepSessionDescription回調(diào)給上層的應(yīng)用,應(yīng)用層可以結(jié)合自己的情況做相應(yīng)更改 PostCreateSessionDescriptionSucceeded(request.observer, offer); }/*onCreateSuccess為org/appspot/apprtc/PeerConnectionClient.java中offer創(chuàng)建成功后調(diào)用的回調(diào),在此會(huì)調(diào)用根據(jù)實(shí)際情況修改了音視頻編解碼相關(guān)的信息private class SDPObserver implements SdpObserver { @Override public 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() { @Override public void run() { if (peerConnection != null && !isError) { Log.d(TAG, "Set local SDP from " + sdp.type); peerConnection.setLocalDescription(sdpObserver, sdp); } } }); }*/下面我們看看PeerConnectionClient.java中回調(diào)的實(shí)現(xiàn)
private class SDPObserver implements SdpObserver { @Override public void onCreateSuccess(final SessionDescription origSdp) { if (localSdp != null) { reportError("Multiple SDP create."); return; } String sdpDescription = origSdp.description; //根據(jù)平臺(tái),更改音視頻的編碼方式 if (preferIsac) { sdpDescription = preferCodec(sdpDescription, AUDIO_CODEC_ISAC, true); } if (videoCallEnabled && preferH264) { sdpDescription = preferCodec(sdpDescription, VIDEO_CODEC_H264, false); } //創(chuàng)建最終的SessionDescription final SessionDescription sdp = new SessionDescription( origSdp.type, sdpDescription); localSdp = sdp; executor.execute(new Runnable() { @Override public void run() { if (peerConnection != null && !isError) { Log.d(TAG, "Set local SDP from " + sdp.type); //更新本地的SessionDescription,若設(shè)置成功將回調(diào)SDPObserver的onSetSuccess方法 peerConnection.setLocalDescription(sdpObserver, sdp); } } }); } @Override public void onSetSuccess() { executor.execute(new Runnable() { @Override public void run() { if (peerConnection == null || isError) { return; } if (isInitiator) { //發(fā)起呼叫端邏輯 // 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發(fā)送到服務(wù)器 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 { //被動(dòng)應(yīng)答端邏輯 // 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);的實(shí)現(xiàn)如下:
void PeerConnection::SetLocalDescription( SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc) { ...... //檢測(cè)傳入的desc是否合法更新狀態(tài)后,設(shè)置到 if (!session_->SetLocalDescription(desc, &error)) { PostSetSessionDescriptionFailure(observer, error); return; } SetSessionDescriptionMsg* msg = new SetSessionDescriptionMsg(observer); //回調(diào)sdpObserver.onSetSuccess將local SDP發(fā)送給服務(wù)器 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. //設(shè)置LocalDescription后向ICE服務(wù)器發(fā)出請(qǐng)求StartGathering session_->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); } ..... //設(shè)置本地description set_local_description(desc->description()->Copy()); local_desc_.reset(desc_temp.release()); // Transport and Media channels will be created only when offer is set. //CreateChannels根據(jù)本地的description創(chuàng)建audiochannel videochannel datachannel if (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()) { //如果會(huì)話已經(jīng)設(shè)置了遠(yuǎn)程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. //從遠(yuǎn)程SDP中獲取condidate并通過 //TransportController::AddRemoteCandidates-->P2PTransportChannel::AddRemoteCandidate--->bool P2PTransportChannel::CreateConnections //最終會(huì)建立P2P的鏈接,前面文章提到在ClientB鏈接到服務(wù)器時(shí),服務(wù)器會(huì)將ClientA的SDP返回給B端,B端首先會(huì)設(shè)置remote_description //所以在ClientB設(shè)置local description時(shí)開始與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;}上面的分析提到,在void PeerConnection::SetLocalDescription中會(huì)調(diào)用 session_->MaybeStartGathering()開始訪問iceserver獲取本地的condidate
void BaseSession::MaybeStartGathering() {//直接調(diào)用TransportController類中的MaybeStartGathering方法 transport_controller_->MaybeStartGathering();}void TransportController::MaybeStartGathering() { //在工作線程worker_thread_中調(diào)用MaybeStartGathering_w方法! worker_thread_->Invoke<void>( rtc::Bind(&TransportController::MaybeStartGathering_w, this));}void TransportController::MaybeStartGathering_w() {//transports_為map<std::string, Transport*> TransportMap,最終會(huì)調(diào)用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 allocator AddAllocatorSession(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();//通過信號(hào)與槽的方式獲取底層的通知事件,此處的port不僅僅是傳統(tǒng)意義上的端口//實(shí)際代表的是一種通信協(xié)議,eg:TCPPort ,UDPPort,StunPort,RelayPort session->SignalPortReady.connect(this, &P2PTransportChannel::OnPortReady); //底層每發(fā)現(xiàn)一個(gè)condiatate都會(huì)通知到P2PTransportChannel::OnCandidatesReady session->SignalCandidatesReady.connect( this, &P2PTransportChannel::OnCandidatesReady); session->SignalCandidatesAllocationDone.connect( this, &P2PTransportChannel::OnCandidatesAllocationDone); // session實(shí)際為BasicPortAllocatorSession session->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);}//經(jīng)過一次處理MSG_CONFIG_START ==>MSG_CONFIG_READY==>MSG_ALLOCATE 消息==>OnAllocate==>DoAllocate()//為本機(jī)每一個(gè)物理網(wǎng)絡(luò)分配portsvoid BasicPortAllocatorSession::DoAllocate() { bool done_signal_needed = false; std::vector<rtc::Network*> networks; //獲取本機(jī)的網(wǎng)絡(luò)信息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分配完畢后會(huì)觸發(fā)BasicPortAllocatorSession::OnPortAllocationComplete 將事件拋給上層 sequence->SignalPortAllocationComplete.connect( this, &BasicPortAllocatorSession::OnPortAllocationComplete); if (running_) //開始分配各種ports PHASE_UDP ->PHASE_RELAY->PHASE_TCP ->PHASE_SSLTCP sequence->Start(); sequences_.push_back(sequence); } } if (done_signal_needed) { network_thread_->Post(this, MSG_SEQUENCEOBJECTS_CREATED); }}//每種端口在分配時(shí),會(huì)根據(jù)相應(yīng)的協(xié)議獲取Condidate將會(huì)通過SignalCandidatesReady信號(hào)通知到上層!//BasicPortAllocatorSession::SignalCandidatesReady==>//P2PTransportChannel::OnCandidatesReady==>P2PTransportChannel::SignalCandidateGathered==>//Transport::OnChannelCandidateGathered==>Transport::SignalCandidatesGathered==>//TransportController::OnTransportCandidatesGathered_w==>TransportController::SignalCandidatesGathered==>//WebRtcSession::OnTransportControllerCandidatesGathered==> ice_observer_->OnIceCandidate(&candidate);//最終會(huì)調(diào)用應(yīng)用層實(shí)現(xiàn)的IceObserver.OnIceCandidate在谷歌的WebRtc的demo中由PeerConnectionClient.java PCObserver實(shí)現(xiàn),如下:
private class PCObserver implements PeerConnection.Observer { @Override public void onIceCandidate(final IceCandidate candidate){ executor.execute(new Runnable() { @Override public void run() { events.onIceCandidate(candidate); } }); } @Override public void onSignalingChange( PeerConnection.SignalingState newState) { Log.d(TAG, "SignalingState: " + newState); }......}//events為PeerConnectionEvents 由CallActivity實(shí)現(xiàn) public class CallActivity extends Activity implements AppRTCClient.SignalingEvents, PeerConnectionClient.PeerConnectionEvents, CallFragment.OnCallEvents{ ...... @Override public void onIceCandidate(final IceCandidate candidate) { runOnUiThread(new Runnable() { @Override public void run() { if (appRtcClient != null) { appRtcClient.sendLocalIceCandidate(candidate); } } }); } ......}public class WebSocketRTCClient implements AppRTCClient, WebSocketChannelEvents{ ..... // Send Ice candidate to the other participant. @Override public void sendLocalIceCandidate(final IceCandidate candidate) { executor.execute(new Runnable() { @Override public 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發(fā)送到遠(yuǎn)程服務(wù)器,再由遠(yuǎn)程服務(wù)器發(fā)送到響應(yīng)端! 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端的工作告一段落,假如服務(wù)器服務(wù)器接受到響應(yīng)端的SDP并轉(zhuǎn)發(fā)給ClientA后,ClientA接下來會(huì)發(fā)生些什么呢?
服務(wù)器通過WebSocket將SDP發(fā)送到ClientA端后,最終會(huì)觸發(fā)WebSocketChannelEvents.onWebSocketMessage方法,實(shí)現(xiàn)如下:
/*WebSocketRTCClient.java*/public class WebSocketRTCClient implements AppRTCClient, WebSocketChannelEvents { ...... @Override public 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"); //從服務(wù)器消息中獲取到candidate if (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端收到了響應(yīng)端的SDP SessionDescription 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) { //響應(yīng)端收到了Offer端的SDP SessionDescription 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)的實(shí)現(xiàn),后面再看 events.onRemoteIceCandidate(candidate)
events.onRemoteDescription(sdp)--> peerConnectionClient.setRemoteDescription(sdp); public void setRemoteDescription(final SessionDescription sdp) { executor.execute(new Runnable() { @Override public 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; //設(shè)置會(huì)話中的RemoteDescription,會(huì)根據(jù)RemoteDescription創(chuàng)建響應(yīng)的channel if (!session_->SetRemoteDescription(desc, &error)) { PostSetSessionDescriptionFailure(observer, error); return; } SetSessionDescriptionMsg* msg = new SetSessionDescriptionMsg(observer); //設(shè)置成功后調(diào)用相應(yīng)的回調(diào)通知應(yīng)用 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()); //根據(jù)遠(yuǎn)程的SDP 創(chuàng)建會(huì)話需要的channel if (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. //設(shè)置遠(yuǎn)程的description set_remote_description(desc->description()->Copy()); if (!UpdateSessionState(action, cricket::CS_REMOTE, err_desc)) { return false; }......}響應(yīng)端的各個(gè)過程與此類似,只是相應(yīng)的順序不一樣,結(jié)合Offer端不難明白!
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注