原文出处:WebRTC手记之初探

WebRTC是HTML5支持的重要特性之一,有了它,不再需要借助音视频相关的客户端,直接通过浏览器的Web页面就可以实现音视频对聊功能。而且WebRTC项目是开源的,我们可以借助WebRTC源码快速构建自己的音视频对聊功能。无论是使用前端JS的WebRTC API接口,还是在WebRTC源码上构建自己的对聊框架,都需要遵循以下执行流程:

上述序列中,WebRTC并不提供Stun服务器和Signal服务器,服务器端需要自己实现。Stun服务器可以用google提供的实现stun协议的测试服务器(stun:stun.l.google.com:19302),Signal服务器则完全需要自己实现了,它需要在ClientA和ClientB之间传送彼此的SDP信息和candidate信息,ClientA和ClientB通过这些信息建立P2P连接来传送音视频数据。由于网络环境的复杂性,并不是所有的客户端之间都能够建立P2P连接,这种情况下就需要有个relay服务器做音视频数据的中转,本文本着源码剖析的态度,这种情况就不考虑了。这里说明一下,stun/turn、relay服务器的实现在WebRTC源码中都有示例,真是个名副其实的大宝库。

上述序列中,标注的场景是ClientA向ClientB发起对聊请求,调用描述如下:

这里的流程仅仅是从使用层面上描述了一下,具体内部都做了什么、怎么做的,以后的文章中会慢慢细扒,万事开头难,自我鼓励一下。


原文出处:WebRTC手记之框架与接口

上一篇文章简单地介绍了下WebRTC的协议流程,这一篇就开始介绍框架与接口。

一提到框架,本能地不知道从什么地方入手了。曾经直接从Chromium项目对WebRTC的源码的集成方面入手,后来发现这个步子迈的太大了,看的越多,概念越混乱,看了半个月感觉也没啥沉淀。还是从WebRTC提供的示例工程peerconnection_client入手比较轻便。先抛开音视频流的构建和渲染流程,示例工程

核心的代码结构如下:

从面向对象的视角来看,WebRTC的设计还是非常棒的,真正地做到了接口编程的概念,对WebRTC功能的使用都通过接口来进行,这样最大程度上保证了WebRTC模块的可定制性,这样就可以让WebRTC更多地回归到描述协议的本质。如果WebRTC对这些接口的实现不能满足你的业务需求,理论上你可以提供自己的实现逻辑。本图中的PeerConnectionFactoryInterface和PeerConnectionInterface没有这种定制的代表性,因为重新提供它们的实现逻辑的需求场景基本上不存在(即便不用重写,但也支持参数的定制,具体请参见CreatePeerConnectionFactory的重载方法)。但是音视频相关的接口定制的场景就很普遍了,比如Chromium浏览器集成WebRTC,但是音视频采集需要走Chromium自己的音视频模块,所以Chromium对WebRTC音视频的采集接口重新做了实现适配,以后有机会肯定非常乐意分享下Chromium源码对WebRTC的集成,不过那也是在对WebRTC熟悉完之后的工作了。

图中Conductor是该示例工程提供的核心业务类,整个WebRTC的使用都浓缩在这个类中。Conductor通过CreatePeerConnectionFactory方法创建了一个PeerConnectionFactoryInterface接口的实现对象,通过这个接口,可以创建关键的PeerConnectionInterface接口,PeerConnectionInterface接口是WebRTC的协议核心。此外,PeerConnectionFactoryInterface接口还提供了创建本地音视频流的功能接口,这个部分稍后再述。根据图中PeerConnectionInterface接口的成员方法可以看出,WebRTC通信流程的交互接口基本上都在这里面了,给Conductor的回调通知是通过PeerConnectionObserver接口来完成。具体的交互流程请参见上一篇博文。

接下来分析本地音视频的相关接口,由于音视频内容较多,这里先介绍下接口概念,不谈具体实现(下一节专门讲解WebRTC原生的音视频采集),还是以peerconnection_client工程为例:

这里涉及到非常多的音视频相关接口,基本上都是概念性的,最怕遇到新的设计概念,主要是怕自己理解有误差,下面谈一下我对这些接口概念的理解:

MediaStream概念: 表示媒体流,由MediaStreamInterface接口抽象,每个媒体流都有一个唯一的标识(通过label成员方法返回),它由一系列的音频Track(由AudioTrackInterface接口抽象)和视频Track组成(由VideoTrackInterface接口抽象)。

