WebRTC源码分析——呼叫建立过程之二(创建PeerConnectionFactory)

发布于 2023-05-24 | 作者: ice_ly000 | 来源: CSDN博客 | 转载于: CSDN博客

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.h和pc/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方法比较简单,但是也有这么几点需要注意:

	// CreateMediaEngine may be called on any thread, though the engine is
// only expected to be used on one thread, internally called the "worker
// thread". This is the thread Init must be called on.
std::unique_ptr<MediaEngineInterface> CreateMediaEngine(
   MediaEngineDependencies dependencies) {
   auto audio_engine = std::make_unique<WebRtcVoiceEngine>(
   dependencies.task_queue_factory, std::move(dependencies.adm),
	   std::move(dependencies.audio_encoder_factory),
	   std::move(dependencies.audio_decoder_factory),
	   std::move(dependencies.audio_mixer),
	   std::move(dependencies.audio_processing));
   
   #ifdef HAVE_WEBRTC_VIDEO
       auto video_engine = std::make_unique<WebRtcVideoEngine>(std::move(dependencies.video_encoder_factory), std::move(dependencies.video_decoder_factory));
   #else
       auto video_engine = std::make_unique<NullWebRtcVideoEngine>();
   #endif
   
   return std::make_unique<CompositeMediaEngine>(std::move(audio_engine), std::move(video_engine));
}
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对象提供的能力也做了基本的介绍。简单回顾下整个源码分析过程,有这么几点提炼出来以作最后的总结: