WebRTC之视频采集

基于WebRTC的实时音视频会议中对于视频处理流水,第一级就是视频采集,视频内容可以摄像头、屏幕和视频文件,视频来源的操作系统可以是Linux、Windows、Mac,IOS以及Android,不同的平台由不同的公司开发设计,因而他们从camera获取视频的底层框架并不一样,Linux系统使用V4L2(Video for Linux Version 2),Mac和IOS都是苹果公司开发的,都使用AVFoundation框架,Windows使用的是微软开发的DS(Direct Show)框架,Android使用camera2.0接口(Camera2Capturer)采集视频。

视频设备封装

视频采集的通用代码位于modules/video_capture目录,该目录下的文件组织结构如下图:

视频采集使用c++的类和对象的封装思想,不论什么平台都有启动采集(StartCapture)和停止采集(StopCapture)等类似的视频采集控制功能需求,这样上层在调用启动采集时可以只调用StartCapture方法,而忽略具体的平台细节,图中的video_capture.h头文件定义了视频采集的虚基类VideoCaptureModule。linux和windows目录定义了调用操作系统API进行视频采集平台相关的代码,由于Android和评估平台的提供系统API分别是基于Java和Object C所以没有放在这个目录,WebRTC提供了sdk目录用于存放和APPLE和Android平台相关的代码,sdk/objc/components/capturer和sdk/android/api/org/webrtc两个目录。为了简便性,这里仅分析视频采集的架构,具体平台细节这里不再讨论。

虚基类中的Start/StopCapture用于开始和结束数据采集,Register/DeCaptureDataCallback用于注册和注销数据回调模块,数据回调模块用于把视频数据推送给上层,而不需要上层轮训底层有没有视频数据,这样效率上会高。

VideoCaptureImpl是虚基类VideoCaptureModule实现的子类,其实现了子类的各种方法,并且也定义了通用平台无关的一些成员变量,对于平台相关在平台相关的类中实现,也就是VideoCaptureImpl会根据不同的平台泛化成对应的具体实现,它们之间的UML关系如下图:

视频采集过程

各个平台采集到数据之后,会调用VideoCaptureImpl类中的IncomingFrame方法将数据通过DeliverCapturedFrame传递给上层。控制流程上视频采集模块在初始阶段由上层创建并StartCapture,结束阶段由上层调用StopCapture停止并销毁改模块,数据流阶段采集到的数据通过回调的(高效方式)传递给上层回显和编码环节以进一步处理。其控制路和数据流如下:

WebRTC自带的PeerConnection例程采集视频过程如下:

1.VideoCaptureFactory类可用于创建VideoCaptureModule实例,当然也可自己include相应头文件直接创建;

2.一个系统可以同时连接多个camera,这可以通过NumberOfDevices获取摄像头数目,并循环调用GetDeviceName或得每个摄像头的信息,可以通过将摄像头信息(通常是名字或和UUID相关的字段)回显到UI上供用户自己选择;

3.WebRtcVideoDeviceCapturerFactory和create创建VideoCaptureModule实例,这调用了对应平台的Create方法,如Linux平台创建的是VideoCaptureModuleV4L2对象,代码如下:

rtc::scoped_refptr<VideoCaptureModule> VideoCaptureFactory::Create(
    const char* deviceUniqueIdUTF8) {
#if defined(WEBRTC_ANDROID) || defined(WEBRTC_MAC)
  return nullptr;
#else
  return videocapturemodule::VideoCaptureImpl::Create(deviceUniqueIdUTF8);
#endif
}

4.根据视频帧率、分辨率等选择最合适的一个,这可以通过UI回显给用户选择,当然也会根据系统的负载和网络情况综合选择最优的视频参数;

5.调用RegisterCaptureDataCallback注册采集到视频数据帧的回调函数,这里的this就是其本身,所以这个函数里重写了OnFrame函数。

void VcmCapturer::OnFrame(const VideoFrame& frame) {
  TestVideoCapturer::OnFrame(frame);
}

采集之后的回显和编码在后续文章再叙述,这里不做展开。

6.启动视频传输,这里调用了StartCapture方法,该方法最终调用到具体的平台代码。

视频捕获过程

上一节得到了一帧视频数据,为了便于管理,如编码,回显等,需要在应用层和底层一帧数据做一个桥接层,以隔离上下层。桥接层的代码主要位于media/base 和media/engine,WebRTC中抽象了VideoSourceInterface和VideoSinkInterface这两个模板类分别表示产生和吸收视频流。

// api/video/video_source_interface.h
template <typename VideoFrameT>
class VideoSourceInterface {
 public:
  virtual ~VideoSourceInterface() = default;

  virtual void AddOrUpdateSink(VideoSinkInterface<VideoFrameT>* sink,
                               const VideoSinkWants& wants) = 0;
  // RemoveSink must guarantee that at the time the method returns,
  // there is no current and no future calls to VideoSinkInterface::OnFrame.
  virtual void RemoveSink(VideoSinkInterface<VideoFrameT>* sink) = 0;
};

// api/video/video_sink_interface.h
template <typename VideoFrameT>
class VideoSinkInterface {
 public:
  virtual ~VideoSinkInterface() = default;

  virtual void OnFrame(const VideoFrameT& frame) = 0;

  // Should be called by the source when it discards the frame due to rate
  // limiting.
  virtual void OnDiscardedFrame() {}
};

如果能提供视频数据,需要实现 VideoSourceInterface,此接口类暴露了 AddOrUpdateSink 函数,可以将 Sink 注册给 Source。

如果想要接收视频数据,需要实现 VideoSinkInterface,此接口类暴露了 OnFrame 函数。只要将 Sink 通过 AddOrUpdateSink 函数注册给 Source,那么 Source 就会通过 OnFrame 接口将数据传给 Sink。对于WebRTC的PeerConnection例子,其VcmCapturer类继承了VideoSinkInterface。

class VcmCapturer : public TestVideoCapturer,
                    public rtc::VideoSinkInterface<VideoFrame> {
 public:
  static VcmCapturer* Create(size_t width,
                             size_t height,
                             size_t target_fps,
                             size_t capture_device_index);
  virtual ~VcmCapturer();

  void OnFrame(const VideoFrame& frame) override;

 private:
  VcmCapturer();
  bool Init(size_t width,
            size_t height,
            size_t target_fps,
            size_t capture_device_index);
  void Destroy();

  rtc::scoped_refptr<VideoCaptureModule> vcm_;
  VideoCaptureCapability capability_;
};

VcmCapturer实现了实现了 VideoSinkInterface 接口,在初始化该对象时,会调用RegisterCaptureDataCallback将自身注册给视频设备管理层,当视频设备采集到一帧数据时,会调用注册对象(VcmCapturer)的OnFrame方法。

VideoSourceBase保存了注册给VcmCapturer的所有sink,因此其实现了VideoSourceInterface作为一个视频源,VideoBroadCaster继承了VideoSourceBase类,同时实现了VideoSinkInterface,其作用是VcmCapturer接受数据,然后分发给所有向其注册的Sink。

CaptureTrackSource作为视频源添加到VideoTrack组合到MediaStream中,MediaStream最终会添加到PeerConnection中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章