WebRTC学习进阶之路 --- 十六、源码分析之最核心的内容PeerConnection

WebRTC学习进阶之路系列总目录:https://blog.csdn.net/xiaomucgwlmx/article/details/103204274

本文我们来看WebRTC的核心通信模块PeerConnection,来看下酒精是如何为通信多方建立连接,提供通信机制的。

一、核心类整体介绍

看过前边文章的应该都已经知道,WebRTC的源码中api中的都是给应用层提供的内容,具体的实现都会放到内部的各个模块中去,PC的整个设计亦是如此:

api/peer_connection_interface.h  
PeerConnectionInterface PC对外提供的所有方法。使用时应用层都是通过创建该对象然后进行各个方法的应用。
PeerConnectionFactoryInterface PeerConnectionFactoryInterface是用于创建PeerConnection,MediaStream和MediaStreamTrack对象的工厂接口。通过CreatePeerConnection()创建PeerConnectionInterface。
pc/peer_connection_factory.h  
PeerConnectionFactory PeerConnectionFactory类提供了用于创建PeerConnection,MediaStream和MediaStreamTrack对象的工厂方法。PeerConnectionFactory继承于PeerConnectionFactoryInterface,做具体实现。
pc/peer_connection.h  
PeerConnection PeerConnection是PeerConnectionInterface API表面定义的PeerConnection对象的实现

二、建立连接的过程

WebRTC Peerconnection通信过程中的四种角色:

  • Signaling Server
  • ICE/TURN/STUN Server
  • Remote Peer
  • Local Peer

通信过程中的基本概念:

  • Room:Signaling Server使用Room的概念对同一组通信的peers进行配对管理,一个room中包含有1个或者多个peers。当没有peers存在时room销毁;当第一个peer连接到signaling server时执行create room动作,此时因为没有其他peers,建立room的这个peer不与其他节点建立P2P通信;其他peers随后加入room,加入room后会主动与room已经存在的peer建立连接;
  • Offer:主动与其他peer建立P2P链接的peer把自己的SDP信息整理好,通过signaling server转发给room里面的其他peer,这个SDP信息包就是Offer;
  • Answer:被动连接的Peer在收到signaling server转发的其他peer的offer信息以后,也把自己的SDP信息整理好,同样通过signaling server转发给主动连接它的peer,他自己的SDP信息包就是Answer;
  • IceCandidate:Peer与ICE/TURN/STUN Server直接建立连接,获取自己的NAT类型以及外网IP和端口,这些ICE/TURN/STUN Server返回的消息就是IceCandidate或者直接简称Candidate;
     

PeerConnection连接建立流程图:

WebRTC学习进阶之路 --- 十六、源码分析之最核心的内容PeerConnection_第1张图片

WebRTC学习进阶之路 --- 十六、源码分析之最核心的内容PeerConnection_第2张图片

对于上图中描述的PeerConnection建立的完整流程进行以下说明(上图是以ClientA主动向ClientB发起连接为例):

  • 首先ClientA和ClientB均通过双向通信方式如WebSocket连接到Signaling Server上;
  • ClientA在本地首先通GetMedia访问本地的media接口和数据,并创建PeerConnection对象,调用其AddStream方法把本地的Media添加到PeerConnection对象中。对于ClientA而言,既可以在与Signaling Server建立连接之前就创建并初始化PeerConnection如阶段1,也可以在建立Signaling Server连接之后创建并初始化PeerConnection如阶段2;ClientB既可以在上图的1阶段也可以在2阶段做同样的事情,访问自己的本地接口并创建自己的PeerConnection对象。
  • 通信由ClientA发起,所以ClientA调用PeerConnection的CreateOffer接口创建自己的SDP offer,然后把这个SDP Offer信息通过Signaling Server通道中转发给ClientB;
  • ClientB收到Signaling Server中转过来的ClientA的SDP信息也就是offer后,调用CreateAnswer创建自己的SDP信息也就是answer,然后把这个answer同样通过Signaling server转发给ClientA;
  • ClientA收到转发的answer消息以后,两个peers就做好了建立连接并获取对方media streaming的准备;
  • ClientA通过自己PeerConnection创建时传递的参数等待来自于ICE server的通信,获取自己的candidate,当candidate available的时候会自动回掉PeerConnection的OnIceCandidate;
  • ClientA通过Signling Server发送自己的Candidate给ClientB,ClientB依据同样的逻辑把自己的Candidate通过Signaling Server中转发给ClientA;
  • 至此ClientA和ClientB均已经接收到对方的Candidate,通过PeerConnection建立连接。至此P2P通道建立。