Track概念:具体指上图结构中AudioTrackInterface和VideoTrackInterface接口,Track表示的是在媒体流中轨的概念,AudioTrackInterface标识的是音频轨,VideoTrackInterface标识的是视频轨,一个MediaStreamInterface标识的媒体流中允许携带多个媒体轨数据,它们之间是独立的,在编码渲染的流程中各自处理。如果概念还很模糊,轨的概念就相当于音频数据中的声道概念(左声道、右声道)、视频数据中的YUV场的概念。Track既然封装了媒体轨数据,那就必然有个媒体源做为数据的提供者,如音频Track由AudioSourceInterface接口作为数据源提供者,视频Track由VideoSourceInterface接口作为数据的提供者。有输入接口就必然有输出接口,这部分在该图中只展示了视频数据的输出接口VideoRendererInterface,这里的Render命名的意思并不是仅限于将视频和音频数据渲染出来,应该理解成输出接口。

VideoSourceInterface:抽象视频源接口供VideoTracks使用,同一个源可以被多个VideoTracks共用。视频源接纳了一个VideoCapturer接口,抽象视频采集的逻辑,我们可以提供自己的VideoCapturer实现做为视频数据的采集源。VideoCapturer是个关键的定制接口,比如Chromium源码就是自己实现了VideoCapturer接口而没用原生的WebRTC采集实现,但Chromium的音视频采集都在browser进程,因此它对VideoCapturer接口的实现要比想象的复杂,它需要从主进程接收到视频帧数据然后触发VideoCapturer的SignalFrameCaptured信号。

AudioSourceInterface:概念上同VideoSourceInterface类似,抽象音频源接口供AudioTracks使用,但是从源码中理解,这是个伪概念,因为没有提供一个类似于VideoCapturer的AudioCapturer接口,这里没有音频的采集逻辑,实际上WebRTC的音频采集接口使用的是AudioDeviceModule,在创建PeerConnectionFactory的时候可以由外界定制,如果没有,则内部创建AudioDeviceModuleImpl来实现此接口完成音频设备的采集工作。可能是功力不够,反正我是不太理解音频采集和视频采集这种设计的不对称性。如果也封装一个AudioCapturer接口的概念,这样可定制性是不是可以更高。

构建媒体流的过程基本上就是构建Video Track和Audio Track,并将其添加到Media Stream里。在peerconnection_client工程中,Conductor依赖DeviceManagerInterface接口的CreateVideoCapturer方法创建一个当前可用的视频设备采集对象VideoCapturer,将它作为视频采集源中的数据来源(通过挂接VideoCapturer的SignalVideoFrame信号来接收视频数据),此外MainWnd还创建了一个内部类VideoRenderer从VideoRendererInterface接口派生,并将其添加到Video Track中, VideoRenderer的实现就是将接收到的视频帧数据渲染到窗口上。

下一篇开始分析WebRTC原生的音视频本地采集模块。


原文出处:WebRTC手记之本地视频采集

前面两篇文章介绍WebRTC的运行流程和使用框架接口,接下来就开始分析本地音视频的采集流程。由于篇幅较大,视频采集和音频采集分成两篇博文,这里先分析视频采集流程。分析的时候先分析WebRTC原生的视频采集流程,再捎带提一下Chromium对WebRTC视频采集的适配,这样能更好地理解WebRTC的接口设计。

1. WebRTC原生视频采集

在介绍视频设备的采集之前,首先要分析一下WebRTC的DeviceManager结构,因为视频采集的抽象接口VideoCapturer的WebRTC原生实现就是通过它来创建的。这个类的功能还包括枚举音视频设备的相关信息等。结构如下:

限于篇幅,该UML中没有标出DeviceManagerInterface接口的所有功能接口,具体包括:获取音频输入/输出设备列表、获取视频输入设备列表、根据设备信息创建VideoCapturer视频采集对象等。由于获取硬件设备列表,涉及到平台相关的调用,在Windows平台下的实现是Win32DeviceManager类(可以调用DeviceManagerFactory的静态方法Create()返回当前平台相应的DeviceManager对象)。关注一下DeviceWatcher,顾名思义,它的功能在于监控设备的变化。在Windows平台下的实现Win32DeviceWatcher通过API函数RegisterDev iceNotification监控视频类设备和音频类设备的变化。当有监视的类型设备发送变化时,会通过DeviceManagerInterface接口的SignalDevicesChange信号向外投递通知。最后分析一下创建VideoCapturer的流程。DeviceManager创建VideoCapturer对象时通过VideoDeviceCapturerFactory接口来完成的。VideoDeviceCapturerFactory接口的默认实现是WebRtcVideoDeviceCapturerFactory类,该类创建WebRtcVideoCapturer对象做为VideoCapturer接口的实现。可以理解成WebRtcVideoCapturer就是WebRTC原生的视频采集的实现,但这种说法不确切,因为视频采集涉及到跨平台,没这么简单。下面再细扒一下WebRtcVideoCapturer:

由于平台相关性,WebRtcVideoCapturer仍然不是视频采集的真正实现,它创建一个VideoCaptureModule接口对象来完成真正的视频采集工作。该抽象接口是视频采集的实现接口,最终在Windows平台下由VideoCaptureDS(传统的DirectShow方式)和VideoCaptureMF(Vista之后的Media Foundation API实现方式)来实现采集工作。这里要说明一下VideoCaptureMF在WebRTC中还是个空架子,还未真正实现,如果读者对Media Foundation API实现视频采集感兴趣,可以参考Chromium的media库中VideoCaptureDeviceMFWin类实现。

接下来分析一下VideoSourceInterface和VideoCapturer是如何结合,以及采集由谁驱动开始的。

VideoSource是WebRTC对VideoSourceInterface接口的实现, 它容纳一个VideoCapturer对象做为视频采集源,VideoRenderer是供外部从VideoSource中获取视频帧数据。此外VideoSource还依赖ChannelManager对象,使用它所包含的CaptureManager来负责视频的采集任务。VideoSource在创建的时候就会调用 Initialize方法中调用ChannelManager的StartVideoCapture方法开始采集视频数据。CaptureManager内部为每个VideoCapturer对象维护了一个CaptureRenderAdapter,CaptureRenderAdapter在创建的时候将OnVideoFrame成员方法挂接上VideoCapturer的SignalVideoFrame信号来实时接收采集源传送过来的视频帧数据,OnVideoFrame内部将接收到的视频帧数据分发给向其注册的VideoRenderer对象(VideoRenderer对象的注册的流程是VideoSource到ChannelManager,再到CaptureManager,最后注册到CaptureRender Adapter与特定的VideoCapturer关联)。

至此,VideoSourceInterface在WebRTC中的实现已经很清晰了,视频采集的流程和时机也很明了,接下来顺便稍等地简单分析一下WebRTC中VideoTrackInterface接口的实现:

WebRTC创建了一个VideoTrack实现VideoTrackInterface接口,在此之前我一直有个疑问,VideoTrackInterface对外暴露的视频输出接口是VideoRendererInterface,而视频源接口VideoSourceInterface对外暴露的视频输出接口是VideoRenderer,两套接口是如何适配的。看到这里,我发现原来VideoTrack新建了一个VideoTrackRenderers对象来完成VideoRendererInterface接口到VideoRenderer接口的适配工作。VideoTrackRenderers一方面从VideoRenderer接口派生,这样就可以将自己通过VideoSourceInterface的AddSink方法挂接进去来接收视频帧数据,另一方面将接收到的视频帧数据分发给外部挂接给VideoTrackInterface的VideoRendererInterface接口。

2. Chromium对WebRTC的视频采集适配

Chromium创建WebRtcVideoCapturerAdapter类来实现VideoCapturer接口,相关结构如下:

Chromium自己也封装了Track、Source概念,所以当初看这块的时候脑袋不容易转弯费了不少心思。WebRtcVideoCapturerAdapter需要接收Chromium的视频采集模块传输过来的帧数据,通过一层层的挂接,最终挂接到MediaStreamVideoSource类中。MediaStreamVideoSource接收到视频帧数据时,再一层层地通知回来,最终通知到WebRtcVideoCapturerAdapter的OnFrameCaptured方法,该方法内部触发SignalFrameCaptured信号。

MediaStreamVideoSource封装了Chromium视频采集的入口,这块结构就复杂了,牵涉到跨进程的架构,如下:

这部分不打算细说,如果细说就很可能混淆到目前为止建立的仅有的一点点概念了,本节主要是介绍的是Chromium对WebRTC视频采集接口的定制。


原文出处:WebRTC手记之本地音频采集

上一篇博文介绍了本地视频采集,这一篇就介绍下音频采集流程,也是先介绍WebRTC原生的音频采集,再介绍Chromium源码对它的定制。

1. WebRTC原生音频采集

先介绍一下WebRTC中与音频采集貌似相关的接口概念:

结构上看起来是不是和视频Track的结构类似?不过前面提过,如果你以对称的思维,在此结构中找出与视频track相似的采集源和输出源,那就肯定无功而返了,LocalAudioSource对AudioSourceInterface的实现就是一个空实现,没有了音频源,那音频处理接口AudioProcessorInterface和输出接口AudioRenderer都成了无米之炊了。这些接口先摆在这,可能类似于AudioCapturer的框架正在实现的途中,也可能这些接口有别的用处,比如远程音频流的抽象等,这里就暂且搁置,先记下有这回事吧。这里只谈WebRTC本地音频的采集处理。前面介绍音视频接口的时候也提到的,本地音频的采集由AudioDeviceModule接口统一封装:

AudioDeviceModule是个大而全的接口,恨不得将所有音频相关的接口都封装在里面(实际也差不多了),具体包括:枚举音频采集设备(Record)和播放设备(Playout)、设置当前的采集设备/播放设备、开始/停止音频的采集/播放、设置音频增益控制开关(AGC)等。AudioTransport是个关键的对外接口,负责音频数据的传入(调用NeedMorePlayData方法,供Playout使用)和输出(调用RecordedDataIsAvailable方法,数据由Record采集操作产生)。

AudioDeviceModuleImpl实现了AudioDeviceModule接口,创建的时候调用CreatePlatformSpecificObjects方法创建平台相关的AudioDeviceGeneric接口实现。该接口抽象了音频的采集和播放逻辑,在Windows平台下有两种实现方案:

此外,AudioDeviceModuleImpl还维护了一个AudioDeviceBuffer对象来管理音频数据的缓冲区,由它直接与对外接口AudioTransport交互。比如:

总之,音频采集模块处处都透露出大而全的结构设计。如果可以,真的应该细化一下概念设计,比如将音频采集和音频播放逻辑分离、音频输入和输出的接口拆分等等,那样才能 谈得上结构设计。

2. Chromium对WebRTC的音频采集适配

根据WebRTC的本地音频接口设计,Chromium提供了一个WebRtcAudioDeviceImpl类来实现AudioDeviceModule接口,该类对象由PeerConnectionDependencyFactory负责创建和维护,结构如下:

如图所示,WebRtcAudioDeviceImpl摒弃了原生的AudioDeviceModuleImpl实现中大而全的设计,而是将音频采集和音频渲染逻辑分开,分别对应于WebRtcAudioCapturer和WebRtcAudioRenderer。WebRtcAudioRenderer通过WebRtcAudioRendererSource接口的RenderData方法向WebRtcAudioDeviceImpl请求音频流数据来渲染,WebRtcAudioDevi ceImpl将该请求转发给前面提到的对外交互接口AudioTransport。WebRtcAudioCapturer封装音频采集逻辑,它将采集到的数据通过WebRtcLocalAudioTrack对象所持有的PeerConnectionAudioSink接口派发出去,WebRtcAudioDeviceImpl正是实现了该接口来接收音频采集数据,然后也是通过AudioTransport接口往外传递。至于WebRtcAudioCapturer对象的持有者MediaStreamAudioSource和WebMediaStreamTrack,这里暂时有个概念就行,它们是Chromium对HTML5媒体流的实现接口。接下来仔细分析一下WebRtcAudioCapturer和WebRtcAudioRenderer两个关键类,毋庸置疑,它们都涉及到了特定平台实现,而且在Chromium中还跨越了Render和Browser进程。和介绍Chromium视频采集的模式一样,由于不是本文重点,这里只列出结构图,不打算详解,如果你有开发上的需要,可以照着该结构图细看源码。

这是WebRtcAudioCapturer采集音频数据的结构,牵涉到跨进程通信,结构还是非常复杂的。WebRtcAudioRenderer的结构就不准备介绍了,因为Chromium的这块设计非常具备对称性,基本上图中类命名中的Input改成Output就差不多是WebRtcAudioRenderer的架构了。


原文出处:WebRTC手记Channel概念

前面两篇博文完整地介绍了WebRTC音视频的采集模块,接下来应该开始介绍关键的音视频编码模块。不过在介绍音视频编码模块之前,需要介绍一下Channel概念,WebRTC的每路数据的传输流程都封装成一个Channel对象。详细UML图如下:

