webrtc源码阅读之视频采集、编码、发送

版本m98。以Offer端的视角进行源码分析。
在examples/peerconnection源码阅读中,我们有个关键函数Conductor::AddTracks,在这个函数中会先Create一个video track,然后把这个track add到peerconnection中。从而进行视频采集、编码和发送。

void Conductor::AddTracks() {
  if (!peer_connection_->GetSenders().empty()) {
    return;  // Already added tracks.
  }
  //生成audio source和track并添加到peerconnection中
  rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
      peer_connection_factory_->CreateAudioTrack(
          kAudioLabel, peer_connection_factory_->CreateAudioSource(
                           cricket::AudioOptions())));
  auto result_or_error = peer_connection_->AddTrack(audio_track, {kStreamId});
  if (!result_or_error.ok()) {
    RTC_LOG(LS_ERROR) << "Failed to add audio track to PeerConnection: "
                      << result_or_error.error().message();
  }

  //生成video source和track并添加到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_);   //把localRenderer作为sink添加到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";
  }

  //切换到streaming ui
  main_wnd_->SwitchToStreamingUI();
}

一、 视频采集

1.1 采集

视频的采集就是通过CapturerTrackSource实现的。

rtc::scoped_refptr<CapturerTrackSource> video_device =
      CapturerTrackSource::Create();
  static rtc::scoped_refptr<CapturerTrackSource> Create() {
    const size_t kWidth = 640;
    const size_t kHeight = 480;
    const size_t kFps = 30;
    std::unique_ptr<webrtc::test::VcmCapturer> capturer;
    std::unique_ptr<webrtc::VideoCaptureModule::DeviceInfo> info(
        webrtc::VideoCaptureFactory::CreateDeviceInfo());
    if (!info) {
      return nullptr;
    }
    //获取并遍历所有的显示设备,并找到能够采集的设备,实际的采集工作都是由VcmCapturer来实现的
    int num_devices = info->NumberOfDevices(); 
    for (int i = 0; i < num_devices; ++i) {
      capturer = absl::WrapUnique(
          webrtc::test::VcmCapturer::Create(kWidth, kHeight, kFps, i));
      if (capturer) {
        return new rtc::RefCountedObject<CapturerTrackSource>(
            std::move(capturer));
      }
    }

    return nullptr;
  }

VcmCapturer::Create函数中,会生成一个VcmCapturer对象,并初始化。
在初始化的过程中,会生成一个与平台相关的vcm采集对象,并且将捕获视频数据的回调设置为自己,也就是视频捕获成功后会回调自己的OnFrame函数。然后就开始采集了。

  • 以linux 为例,看一下采集的流程
    VideoCaptureModuleV4L2::StartCapture > VideoCaptureModuleV4L2::CaptureProcess > VideoCaptureImpl::IncomingFrame > VideoCaptureImpl::DeliverCapturedFrame > VcmCapturer::OnFrame > TestVideoCapturer::OnFrame > VideoBroadcaster::OnFrame
    VideoCaptureModuleV4L2::StartCapture中会做一些采集的初始化,并设置一些采集的参数,如分辨率、帧率、格式等,然后循环执行VideoCaptureModuleV4L2::CaptureProcess,也就是真正的采集过程。
    采集到视频数据后交给VideoCaptureImpl::IncomingFrame函数进行处理,比如选择操作等,并把输出格式转换为YUV I420,同时设置采集的时间戳。
    最终,视频数据会到视频广播器中,广播器会把视频发送给所有合适的sink。

二、视频编码

而sink是如何注册的呢?

2.1 AddTrack

我们要看Conductor::AddTracks中的另一个关键函数result_or_error = peer_connection_->AddTrack(video_track_, {kStreamId});

  • 首先是Create一个RtpTransceiver
    PeerConnection::AddTrack > RtpTransmissionManager::AddTrack > RtpTransmissionManager::AddTrackUnifiedPlan > RtpTransmissionManager::CreateSender > VideoRtpSender::Create
    RtpTransmissionManager::AddTrackUnifiedPlan函数中会创建一个RtpTransceiver,一个RtpTransceiver里面有一个RtpSender和一个RtpReceiver
  • 然后通过RtpSenderBase::SetTrack 函数把track设置到RtpSender里面,这里只是做了赋值操作,因为此时ssrc还没设置,所以不会做SetSend的操作。

2.2 PeerConnection::SetLocalDescription

接下来我们回到examples/peerconnection的Conductor::ConnectToPeer,当InitializePeerConnection以后会PeerConnection::CreateOffer,生成offer成功后回调Conductor::OnSuccess,然后调用PeerConnection::SetLocalDescription
PeerConnection::SetLocalDescription > SdpOfferAnswerHandler::SetLocalDescription > SdpOfferAnswerHandler::DoSetLocalDescription > SdpOfferAnswerHandler::ApplyLocalDescription