官方源码对整个建立的过程也有详细表述:

呼叫方:

// The following steps are needed to setup a typical call using WebRTC:
//
// 1. Create a PeerConnectionFactoryInterface. Check constructors for more
// information about input parameters.
//
// 2. Create a PeerConnection object. Provide a configuration struct which
// points to STUN and/or TURN servers used to generate ICE candidates, and
// provide an object that implements the PeerConnectionObserver interface,
// which is used to receive callbacks from the PeerConnection.
//
// 3. Create local MediaStreamTracks using the PeerConnectionFactory and add
// them to PeerConnection by calling AddTrack (or legacy method, AddStream).
//
// 4. Create an offer, call SetLocalDescription with it, serialize it, and send
// it to the remote peer
//
// 5. Once an ICE candidate has been gathered, the PeerConnection will call the
// observer function OnIceCandidate. The candidates must also be serialized and
// sent to the remote peer.
//
// 6. Once an answer is received from the remote peer, call
// SetRemoteDescription with the remote answer.
//
// 7. Once a remote candidate is received from the remote peer, provide it to
// the PeerConnection by calling AddIceCandidate.
//

接收方:

// The receiver of a call (assuming the application is "call"-based) can decide
// to accept or reject the call; this decision will be taken by the application,
// not the PeerConnection.
//
// If the application decides to accept the call, it should:
//
// 1. Create PeerConnectionFactoryInterface if it doesn't exist.
//
// 2. Create a new PeerConnection.
//
// 3. Provide the remote offer to the new PeerConnection object by calling
// SetRemoteDescription.
//
// 4. Generate an answer to the remote offer by calling CreateAnswer and send it
// back to the remote peer.
//
// 5. Provide the local answer to the new PeerConnection by calling
// SetLocalDescription with the answer.
//
// 6. Provide the remote ICE candidates by calling AddIceCandidate.
//
// 7. Once a candidate has been gathered, the PeerConnection will call the
// observer function OnIceCandidate. Send these candidates to the remote peer.

应用层具体使用流程之前将客户端和服务端通信案例的时候已经详细介绍过了,可以再去回顾下:WebRTC学习进阶之路 --- 十三、分析源码音视频互动peerconnection-client+server实例:https://blog.csdn.net/xiaomucgwlmx/article/details/103199930。下面我们来对核心的几个类和类中的内容的进行详细介绍和学习。

二、CreatePeerConnection()

在example/peerconnection_client工程中conductor.cc中,发起方调用如下代码来创建PeerConnection对象。

bool Conductor::CreatePeerConnection(bool dtls) {
  RTC_DCHECK(peer_connection_factory_);
  RTC_DCHECK(!peer_connection_);

  webrtc::PeerConnectionInterface::RTCConfiguration config;
  config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
  config.enable_dtls_srtp = dtls;
  webrtc::PeerConnectionInterface::IceServer server;
  server.uri = GetPeerConnectionString();
  config.servers.push_back(server);

  peer_connection_ = peer_connection_factory_->CreatePeerConnection(
      config, nullptr, nullptr, this);
  return peer_connection_ != nullptr;
}

核心就是调用CreatePeerConnection()方法,具体实现如下:

rtc::scoped_refptr
PeerConnectionFactory::CreatePeerConnection(
    const PeerConnectionInterface::RTCConfiguration& configuration,
    std::unique_ptr allocator,
    std::unique_ptr cert_generator,
    PeerConnectionObserver* observer) {
  // Convert the legacy API into the new dependency structure.
  PeerConnectionDependencies dependencies(observer);
  dependencies.allocator = std::move(allocator);
  dependencies.cert_generator = std::move(cert_generator);
  // Pass that into the new API.
  return CreatePeerConnection(configuration, std::move(dependencies));
}

