原文出处:WebRTC源码分析-呼叫建立过程之一(综述)

前言

基于WebRTC源码下example/peerconnect_clientexample/peerconnect_server工程打算写一个典型的呼叫建立过程的源码分析系列文章,本文是一个序章。example/peerconnect_clientexample/peerconnect_server实现了一个Demo性质的P2P音视频会话程序,其中有3个主要的类:MainWnd类进行界面显示,视频渲染;PeerConnectionClient类负责与信令服务器peerconnect_server`进行Http信令交互;Conductor类是核心业务类,持有MainWnd与PeerConnectionClient对象,整个WebRTC的使用都浓缩在这个类中。

在这里插入图片描述

对这两个工程的叙述就说到此,整体的架构实现不再多讲。刚入坑WebRTC的同学可以以此为学习的入口点,查看源码或者参考以下几篇博客来分析。

本文省略工程中信令交互部分(信令交互只在UML时序图中体现),直接从PeerConnectionFactory对象的创建开描述建立一个典型p2p WebRTC呼叫建立过程。

呼叫建立过程时序

如下所示为P2P建立过程详细的UML时序图,为了便于识别以不同的颜色来表示不同的对象,对象之间的消息传递也以不同颜色区分。

WebRTC P2P建立过程

在WebRTC的源码api/peer_connection_interface.h中,有很长一段注释,分别从呼叫方和被呼方的视角说明了整个流程。此处直接粘贴过来,不做翻译了,因为很容易理解。


原文出处:WebRTC源码分析——呼叫建立过程之二(创建PeerConnectionFactory)

目录

1. 引言

当WebRTC的端与信令服务器建立连接之后,可以通过与服务器的信令交互获知对等端点的存在,进一步通过信令向对端发起呼叫。在发起呼叫之前,发起方需要在本地做一些初始化工作,创建两个重要的对象:PeerConnectionFactory和PeerConnection,这两个C++对象提供了建立WebRTC会话的API(注意:在JS层,没有PeerConnectionFactory,只有PeerConnection,是基于C++ API层的全局方法以及PeerConnectionFactory和PeerConnection对象的进一步封装)。

本文将重点介绍PeerConnectionFactory对象详细的创建过程及其提供的能力,典型的WebRTC p2p会话建立过程如下图所示,其中红色标注了PeerConnectionFactory创建的位置。

在这里插入图片描述

2.PeerConnectionFactory对象的创建

example/peerconnection_client工程中,发起方调用如下代码来创建PeerConnectionFactory对象。

peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
      nullptr /* network_thread */, nullptr /* worker_thread */,
      nullptr /* signaling_thread */, nullptr /* default_adm */,
      webrtc::CreateBuiltinAudioEncoderFactory(),
      webrtc::CreateBuiltinAudioDecoderFactory(),
      webrtc::CreateBuiltinVideoEncoderFactory(),
      webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,
      nullptr /* audio_processing */);

2.1 CreatePeerConnectionFactory方法解析

关于该方法,有以下几点需要注意:

首先, 该方法位于源码的API层,是一个全局方法。头文件为api/create_peerconnection_factory.h;源文件为api/create_peerconnection_factory.cc。对于应用层而言,对WebRTC可调用的方法,要么是API层的全局方法,要么是PeerConnectionFactory和PeerConnection这两个对象的公有方法,比如JS层API就是基于WebRTC的C++ API层来进行封装的。当然,也不能排除第三方的RTC SDK使用别的方式封装,而没有基于API层的PeerConnection

其次, 该方法有10个入参,其中3个是WebRTC最重要的线程(WebRTC是个多线程架构,而非多进程架构),7个是音视频多媒体引擎相关对象。

最后, 该方法的返回是PeerConnectionFactoryInterface对象,我们会发现WebRTC的API层暴露的都是某某Interface,指向一个具体实现类,具体实现类是WebRTC内部其他层的internal class,不对外暴露,这符合C++封装的一般思想,对外只暴露必要的接口,隐藏内部实现。我们可以大胆猜测,该方法返回的是PeerConnectionFactory这个类实例,该实体类位于pc/peer_connection_factory.hpc/peer_connection_factory.cc中。果然如此嘛?先卖个关子,继续往后看

源码如下:

rtc::scoped_refptr<PeerConnectionFactoryInterface> CreatePeerConnectionFactory(
    rtc::Thread* network_thread,
    rtc::Thread* worker_thread,
    rtc::Thread* signaling_thread,
    rtc::scoped_refptr<AudioDeviceModule> default_adm,
    rtc::scoped_refptr<AudioEncoderFactory> audio_encoder_factory,
    rtc::scoped_refptr<AudioDecoderFactory> audio_decoder_factory,
    std::unique_ptr<VideoEncoderFactory> video_encoder_factory,
    std::unique_ptr<VideoDecoderFactory> video_decoder_factory,
    rtc::scoped_refptr<AudioMixer> audio_mixer,
    rtc::scoped_refptr<AudioProcessing> audio_processing) {

  PeerConnectionFactoryDependencies dependencies;
  dependencies.network_thread = network_thread;
  dependencies.worker_thread = worker_thread;
  dependencies.signaling_thread = signaling_thread;
  dependencies.task_queue_factory = CreateDefaultTaskQueueFactory();
  dependencies.call_factory = CreateCallFactory();
  dependencies.event_log_factory = std::make_unique<RtcEventLogFactory>(
      dependencies.task_queue_factory.get());

  cricket::MediaEngineDependencies media_dependencies;
  media_dependencies.task_queue_factory = dependencies.task_queue_factory.get();
  media_dependencies.adm = std::move(default_adm);
  media_dependencies.audio_encoder_factory = std::move(audio_encoder_factory);
  media_dependencies.audio_decoder_factory = std::move(audio_decoder_factory);
  if (audio_processing) {
    media_dependencies.audio_processing = std::move(audio_processing);
  } else {
    media_dependencies.audio_processing = AudioProcessingBuilder().Create();
  }
  media_dependencies.audio_mixer = std::move(audio_mixer);
  media_dependencies.video_encoder_factory = std::move(video_encoder_factory);
  media_dependencies.video_decoder_factory = std::move(video_decoder_factory);
  dependencies.media_engine =
      cricket::CreateMediaEngine(std::move(media_dependencies));

  return CreateModularPeerConnectionFactory(std::move(dependencies));
}

该方法中大致创建了两个Struct对象,调用了两个Create方法来最终创建PeerConnectionFactoryInterface实体对象。

为了调用CreateModularPeerConnectionFactory方法来实际创建PeerConnectionFactoryInterface对象,需要先构造其入参——结构体PeerConnectionFactoryDependencies对象。我们发现最初传入的三个线程对象传递给了PeerConnectionFactoryDependencies;另外,还为其提供了三个工厂类对象TaskQueueFactory、CallFactory、RtcEventLogFactory;最后,还需要MediaEngine对象。实际上,如果查看PeerConnectionFactoryDependencies结构体,发现其他依赖成员不止如此,不过,要么是为空亦可,要么在后续的过程中会继续创建一个内部默认的,而绝大多数是后者。

为了给PeerConnectionFactoryDependencies对象提供多媒体引擎对象MediaEngine,调用了CreateMediaEngine方法,当然需要先构造其入参——结构体MediaEngineDependencies。我们发现最初传入的7个音视频相关对象都是构建MediaEngine的依赖项,这为我们提供了一个思路或者途径:如果我们不想使用WebRTC内置的音视频编解码器,我们可以自行提供。比如,WebRTC内置音频处理不支持AAC,那么可以自己引入AAC的编解码器,构造自己的工厂类,通过该方法传入。另外,再传递这7个音视频相关对象时,使用了C++11的std::move语义,进行了所属权的转移(应用层–>WebRTC内部),使得应用层无法再次通过这几个对象的指针来访问他们,只能通过API层既定的接口来间接访问,从而提高安全性。另外,别忘了,MediaEngineDependencies还使用了与PeerConnectionFactoryDependencies相同的任务队列工厂TaskQueueFactory。

接下来,我们具体看看媒体引擎MediaEngine的方法CreateMediaEngine和具体创建PeerConnectionFactoryInterface的方法CreateModularPeerConnectionFactory是如何工作的。

2.1.1 创建媒体引擎MediaEngine——CreateMediaEngine方法

CreateMediaEngine方法比较简单,但是也有这么几点需要注意:

2.1.2 创建实体类PeerConnectionFactory——CreateModularPeerConnectionFactory方法

改方法大致做了三件事:

  1. 创建了一个PeerConnectionFactory实体对象;
  2. 在信令线程上同步调用PeerConnectionFactory的Initialize方法,使其在信令线程上进行初始化;
  3. 创建PeerConnectionFactoryProxy对象,并返回该对象。

看到这儿,令人疑惑的点就出来了,原来最终返给应用层使用的实体对象居然不是PeerConnectionFactory,而是PeerConnectionFactoryProxy,为什么?跟着源码继续分析。

rtc::scoped_refptr<PeerConnectionFactoryInterface>
CreateModularPeerConnectionFactory(
    PeerConnectionFactoryDependencies dependencies) {
  // 创建PeerConnectionFactory对象
  rtc::scoped_refptr<PeerConnectionFactory> pc_factory(
      new rtc::RefCountedObject<PeerConnectionFactory>(
          std::move(dependencies)));

  // 在信令线程上同步调用PeerConnectionFactory的Initialize方法
  // Call Initialize synchronously but make sure it is executed on
  // |signaling_thread|.
  MethodCall0<PeerConnectionFactory, bool> call(
      pc_factory.get(), &PeerConnectionFactory::Initialize);
  bool result = call.Marshal(RTC_FROM_HERE, pc_factory->signaling_thread());
  if (!result) {
    return nullptr;
  }

  // 创建PeerConnectionFactoryProxy代理对象,并返回该对象
  return PeerConnectionFactoryProxy::Create(pc_factory->signaling_thread(),
                                            pc_factory);
}

1)PeerConnectionFactory的创建——构造

首先,我们看到在创建PeerConnectionFactory对象时,使用了引用计数对象rtc::RefCountedObject,并且将创建的带引用计数的PeerConnectionFactory传递给了WebRTC中的智能指针对象rtc::scoped_refptr pc_factory。WebRTC中的rtc::scoped_refptr类似于C++11中的共享指针std::shared_ptr的作用。关于WebRTC中的引用计数以及共享指针分别在另外两篇文 章中分析:

其次,PeerConnectionFactory构造方法源码如下(省略了成员赋值)。一方面,在成员赋值时,仍然按照std:move的移动语义,将PeerConnectionFactoryDependencies成员所有权继续移动到PeerConnectionFactory中,由于代码过长,所以省略。另一方面,构造函数中主要处理WebRTC中的3个重要线程对象:network_thread_worker_thread_signaling_thread_。有以下几点需要注意:

PeerConnectionFactory::PeerConnectionFactory(PeerConnectionFactoryDependencies dependencies) {
  if (!network_thread_) {
    owned_network_thread_ = rtc::Thread::CreateWithSocketServer();
    owned_network_thread_->SetName("pc_network_thread", nullptr);
    owned_network_thread_->Start();
    network_thread_ = owned_network_thread_.get();
  }

  if (!worker_thread_) {
    owned_worker_thread_ = rtc::Thread::Create();
    owned_worker_thread_->SetName("pc_worker_thread", nullptr);
    owned_worker_thread_->Start();
    worker_thread_ = owned_worker_thread_.get();
  }

  if (!signaling_thread_) {
    signaling_thread_ = rtc::Thread::Current();
    if (!signaling_thread_) {
      // If this thread isn't already wrapped by an rtc::Thread, create a
      // wrapper and own it in this class.
      signaling_thread_ = rtc::ThreadManager::Instance()->WrapCurrentThread();
      wraps_current_thread_ = true;
    }
  }
}
2)PeerConnectionFactory初始化——Initialize方法

创建PeerConnectionFactory之后紧接着就是调用PeerConnectionFactory的初始化函数Initialize。不过这儿看起来稍微有点复杂,原因在于PeerConnectionFactory的Initialize必须在signaling_thread_线程上执行,并且此处还需要阻塞的获取执行结果,以便根据结果确定是否要执行后续的代码。

为了实现上述意图:即在signaling_thread_线程上同步调用PeerConnectionFactory的Initialize()方法,并获取方法的执行结果。此处利用了MethodCall0类的能力来实现该功能,具体是如何实现的,可以参考该文:WebRTCWebRTC源码分析-跨线程同步MethodCall

回归正题,如下是Initialize()方法的源码,此处不做深究,只要知道在PeerConnectionFactory的初始化时,根据当前时间提供了初始的随机数,创建了网络管理对象BasicNetworkManager,创建了默认的Socket工厂类对象BasicPacketSocketFactory,创建了通道管理类ChannelManager并初始化。至于这几个对象是提供什么功能,如何工作的, 待后续分析到相关功能时,我们再来分析。

bool PeerConnectionFactory::Initialize() {
  // 断言,本函数只能在signaling_thread_线程上执行
  RTC_DCHECK(signaling_thread_->IsCurrent());
  // 初始化随机数
  rtc::InitRandom(rtc::Time32());
  // 创建网络管理类BasicNetworkManager
  default_network_manager_.reset(new rtc::BasicNetworkManager());
  if (!default_network_manager_) {
    return false;
  }
  // 创建BasicPacketSocketFactory
  default_socket_factory_.reset(
      new rtc::BasicPacketSocketFactory(network_thread_));
  if (!default_socket_factory_) {
    return false;
  }
  // 创建通道管理类ChannelManager,并初始化
  channel_manager_ = absl::make_unique<cricket::ChannelManager>(
      std::move(media_engine_), absl::make_unique<cricket::RtpDataEngine>(),
      worker_thread_, network_thread_);
  channel_manager_->SetVideoRtxEnabled(true);
  if (!channel_manager_->Init()) {
    return false;
  }
  return true;
}
3)PeerConnectionFactoryProxy代理的创建与返回——防止线程乱入

也许,PeerConnectionFactoryProxy代理创建及其作用才是本文最需要重点阐述的地方。随着代码分析我们发现最初的猜想,即:CreatePeerConnectionFactory()方法返回给应用层的实例对象并非是PeerConnectionFactory对象。而是调用PeerConnectionFactoryProxy::Create(pc_factory->signaling_thread(),pc_factory)创建了一个PeerConnectionFactoryProxy对象,并返回了该对象。

为什么如此设计?如果不这么做会有什么问题?

线程乱入问题:

由于WebRTC中存在着信令线程、工作者线程、网络线程,为了让他们各司其职(从性能和安全性上考虑),WebRTC内部实现各个模块时,会明确某些模块的某些部分必须在某个线程中执行。比如, PeerConnectionFactory的方法必须在signaling_thread_线程上执行。为了确保这点,PeerConnectionFactory对象的几乎所有公有方法内部第一句就是类似于RTC_DCHECK(signaling_thread_->IsCurrent()); 这样的一个断言。 同样,有这样的需求的对象有很多,不止PeerConnectionFactory一个。并且,对象的某些方法要求在signaling线程上执行,其他方法可能是要求在worker线程上执行,这种现象也不少见。为了确保这点,方法内部也是类似的断言。

断言确保了代码在正确的线程上执行这点,否则触发断言,程序中断,这说明程序写得有BUG,必须调试消除BUG。断言本身的做法是没问题的,但是,如果我们直接如此使用的话会带来很多的不便。

考虑当前的情形:如果WebRTC对外返回了PeerConnectionFactory对象,势必使用方得很清楚PeerConnectionFactory的哪些方法得在哪个线程上执行。一方面,要么使用方记得所有方法该如何正确调用,要么有非常详细的接口文档,小心翼翼的按照文档来做;另一方面,即便你记忆高超,记得所有方法的使用方式,但也会遇到使用上的不便,因为在某个别的线程上需要调用PeerConnectionFactory的某个方法时,方法调用处不一定能够得着对应线程(持有对应线程的引用)。

线程乱入解决:

WebRTC中使用代理完美的解决了上述线程安全问题(有的地方被成为线程乱入问题)。

正如CreatePeerConnectionFactory()方法所做这样,对应用层返回的不是PeerConnectionFactory对象,而是对应的代理对象PeerConnectionFactoryProxy。对于应用层,我们想调用PeerConnectionFactory的某个方法时,实际上都是调用代理类PeerConnectionFactoryProxy的对应方法(那是当然,因为应用层是拿不到PeerConnectionFactory对象的,拿到的是PeerConnectionFactoryProxy对象,真正的PeerConnectionFactory对象被封装在此代理对象之中,使用者只是不自知而已),然后这个方法内部代理地调用PeerConnectionFactory对应方法,并且是在指定的signaling_thread中执行。通过这层代理,可以让用户侧可以非常容易地、安全地使用接口,达到预期目标。

那么,神奇的PeerConnectionFactoryProxy是如何做到这点的?详细分析见WebRTCWebRTC源码分析-线程安全之Proxy,防止线程乱入

3 PeerConnectionFactory对象简析

PeerConnectionFactory对象创建过程已经分析完毕,虽然出现了Proxy层,最终的实际功能仍然由PeerConnectionFactory对象提供。那么,PeerConnectionFactory对象到底提供了哪些能力呢?其实,从该类的名称上就很容易了解到:作为PeerConnection的工厂类存在,必然可以用来创建PeerConnection对象。也不止如此,它还能做其他事。先看看该对象持有哪些成员吧,毕竟,它提供的能力需要得到其成员的支持。

3.1 PeerConnectionFactory拥有的资源——私有成员

我们用一张图来展示PeerConnectionFactory所持有的成员:

在这里插入图片描述

3.1 PeerConnectionFactory提供的能力——公有方法

PeerConnectionFactory提供的能力并不太多,重要的有以下几点:

其他的不太常用:

4 总结

至此,PeerConnectionFactory对象的创建过程已经阐述完毕,PeerConnectionFactory对象提供的能力也做了基本的介绍。简单回顾下整个源码分析过程,有这么几点提炼出来以作最后的总结:


原文出处:WebRTC源码分析——呼叫建立过程之三(创建PeerConnection)

目录

1 引言

当WebRTC的端与信令服务器建立连接之后,可以通过与服务器的信令交互获知对等端点的存在,进一步通过信令向对端发起呼叫。在发起呼叫之前,发起方需要在本地做一些初始化工作,创建两个重要的对象:PeerConnectionFactory和PeerConnection,这两个C++对象提供了建立WebRTC会话的API(注意:在JS层,没有PeerConnectionFactory,只有PeerConnection,是基于C++API层的全局方法以及PeerConnectionFactory和PeerConnection对象的进一步封装)。

WebRTC源码分析-呼叫建立过程之二(创建PeerConnectionFactory) 文中已经对PeerConnectionFactory的创建及其功能进行了详尽的分析。 本文将对PeerConnection的创建及其功能进行分析,创建的时机如图中红色字体所示。

在这里插入图片描述

2 PeerConnection对象的创建

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

webrtc::PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
config.enable_dtls_srtp = dtls;
webrtc::PeerConnectionInterface::IceServer server;
server.uri = "stun:stun.l.google.com:19302";
config.servers.push_back(server);

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

2.1 CreatePeerConnection方法参数解析

WebRTC中PeerConnectionFactory提供了两个重载的CreatePeerConnection方法,其声明如下:

rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
    const PeerConnectionInterface::RTCConfiguration& configuration,
    std::unique_ptr<cricket::PortAllocator> allocator,
    std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator,
    PeerConnectionObserver* observer) override;

rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
    const PeerConnectionInterface::RTCConfiguration& configuration,
    PeerConnectionDependencies dependencies) override;

实际上,第一个方法的后三个参数用于填充PeerConnectionDependencies结构体,然后作为了第二个方法的入参。因此,可以将CreatePeerConnection的参数分为两类:

2.1.1 PeerConnectionDependencies依赖

PeerConnectionDependencies声明的源码如下:

struct PeerConnectionDependencies final {
  explicit PeerConnectionDependencies(PeerConnectionObserver* observer_in);
  // This object is not copyable or assignable.
  PeerConnectionDependencies(const PeerConnectionDependencies&) = delete;
  PeerConnectionDependencies& operator=(const PeerConnectionDependencies&) =
      delete;
  // This object is only moveable.
  PeerConnectionDependencies(PeerConnectionDependencies&&);
  PeerConnectionDependencies& operator=(PeerConnectionDependencies&&) = default;
  ~PeerConnectionDependencies();
  // Mandatory dependencies
  PeerConnectionObserver* observer = nullptr;
  // Optional dependencies
  std::unique_ptr<cricket::PortAllocator> allocator;
  std::unique_ptr<webrtc::AsyncResolverFactory> async_resolver_factory;
  std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator;
  std::unique_ptr<rtc::SSLCertificateVerifier> tls_cert_verifier;
};

有如下几点需要注意:

2.1.1.1 强制性依赖PeerConnectionObserver

PeerConnectionObserver是PeerConnection的回调接口,应用层可以必须提供回调接口的实现,以便响应PeerConnection的事件。这些接口大致分为如下几类:

几个状态相关回调:

远端流或者轨道的添加或者移出:

ICE过程相关:

DataChannel相关:

2.1.2 RTCConfiguration配置参数

RTCConfiguration声明的源码如下,由于源码太长,此处删减了RTCConfiguration的构造函数,删减了RTCConfiguration的getter和setter方法。参数罗列如下,分四类:

更多的关于RTCConfiguration 将于以后学习到各个参数所起作用再详述,本章只介绍与W3C标准一致的选项信息。

2.1.2.1 ICE服务器信息列表 IceServers

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

struct IceServer {
  std::string uri;
  std::vector<std::string> urls;
  std::string username;
  std::string password;
  std::string hostname;
  TlsCertPolicy tls_cert_policy = kTlsCertPolicySecure;
  std::vector<std::string> tls_alpn_protocols;
  std::vector<std::string> tls_elliptic_curves;
};
// TLS certificate policy.
enum TlsCertPolicy {
  // 对于基于TLS的协议,确保不会绕过证书验证
  kTlsCertPolicySecure,
  // 对于基于TLS协议,跳过证书验证忽略安全性检查
  kTlsCertPolicyInsecureNoCheck,
};
2.1.2.2 IceTransportsType type

这是一个枚举,决定了ICE过程中需要收集哪些候选地址,并且只使用这些地址进行联通性检测。候选地址分为如下几类:主机地址、反射地址、replay中继地址。默认收集所有地址,即kAll,这种方式可以显著的减小使用TurnServer的资源,因为会优先使用host和反射地址。可以通过修改该参数来改变默认行为。

enum IceTransportsType {
  kNone,    // 不收集ICE候选地址
  kRelay,   // 只收集relay地址
  kNoHost,  // 不收集主机地址
  kAll      // 收集所有地址
};
2.1.2.3 BundlePolicy bundle_policy

这是一个枚举,决定了音频轨,视频轨等是否绑定传输,以及如何绑定传输的。参数的详细分析以及如何影响SDP参数的,见https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24#section-4.1.1。默认多路音频和多路视频按照媒体类型分别绑定到各自的传输通道,即kBundlePolicyBalanced。

enum BundlePolicy {
  kBundlePolicyBalanced,   // 多路音频,多路视频按照媒体类型分别绑定传输
  kBundlePolicyMaxBundle,  // 多路音频,多路视频都绑定到一个传输通道
  kBundlePolicyMaxCompat   // 每路音频,每路视频都分开传输。
};
2.1.2.4 RtcpMuxPolicy rtcp_mux_policy

这是一个枚举,决定了RTP和RTCP是否复用同一个传输通道。当不复用时,比如RTP采用传输端口8000,那么对应的RTCP端口一般采用8001。参数的详细分 析以及如何影响SDP参数的,见 https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24#section-4.1.1

enum RtcpMuxPolicy {
  kRtcpMuxPolicyNegotiate,  // 双方协商决定
  kRtcpMuxPolicyRequire,    // 必须复用同一个通道
};
2.1.2.5 证书RTCCertificate

这是一个证书列表std::vector>certificates,对于每个传输通道都需要一个证书,用于建立连接时进行安全性验证。

2.1.2.6 候选项池大小ice_candidate_pool_size

一个典型的WebRTC应用一般在调用setLocalDescription方法后才开始进行ICE过程,开启候选者收集,因为setLocalDescription得到得本地SDP信息中指示了需要的ICE组件个数(跟需要建立的传输通道数相同,而传输通道数与媒体轨道个数、媒体轨道的绑定策略BundlePolicy、RTP/RTCP的通道复用策略RtcpMuxPolicy都有关系)以及哪些候选项(与IceTransportsType参数有关)需要被收集。然而,为了加速媒体通道建立,一些应用如果提前知道需要的ICE组件个数,那么它可以提前(在调用setLocalDescription之前就开始)收集一个池子的ICE候选项来帮助快速的建立媒体通道。

当setLocalDescription被调用时,开始收集需要的ICE候选者,首先要检查候选项池中是否已经有可用的候选项了。如果候选池中已经有可用的,应该立马通过ICE候选项事件来回调告知应用层已经收集到可用的候选项。如果候选池将耗尽,或者是需要使用的ICE组件个数比预期的要大,或者是候选池没有足够的时间去收集完候选项,那么剩余的候选项将在调用setLocalDescription时照常收集。这个只会出现在首次的offer/answer交换过程中,这个过程结束后候选池将被清空不再被使用。

举个列子:比如一个应用在不久将来的某个时间点有来电呼叫,并希望尽可能减少建立连接所需要的时间。通过预收集候选项到候选池,使得可以在接收到来电呼叫时,可以立马交换候选项并进行联通性检测。注意持有这些预收集的候选项,并保持这些候选项的有效性,应用将占用STUN/TURN资源。

该配置选项影响到上述候选池的大小。默认候选池大小为0,也即不进行预收集候选项。

2.1.2.7 SDP语法 SdpSemantics

该配置不属于标准配置,但是非常重要,SdpSemantics sdp_semantics,该参数影响到了整个SDP数据的格式。有两种类型的语法:kPlanB 和 kUnifiedPlan

// For users who wish to send multiple audio/video streams and need to stay
// interoperable with legacy WebRTC implementations or use legacy APIs,
// specify kPlanB.
//
// For all other users, specify kUnifiedPlan.
enum class SdpSemantics { kPlanB, kUnifiedPlan };

2.2 CreatePeerConnection方法的实现

PeerConnectionFactory.CreatePeerConnection位于pc/peer_connection_factory.cc中。源码如下所示:

rtc::scoped_refptr<PeerConnectionInterface>
PeerConnectionFactory::CreatePeerConnection(
    const PeerConnectionInterface::RTCConfiguration& configuration,
    PeerConnectionDependencies dependencies) {
  // 1 断言:改方法必须在信令线程上执行;
  //        外部传入的PortAllocator与PacketSocketFactory只能有其一
  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";

  // 2 可选依赖为空,此处来创建
  // 2.1 创建证书生成器RTCCertificateGenerator
  if (!dependencies.cert_generator) {
    dependencies.cert_generator =
        std::make_unique<rtc::RTCCertificateGenerator>(signaling_thread_,
                                                       network_thread_);
  }
  // 2.2 创建端口分配器PortAllocator,PortAllocator需要在网络线程中创建,
  //     PacketSocketFactory是构造PortAllocator的参数
  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<void>(RTC_FROM_HERE, [this, &configuration,
                                                  &dependencies,
                                                  &packet_socket_factory]() {
      dependencies.allocator = std::make_unique<cricket::BasicPortAllocator>(
          default_network_manager_.get(), packet_socket_factory,
          configuration.turn_customizer);
    });
  }
  // 2.3 创建ICE传输工厂
  if (!dependencies.ice_transport_factory) {
    dependencies.ice_transport_factory =
        std::make_unique<DefaultIceTransportFactory>();
  }

 // 3. 做一些初始化工作
 // 3.1 在网络线程上执行PortAllocator.SetNetworkIgnoreMask方法,使得端口分配器在进行操作时忽略特定类型的网络。
 //     默认情况network_ignore_mask为ADAPTER_TYPE_LOOPBACK,也即默认情况下忽略回环地址
 //     网络类型有如下几类:
 //         ADAPTER_TYPE_ETHERNET:以太网
 //         ADAPTER_TYPE_WIFI:无线WIFI网络
 //         ADAPTER_TYPE_CELLULAR:蜂窝网络(2g,3g,4g,5g)
 //         ADAPTER_TYPE_VPN:VPN
 //         ADAPTER_TYPE_LOOPBACK:回环地址
  network_thread_->Invoke<void>(
      RTC_FROM_HERE,
      rtc::Bind(&cricket::PortAllocator::SetNetworkIgnoreMask,
                dependencies.allocator.get(), options_.network_ignore_mask));
  // 3.2 在工作线程上调用PeerConnectionFactory.CreateRtcEventLog_w来创建RtcEventLog对象。
  //     该对象能提供什么功能暂且不提,但注意跟RTC中的一般日志是区别的,RTC_LOG宏与RtcEventLog
  //     是两个不相干的系统。
  std::unique_ptr<RtcEventLog> event_log =
      worker_thread_->Invoke<std::unique_ptr<RtcEventLog>>(
          RTC_FROM_HERE,
          rtc::Bind(&PeerConnectionFactory::CreateRtcEventLog_w, this));
  // 3.3 在工作线程上调用PeerConnectionFactory.CreateCall_w创建Call对象。
  //     Call对象提供了创建AudioReceiveStream/AudioSendStream
  //     /VideoSendStream/VideoReceiveStream的功能
  std::unique_ptr<Call> call = worker_thread_->Invoke<std::unique_ptr<Call>>(
      RTC_FROM_HERE,
      rtc::Bind(&PeerConnectionFactory::CreateCall_w, this, event_log.get()));

  // 4. 创建并初始化PeerConnection对象
  // 4.1 构造PeerConnection对象:传入之前创建的RtcEventLog和Call对象
  rtc::scoped_refptr<PeerConnection> pc(
      new rtc::RefCountedObject<PeerConnection>(this, std::move(event_log),
                                                std::move(call)));
  // 4.2 测试用:在构造PeerConnection对象与初始化PeerConnection对象之间塞入测试代码
  //     该方法为虚方法,{}中无具体代码,因此,什么也不干。测试时,可以继承并实现该方法
  //     以达测试的目的。
  ActionsBeforeInitializeForTesting(pc);
  // 4.3 初始化PeerConnection对象:外部传入的全局配置参数和依赖参数用来初始化PeerConnection
  if (!pc->Initialize(configuration, std::move(dependencies))) {
    return nullptr;
  }

  // 5. 创建并返回PeerConnectionProxy对象。
  return PeerConnectionProxy::Create(signaling_thread(), pc);
}

CreatePeerConnection创建PeerConnection过程大致分为5步,如源码以及注释所示,此处不再赘述。仍有如下几点需要注意。

2.2.1 创建RtcEventLog对象

RtcEventLog对象的创建直接依赖于工厂对象RtcEventLogFactory。创建过程如下图所示

在这里插入图片描述

需要留意的有如下几点:

RtcEventLog作为WebRTC中的重要模块,将单独列一篇文章来分析 WebRTC源码分析——RtcEventLog事件日志

2.2.2 创建Call对象

Call对象的创建直接依赖于工厂对象CallFactory。创建过程如下图所示:

在这里插入图片描述

需要留意的有如下几点:

Call模块是WebRTC会话中的特别重要的模块,将单列一篇文章来分析WebRTC源码分析——Call模块

3 PeerConnection简介

PeerConnection对象是WebRTC对应用层暴露的重要的API对象,其持有了大量的低层次内部对象,并提供了相当多的功能。一篇文章是不可能尽述的,因此,本文只做粗浅的分析,并着重分析CreatePeerConnection方法中调用的PeerConnection构造函数以及PeerConnection初始化函数Initialize。

PeerConnection实体类的位于pc/peer_connection.hpc/peer_connection.cc中,其声明如下:

// PeerConnection is the implementation of the PeerConnection object as defined
// by the PeerConnectionInterface API surface.
// The class currently is solely responsible for the following:
// - Managing the session state machine (signaling state).
// - Creating and initializing lower-level objects, like PortAllocator and
//   BaseChannels.
// - Owning and managing the life cycle of the RtpSender/RtpReceiver and track
//   objects.
// - Tracking the current and pending local/remote session descriptions.
// The class currently is jointly responsible for the following:
// - Parsing and interpreting SDP.
// - Generating offers and answers based on the current state.
// - The ICE state machine.
// - Generating stats.
class PeerConnection : public PeerConnectionInternal,
                        public JsepTransportController::Observer,
                        public RtpSenderBase::SetStreamsObserver,
                        public rtc::MessageHandler,
                        public sigslot::has_slots<> {
                        ...
}

PeerConnection继承了PeerConnectionInternal、JsepTransportController::Observer、RtpSenderBase::SetStreamsObserver、rtc::MessageHandler、sigslot::has_slots<>,从继承中获取了不少的功能,同时其本身自带了很多功能。尤其注意sigslot::has_slots<>所带来的信号-槽机制,可以让PeerConnection对象接收低层次对象所发射的信号,以获知某个事态的发生、状态的改变,并在自己的方法中对信号进行响应处理。比如后续将要提到的JsepTransportController对象会发射一些列的连接状态,ICE状态相关的信号SignalXxx,PeerConnection将以OnXxx的方法予以绑定信号,响应并处理。

这些功能大致可以从两方面简要说明(正如PeerConnection英文注释告知的那样):

一方面PeerConnection单独提供如下功能:

一方面PeerConnection与其他对象一起提供如下功能:

3.1 PeerConnection构造

源码如下:

PeerConnection::PeerConnection(PeerConnectionFactory* factory,
                                std::unique_ptr<RtcEventLog> event_log,
                                std::unique_ptr<Call> call)
    : factory_(factory),
      event_log_(std::move(event_log)),
      event_log_ptr_(event_log_.get()),
      operations_chain_(rtc::OperationsChain::Create()),
      datagram_transport_config_(
          field_trial::FindFullName(kDatagramTransportFieldTrial)),
      datagram_transport_data_channel_config_(
          field_trial::FindFullName(kDatagramTransportDataChannelFieldTrial)),
      rtcp_cname_(GenerateRtcpCname()),
      local_streams_(StreamCollection::Create()),
      remote_streams_(StreamCollection::Create()),
      call_(std::move(call)),
      call_ptr_(call_.get()),
      local_ice_credentials_to_replace_(new LocalIceCredentialsToReplace()),
      data_channel_controller_(this),
      weak_ptr_factory_(this) {}

PeerConnection的构造无非就是给成员赋值,虽然简单,但也给我们提供了一些值得注意的信息,其中想要重点说明的是rtcp_cname_这个成员,接下来的一小节对RTCP CNAME进行比较详细的介绍。

3.1.1 CNAME

一个PeerConnection仅有一个RTCP CNAME成员rtcpcname,该成员是一个长达16字符的随机字符串,随着PeerConnection的创建,由GenerateRtcpCname()方法创建这个字符串。ietf的RTC规范中详细的描述了RTCPCNAME的作用:https://tools.ietf.org/html/draft-ietf-rtcweb-rtp-usage-26#section-4.9

以下是对规范中关于RTCP CNAME介绍的一个翻译,有可能解释的不够清楚,请查看上述英文原文:

RTCP规范名称(CNAME) 为一个RTP端点提供了持久的传输层次的唯一标识。RTP端点的SSRC标识可能会因为检测到和其他RTP端点的SSRC冲突或者当RTP应用重启时发生更改,但是CNAME是不会更改的,只要RTCPeerConnection对象没有更改。因此,RTP端点可以很容易的在相关RTP会话集合中识别出与其相关流的RTP包

一个RTP端点必须至少有一个CNAME,并且在RTCPeerConnection中是唯一的。CNAME可以标识一个特别的同步上下文,所有与这个CNAME关联的SSRCs共享同一个参考时钟。如果一个端点有多个SSRCs,它们关联多个不同步的参考时钟,因此,是不同的同步上下文。那么就需要不同的CNAMEs,每个同步上下文一个CNAME。

一个WebRTC端点在单个RTCPeerConnection中有且仅有一个CNAME用于属于RTCPeerConnection的多个RTP会话(因为一个RTCPeerConnection就是一个同步上下文)。 RTP middleboxes可能会产生关联不同CANME的多个流,这样就可以避免来自不同端点的多方RTP会话对这些流进行媒体时钟重新同步。

3.2 PeerConnection初始化

源码及分析如下:

bool PeerConnection::Initialize(
    const PeerConnectionInterface::RTCConfiguration& configuration,
    PeerConnectionDependencies dependencies) {
  // 1. 确保初始化是在信令线程中 
  RTC_DCHECK_RUN_ON(signaling_thread());
  TRACE_EVENT0("webrtc", "PeerConnection::Initialize");

  // 2. 检查全局参数配置的有效性
  RTCError config_error = ValidateConfiguration(configuration);
  if (!config_error.ok()) {
    RTC_LOG(LS_ERROR) << "Invalid configuration: " << config_error.message();
    return false;
  }

  // 3. 依赖参数——PortAllocator和PeerConnectionObserver不可为空
  if (!dependencies.allocator) {
    RTC_LOG(LS_ERROR)
        << "PeerConnection initialized without a PortAllocator? "
           "This shouldn't happen if using PeerConnectionFactory.";
    return false;
  }
  if (!dependencies.observer) {
    // TODO(deadbeef): Why do we do this?
    RTC_LOG(LS_ERROR) << "PeerConnection initialized without a "
                         "PeerConnectionObserver";
    return false;
  }

  // 4. 成员赋值
  observer_ = dependencies.observer;
  async_resolver_factory_ = std::move(dependencies.async_resolver_factory);
  port_allocator_ = std::move(dependencies.allocator);
  ice_transport_factory_ = std::move(dependencies.ice_transport_factory);
  tls_cert_verifier_ = std::move(dependencies.tls_cert_verifier);

  // 5. 处理STUN server和TURN server
  // 5.1 解析并获取stun_servers和turn_servers
  cricket::ServerAddresses stun_servers;
  std::vector<cricket::RelayServerConfig> turn_servers;
  RTCErrorType parse_error =
      ParseIceServers(configuration.servers, &stun_servers, &turn_servers);
  if (parse_error != RTCErrorType::NONE) {
    return false;
  }
  // 5.2 给所有的turn_server配置日志id
  // Add the turn logging id to all turn servers
  for (cricket::RelayServerConfig& turn_server : turn_servers) {
    turn_server.turn_logging_id = configuration.turn_logging_id;
  }
  // 5.3 给stun_servers和turn_servers进行端口分配器的初始化
  // The port allocator lives on the network thread and should be initialized
  // there.
  const auto pa_result =
      network_thread()->Invoke<InitializePortAllocatorResult>(
          RTC_FROM_HERE,
          rtc::Bind(&PeerConnection::InitializePortAllocator_n, this,
                    stun_servers, turn_servers, configuration));
  // 5.4 通知STUN_SERVER和TURN_SERVER被使用
  // If initialization was successful, note if STUN or TURN servers
  // were supplied.
  if (!stun_servers.empty()) {
    NoteUsageEvent(UsageEvent::STUN_SERVER_ADDED);
  }
  if (!turn_servers.empty()) {
    NoteUsageEvent(UsageEvent::TURN_SERVER_ADDED);
  }

  // 6. 发送IPV4/IPv6状态
  // Send information about IPv4/IPv6 status.
  PeerConnectionAddressFamilyCounter address_family;
  if (pa_result.enable_ipv6) {
    address_family = kPeerConnection_IPv6;
  } else {
    address_family = kPeerConnection_IPv4;
  }
  RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IPMetrics", address_family,
                            kPeerConnectionAddressFamilyCounter_Max);

  const PeerConnectionFactoryInterface::Options& options = factory_->options();

  // 7. 创建64位有符号整型会话id
  // RFC 3264: The numeric value of the session id and version in the
  // o line MUST be representable with a "64 bit signed integer".
  // Due to this constraint session id |session_id_| is max limited to
  // LLONG_MAX.
  session_id_ = rtc::ToString(rtc::CreateRandomId64() & LLONG_MAX);

  // 8. 1) 填充JSEP传输控制的参数
  //    2) 并创建JsepTransportController,
  //    3) 关联JsepTransportController的信号与PeerConnection的槽方法
  // 8.1.1 创建JSEP的参数结构体JsepTransportController::Config
  JsepTransportController::Config config;
  // 8.1.2 用应用层传入的全局配置参数来填充JSEP传输控制的参数
  config.redetermine_role_on_ice_restart =
      configuration.redetermine_role_on_ice_restart;
  config.ssl_max_version = factory_->options().ssl_max_version;
  config.disable_encryption = options.disable_encryption;
  config.bundle_policy = configuration.bundle_policy;
  config.rtcp_mux_policy = configuration.rtcp_mux_policy;
  // TODO(bugs.webrtc.org/9891) - Remove options.crypto_options then remove this
  // stub.
  config.crypto_options = configuration.crypto_options.has_value()
                              ? *configuration.crypto_options
                              : options.crypto_options;
  config.transport_observer = this;
  // It's safe to pass |this| and using |rtcp_invoker_| and the |call_| pointer
  // since the  JsepTransportController instance is owned by this PeerConnection
  // instance and is destroyed before both |rtcp_invoker_| and the |call_|
  // pointer.
  config.rtcp_handler = [this](const rtc::CopyOnWriteBuffer& packet,
                               int64_t packet_time_us) {
    RTC_DCHECK_RUN_ON(network_thread());
    rtcp_invoker_.AsyncInvoke<void>(
        RTC_FROM_HERE, worker_thread(), [this, packet, packet_time_us] {
          RTC_DCHECK_RUN_ON(worker_thread());
          // |call_| is reset on the worker thread in the PeerConnection
          // destructor, so we check that it's still valid before propagating
          // the packet.
          if (call_) {
            call_->Receiver()->DeliverPacket(MediaType::ANY, packet,
                                             packet_time_us);
          }
        });
  };
  config.event_log = event_log_ptr_;
#if defined(ENABLE_EXTERNAL_AUTH)
  config.enable_external_auth = true;
#endif
  config.active_reset_srtp_params = configuration.active_reset_srtp_params;
  // 8.1.3 外部应该提供MediaTransportFactory时,WebRTC内部应该使用使用DatagramTransport接口取代dtls。
  //       此处填充相关的4个参数。
  use_datagram_transport_ = datagram_transport_config_.enabled &&
                            configuration.use_datagram_transport.value_or(
                                datagram_transport_config_.default_value);
  use_datagram_transport_for_data_channels_ =
      datagram_transport_data_channel_config_.enabled &&
      configuration.use_datagram_transport_for_data_channels.value_or(
          datagram_transport_data_channel_config_.default_value);
  use_datagram_transport_for_data_channels_receive_only_ =
      configuration.use_datagram_transport_for_data_channels_receive_only
          .value_or(datagram_transport_data_channel_config_.receive_only);
  if (use_datagram_transport_ || use_datagram_transport_for_data_channels_) {
    if (!factory_->media_transport_factory()) {
      RTC_DCHECK(false)
          << "PeerConnecton is initialized with use_datagram_transport = true "
             "or use_datagram_transport_for_data_channels = true "
          << "but media transport factory is not set in PeerConnectionFactory";
      return false;
    }

    config.use_datagram_transport = use_datagram_transport_;
    config.use_datagram_transport_for_data_channels =
        use_datagram_transport_for_data_channels_;
    config.use_datagram_transport_for_data_channels_receive_only =
        use_datagram_transport_for_data_channels_receive_only_;
    config.media_transport_factory = factory_->media_transport_factory();
  }
  // 8.1.4 判断DataChannel传输方式,使用SCTP则填充sctp_factory
  //     首先,根据根据证书是否存在,证书生成器是否存在,是否允许加密来判断是不是dtls使能
  //     然后,确定datachannel传输数据的协议:
  //          DCT_DATA_CHANNEL_TRANSPORT:使用UDP传输,无dtls
  //          DCT_DATA_CHANNEL_TRANSPORT_SCTP:使用UDP传输,失败可以回退到使用SCTP
  //          DCT_RTP:使用RTP传输
  //          DCT_SCTP:使用SCTP传输
  //     最后,在DCT_DATA_CHANNEL_TRANSPORT_SCTP和DCT_SCTP情况下需要填充sctp_factory
  // Obtain a certificate from RTCConfiguration if any were provided (optional).
  rtc::scoped_refptr<rtc::RTCCertificate> certificate;
  if (!configuration.certificates.empty()) {
    // TODO(hbos,torbjorng): Decide on certificate-selection strategy instead of
    // just picking the first one. The decision should be made based on the DTLS
    // handshake. The DTLS negotiations need to know about all certificates.
    certificate = configuration.certificates[0];
  }
  if (options.disable_encryption) {
    dtls_enabled_ = false;
  } else {
    // Enable DTLS by default if we have an identity store or a certificate.
    dtls_enabled_ = (dependencies.cert_generator || certificate);
    // |configuration| can override the default |dtls_enabled_| value.
    if (configuration.enable_dtls_srtp) {
      dtls_enabled_ = *(configuration.enable_dtls_srtp);
    }
  }
  sctp_factory_ = factory_->CreateSctpTransportInternalFactory();
  if (use_datagram_transport_for_data_channels_) {
    if (configuration.enable_rtp_data_channel) {
      RTC_LOG(LS_ERROR) << "enable_rtp_data_channel and "
                           "use_datagram_transport_for_data_channels are "
                           "incompatible and cannot both be set to true";
      return false;
    }
    if (configuration.enable_dtls_srtp && !*configuration.enable_dtls_srtp) {
      RTC_LOG(LS_INFO) << "Using data channel transport with no fallback";
      data_channel_controller_.set_data_channel_type(
          cricket::DCT_DATA_CHANNEL_TRANSPORT);
    } else {
      RTC_LOG(LS_INFO) << "Using data channel transport with fallback to SCTP";
      data_channel_controller_.set_data_channel_type(
          cricket::DCT_DATA_CHANNEL_TRANSPORT_SCTP);
      config.sctp_factory = sctp_factory_.get();
    }
  } else if (configuration.enable_rtp_data_channel) {
    // Enable creation of RTP data channels if the kEnableRtpDataChannels is
    // set. It takes precendence over the disable_sctp_data_channels
    // PeerConnectionFactoryInterface::Options.
    data_channel_controller_.set_data_channel_type(cricket::DCT_RTP);
  } else {
    // DTLS has to be enabled to use SCTP.
    if (!options.disable_sctp_data_channels && dtls_enabled_) {
      data_channel_controller_.set_data_channel_type(cricket::DCT_SCTP);
      config.sctp_factory = sctp_factory_.get();
    }
  }
  // 8.1.5 填充ICE传输工厂
  config.ice_transport_factory = ice_transport_factory_.get();
  // 8.2 创建JsepTransportController
  transport_controller_.reset(new JsepTransportController(
      signaling_thread(), network_thread(), port_allocator_.get(),
      async_resolver_factory_.get(), config));
  // 8.3 绑定JsepTransportController的信号与PeerConnection相应的槽函数
  //     让PeerConnection能够响应JsepTransportController状态的改变
  transport_controller_->SignalIceConnectionState.connect(
      this, &PeerConnection::OnTransportControllerConnectionState);
  transport_controller_->SignalStandardizedIceConnectionState.connect(
      this, &PeerConnection::SetStandardizedIceConnectionState);
  transport_controller_->SignalConnectionState.connect(
      this, &PeerConnection::SetConnectionState);
  transport_controller_->SignalIceGatheringState.connect(
      this, &PeerConnection::OnTransportControllerGatheringState);
  transport_controller_->SignalIceCandidatesGathered.connect(
      this, &PeerConnection::OnTransportControllerCandidatesGathered);
  transport_controller_->SignalIceCandidateError.connect(
      this, &PeerConnection::OnTransportControllerCandidateError);
  transport_controller_->SignalIceCandidatesRemoved.connect(
      this, &PeerConnection::OnTransportControllerCandidatesRemoved);
  transport_controller_->SignalDtlsHandshakeError.connect(
      this, &PeerConnection::OnTransportControllerDtlsHandshakeError);
  transport_controller_->SignalIceCandidatePairChanged.connect(
      this, &PeerConnection::OnTransportControllerCandidateChanged);

  // 9. 初始化两个数据统计收集器
  stats_.reset(new StatsCollector(this));
  stats_collector_ = RTCStatsCollector::Create(this);

  // 10. 保存全局的配置参数
  configuration_ = configuration;

  // 11. 从全局配置参数中抽取ICE相关的参数,并设置到JsepTransportController中
  transport_controller_->SetIceConfig(ParseIceConfig(configuration));

  // 12. 设置音视频相关的网络传输相关参数
  video_options_.screencast_min_bitrate_kbps =
      configuration.screencast_min_bitrate;
  audio_options_.combined_audio_video_bwe =
      configuration.combined_audio_video_bwe;
  audio_options_.audio_jitter_buffer_max_packets =
      configuration.audio_jitter_buffer_max_packets;
  audio_options_.audio_jitter_buffer_fast_accelerate =
      configuration.audio_jitter_buffer_fast_accelerate;
  audio_options_.audio_jitter_buffer_min_delay_ms =
      configuration.audio_jitter_buffer_min_delay_ms;
  audio_options_.audio_jitter_buffer_enable_rtx_handling =
      configuration.audio_jitter_buffer_enable_rtx_handling;

  // 13. 1)创建WebRtcSessionDescriptionFactory对象
  //     2)绑定WebRtcSessionDescriptionFactory信号与PeerConnection的槽
  //     3)WebRtcSessionDescriptionFactory参数赋值
  // 13.1 创建WebRtcSessionDescriptionFactory对象
  // Whether the certificate generator/certificate is null or not determines
  // what PeerConnectionDescriptionFactory will do, so make sure that we give it
  // the right instructions by clearing the variables if needed.
  if (!dtls_enabled_) {
    dependencies.cert_generator.reset();
    certificate = nullptr;
  } else if (certificate) {
    // Favor generated certificate over the certificate generator.
    dependencies.cert_generator.reset();
  }
  webrtc_session_desc_factory_.reset(new WebRtcSessionDescriptionFactory(
      signaling_thread(), channel_manager(), this, session_id(),
      std::move(dependencies.cert_generator), certificate, &ssrc_generator_));
  // 13.2 绑定WebRtcSessionDescriptionFactory信号与PeerConnection的槽
  webrtc_session_desc_factory_->SignalCertificateReady.connect(
      this, &PeerConnection::OnCertificateReady);
  // 13.3 WebRtcSessionDescriptionFactory参数赋值
  if (options.disable_encryption) {
    webrtc_session_desc_factory_->SetSdesPolicy(cricket::SEC_DISABLED);
  }
  webrtc_session_desc_factory_->set_enable_encrypted_rtp_header_extensions(
      GetCryptoOptions().srtp.enable_encrypted_rtp_header_extensions);
  webrtc_session_desc_factory_->set_is_unified_plan(IsUnifiedPlan());

  // 14 Plan B SDP下,添加默认的音视频transceivers
  // Add default audio/video transceivers for Plan B SDP.
  if (!IsUnifiedPlan()) {
    transceivers_.push_back(
        RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
            signaling_thread(), new RtpTransceiver(cricket::MEDIA_TYPE_AUDIO)));
    transceivers_.push_back(
        RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
            signaling_thread(), new RtpTransceiver(cricket::MEDIA_TYPE_VIDEO)));
  }
  int delay_ms =
      return_histogram_very_quickly_ ? 0 : REPORT_USAGE_PATTERN_DELAY_MS;
  signaling_thread()->PostDelayed(RTC_FROM_HERE, delay_ms, this,
                                  MSG_REPORT_USAGE_PATTERN, nullptr);

  // 15 创建视频比特分配器工厂
  if (dependencies.video_bitrate_allocator_factory) {
    video_bitrate_allocator_factory_ =
        std::move(dependencies.video_bitrate_allocator_factory);
  } else {
    video_bitrate_allocator_factory_ =
        CreateBuiltinVideoBitrateAllocatorFactory();
  }
  return true;
}

关于PeerConnection初始化过程如源码注释,分了15个部分进行分割阐述。其中比较重要或者复杂的是

3.2.1 会话ID——Session ID

如上对PeerConnection.Initialize方法的分析过程得知:此处会创建 “64位有符号整型"的Session ID。使用的是rtc_base/helpers.cc中随机数生成函数CreateRandomId64()来产生。WebRTC中的随机值生成系统的分析可见另外的文章——WebRTC源码分析——随机值(数、字符串)生成模块

会话id将会出现在SDP的o line中,如下一个简单的Offser SDP:

v=0
o=alice 2890844526 2890844526 IN IP4 host.anywhere.com
s=
c=IN IP4 host.anywhere.com
t=0 0
m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000
m=video 51372 RTP/AVP 31
a=rtpmap:31 H261/90000
m=video 53000 RTP/AVP 32
a=rtpmap:32 MPV/90000

o line格式如下

o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>

各字段含义如下:

示例中的2890844526就是取得Session ID值。

4 总结

至此,PeerConnection对象的创建过程已经阐述完毕,也对PeerConnection对象提供的能力也做了基本的介绍。简单回顾下整个源码分析过程,有这么几点提炼出来以作最后的总结: