webrtc 中怎麼根據 SDP 建立或關聯底層的 socket 物件?

語言: CN / TW / HK

----------------------------------------------------------------------------------------------------------------------------------------

一分鐘快速搭建 rtmpd 伺服器: http://blog.csdn.net/freeabc/article/details/102880984

軟體下載地址: http://www.qiyicc.com/download/rtmpd.rar

github 地址:http://github.com/superconvert/smart_rtmpd

-----------------------------------------------------------------------------------------------------------------------------------------

webrtc 中怎麼根據 SDP 建立或關聯底層的 socket 物件?

我們知道上層需要兩個介面設定 SDP 的相關資訊,體現在底層就是下面兩個介面:

./pc/peer_connection.cc 

1. void PeerConnection::SetLocalDescription(SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc_ptr) 
2. void PeerConnection::SetRemoteDescription(SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc_ptr)

這兩個介面最終都會呼叫,具體呼叫流程,我就不闡述了,參見我的其它部落格

transport_controller_->SetLocalDescription(type, sdesc->description())
transport_controller_->SetRemoteDescription(type,  sdesc->description()); 

我們知道 PeerConnection 的屬性 transport_controller_ 就是所有 transport 的控制器,其實就是一個 JsepTransportController,我們分別跟蹤 

 ./pc/jsep_transport_controller.cc

RTCError JsepTransportController::SetLocalDescription(SdpType type, const cricket::SessionDescription* description)
RTCError JsepTransportController::SetRemoteDescription(SdpType type, const cricket::SessionDescription* description)

這兩個介面最終呼叫 

./pc/jsep_transport_controller.cc

RTCError JsepTransportController::ApplyDescription_n(bool local, SdpType type, const cricket::SessionDescription* description)

  for (const cricket::ContentInfo& content_info : description->contents()) {
    // Don't create transports for rejected m-lines and bundled m-lines."
    if (content_info.rejected ||
        (IsBundled(content_info.name) && content_info.name != *bundled_mid())) {
      continue;
    }
    error = MaybeCreateJsepTransport(local, content_info, *description);
    if (!error.ok()) {
      return error;
    }
  }

其實每個 content 就是 SDP 裡面的一個 m 媒體段,具體參考部落格 http://blog.csdn.net/freeabc/article/details/109784860

通過介面 MaybeCreateJsepTransport 建立 transport 物件

