视频采集模块在WebRTC数据流水线中负责从视频源采集视频数据,推送给流水线下一模块(本地回显模块或者编码模块)进行处理。视频源除了常见的摄像头,也可以是桌面抓屏或者窗口抓屏,或者是一个视频文件。视频采集模块是平台相关的,MacOS和IOS平台一般使用AVFoundation框架,Linux平台一般使用V4L2库,而Windows平台则使用MediaFoundation库。</br>
本文在深入分析WebRTC视频采集、SDP协商和VideoEngine源代码的基础上,重点研究其视频采集模块的实现,视频数据流水线的建立,和视频数据从采集到待编码的流向。为继续进一步学习WebRTC的音视频处理技术奠定基础。</br>
1 视频采集模块实现
</br>
</br>如文章[1]所述,WebRTC的视频采集模块处于数据流水线的开始位置,其实现采用WebRTC的通用模块机制。源代码分布在webrtc/modules/video_capture目录下,分为平台无关部分和平台相关部分:前者定义视频采集的通用接口如Start/StopCapture,RegisterCaptureDataCallback等,后者则在不同平台上实现这些接口,如在Mac平台上使用AVFoundation实现开始/停止视频采集、视频数据向上推送等功能。</br>
WebRTC 的视频采集模块UML类图如图1所示。</br>
视频采集模块的虚基类为VideoCaptureModule,它定义一系列视频采集的通用接口函数: Start/StopCapture用来开始/结束视频采集;Register/DeCaptureDataCallback用来注册/注销数据回调模块,数据回调模块用来把视频数据向上层模块推送;CaptureCallback则是向上层报告采集模块自身的运行状态。视频采集模块采用WebRTC的通用模块机制,因此它也继承自Module类,用来完成通用的模块操作。</br>
VideoCaptureImpl类是VideoCaptureModule的实现子类,它实现父类定义的通用平台无关接口。对于平台相关接口,则留在平台相关的子类中实现。该类定义一系列工厂方法来创建平台相关的具体子类。不同平台上实现的子类负责在各自平台下实现平台相关功能,主要是开始/结束视频采集和视频数据导出。在Linux平台上实现的子类是VideoCaptureV4L2,在IOS平台的实现为VideoCaptureIos,在Windows平台上的实现为VideoCaptureMF和VideoCaptureDS,等等。本文以Mac平台的VideoCaptureAvf实现为例进行简要分析。</br>
VideoCaptureAvf采用AVFoundation库实现视频采集功能,它把真正要做的事情委托给RTCVideoCaptureAvfObjC对象,后者同时实现了AVCaptureVideoDataOutputSampleBufferDelegate协议,采集到的视频数据通过该协议的captureOutput接口导出,由IncomingFrame()函数向上传递到VideoCaptureImpl进行下一步处理。开始/结束视频采集由AVCaptureSession负责实现。</br>
至此,我们分析了WebRTC中视频采集模块的实现细节。值得注意的是,在Chrome52 Codebase中,该模块并不支持WindowsPhone平台。另外,Android平台的视频采集不通过该模块进行,其相关代码分布在webrtc/api/java目录下。</br>
2 视频数据流水线建立
</br>
</br>视频采集模块作为底层模块,需要和上层模块协作才能把采集到的视频数据发送到上层的显示和编码模块,为数据流水线提供源源不断的视频数据。从控制流来讲,视频采集模块在初始化阶段由上层模块进行创建并开启视频采集,在结束的时候由上层模块停止视频采集并销毁模块。从数据流来讲,采集到的视频数据通过回调接口传递到上层模块,进行数据流水线上的下一步处理。</br>
本节分析和视频采集紧密相关的发送端数据流水线建立过程,主要包括视频采集模块创建后经过VideoSource、VideoTrack、MediaStream向上添加到到PeerConnection,SDP本地音视频能力收集创建offer,以及offer创建后的本地设置生效。</br>
2.1 相关UML类图
</br>WebRTC视频数据流水线的相关代码分布在webrtc/modules/video_capture,webrtc/api,webrtc/media等目录中,这些共同协作完成视频数据流水线的建立。相关UML类图如图2和图3所示。</br>
VideoCapture模块对内完成视频采集任务,对外通过VideoCaptureDataCallbak接口把视频数据推送到上层模块。Media模块的WebRtcVideoCapturer对象实现该接口,作为视频数据从VideoCapture模块向上推送的下一站。而WebRtcVideoCapturer则作为VideoCaptureTrackSource的成员变量,继续向上完成逻辑控制过程。</br>
VideoCaptureTrackSource作为数据源组合到VideoTrack中,而VideoTrack进一步组合到MediaStream中。MediaStream最终会添加到PeerConnection中,通过RtpSender和WebRtcSession建立联系。WebRtcSession在SDP创建offer和本地设置过程中通过VideoChannel把WebRtcVideoCapturer的数据流下一站设置到WebRtcVideoSendStream处,使得视频数据到达编码模块。</br>
2.2 创建MediaStream并添加到PeerConnection
</br>我们知道,如果在JS端创建WebRTC P2P通信,需要首先创建PeerConnection,然后通过getUserMedia()得到本端的MediaStream,接下来把MediaStream添加到PeerConnection,最终执行CreateOffer/OnSuccess等SDP协商操作。在Native层,我们可以用以下代码实现这个过程。</br>
Conductor::ConnectToPeer() {
PeerConnectionFactoryInterface *pcFactory = webrtc::CreatePeerConnectionFactory();
PeerConnectionInterface *pc = pcFactory->CreatePeerConnection(config);
VideoTrackInterface *videotrack = pcFactory->CreateVideoTrack(“video_label”,
pcFactory->CreateVideoSource(OpenVideoCaptureDevice()));
MediaStreamInterface *stream = pcFactory->CreateLocalMediaStream(“stream_label”);
stream->AddTrack(videotrack);
pc->AddStream(stream);
pc->CreateOffer();
}
Conductor::OnSuccesss(SessionDescriptonInterface *desc) {
Pc->SetLocalDescription(desc);
sendMessage(desc);
}
创建MediaStream是为了获取本端的音视频数据源,然后MediaStream设置到PeerConnection中,参与接下来的SDP协商过程。综合2.1节内容和上述代码,MediaStream的创建和设置可由图4来表示。</br>
当创建VideoTrack时,会以VideoCaptureTrackSource作为自己的videosource,而后者则进一步把WebRtcVideoCapturer作为自己的videocapture。在WebRtcVideoCapturer对象中,VideoCaptureImpl作为module_成员。不同平台下的VideoCaptureImpl有不同的实现子类,如Mac平台下的实现类为VideoCaptureAvf。需要注意的是,当VideoCaptureTrackSource在初始化时,会调用WebRtcVideoCapturer对象的StartCapturing()函数开始视频采集工作。</br>
VideoTrack创建后,会被添加到MediaStream,然后MediaStream被添加到PeerConnection,存储在local_streams_成员变量中。接下来调用OnVideoTrackAdded()创建VideoRtpSender,该对象把VideoTrack、StreamLabel和WebRtcSession组合在一起。这样以来,底层的VideoCaptureImpl和WebRtcSession联系在一起,为接下来的SDP协商过程做好准备。</br>
2.3 PeerConnection的CreateOffer过程
</br>CreateOffer是WebRTC建立P2P连接最重要的一步,它收集本地的音视频能力和网络层传输能力形成SDP描述结构。关于CreateOffer的详细过程分析是一项浩大工程,本文仅分析视频相关的CreateOffer过程,如图5所示。</br>
PeerConnection在CreateOffer时,首先调用GetOptionsForOffer()函数获取本端的MediaSession描述信息,该信息描述了本端是否接收音视频数据、传输层选项、Stream信息等。其中Stream信息是对MediaStream的描述,在AddSendStreams()函数中,会获取PeerConnection中RtpSender对象的MediaStream信息,形成Stream结构后存储在MediaSessionOptions中。</br>
接下来PeerConnection把CreateOffer委托给WebRtcSession,后者进一步委托给WebRtcSessionDescriptionFactory,而最终的SDP创建工作都在MediaSessionDescriptionFactory中完成,其CreateOffer()函数完成Audio、Video和Data的MediaContent生成工作。在VideoContent生成过程中,会根据MediaSessionOptions中的Stream信息生成StreamParams结构(该结构中包含生成的ssrc信息),存储在最终返回的VideoContent中。</br>
至此,CreateOffer操作完成,接下来会调用PeerConnectionObserver的OnSuccess()函数执行offer生成后的本地设置操作和发送到对端等操作。</br>
2.4 SetLocalDescription过程
</br>PeerConnection在创建CreateOffer结束之后,会调用其Observer的OnSuccess()接口进行回调。在该回调函数中,首先把生成的offer进行本地设置,然后把offer发送到P2P的对端。本节我们分析offer的本地设置过程,在这里将会最终建立发送端视频数据流水线,如图6所示。</br>
PeerConnection在进行offer本地设置时,首先在WebRtcSession中进行设置。在这里会根据SDP的内容创建VideoChannel。VideoChannel是WebRtcSession连接VideoMediaChannel的桥梁,而后者则是通向VideoEngine模块的通道。VideoChannel创建完成以后,存储在WebRtcSession中。接下来WebRtcSession通过PushDownLocalDescription()把offer内容向下设置到VideoChannel,后者的SetLocalContent_w()函数真正完成offer的本地设置工作。其中根据offer中关于Stream的描述信息调用WebRtcVideoChannel2对象的AddSendStream()函数创建WebRtcVideoSendStream对象,这个对象是视频数据从VideoCapture模块出来后的下一站。注意该对象的存储以ssrc为索引,这个ssrc是在CreateOffer过程中为Stream对象生成的,代表了VideoTrack。</br>
WebRtcSession的本地设置工作完成以后,PeerConnection以offer中的Stream为参数调用UpdateLocalTracks()更新本地MediaTrack,根据每个Stream中的stream_label、track_id、ssrc、media_type信息调用OnLocalTrackSeen()函数。该函数最重要的功能是通过track_id找到VideoRtpSender,然后设置其ssrc。VideoRtpSender调用和它关联的WebRtcSession进行下一步SetSource(ssrc, track)设置操作。设置流程经过VideoChannel最终到达WebRtcVideoSendStream,即后者的数据源是VideoTrack。VideoTrack通过AddOrUpdateSink()反向设置自己的数据Sink是WebRtcVideoSendStream,该设置过程会一路回溯直到VideoBroadCaster。在那里完成设置后,VideoCapture模块采集到的视频数据便会经过OnFrame()接口到达WebRtcVideoSendStream,进而到达VideoEngine模块,进行下一步的编码操作。</br>
至此,PeerConnection根据本地MediaStream建立视频数据流水线的过程分析完毕。这是一个复杂的过程,其重难点在于CreateOffer过程收集本地音视频信息描述以及接下来的本地offer设置过程。我们可以看到,ssrc在SDP建立过程中发挥非常重要的作用。在随后的学习过程中,会针对WebRTC的SDP协商过程进行更深入全面的分析。</br>
3 视频采集端数据流
</br>
</br>通过上一节分析视频流水线的建立过程,接下来分析VideoCapture模块采集到的视频数据流走向就很容易了。我们以Mac平台的VideoCaptureAvf为例进行分析,如图7所示。</br>
VideoCaptureTrackSource在初始化时已经开启视频采集工作,创建系统线程进行采集。在Mac平台上,视频数据从硬件采集后通过RTCVideoCaptureAvfObjc的 captureoutput接口导出,经过初步处理后到达VideoCaptureImpl对象,该对象通过回调继续向上传递视频数据到达WebRtcVideoCapturer。从此处开始线程从Mac系统线程切换到Worker线程。Worker线程在对视频帧进行预处理之后,推送到VideoBroadcaster的OnFrame()接口。在此处视频数据分两路处理:如果我们在之前添加了本地回显VideoRender,则视频数据会进行本地显示;另一路数据则到达WebRtcVideoSendStream,最终到达VideoCaptureInput对象处。视频数据会保存在VideoCaptureInput处,等待下一步Encoder线程来此拉取进行编码操作。</br>
至此,我们分析了视频数据从采集到待编码的流程。</br>
4 总结
</br>
</br>本文深入WebRTC视频采集模块的源代码,分析其平台无关部分和平台相关部分的实现细节,视频数据流水线的建立过程,和视频数据从采集到待编码的流向。其中重点分析了视频数据流水线的建立过程,对WebRTC的SDP协商过程有初步深入了解,为继续进一步学习WebRTC的技术原理奠定基础。</br>
</br>
</br>
参考文献
</br>
[1] WebRTC的模块处理机制:http://www.jianshu.com/p/9f4d4a725efb