版本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();
}
视频的采集就是通过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
函数。然后就开始采集了。
VideoCaptureModuleV4L2::StartCapture
> VideoCaptureModuleV4L2::CaptureProcess
> VideoCaptureImpl::IncomingFrame
> VideoCaptureImpl::DeliverCapturedFrame
> VcmCapturer::OnFrame
> TestVideoCapturer::OnFrame
> VideoBroadcaster::OnFrame
VideoCaptureModuleV4L2::StartCapture
中会做一些采集的初始化,并设置一些采集的参数,如分辨率、帧率、格式等,然后循环执行VideoCaptureModuleV4L2::CaptureProcess
,也就是真正的采集过程。VideoCaptureImpl::IncomingFrame
函数进行处理,比如选择操作等,并把输出格式转换为YUV I420,同时设置采集的时间戳。而sink是如何注册的呢?
AddTrack
我们要看Conductor::AddTracks
中的另一个关键函数result_or_error = peer_connection_->AddTrack(video_track_, {kStreamId});
RtpTransceiver
。PeerConnection::AddTrack
> RtpTransmissionManager::AddTrack
> RtpTransmissionManager::AddTrackUnifiedPlan
> RtpTransmissionManager::CreateSender
> VideoRtpSender::Create
RtpTransmissionManager::AddTrackUnifiedPlan
函数中会创建一个RtpTransceiver
,一个RtpTransceiver
里面有一个RtpSender
和一个RtpReceiver
。RtpSenderBase::SetTrack
函数把track
设置到RtpSender
里面,这里只是做了赋值操作,因为此时ssrc
还没设置,所以不会做SetSend
的操作。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::UpdateTransceiversAndDataChannels
和SdpOfferAnswerHandler::UpdateSessionState
SdpOfferAnswerHandler::UpdateTransceiversAndDataChannels
会更新Transceiver和DataChannel相关信息。SdpOfferAnswerHandler::AssociateTransceiver
中Transceiver在RtpTransmissionManager::AddTrackUnifiedPlan
已经创建成功,本函数就只是更新Transceiver相关状态信息。SdpOfferAnswerHandler::UpdateTransceiverChannel
会创建VideoChannel
,也就是WebRtcVideoChannel
。并把WebRtcVideoChannel
设置到Transceiver
的RtpSender
和RtpReceiver
里。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)
将编码完成的回调设置为自己,也就是说当编码完成时就会回到VideoStreamEncoder
的OnEncodedImage
函数。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
函数了。
上面我们已经知道了,当采集完一帧图像后,会调用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
,将VideoStreamEncoder
的sink_
设置为了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视频的采集、编码、发送的全过程。