创建完PeerConnectionFactory 和 PeerConnection这两个API层的操盘对象之后,紧接着需要初始化本地的媒体,也即创建本地的音频轨、视频轨、数据通道,并将这些本地的媒体轨道添加到PeerConnection对象中。如图中红色标注所示。
本文将详细描述上述视频轨道的创建细节 以及 轨道被添加到PeerConnection中的存储情况。
rtc::scoped_refptr<CapturerTrackSource> video_device =
CapturerTrackSource::Create();
if (video_device) {
rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track_(
peer_connection_factory_->CreateVideoTrack(kVideoLabel, video_device));
main_wnd_->StartLocalRenderer(video_track_);
result_or_error = peer_connection_->AddTrack(video_track_, {kStreamId});
if (!result_or_error.ok()) {
RTC_LOG(LS_ERROR) << "Failed to add video track to PeerConnection: "
<< result_or_error.error().message();
}
} else {
RTC_LOG(LS_ERROR) << "OpenVideoCaptureDevice failed";
}
rtc::scoped_refptr<CapturerTrackSource> video_device =
CapturerTrackSource::Create();
static rtc::scoped_refptr<CapturerTrackSource> Create() {
// 1. 预设视频参数
const size_t kWidth = 640;
const size_t kHeight = 480;
const size_t kFps = 30;
std::unique_ptr<webrtc::test::VcmCapturer> capturer;
// 2. 获取视频设备信息对象info,并获取机器上视频设备个数
std::unique_ptr<webrtc::VideoCaptureModule::DeviceInfo> info(
webrtc::VideoCaptureFactory::CreateDeviceInfo());
if (!info) {
return nullptr;
}
int num_devices = info->NumberOfDevices();
// 3. 枚举音频设备,查找支持预设视频参数的设备,创建对应的视频源,然后创建对应的轨道源
for (int i = 0; i < num_devices; ++i) {
// 3.1 创建满足预设视频参数的视频源对象VcmCapturer
capturer = absl::WrapUnique(
webrtc::test::VcmCapturer::Create(kWidth, kHeight, kFps, i));
// 3.2 创建视频轨源对象CapturerTrackSource
if (capturer) {
return new rtc::RefCountedObject<CapturerTrackSource>(
std::move(capturer));
}
}
return nullptr;
}
从上创建视频源的代码可以知道如下几点:
CapturerTrackSource、VcmCapturer、VideoCapture对象的继承树,以及这三者的关系如下UML类图所示:
视频采集模块是数据流水线的起始点,负责从视频源采集原始视频帧,推送给流水线的下一站:可以是本地渲染模块进行本地回显,也可以是编码模块进行数据编码压缩。
视频源可以是摄像头,也可以是桌面、窗口抓屏(远程桌面,基于视频流的电子白板等应用),甚至可以是磁盘上的视频文件,图片文件。WebRTC中提供了基于摄像头的视频采集框架,是本文要讨论的重点。当然WebRTC也提供了桌面,窗口抓屏框架,这套框架对外所提供的接口与基于摄像头的采集接口有所不同。整个视频流水线建立是以摄像头采集接口为基础的,从而导致这么个问题:当需要将抓屏数据当做视频源往外推送时,需要使用适配器模式来实现一套基于摄像头的视频采集接口。
视频采集模块是平台相关的模块,MacOS/IOS一般使用AVFoundation框架或者QuikTime框架,Linux平台一般使用V4L2库,Android上一般使用Camera1或者Camera2框架,Windows平台则使用DS(DirectShow)或者是MF(MediaFoundation)。由于WebRTC是个非常活跃的工程,代码架构一直在不停的变动之中,比如2019年4月份的代码还有VideoCaptureMF的代码,并且还注释着Vista及以上的版本建议使用MediaFoundation采集框架,而2019年11月份的代码MediaFoundation相关的代码已经被移除。再比如MacOs/IOS,Android的相关代码已经被移动到sdk/objc和sdk/android目录下。本文以modules/video_capture下的代码来做阐述,平台无关的代码在该直接目录下,平台相关的实现在modules/video_capture/windows,modules/video_capture/linux目录下,如图所示:
DeviceInfo接口提供了设备枚举相关功能,其平台相关子类实例以组合的形式提供给VideoCapture。
VideoCaptureModule视频采集模块的虚基类,它定义一系列视频采集的通用接口函数:
VideoCaptureImpl类是VideoCaptureModule的实现子类。做了3个事:
VcmCapturer是创建的视频源对象,虽然从名字上来看像是视频采集类,但实质上它实现了VideoSourceInterface接口。我们认为其是一个视频源。
VideoCaptureModule作为数据源头组合到视频源对象VcmCapturer中,同时VcmCapturer又实现了VideoSinkInterface
VcmCapturer在构造成功时就会启动VideoCaptureModule进行视频采集。
视频源VcmCapturer持有一个非常重要的成员VideoBroadcaster对象,该对象的UML类图如下。
一方面VideoBroadcaster实现了VideoSinkInterface接口,成为一个Sink,这样VideoSource得到采集模块的视频帧后,首先会流入到内部的VideoBroadcaster成员对象,而非直接从VideoSource流出;另一方面VideoSource和VideoBroadcaster都实现了VideoSourceInterface接口,对外VideoSource作为视频源存在,向数据流下一站提供注册方法AddOrUpdateSink();该方法内部调用VideoBroadcaster的AddOrUpdateSink(),从而将数据流下一站VideoSink注册到VideoBroadcaster,存入成员std::vector
为什么要如此设计?因为,在WebRTC 1.0的官方规范中说明了一个视频源是可以被多个视频轨共用的。通过上述方式可以实现共用的概念。
VideoTrackSource没有实现VideoSinkInterface接口,因此,实质上视频数据是不会流入到VideoTrackSource中的,但其组合了VideoSource对象,并且实现了VideoSourceInterface接口,添加到VideoTrackSource中的VideoSink会被添加到VideoSource,然后进一步添加到VideoBroadcast中。对外部来说,VideoTrackSource就是视频源。
VideoTrackSource另外实现了视频源状态相关的接口,以及状态通告相关的接口NotifierInterface,用于向更高一层(VideoTrack)通告视频源的状态。
rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track_(
peer_connection_factory_->CreateVideoTrack(kVideoLabel, video_device));
rtc::scoped_refptr<VideoTrackInterface> PeerConnectionFactory::CreateVideoTrack(
const std::string& id,
VideoTrackSourceInterface* source) {
RTC_DCHECK(signaling_thread_->IsCurrent());
rtc::scoped_refptr<VideoTrackInterface> track(
VideoTrack::Create(id, source, worker_thread_));
return VideoTrackProxy::Create(signaling_thread_, worker_thread_, track);
}
rtc::scoped_refptr<VideoTrack> VideoTrack::Create(
const std::string& id,
VideoTrackSourceInterface* source,
rtc::Thread* worker_thread) {
rtc::RefCountedObject<VideoTrack>* track =
new rtc::RefCountedObject<VideoTrack>(id, source, worker_thread);
return track;
}
VideoTrack::VideoTrack(const std::string& label,
VideoTrackSourceInterface* video_source,
rtc::Thread* worker_thread)
: MediaStreamTrack<VideoTrackInterface>(label),
worker_thread_(worker_thread),
video_source_(video_source),
content_hint_(ContentHint::kNone) {
video_source_->RegisterObserver(this);
}
由上视频轨创建过程可知,
如下是VideoTrack类的UML类图。
从上VideoTrack类的UML类图我们可以得知如下几个结论:
纵观上述几个对象,我们可以得出如下的类图:
我们可以认为视频数据原始帧从VideoCapture流向了VideoSource,然后又流向了VideoTrackSource,然后流向VideoTrack。向VideoTrack注册的Sink又可以进一步获取到视频原始帧。
虽然比较细致地拆解了视频源为VideoTrackSource、VideoSource、VideoCapture,但从宏观概念上,我们要知晓:可以直接认为VideoTrackSource是视频源。数据从视频源流向视频轨。
视频轨既要从视频源获取视频数据,还要观察视频源地状态,从而同步更改自己地状态。
由于VideoTrack同AudioTrack一样,都实现了MediaStreamTrackInterface接口,对于PC而言,视频轨和音频轨并无实质区别,因为都是通过如下方法进行添加的。因此,在此处就不再赘述VideoTrack如何被添加?存储在PC的何处?是否将引发PC状态的改变? 等等,见上篇文章:WebRTC源码分析-呼叫建立过程之四(上)(创建并添加本地音频轨到PeerConnection)
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_ids) override;
行文知此,大致对如何创建一个视频轨并添加到PC进行了一个较细致的分析。即便忘记了细节,那么如下要点是需要记住的