rtc::scoped_refptr
PeerConnectionFactory::CreatePeerConnection(
    const PeerConnectionInterface::RTCConfiguration& configuration,
    PeerConnectionDependencies dependencies) {
  RTC_DCHECK(signaling_thread_->IsCurrent());
  RTC_DCHECK(!(dependencies.allocator && dependencies.packet_socket_factory))
      << "You can't set both allocator and packet_socket_factory; "
         "the former is going away (see bugs.webrtc.org/7447";

  // Set internal defaults if optional dependencies are not set.
  if (!dependencies.cert_generator) {
    dependencies.cert_generator =
        std::make_unique(signaling_thread_,
                                                       network_thread_);
  }
  if (!dependencies.allocator) {
    rtc::PacketSocketFactory* packet_socket_factory;
    if (dependencies.packet_socket_factory)
      packet_socket_factory = dependencies.packet_socket_factory.get();
    else
      packet_socket_factory = default_socket_factory_.get();

    network_thread_->Invoke(RTC_FROM_HERE, [this, &configuration,
                                                  &dependencies,
                                                  &packet_socket_factory]() {
      dependencies.allocator = std::make_unique(
          default_network_manager_.get(), packet_socket_factory,
          configuration.turn_customizer);
    });
  }

  if (!dependencies.ice_transport_factory) {
    dependencies.ice_transport_factory =
        std::make_unique();
  }

  // TODO(zstein): Once chromium injects its own AsyncResolverFactory, set
  // |dependencies.async_resolver_factory| to a new
  // |rtc::BasicAsyncResolverFactory| if no factory is provided.

  network_thread_->Invoke(
      RTC_FROM_HERE,
      rtc::Bind(&cricket::PortAllocator::SetNetworkIgnoreMask,
                dependencies.allocator.get(), options_.network_ignore_mask));

  std::unique_ptr event_log =
      worker_thread_->Invoke>(
          RTC_FROM_HERE,
          rtc::Bind(&PeerConnectionFactory::CreateRtcEventLog_w, this));

  std::unique_ptr call = worker_thread_->Invoke>(
      RTC_FROM_HERE,
      rtc::Bind(&PeerConnectionFactory::CreateCall_w, this, event_log.get()));

  rtc::scoped_refptr pc(
      new rtc::RefCountedObject(this, std::move(event_log),
                                                std::move(call)));
  ActionsBeforeInitializeForTesting(pc);
  if (!pc->Initialize(configuration, std::move(dependencies))) {
    return nullptr;
  }
  return PeerConnectionProxy::Create(signaling_thread(), pc);
}

实际上,第一个方法的后三个参数用于填充PeerConnectionDependencies结构体,然后作为了第二个方法的入参。因此,将CreatePeerConnection的参数分为两类,一类为RTCConfiguration,表征PeerConnection配置项;另一类为PeerConnectionDependencies,表征PeerConnection的依赖项。

依赖与配置最重要的区别在于,依赖定义了由用户提供的可执行代码,用于执行用户定义的逻辑。而配置是提供给WebRTC内部使用的参数信息,可以通过参数来控制WebRTC的内部逻辑或者是行为。

三、PeerConnectionFactory

PeerConnectionFactory的数据成员

 

  •   bool wraps_current_thread_;     // 信令线程是否Wrap而来
  •   rtc::Thread* network_thread_;   // 网络线程
  •   rtc::Thread* worker_thread_;    // 工作者线程
  •   rtc::Thread* signaling_thread_; // 信令线程
  •   std::unique_ptr owned_network_thread_; // 持有网络线程
  •   std::unique_ptr owned_worker_thread_;  // 持有工作者线程
  •   Options options_;    // 选项
  •   std::unique_ptr channel_manager_;               // 通道管理器
  •   std::unique_ptr default_network_manager_;      // 网络管理器
  •   std::unique_ptr default_socket_factory_;  // Socket工厂
  •   std::unique_ptr media_engine_;            // 媒体引擎
  •   std::unique_ptr call_factory_;             // Call工厂
  •   std::unique_ptr event_log_factory_;         // EventLog工厂
  •   std::unique_ptr fec_controller_factory_;  // Fec控制工厂
  •   std::unique_ptr                       // 网络控制工厂
  •       injected_network_controller_factory_;
  •   std::unique_ptr media_transport_factory_;         // 媒体传输工厂