MediaChannel及其派生类封装了待传输的编解码、RTP/RTCP封包解包等逻辑,具体对象由相应的Media Engine类创建,如图:视频Channel最终的实现类WebRtcVideoChannel2由WebRtcVideoEngine2创建,音频Channel最终的实现类WebRtcVoiceMediaChannel由WebRtcVoiceEngine创建。

Channel部分暴露给外界的操作接口还是ChannelManager类中管理的BaseChannel及其派生类,通过这些类,外部模块可以设置音视频的采集源(如VideoCapturer)、为网络发送过来的音视频数据指定渲染器(如AudioRenderer/VideoRenderer),这些类对MediaCha nnel及其派生类的基础上再包装了一层,如图所示,BaseChannel实现MediaChannel的NetworkInterface接口完成封装好的RTP/RTCP数据包包的发送操作,具体纯数据的网络发送请求最终委托给TransportChannel对象。TransportChannel对象的逻辑以后介绍网络 层的时候再讲。

好了,下一篇开始介绍WebRtcVideoEngine2和由它创建的视频Channel类WebRtcVideoChannel2。


原文出处:WebRTC手记之WebRtcVideoEngine2模块

终于讲到视频数据的编码发送模块了,不容易。总体来说也看了不少时间WebRTC的源码了,最大的感触就是各个模块在开发的时候非常独立,每个模块都定义了自己的一套接口,最后串起来的时候添加各种适配对象来转接。这给我们这些刚开始源码阅读的人带来非常大的苦恼,不过WebRTC的模块内的结构设计还是很不错的,不然我也没有看下去的动力。

注意命名,WebRtcVideoEngine2带了个2字,不用想,这肯定是个升级版本的VideoEngine,还有个WebRtcVideoEngine类。从目前我的理解来看,WebRtcVideoEngine2比WebRtcVideoEngine改进之处在于将视频流一分为二:发送流(WebRtcVideoSendStream)和接收流(WebRtcVideoReceiveStream),从而结构上更合理,源码更清晰。这个部分等下会细说。在介绍WebRtcVideoEngine2之前,先简单地分析一下WebRTC的MediaEngine结构,说实话,我真不会表达Engine是个怎样的概念,但既然这样命名,核心模块肯定是错不了的。结构很简单:

WebRtcVideoEngine2主要作用在于创建视频channel对象WebRtcVideoChannel2。结构如下:

当调用WebRtcVideoChannel2的AddSendStream方法时,会创建一个WebRtcVideoSendStream对象,同样,调用AddRecvStream成员方法,会创建一个WebRtcVideoReceiveStream对象。

当外部调用WebRtcVideoChannel2的SetCapturer方法时,会转给WebRtcVideoSendStream来响应,WebRtcVideoSendStream内部将InputFrame成员方法挂接VideoCapturer的SignalVideoFrame信号来接收视频采集器传输过来的视频帧数据。

WebRtcVideoChannel2的AddSendStream和SetCapturer的调用时机这里暂时不考虑,这些涉及到网络连接,等每个节点的内容分析完后,再探讨整个流程。

如图所示,WebRtcVideoSendStream和WebRtcVideoReceiveStream也只是个包装类,内部依赖Call接口创建对应的VideoSendStream接口实现类和VideoReceiveStream接口实现类。在internal命名空间内,分别有一个Call类、VideoSendStream类、VideoReceiveStream类来实现这三个接口,Call类创建关键的VideoEngine对象来管理视频数据发送过程中的一系列处理逻辑。从代码结构上看,VideoEngine是一个相对独立的模块,它封装视频数据采集后的处理、编码等逻辑,下面仔细分析一下VideoEngine的结构:

VideoEngine模块里有ViEBase、ViECodec、ViECapture、ViEImageProcess、ViENetwork、ViERender、ViERTP_RTCP、ViEExternalCodec接口,注意,这些都是功能性的接口,它们相应的实现分别对应于上图中的XXXImpl类,VideoEngineImpl类从所有的XXXImpl接口派生,因此外部有了VideoEngine接口,都可以通过强转的方式获取ViEBase、ViECapture等之类的接口(根据VideoEngine强转成相应的接口的逻辑封装在目标接口的GetInterface静态方法中),外界可以通过这些接口来完成视频数据做相应的设置,而这些设置最终都反映到一个名叫ViESharedData的类对象里。该对象由ViEBaseImpl创建并在各接口的实现之间共享,XXXImpl可以通过ViEBaseImpl的shared_data方法来访问,用于共享的数据有三类:ViEInputManager、ViEChannelManager和ViERenderManager。下面分别介绍一下这关键的三个对象。