RTCError JsepTransportController::MaybeCreateJsepTransport(
    bool local,
    const cricket::ContentInfo& content_info,
    const cricket::SessionDescription& description) {
  RTC_DCHECK(network_thread_->IsCurrent());
  cricket::JsepTransport* transport = GetJsepTransportByName(content_info.name);
  if (transport) {
    return RTCError::OK();
  }

  const cricket::MediaContentDescription* content_desc =
      content_info.media_description();
  if (certificate_ && !content_desc->cryptos().empty()) {
    return RTCError(RTCErrorType::INVALID_PARAMETER,
                    "SDES and DTLS-SRTP cannot be enabled at the same time.");
  }

  rtc::scoped_refptr<webrtc::IceTransportInterface> ice =
      CreateIceTransport(content_info.name, /*rtcp=*/false);
  RTC_DCHECK(ice);

  std::unique_ptr<DatagramTransportInterface> datagram_transport =
      MaybeCreateDatagramTransport(content_info, description, local);
  if (datagram_transport) {
    datagram_transport->Connect(ice->internal());
  }

  std::unique_ptr<cricket::DtlsTransportInternal> rtp_dtls_transport =
      CreateDtlsTransport(content_info, ice->internal(), nullptr);

  std::unique_ptr<cricket::DtlsTransportInternal> rtcp_dtls_transport;
  std::unique_ptr<RtpTransport> unencrypted_rtp_transport;
  std::unique_ptr<SrtpTransport> sdes_transport;
  std::unique_ptr<DtlsSrtpTransport> dtls_srtp_transport;
  std::unique_ptr<RtpTransportInternal> datagram_rtp_transport;

  rtc::scoped_refptr<webrtc::IceTransportInterface> rtcp_ice;
  if (config_.rtcp_mux_policy !=
          PeerConnectionInterface::kRtcpMuxPolicyRequire &&
      content_info.type == cricket::MediaProtocolType::kRtp) {
    RTC_DCHECK(datagram_transport == nullptr);
    rtcp_ice = CreateIceTransport(content_info.name, /*rtcp=*/true);
    rtcp_dtls_transport =
        CreateDtlsTransport(content_info, rtcp_ice->internal(),
                            /*datagram_transport=*/nullptr);
  }

  // Only create a datagram RTP transport if the datagram transport should be
  // used for RTP.
  if (datagram_transport && config_.use_datagram_transport) {
    // TODO(sukhanov): We use unencrypted RTP transport over DatagramTransport,
    // because MediaTransport encrypts. In the future we may want to
    // implement our own version of RtpTransport over MediaTransport, because
    // it will give us more control over things like:
    // - Fusing
    // - Rtp header compression
    // - Handling Rtcp feedback.
    RTC_LOG(LS_INFO) << "Creating UnencryptedRtpTransport, because datagram "
                        "transport is used.";
    RTC_DCHECK(!rtcp_dtls_transport);
    datagram_rtp_transport = std::make_unique<DatagramRtpTransport>(
        content_info.media_description()->rtp_header_extensions(),
        ice->internal(), datagram_transport.get());
  }
  // used for RTP.
  if (datagram_transport && config_.use_datagram_transport) {
    // TODO(sukhanov): We use unencrypted RTP transport over DatagramTransport,
    // because MediaTransport encrypts. In the future we may want to
    // implement our own version of RtpTransport over MediaTransport, because
    // it will give us more control over things like:
    // - Fusing
    // - Rtp header compression
    // - Handling Rtcp feedback.
    RTC_LOG(LS_INFO) << "Creating UnencryptedRtpTransport, because datagram "
                        "transport is used.";
    RTC_DCHECK(!rtcp_dtls_transport);
    datagram_rtp_transport = std::make_unique<DatagramRtpTransport>(
        content_info.media_description()->rtp_header_extensions(),
        ice->internal(), datagram_transport.get());
  }

  if (config_.disable_encryption) {
    RTC_LOG(LS_INFO)
        << "Creating UnencryptedRtpTransport, becayse encryption is disabled.";
    unencrypted_rtp_transport = CreateUnencryptedRtpTransport(
        content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
  } else if (!content_desc->cryptos().empty()) {
    sdes_transport = CreateSdesTransport(
        content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
    RTC_LOG(LS_INFO) << "Creating SdesTransport.";
  } else {
    RTC_LOG(LS_INFO) << "Creating DtlsSrtpTransport.";
    dtls_srtp_transport = CreateDtlsSrtpTransport(
        content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
  }

  std::unique_ptr<cricket::SctpTransportInternal> sctp_transport;
  if (config_.sctp_factory) {
    sctp_transport =
        config_.sctp_factory->CreateSctpTransport(rtp_dtls_transport.get());
  }

  DataChannelTransportInterface* data_channel_transport = nullptr;
  if (config_.use_datagram_transport_for_data_channels) {
    data_channel_transport = datagram_transport.get();
  }

  std::unique_ptr<cricket::JsepTransport> jsep_transport =
      std::make_unique<cricket::JsepTransport>(
          content_info.name, certificate_, std::move(ice), std::move(rtcp_ice),
          std::move(unencrypted_rtp_transport), std::move(sdes_transport),
          std::move(dtls_srtp_transport), std::move(datagram_rtp_transport),
          std::move(rtp_dtls_transport), std::move(rtcp_dtls_transport),
          std::move(sctp_transport), std::move(datagram_transport),
          data_channel_transport);

  jsep_transport->rtp_transport()->SignalRtcpPacketReceived.connect(
      this, &JsepTransportController::OnRtcpPacketReceived_n);

  jsep_transport->SignalRtcpMuxActive.connect(
      this, &JsepTransportController::UpdateAggregateStates_n);
  jsep_transport->SignalDataChannelTransportNegotiated.connect(
      this, &JsepTransportController::OnDataChannelTransportNegotiated_n);
  SetTransportForMid(content_info.name, jsep_transport.get());

  jsep_transports_by_name_[content_info.name] = std::move(jsep_transport);
  UpdateAggregateStates_n();
  return RTCError::OK();
}

這個函式根據 SDP 的內容建立各種各樣的 transport ,這裡我們看到熟悉的介面 CreateDtlsSrtpTransport ,這個建立了 webrtc::DtlsSrtpTransport 物件,webrtc::DtlsSrtpTransport 物件下面的兩個成員物件非常關鍵

  // Owned by the TransportController.
  cricket::DtlsTransportInternal* rtp_dtls_transport_ = nullptr;
  cricket::DtlsTransportInternal* rtcp_dtls_transport_ = nullptr;

我們先分析一下 cricket::DtlsTransportInternal* rtp_dtls_transport_  物件是由下面的介面建立的,我們看到有個 ice 介面傳遞進去!!!!

std::unique_ptr<cricket::DtlsTransportInternal>
JsepTransportController::CreateDtlsTransport(
    const cricket::ContentInfo& content_info,
    cricket::IceTransportInternal* ice,
    DatagramTransportInterface* datagram_transport) {
  RTC_DCHECK(network_thread_->IsCurrent());

  std::unique_ptr<cricket::DtlsTransportInternal> dtls;

  if (datagram_transport) {
    RTC_DCHECK(config_.use_datagram_transport ||
               config_.use_datagram_transport_for_data_channels);
  } else if (config_.dtls_transport_factory) {
    dtls = config_.dtls_transport_factory->CreateDtlsTransport(
        ice, config_.crypto_options);
  } else {
    // 這個類的定義參見檔案 ./p2p/base/dtls_transport.h
    dtls = std::make_unique<cricket::DtlsTransport>(ice, config_.crypto_options,
                                                    config_.event_log);
  }

  RTC_DCHECK(dtls);
  dtls->SetSslMaxProtocolVersion(config_.ssl_max_version);
  dtls->ice_transport()->SetIceRole(ice_role_);
  dtls->ice_transport()->SetIceTiebreaker(ice_tiebreaker_);
  dtls->ice_transport()->SetIceConfig(ice_config_);
  if (certificate_) {
    bool set_cert_success = dtls->SetLocalCertificate(certificate_);
    RTC_DCHECK(set_cert_success);
  }

  // Connect to signals offered by the DTLS and ICE transport.
  dtls->SignalWritableState.connect(
      this, &JsepTransportController::OnTransportWritableState_n);
  dtls->SignalReceivingState.connect(
      this, &JsepTransportController::OnTransportReceivingState_n);
  dtls->SignalDtlsHandshakeError.connect(
      this, &JsepTransportController::OnDtlsHandshakeError);
  dtls->ice_transport()->SignalGatheringState.connect(
      this, &JsepTransportController::OnTransportGatheringState_n);
  dtls->ice_transport()->SignalCandidateGathered.connect(
      this, &JsepTransportController::OnTransportCandidateGathered_n);
  dtls->ice_transport()->SignalCandidateError.connect(
      this, &JsepTransportController::OnTransportCandidateError_n);
  dtls->ice_transport()->SignalCandidatesRemoved.connect(
      this, &JsepTransportController::OnTransportCandidatesRemoved_n);
  dtls->ice_transport()->SignalRoleConflict.connect(
      this, &JsepTransportController::OnTransportRoleConflict_n);
  dtls->ice_transport()->SignalStateChanged.connect(
      this, &JsepTransportController::OnTransportStateChanged_n);
  dtls->ice_transport()->SignalIceTransportStateChanged.connect(
      this, &JsepTransportController::OnTransportStateChanged_n);
  dtls->ice_transport()->SignalCandidatePairChanged.connect(
      this, &JsepTransportController::OnTransportCandidatePairChanged_n);
  return dtls;
}

ICE 介面的建立程式碼 

RTCError JsepTransportController::MaybeCreateJsepTransport(
    bool local,
    const cricket::ContentInfo& content_info,
    const cricket::SessionDescription& description)

    // 我們看到這裡會根據 SDP 裡 m 段落的 name 建立 ice
    rtc::scoped_refptr<webrtc::IceTransportInterface> ice = CreateIceTransport(content_info.name, /*rtcp=*/false);


rtc::scoped_refptr<webrtc::IceTransportInterface>
JsepTransportController::CreateIceTransport(const std::string& transport_name,
                                            bool rtcp) {
  int component = rtcp ? cricket::ICE_CANDIDATE_COMPONENT_RTCP
                       : cricket::ICE_CANDIDATE_COMPONENT_RTP;

  IceTransportInit init;
  init.set_port_allocator(port_allocator_);
  init.set_async_resolver_factory(async_resolver_factory_);
  init.set_event_log(config_.event_log);
  return config_.ice_transport_factory->CreateIceTransport(
      transport_name, component, std::move(init));
}


//----------------------------------------------------------
// ./p2p/base/default_ice_transport_factory.cc
//----------------------------------------------------------
rtc::scoped_refptr<IceTransportInterface>
DefaultIceTransportFactory::CreateIceTransport(
    const std::string& transport_name,
    int component,
    IceTransportInit init) {
  BasicIceControllerFactory factory;
  return new rtc::RefCountedObject<DefaultIceTransport>(
      std::make_unique<cricket::P2PTransportChannel>(
          transport_name, component, init.port_allocator(),
          init.async_resolver_factory(), init.event_log(), &factory));
}

由上述我們可以看到 JsepTransportController 的成員物件 port_allocator_ 是個很重要的東東!這個物件是 JsepTransportController 初始化時候傳遞過來的,就是這個物件 cricket::BasicPortAllocator 這個的具體流程 請參考部落格

http://blog.csdn.net/freeabc/article/details/106000923

這樣每個 transport 就具有收發網路資料的能力了。而 transport 會通過下面的介面繫結到每個 RtpTransceiver 裡面,

        transceiver->internal()->sender_internal()->set_transport(dtls_transport);
        transceiver->internal()->receiver_internal()->set_transport(dtls_transport);

Good luck!