需要注意的有一点是:对于网络线程和工作者线程来说,不仅通过Thread* worker_thread_直接引用了工作者线程,同时std::unique_ptr owned_worker_thread_持有了工作者线程。意味着工作者线程和网络线程的管理者是PeerConnectionFactory,由PeerConnectionFactory负责它们的生命周期,对象清理。

PeerConnectionFactory的方法

PeerConnectionFactory类的方法列举如下:

  • CreatePeerConnection():创建PeerConnection;
  • GetRtpSenderCapabilities():获取发送方能力,包括编解码,RTP扩展头,FEC策略;
  • GetRtpReceiverCapabilities():获取接收方能力,与上方相同;
  • CreateLocalMediaStream():创建本地媒体流MediaStream;
  • CreateAudioSource():创建音频源;
  • CreateVideoTrack():创建视频轨VideoTrack;
  • CreateAudioTrack():创建音频轨AudioTrack;
  • StartAecDump():开始dump回声消除的数据到本地文件;
  • StopAecDump():停止dump
  • CreateSctpTransportInternalFactory():创建SCTP协议的工厂类

ICE服务器信息列表 IceServers

IceServers servers是一个IceServer的列表,每一个列表项IceServer用于存储一个STUN or TURN服务器信息,Peer可以向STUN或者TURN服务器查询候选ip地址。

  struct RTC_EXPORT IceServer {
    IceServer();
    IceServer(const IceServer&);
    ~IceServer();

    // TODO(jbauch): Remove uri when all code using it has switched to urls.
    // List of URIs associated with this server. Valid formats are described
    // in RFC7064 and RFC7065, and more may be added in the future. The "host"
    // part of the URI may contain either an IP address or a hostname.
    std::string uri;
    std::vector urls;
    std::string username;
    std::string password;
    TlsCertPolicy tls_cert_policy = kTlsCertPolicySecure;
    // If the URIs in |urls| only contain IP addresses, this field can be used
    // to indicate the hostname, which may be necessary for TLS (using the SNI
    // extension). If |urls| itself contains the hostname, this isn't
    // necessary.
    std::string hostname;
    // List of protocols to be used in the TLS ALPN extension.
    std::vector tls_alpn_protocols;
    // List of elliptic curves to be used in the TLS elliptic curves extension.
    std::vector tls_elliptic_curves;

    bool operator==(const IceServer& o) const {
      return uri == o.uri && urls == o.urls && username == o.username &&
             password == o.password && tls_cert_policy == o.tls_cert_policy &&
             hostname == o.hostname &&
             tls_alpn_protocols == o.tls_alpn_protocols &&
             tls_elliptic_curves == o.tls_elliptic_curves;
    }
    bool operator!=(const IceServer& o) const { return !(*this == o); }
  };
  typedef std::vector IceServers;
  • uri: server的地址,可以存储多个与服务器相关的地址,其格式定义在RFC7064和RFC7065中,单个地址如文章开头的示例所示:“stun:stun.l.google.com:19302”;
  • urls:server的地址列表,用于替代uri;
  • username, password:用于服务器进行用户验证;
  • hostname:当uri或者是urls为直接ip地址时,该字段用来存储hostname;
  • tls_alpn_protocols: TLS的扩展,用于支持应用层协商;
  • tls_elliptic_curves: TLS扩展,椭圆曲线加密算法;
  • tls_cert_policy :TLS证书策略。
     

PeerConnection中的各个接口的具体实现展开就太多了,感兴趣的可以参考我的WebRTC学习进阶之路 --- 十二、下载WebRTC源码及各操作系统的WebRTC源码编译详细步骤:https://blog.csdn.net/xiaomucgwlmx/article/details/103266213,下载编译源码自己探究下,有问题可以留言讨论。

 

WebRTC学习进阶之路系列总目录:https://blog.csdn.net/xiaomucgwlmx/article/details/103204274

你可能感兴趣的:(WebRTC学习进阶之路系列)