webrtc源码分析之-从视频采集到编码流程

本文详细解析了在WebRTC中,视频从视频采集模块到编码器的完整流程,涉及VideoCaptureModule、VideoCaptureImpl、VideoSinkInterface等关键组件,以及如何通过注册回调和接口流转实现数据传递。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

peer_connection中从视频采集到编码的流程

摘要:本篇文章主要讲述当我们通过peer_connection完成推流时,视频从采集到编码是如何衔接的。
既,视频采集后如何传送到编码器。重点分析传输的过程,方便我们对webrtc不同模块的拆分使用。

在webrtc源码中,modules目录下的video_capture目录中,有关于linux平台,windows平台视频采集的实现,

下面介绍 VideoCaptureModule 类,此类提供了如下几个主要方法:

  virtual void RegisterCaptureDataCallback(
      rtc::VideoSinkInterface<VideoFrame>* dataCallback) = 0;
      
  virtual void DeRegisterCaptureDataCallback() = 0;

  virtual int32_t StartCapture(const VideoCaptureCapability& capability) = 0;

  virtual int32_t StopCapture() = 0;

这里的 RegisterCaptureDataCallback 是用来注册获取数据的回调,当我们打开 rtc::VideoSinkInterface

类的时候可以看到 OnFrame函数,如下,

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

使用时,只需要实现 VideoSinkInterface 接口,再调用RegisterCaptureDataCallback注册即可。

下面介绍 VideoCaptureImpl 类,此类继承 VideoCaptureModule ,主要函数IncomingFrame

  // `capture_time` must be specified in NTP time format in milliseconds.
  int32_t IncomingFrame(uint8_t* videoFrame,
                        size_t videoFrameLength,
                        const VideoCaptureCapability& frameInfo,
                        int64_t captureTime = 0);

其实早些版本的webrtc,是有 VideoCaptureExternal 类,提供提供了 IncomingFrame的纯虚方法,

而后 VideoCaptureImpl 类继承VideoCaptureModule 和 VideoCaptureExternal 类,从而具有了

IncomingFrame方法, 但从最新的源码中看,VideoCaptureExternal 类已经删除 ,IncomingFrame

方法也直接放入 VideoCaptureImpl 中。VideoCaptureImpl 的主要功能,是提供了YUV格式的转换,

其实现放在 IncomingFrame 方法中。

那为什么需要yuv格式的转换呢?

不管我们是使用 android 的camera或者 linux中的V4L2,摄像头直接采集出来的都不是 I420(YUV420P),

但是x264 和 x265需要输入的YUV格式 通常 为 I420,所以采集出来的YUV数据在送入编码其之前都

需要做一次YUV之间的转换,转成编码器需要的格式,需要解释一下的是,编码器的输入源并非只是I420,

也可以是其他YUV格式。并且有时我们会选用硬编,根据硬件厂商的不同,可以支持的YUV格式也不同,所以,

是否需要转换需要根据实际情况而定。就 IncomingFrame 实现来看,webrtc提供了转换为I420的转换实现。

以下是 IncomingFrame 中的部分实现。

 const int conversionResult = libyuv::ConvertToI420(
      videoFrame, videoFrameLength, buffer.get()->MutableDataY(),
      buffer.get()->StrideY(), buffer.get()->MutableDataU(),
      buffer.get()->StrideU(), buffer.get()->MutableDataV(),
      buffer.get()->StrideV(), 0, 0,  // No Cropping
      width, height, target_width, target_height, rotation_mode,
      ConvertVideoType(frameInfo.videoType));

下面以linux中 的视频采集为例稍作说明,VideoCaptureModuleV4L2 类继承了 VideoCaptureImpl 类,实现

如下方法。

  virtual int32_t StartCapture(const VideoCaptureCapability& capability) = 0;

  virtual int32_t StopCapture() = 0;

VideoCaptureModuleV4L2 类中主要调用了 V4L2的接口去实现视频的采集, 主要流程是,通过open

打开video fd,调用ioctl接口,结合相应的宏,完成视频的采集。(采集的实现细节就不在此展开,非难点也非

本文重点)

下面说一下 rtc::VideoSourceInterface 和 rtc::VideoSinkInterface 接口类,分别摘取其中重要的接口方法,
如下:

rtc::VideoSourceInterface
  virtual void AddOrUpdateSink(VideoSinkInterface<VideoFrameT>* sink,
                               const VideoSinkWants& wants) = 0;
                               
  virtual void RemoveSink(VideoSinkInterface<VideoFrameT>* sink) = 0;

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

从接口方法中不难看出,source interface为视频的出口接口,sink interface 为视频入口接口

所以我们只需要顺着该思路就能探索出视频音视频流向的原理。

当我们使用peer_connection方法推流时,首先使用 webrtc::CreatePeerConnectionFactory 创建

PeerConnectionFactory ,而后 PeerConnectionFactory 创建 PeerConnection , PeerConnection

通过AddTrack 添加视频源,随后通过 CreateOffer 创建 sdp,server端收到 sdp后解析成功,发送

answer sdp消息,推流端收到后解析answer sdp,完成信息收集,开始推流。以上就是ice 推流大

体过程,当然,这只是通过ice实现我们推流端到公网server的交互,而并非p2p形式的交互。

在上文中我们采集好视频后,是需要通过 PeerConnection 的AddTrack 方法将视频加入到

PeerConnection中,那我们先看两段源码。