ViEInputManager为每个通道分配一个ViECapturer对象来做为视频源,ViECapturer可以传入也可以自己创建一个VideoCaptureModule视频采集模块,并通过VideoCaptureDataCallback接口从其接收数据,也可以直接通过ViEExternalCapture接口接收从外部直接传入的视频帧数据(调用ViEExternalCapture接口的IncomingFrame方法)。VideoSendStream就是通过ViEInputManager创建一个ViEExternalCapture对象来传入外界传来的视频帧数据(通过WebRtcVideoSendStream的InputFrame传来)。这里要注意,ViEInputManager为创建的ViECapturer对象分配一个capture_id,外界可以通过这个capture_id来操作其对应的ViECapturer。视频源传入逻辑已经明了,接下来分析一下视频是怎么传出去的。无论通过哪种视频数据接收方法,ViECapturer都不会立即将数据传递出去,因为它内部需要对这些视频数据做相关的处理。数据处理必然耗时,如果采用同步的方式,必将阻塞视频传入的流程。因此,在创建ViECapturer的时候,会启动一采集线程,该线程调用ViECaptureProcess处理函数,在该处理函数里,先调用VideoProcessingModule对视频 数据进行处理(灯光加亮、去闪烁),如果在ViEImageProcessImpl里注册了ViEEffectFilter处理对象,这里也会调用该对象来处理视频帧数据,最后通过DeliverFrame方法分发到注册进来的所有ViEFrameCallback接口。

ViEChannelManager维护了ViEEncoder和ViEChannel对象,ViEEncoder实现了ViEFrameCallback接口从ViECapturer对象中接收视频帧数据,ViEEncoder对接收到的视频帧数据进行编码,然后将编码后的数据传给ViEChannel(通过两者之间共享的PayloadRouter对象),ViEChannel将编码后的视频数据通过RTP/RTCP协议发送出去。下面分别分析一下ViEEncoder和ViEChannel。

1) ViEEncoder类:封装了视频编码流程。

视频编码由VideoCodingModule模块统一管理,视频帧传入接口是通过VideoCodingModule的的AddVideoFrame方法,编码后的 视频传出接口是借助VCMPacketizationCallback接口来回调。具体选取哪种视频编码的逻辑位于VCMCodecDataBase类,当前支持VP 8编码、VP9编码和视频格式到I420格式的转换。

2)ViEChannel类:封装了编码后的视频数据发送逻辑和视频数据接收解码逻辑。

视频数据发送逻辑是通过PayloadRouter对象委托给RtpRtcp模块做RTP协议的封装,具体的网络发送操作还是回托给ViESender做数据的网络发送操作。ViESender的逻辑相对简单,限于篇幅,图中无法做详细的标注。ViESender的发送操作依赖外部设置给它的Transport接口(通过VideoEngine模块的ViENetwork接口来完成设置)。

当WebRtcVideoChannel2接收到网路数据包后(通过OnPacketReceived或OnRtcpReceived方法响应),会在VideoReceiveStream对象中通过VideoEngine模块暴露出去的ViENetwork接口来响应数据包处理,最终触发到ViEChannel的ReceivedRTPPacket或ReceivedRTCPPacket方法。ViEChannel中将接收并解码网络视频数据的任务分配给ViEReceiver对象。ViEReceiver先调用RTP/RTCP模块做协议的解析(图中限于篇幅未标注出来),解析完成后调用VideoCodingModule模块进行数据的解码操作(参见ViEReceiver的OnReceivedPayloadData方法),VideoCodingModule模块内部维护了一个与VideoSender对应的VideoReceiver来完成解码逻辑,这块与VideoSender的编码逻辑完全对称,这里不再表述。

当ViEChannel接收到网络数据解包并解码后,就会开启触发渲染流程(参见FrameToRender方法),ViEChannel会调用向其注册的ViEFrameCallback接口来派发视频帧数据。ViERenderManager维护了一个ViERenderer对象来实现ViEFrameCallback接口, 它将数据进一步派发,最终通过ExternalRenderer接口派发给WebRtcVideoChannel2的VideoReceiveStream对象。VideoReceiveStream通过VideoSource设置进来的VideoRenderer接口将数据派发给VideoTrack,用户可以挂接VideoRendererInterface接口来接收视频帧数据。真够绕的,而且那么多命名的相似性(比如VideoRender/VideoRenderer),感觉各模块开发期间,都实现了自己的一套接口规范,最后强行串在一起了。