SdpOfferAnswerHandler::ApplyLocalDescription中会调用两个关键函数SdpOfferAnswerHandler::UpdateTransceiversAndDataChannelsSdpOfferAnswerHandler::UpdateSessionState

  • SdpOfferAnswerHandler::UpdateTransceiversAndDataChannels会更新Transceiver和DataChannel相关信息。
    SdpOfferAnswerHandler::AssociateTransceiver中Transceiver在RtpTransmissionManager::AddTrackUnifiedPlan已经创建成功,本函数就只是更新Transceiver相关状态信息。
    SdpOfferAnswerHandler::UpdateTransceiverChannel会创建VideoChannel,也就是WebRtcVideoChannel。并把WebRtcVideoChannel设置到TransceiverRtpSenderRtpReceiver里。
    DataChannel不属于视频相关内容,本文不再分析。
  • SdpOfferAnswerHandler::UpdateSessionState用来更新session相关状态。
    SdpOfferAnswerHandler::UpdateSessionState > SdpOfferAnswerHandler::PushdownMediaDescription > BaseChannel::SetLocalContent > VideoChannel::SetLocalContent_w > BaseChannel::UpdateLocalStreams_w
    – 在BaseChannel::UpdateLocalStreams_w中会调用WebRtcVideoChannel::AddSendStream创建WebRtcVideoSendStream,并把WebRtcVideoSendStream添加到WebRtcVideoChannel。同时通过赋值操作local_streams_ = all_streams,对local_streams_ 进行赋值。
    – 我们先看看WebRtcVideoChannel::AddSendStream做了什么:
    WebRtcVideoChannel::AddSendStream > WebRtcVideoChannel::WebRtcVideoSendStream::WebRtcVideoSendStream > WebRtcVideoChannel::WebRtcVideoSendStream::SetCodec > WebRtcVideoChannel::WebRtcVideoSendStream::RecreateWebRtcStream > Call::CreateVideoSendStream > VideoSendStream::VideoSendStream > VideoSendStream::ReconfigureVideoEncoder > VideoStreamEncoder::ConfigureEncoder > VideoStreamEncoder::ReconfigureEncoder
    – 在VideoStreamEncoder::ReconfigureEncoder会调用BuiltinVideoEncoderFactory::CreateVideoEncoder创建编码器,然后进行一系列初始化,再通过encoder_->RegisterEncodeCompleteCallback(this)将编码完成的回调设置为自己,也就是说当编码完成时就会回到VideoStreamEncoderOnEncodedImage函数。
    这样就完成了编码器的创建以及编码完成的回调事件。
  • 回过头来看SdpOfferAnswerHandler::ApplyRemoteDescription,在UpdateSessionState以后,会给Sender设置ssrc
 transceiver->sender_internal()->SetSsrc(streams[0].first_ssrc());

设置完ssrc以后,就可以执行SetSend函数了:
RtpSenderBase::SetSsrc > VideoRtpSender::SetSend > WebRtcVideoChannel::SetVideoSend > WebRtcVideoChannel::WebRtcVideoSendStream::SetVideoSend > VideoSendStream::SetSource > VideoStreamEncoder::SetSource > VideoSourceSinkController::SetSource > AdaptedVideoTrackSource::AddOrUpdateSink
这个source就是1.1采集里面的VcmCapturer,这个sink其实就是在创建VideoStreamEncoder时候,创建的FrameCadenceAdapterInterface

  return std::make_unique<VideoStreamEncoder>(
      clock, num_cpu_cores, stats_proxy, encoder_settings,
      std::make_unique<OveruseFrameDetector>(stats_proxy),
      FrameCadenceAdapterInterface::Create(clock, encoder_queue_ptr),
      std::move(encoder_queue), bitrate_allocation_callback_type);

这样,就把FrameCadenceAdapterInterface作为sink,注册给了VcmCapturer。也就是说,当采集到一帧图像后,就会调用FrameCadenceAdapterImpl::OnFrame函数了。

2.3 采集->编码

上面我们已经知道了,当采集完一帧图像后,会调用FrameCadenceAdapterImpl::OnFrame,那这个函数又是如何跟编码联系上的呢?继续看这个函数:
FrameCadenceAdapterImpl::OnFrame > FrameCadenceAdapterImpl::OnFrameOnMainQueue > ZeroHertzAdapterMode::OnFrame > VideoStreamEncoder::OnFrame > VideoStreamEncoder::MaybeEncodeVideoFrame > VideoStreamEncoder::EncodeVideoFrame > H264EncoderImpl::Encode > VideoStreamEncoder::OnEncodedImage
上面的流程就涵盖了从采集完成到编码到编码完成回调的过程。

三、视频发送

上一节我们已经分析完了采集->编码的过程,同时我们也知道了编码后会回调到VideoStreamEncoder::OnEncodedImage。这个函数会把编码数据拷贝一份并发送给自己的sink_

  EncodedImageCallback::Result result =
      sink_->OnEncodedImage(image_copy, codec_specific_info);

那么,这个sink_又是谁呢?
其实在VideoSendStreamImpl创建的时候,就调用了VideoStreamEncoder::SetSink,将VideoStreamEncodersink_设置为了VideoSendStreamImpl

video_stream_encoder_->SetSink(this, rotation_applied);

所以VideoStreamEncoder::OnEncodedImage会调用VideoSendStreamImpl::OnEncodedImage,继而调用到RtpVideoSender::OnEncodedImage
然后调用RTPSenderVideo::SendEncodedImage > RTPSenderVideo::SendVideo
在这个函数中对编码数据进行RTP打包,然后调用RTPSenderVideo::LogAndSendToNetwork把RTP包发送至发送队列中进行发送。

不同编码格式的编码数据有不同的打包方式,我们会在其他文章分析;而webrtc中rtp数据发送是一系列算法完成的,比较复杂,本文中也不再分析。

至此,我们分析完成了webrtc视频的采集、编码、发送的全过程。

你可能感兴趣的:(webrtc,webrtc,音视频)