源码一
rtc::scoped_refptr<PeerConnectionFactoryInterface> CreatePeerConnectionFactory(
    rtc::Thread* network_thread,
    rtc::Thread* worker_thread,
    rtc::Thread* signaling_thread,
    rtc::scoped_refptr<AudioDeviceModule> default_adm,
    rtc::scoped_refptr<AudioEncoderFactory> audio_encoder_factory,
    rtc::scoped_refptr<AudioDecoderFactory> audio_decoder_factory,
    std::unique_ptr<VideoEncoderFactory> video_encoder_factory,
    std::unique_ptr<VideoDecoderFactory> video_decoder_factory,
    rtc::scoped_refptr<AudioMixer> audio_mixer,
    rtc::scoped_refptr<AudioProcessing> audio_processing) 

源码二

RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
PeerConnectionInterface::AddTrack(
    rtc::scoped_refptr<MediaStreamTrackInterface> track,
    const std::vector<std::string>& stream_ids) {
  return RTCError(RTCErrorType::UNSUPPORTED_OPERATION, "Not implemented");
}

源码一种我们发现编解码器是在创建 PeerConnectionFactory 时给到PeerConnectionFactory

的,我们先不管编解码器最终交给了哪个对象, 源码二显示我们的 track 至少实现了 MediaStreamTrackInterface

接口,可是当我们查看 MediaStreamTrackInterface 接口时却发现 其中并没有视频传出的接口

我们接着往下跟 AddTrack,下面展示一下方法的调用过程。

在AddTrack 方法中

RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack(
    rtc::scoped_refptr<MediaStreamTrackInterface> track,
    const std::vector<std::string>& stream_ids)

进入到 AddTrackUnifiedPlan 方法 (还有 AddTrackPlanB 方法,根据sdp 格式不同做不同选择,
目前Plan B已经基本不用,所以我们只分析 AddTrackUnifiedPlan 方法)

RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
PeerConnection::AddTrackUnifiedPlan(
    rtc::scoped_refptr<MediaStreamTrackInterface> track,
    const std::vector<std::string>& stream_ids)

进入到 CreateSender  
auto sender = CreateSender(media_type, sender_id, track, stream_ids, {});

调用到 
 bool set_track_succeeded = sender->SetTrack(track);
 
至此,AddTrack的调用完成,在SetTrack中 track 值被赋值给track_ ,我们内部变量
track_最终保留了track的值,我们接着看track_的使用。

在 VideoRtpSender 中(上面 CreateSender创建出的即为 VideoRtpSender)

  rtc::scoped_refptr<VideoTrackInterface> video_track() const {
    return rtc::scoped_refptr<VideoTrackInterface>(
        static_cast<VideoTrackInterface*>(track_.get()));
  }

当我们看到 video_track 中下面这行代码的时候,是否已经恍然大悟,为什么在AddTrack

的MediaStreamTrackInterface接口中没有视频数据出口,没有视频数据出口,采集好的视频

又是怎送到编码器呢,这些疑惑都可以用下面这行代码解释,也就是说,我们传入AddTrack中的

对象,实际是实现了 VideoTrackInterface 接口类的对象。

 static_cast<VideoTrackInterface*>(track_.get())) 

VideoTrackInterface 接口类的继承关系如下,
class VideoTrackInterface : public MediaStreamTrackInterface,
                            public rtc::VideoSourceInterface<VideoFrame>

有下面几个主要方法 

void AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink,
                       const rtc::VideoSinkWants& wants) override {}
void RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) override {}

virtual VideoTrackSourceInterface* GetSource() const = 0;    

我们现在从 VideoRtpSender 中查找哪里调用了 video_track() ,如下

void VideoRtpSender::SetSend() {   
  bool success = worker_thread_->Invoke<bool>(RTC_FROM_HERE, [&] {
    return video_media_channel()->SetVideoSend(ssrc_, &options, video_track());
}  

我们再查找 SetVideoSend 的实现,当看到下面这段代码的时候,知道我们的视频源被送到了这里,赋值

给了 source_ 。

bool WebRtcVideoChannel::WebRtcVideoSendStream::SetVideoSend(
    const VideoOptions* options,
    rtc::VideoSourceInterface<webrtc::VideoFrame>* source) {
     source_ = source;
    }

现在我们在 WebRtcVideoChannel::WebRtcVideoSendStream 中查找 source_ 的调用。

void WebRtcVideoChannel::WebRtcVideoSendStream::AddOrUpdateSink(   
 rtc::VideoSinkInterface<webrtc::VideoFrame>* sink,
    const rtc::VideoSinkWants& wants){
   source_->AddOrUpdateSink(encoder_sink_, wants);
}              

不出意外我找到了想要的代码,source_->AddOrUpdateSink(encoder_sink_, wants);

这行代码很好的解释了,我们的视频源流向了哪里,还记得文章开始时候提到的两个有关数据流向

的接口吗,在这里完美的对上了,因为 encoder需要有视频的输入,所以必然继承了sink的接口

,通过sink中的onframe 接收数据。至此,视频采集和送入编码器的过程已经分析完毕。
              

通过分析,我们知道的视频数据的初步传输过程。当然要想把视频推出去还是有很多

工作要做。 上文中关于编码器在 peer_connection_factory 中最后被存放到了哪里,大家

可以跟一下代码,个人觉得还是比较容易懂的,

总结:本人第一次写文章,排版有点生疏,望见谅, 上文中很多代码示例只是伪代码,只为

方便突出重点。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值