一. 前言
当我们要创建 PeerConnnection 时我们需要先创建 PeerConnectionFactory,创建 PeerConnectionFactory 的函数声明如下,从返回值可以看到它是一个 Interface 接口类型,其指向是继承了该接口的实现类。如果深入源码可以发现,CreatePeerConnectionFactory 函数里并不是直接 new PeerConnectionFactory 的,而是通过 PeerConnectionFactoryProxy::Create 来创建出 PeerConnectionFactory 的代理对象,如果在代码全局搜索 PeerConnectionFactoryProxy 类并不能找到该类的信息,那么 PeerConnectionFactoryProxy 这个类是从哪里产生的呢,通过代理(Proxy)的方式创建 PeerConnectionFactory 的代理对象,如此设计的原因是什么?
WebRTC 是一个多线程的程序,它有三个基础线程(信令线程,工作线程,网络线程),例如 PeerConnection 就是工作在信令线程的,用户不能在工作线程中去调用 PeerConnectionFactory 的 CreateAudioSource 方法,而有些接口又必须工作在工作线程中,而有些用户在使用这些接口时可能根本不了解 WebRTC 的线程模型,为了使用户使用 WebRTC 接口更加方便,我们就需要增加一个代理层(Any problem in computer science can be solved by another layer of indirection),通过代理层对象来调用某个对象的方法,并且使得方法可以在适当的线程上得到运行。
本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部↓↓
二. 如何定义一个代理类
我们以 PeerConnectionFactory 为例说明如何定义一个代理类。
首先定义一个 PeerConnectionFactoryInterface 类,它继承自 rtc::RefCountInterface,然后在接口类中声明需要暴露的接口,例如 SetOptions,CreatePeerConnection,GetRtpSenderCapabilities 等接口都是需要对外暴露的接口。
class RTC_EXPORT PeerConnectionFactoryInterface
: public rtc::RefCountInterface {
public:
class Options {
// Options 定义省略, 详见 src/api/peer_connection_interface.h
};
virtual void SetOptions(const Options& options) = 0;
virtual rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
const PeerConnectionInterface::RTCConfiguration& configuration,
PeerConnectionDependencies dependencies);
virtual rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
const PeerConnectionInterface::RTCConfiguration& configuration,
std::unique_ptr<cricket::PortAllocator> allocator,
std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator,
PeerConnectionObserver* observer);
virtual RtpCapabilities GetRtpSenderCapabilities(
cricket::MediaType kind) const;
virtual RtpCapabilities GetRtpReceiverCapabilities(
cricket::MediaType kind) const;
virtual rtc::scoped_refptr<MediaStreamInterface> CreateLocalMediaStream(
const std::string& stream_id) = 0;
// 剩余接口省略, 详见 src/api/peer_connection_interface.h
};
其次定义 PeerConnectionFactory 的实现类,继承自 PeerConnectionFactoryInterface,并实现 PeerConnectionFactoryInterface 声明的接口,详细代码见 src/pc/peer_connection_factory.h,src/pc/peer_connection_factory.cc。
class PeerConnectionFactory : public PeerConnectionFactoryInterface {
public:
void SetOptions(const Options& options) override;
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;
RtpCapabilities GetRtpSenderCapabilities(
cricket::MediaType kind) const override;
RtpCapabilities GetRtpReceiverCapabilities(
cricket::MediaType kind) const override;
// 省略
};
接下来创建一个 peer_connection_factory_proxy.h 文件,它通过宏定义创建相应的 Proxy 类。
BEGIN_SIGNALING_PROXY_MAP(PeerConnectionFactory) 用来产生代理类,它会生成 PeerConnectionFactoryProxy 类。
PROXY_METHOD1 声明了需要暴露的接口,例如 PROXY_METHOD1(void, SetOptions, const Options&) 表示 SetOptions 的返回值类型为 void,并且该函数有一个输入参数,类型为 const Options&,而 PROXY_METHOD2(rtc::scoped_refptr<PeerConnectionInterface>, CreatePeerConnection, const PeerConnectionInterface::RTCConfiguration&, PeerConnectionDependencies) 表示 CreatePeerConnection 的返回值类型为 rtc::scoped_refptr<PeerConnectionInterface>,并且有两个输入参数,类型分别为 const PeerConnectionInterface::RTCConfiguration&,PeerConnectionDependencies,其他接口声明也同理,注意 PROXY_CONSTMETHOD1 表示该方法被 const 修饰,即不可改变类内容本身。
END_PROXY_MAP 标记代理类结束,该宏展开其实是一个 }; 。
// TODO(deadbeef): Move this to .cc file and out of api/. What threads methods
// are called on is an implementation detail.
BEGIN_SIGNALING_PROXY_MAP(PeerConnectionFactory)
PROXY_SIGNALING_THREAD_DESTRUCTOR()
PROXY_METHOD1(void, SetOptions, const Options&)
PROXY_METHOD4(rtc::scoped_refptr<PeerConnectionInterface>,
CreatePeerConnection,
const PeerConnectionInterface::RTCConfiguration&,
std::unique_ptr<cricket::PortAllocator>,
std::unique_ptr<rtc::RTCCertificateGeneratorInterface>,
PeerConnectionObserver*)
PROXY_METHOD2(rtc::scoped_refptr<PeerConnectionInterface>,
CreatePeerConnection,
const PeerConnectionInterface::RTCConfiguration&,
PeerConnectionDependencies)
PROXY_CONSTMETHOD1(webrtc::RtpCapabilities,
GetRtpSenderCapabilities,
cricket::MediaType)
PROXY_CONSTMETHOD1(webrtc::RtpCapabilities,
GetRtpReceiverCapabilities,
cricket::MediaType)
PROXY_METHOD1(rtc::scoped_refptr<MediaStreamInterface>,
CreateLocalMediaStream,
const std::string&)
PROXY_METHOD1(rtc::scoped_refptr<AudioSourceInterface>,
CreateAudioSource,
const cricket::AudioOptions&)
PROXY_METHOD2(rtc::scoped_refptr<VideoTrackInterface>,
CreateVideoTrack,
const std::string&,
VideoTrackSourceInterface*)
PROXY_METHOD2(rtc::scoped_refptr<AudioTrackInterface>,
CreateAudioTrack,
const std::string&,
AudioSourceInterface*)
PROXY_METHOD2(bool, StartAecDump, FILE*, int64_t)
PROXY_METHOD0(void, StopAecDump)
END_PROXY_MAP()
本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部↓↓
三. 代理类宏源码剖析
BEGIN_SIGNALING_PROXY_MAP
定义 BEGIN_SIGNALING_PROXY_MAP 实际上是调用了 PROXY_MAP_BOILERPLATE,SIGNALING_PROXY_MAP_BOILERPLATE,REFCOUNTED_PROXY_MAP_BOILERPLATE,并定义出 Proxy 类的 Create 方法。
#define BEGIN_SIGNALING_PROXY_MAP(c) \
PROXY_MAP_BOILERPLATE(c) \
SIGNALING_PROXY_MAP_BOILERPLATE(c) \
REFCOUNTED_PROXY_MAP_BOILERPLATE(c) \
public: \
static rtc::scoped_refptr<c##ProxyWithInternal> Create( \
rtc::Thread* signaling_thread, INTERNAL_CLASS* c) { \
return new rtc::RefCountedObject<c##ProxyWithInternal>(signaling_thread, \
c); \
}
PROXY_MAP_BOILERPLATE(c) 定义了 c 的 Proxy 类,例如 PeerConnectionFactoryProxy,其对应的 INTERNAL_CLASS 为 PeerConnectionFactoryInterface。
#define PROXY_MAP_BOILERPLATE(c) \
template <class INTERNAL_CLASS> \
class c##ProxyWithInternal; \
typedef c##ProxyWithInternal<c##Interface> c##Proxy; \
template <class INTERNAL_CLASS> \
class c##ProxyWithInternal : public c##Interface { \
protected: \
typedef c##Interface C; \
\
public: \
const INTERNAL_CLASS* internal() const { return c_; } \
INTERNAL_CLASS* internal() { return c_; }
SIGNALING_PROXY_MAP_BOILERPLATE 定义了 Proxy 类的构造函数,c_ 是接口类指针对象。
#define SIGNALING_PROXY_MAP_BOILERPLATE(c) \
protected: \
c##ProxyWithInternal(rtc::Thread* signaling_thread, INTERNAL_CLASS* c) \
: signaling_thread_(signaling_thread), c_(c) {} \
\
private: \
mutable rtc::Thread* signaling_thread_;
REFCOUNTED_PROXY_MAP_BOILERPLATE 定义了 Proxy 类的析构函数以及 rtc::scoped_refptr<INTERNAL_CLASS> 类对象。
#define REFCOUNTED_PROXY_MAP_BOILERPLATE(c) \
protected: \
~c##ProxyWithInternal() { \
MethodCall<c##ProxyWithInternal, void> call( \
this, &c##ProxyWithInternal::DestroyInternal); \
call.Marshal(RTC_FROM_HERE, destructor_thread()); \
} \
\
private: \
void DestroyInternal() { c_ = nullptr; } \
rtc::scoped_refptr<INTERNAL_CLASS> c_;
PROXY_METHOD1
#define PROXY_METHOD1(r, method, t1) \
r method(t1 a1) override { \
MethodCall<C, r, t1> call(c_, &C::method, std::move(a1)); \
return call.Marshal(RTC_FROM_HERE, signaling_thread_); \
}
我们以 PROXY_METHOD1 为例说明,PROXY_METHOD2/3/4... 也是同理。PROXY_METHOD1 宏会在代理类内部定义返回值类型为 r,函数名为 method,参数列表为 t1 的函数,函数内部会创建 MethodCall<C, r, t1> 的对象 call,再调用 call.Marshal。
MethodCall Marshal 实际上是一个封装,它调用了 internal::SynchronousMethod(this) 的 Invoke 函数。
SynchronousMethod 的 Invoke 逻辑如下,它判断当前线程是否为 t,如果是则直接调用 proxy_->OnMessage(nullptr),proxy_ 在 SynchronousMethod 构造函数进行初始化,其值为 MethodCall 的 this 指针,也就是最终调用到 MethodCall 的 OnMessage 函数,该函数执行体为
{ Invoke(std::index_sequence_for<Arg...>()); },即调用 MethodCall 的 Invoke 函数,MethodCall Invoke 执行体调用 ReturnType<R> 的 Invoke。
ReturnType Invoke 函数逻辑是调用 C->*m,并通过完美转发的方式传入参数,最后获取 R 类型返回值,对于 PeerConnectionFactoryProxy,C 代表 PeerConnectionInterface,M 是对应方法的函数指针类型。
如果当前线程并不是 t,SynchronousMethod Invoke 方法会调用 t->Post 往线程 t 塞入 Message 并唤醒线程 t,并调用 WakeUpSocketServer 唤醒线程。
目标线程被唤醒后会取出 Message,执行 Message phandler 中指定的函数。
原文链接:WebRTC代理类 - 资料 - 我爱音视频网 - 构建全国最权威的音视频技术交流分享论坛
本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部↓↓