webrtc源码阅读之examples/peerconnection

阅读webrtc源码,从examples中的peerconnection开始。版本m98

一、 基本流程

peerconnection 流程图
server端只是做了一个http server,来转发client端的消息。也就是起到了信令服务器的作用,本篇文章不在研究,感兴趣的可以学习一下用cpp搭建http server。

二、Client代码基本流程

client界面部分代码分为linux和windows两个平台,我们以linux为例进行分析。

  • 首先,在main函数里调用GtkMainWnd::Create()创建了gtk的窗口界面;
  • 然后调用SwitchToConnectUI()切换到连接ui界面;
  • 在这个界面里有个名字为Connectbutton,button对应的点击事件会调用OnClickedCallback函数。
g_signal_connect(button, "clicked", G_CALLBACK(OnClickedCallback), this);
  • OnClickedCallback > GtkMainWnd::OnClicked > Conductor::StartLogin > PeerConnectionClient::Connect
  • PeerConnectionClient::Connect函数中,会设置server的ip和port,以及client自己的name,然后调用DoConnect()函数真正的进行连接。
void PeerConnectionClient::DoConnect() {control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family()));hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family()));InitSocketSignals(); //设置连接的一些callbackchar buffer[1024];snprintf(buffer, sizeof(buffer), "GET /sign_in?%s HTTP/1.0\r\n\r\n",client_name_.c_str());onconnect_data_ = buffer;bool ret = ConnectControlSocket(); //发送sign in到serverif (ret)state_ = SIGNING_IN;if (!ret) {callback_->OnServerConnectionFailure();}
}
  • server返回的消息均在PeerConnectionClient::OnRead里进行处理,向server发送sign in后会调用Conductor::OnSignedIn(),然后切换至peer list。
  • 在Peer List界面中会把其他的client全部添加到列表中。

2.1 主动发起连接端

选择列表中的client后可以触发OnRowActivatedCallback函数,从而调用Conductor::ConnectToPeer

void Conductor::ConnectToPeer(int peer_id) {RTC_DCHECK(peer_id_ == -1);RTC_DCHECK(peer_id != -1);if (peer_connection_.get()) {main_wnd_->MessageBox("Error", "We only support connecting to one peer at a time", true);return;}if (InitializePeerConnection()) {  //初始化PeerConnectionpeer_id_ = peer_id;peer_connection_->CreateOffer(   //生成Offerthis, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());} else {main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);}
}

2.1.1 初始化PeerConnection

bool Conductor::InitializePeerConnection() {RTC_DCHECK(!peer_connection_factory_);RTC_DCHECK(!peer_connection_);if (!signaling_thread_.get()) {signaling_thread_ = rtc::Thread::CreateWithSocketServer();signaling_thread_->Start();}peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(   nullptr /* network_thread */, nullptr /* worker_thread */,signaling_thread_.get(), nullptr /* default_adm */,webrtc::CreateBuiltinAudioEncoderFactory(),webrtc::CreateBuiltinAudioDecoderFactory(),webrtc::CreateBuiltinVideoEncoderFactory(),webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,nullptr /* audio_processing */);if (!peer_connection_factory_) {main_wnd_->MessageBox("Error", "Failed to initialize PeerConnectionFactory",true);DeletePeerConnection();return false;}if (!CreatePeerConnection()) { main_wnd_->MessageBox("Error", "CreatePeerConnection failed", true);DeletePeerConnection();}AddTracks();return peer_connection_ != nullptr;
}
  • 首先CreatePeerConnectionFactory,参数中设定了Audio和Video的编解码工厂,全部使用webrtc内置的编解码器。如果想用自己的编解码器就可以从这里入手,实现自己的编解码工厂和编解码器,并且在CreatePeerConnectionFactory中指定。
  • 然后利用peer_connection_factory_来创建PeerConnection
  • 最后是添加音视频tracks。一个stream中可以包含音频track和视频track。每个track中可以有一个source和多个sinksource可以理解为音视频的生产者,sink可以理解为音视频的消费者。
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 uimain_wnd_->SwitchToStreamingUI();
}

2.1.2 CreateOffer

初始化PeerConnection成功以后,发起端会调用CreateOffer函数生成Offer

peer_connection_->CreateOffer(   //生成Offerthis, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());

CreateOffer函数的第一个参数是CreateSessionDescriptionObserver。生成Offer成功会回调CreateSessionDescriptionObserverOnSuccess。而Conductor类继承于CreateSessionDescriptionObserver,所以生成Offer成功后会回调Conductor::OnSuccess

void Conductor::OnSuccess(webrtc::SessionDescriptionInterface* desc) {peer_connection_->SetLocalDescription(DummySetSessionDescriptionObserver::Create(), desc);  //设置local descriptionstd::string sdp;desc->ToString(&sdp);  //将offer转换为字符串// For loopback test. To save some connecting delay.if (loopback_) {// Replace message type from "offer" to "answer"std::unique_ptr<webrtc::SessionDescriptionInterface> session_description =webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp);peer_connection_->SetRemoteDescription(DummySetSessionDescriptionObserver::Create(),session_description.release());return;}Json::StyledWriter writer;Json::Value jmessage;jmessage[kSessionDescriptionTypeName] =webrtc::SdpTypeToString(desc->GetType());jmessage[kSessionDescriptionSdpName] = sdp;SendMessage(writer.write(jmessage)); //将offer发送给server,由server转发给peer client
}

OnSuccess函数中,首先设置peerconnection的local description(这是webrtc的连接的基本流程之一),然后转换为字符串发送给对端。

2.2 被动连接端

被动端跟主动端一样,先在server中注册,然后通过PeerConnectionClient::OnHangingGetRead读取server端的信息,如果收到主动端的信息,就会调用OnMessageFromPeer函数。

void Conductor::OnMessageFromPeer(int peer_id, const std::string& message) {RTC_DCHECK(peer_id_ == peer_id || peer_id_ == -1);RTC_DCHECK(!message.empty());if (!peer_connection_.get()) {RTC_DCHECK(peer_id_ == -1);peer_id_ = peer_id;//初始化peerconnectionif (!InitializePeerConnection()) {RTC_LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance";client_->SignOut();return;}} else if (peer_id != peer_id_) {RTC_DCHECK(peer_id_ != -1);RTC_LOG(LS_WARNING)<< "Received a message from unknown peer while already in a ""conversation with a different peer.";return;}Json::Reader reader;Json::Value jmessage;if (!reader.parse(message, jmessage)) {RTC_LOG(LS_WARNING) << "Received unknown message. " << message;return;}std::string type_str;std::string json_object;rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionTypeName,&type_str);if (!type_str.empty()) {if (type_str == "offer-loopback") {// This is a loopback call.// Recreate the peerconnection with DTLS disabled.if (!ReinitializePeerConnectionForLoopback()) {RTC_LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance";DeletePeerConnection();client_->SignOut();}return;}absl::optional<webrtc::SdpType> type_maybe =webrtc::SdpTypeFromString(type_str);if (!type_maybe) {RTC_LOG(LS_ERROR) << "Unknown SDP type: " << type_str;return;}webrtc::SdpType type = *type_maybe;std::string sdp;if (!rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionSdpName,&sdp)) {RTC_LOG(LS_WARNING)<< "Can't parse received session description message.";return;}webrtc::SdpParseError error;std::unique_ptr<webrtc::SessionDescriptionInterface> session_description =webrtc::CreateSessionDescription(type, sdp, &error);  //根据对端sdp字符串生成webrtc的sdpif (!session_description) {RTC_LOG(LS_WARNING)<< "Can't parse received session description message. ""SdpParseError was: "<< error.description;return;}RTC_LOG(LS_INFO) << " Received session description :" << message;peer_connection_->SetRemoteDescription(    //设置远程sdpDummySetSessionDescriptionObserver::Create(),session_description.release());if (type == webrtc::SdpType::kOffer) {   //如果是offer信息的话,就生成answer并发送给对方peer_connection_->CreateAnswer(this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());}} else {std::string sdp_mid;int sdp_mlineindex = 0;std::string sdp;if (!rtc::GetStringFromJsonObject(jmessage, kCandidateSdpMidName,&sdp_mid) ||!rtc::GetIntFromJsonObject(jmessage, kCandidateSdpMlineIndexName,&sdp_mlineindex) ||!rtc::GetStringFromJsonObject(jmessage, kCandidateSdpName, &sdp)) {RTC_LOG(LS_WARNING) << "Can't parse received message.";return;}webrtc::SdpParseError error;std::unique_ptr<webrtc::IceCandidateInterface> candidate(     //生成candidate,如果sdp中包含由candidate信息的话,不需要这个过程webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, &error));if (!candidate.get()) {RTC_LOG(LS_WARNING) << "Can't parse received candidate message. ""SdpParseError was: "<< error.description;return;}if (!peer_connection_->AddIceCandidate(candidate.get())) { //添加candidateRTC_LOG(LS_WARNING) << "Failed to apply the received candidate";return;}RTC_LOG(LS_INFO) << " Received candidate :" << message;}
}

收到对端信息后,进行解析,解析到sdp信息的话,安装webrtc标准流程设置sdp。如果是被动端,就会收到主动端的offer信息,那么就需要生成answer并发送给对端。
CreateAnswerCreateOffer类似,成功后会回调OnSuccess函数,与主动端的流程一致。

2.3 视频播放

peerconnectionSetRemoteDescription信息时,如果sdp中有media,则会回调PeerConnectionObserverOnAddTrack函数。Conductor继承于PeerConnectionObserver,也就是会回调Conductor::OnAddTrack,从而触发Conductor::UIThreadCallbackNEW_TRACK_ADDED

    case NEW_TRACK_ADDED: {auto* track = reinterpret_cast<webrtc::MediaStreamTrackInterface*>(data);if (track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {auto* video_track = static_cast<webrtc::VideoTrackInterface*>(track);main_wnd_->StartRemoteRenderer(video_track);}track->Release();break;}

在这里,会像本地预览那样,将远程video tracksink设置为remote_renderer_。这样当收到对端的视频数据后,就会触发remote_renderer_OnFrame函数。

void MainWnd::VideoRenderer::OnFrame(const webrtc::VideoFrame& video_frame) {{AutoLock<VideoRenderer> lock(this);rtc::scoped_refptr<webrtc::I420BufferInterface> buffer(video_frame.video_frame_buffer()->ToI420());if (video_frame.rotation() != webrtc::kVideoRotation_0) {buffer = webrtc::I420Buffer::Rotate(*buffer, video_frame.rotation());}//设置宽高SetSize(buffer->width(), buffer->height());RTC_DCHECK(image_.get() != NULL);//将yuv转换为RGBlibyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(), buffer->DataU(),buffer->StrideU(), buffer->DataV(), buffer->StrideV(),image_.get(),bmi_.bmiHeader.biWidth * bmi_.bmiHeader.biBitCount / 8,buffer->width(), buffer->height());}InvalidateRect(wnd_, NULL, TRUE);
}

Onfame函数中,会把接收到的yuv数据利用libyuv库转换为rgb数据,并存储在image_中。

当MainWnd触发OnPaint事件时,就可以把image_数据渲染到窗口上了。这个在不同的平台可以用不同的方法进行渲染,本文就不再分析了。

2.4 建立连接

双方交换完成offeranswer以后,如果sdp中包含了candidate信息,则会直接按照sdp中的candidate信息尝试连接;如果sdp中不包含candidate信息,则需要手动发送candidate到对端,流程也在OnMessageFromPeer中。这样就建立起了连接,可以进行正常的音视频交互了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/4649.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【技术新趋势】面向图像文档的版面智能分析与理解

目录 一、什么是OCR&#xff1f;什么是版面分析理解&#xff1f;二、文档版面分析2.1、版面布局类型2.2、面向文档图像版面分析的实例分割2.3、逻辑结构分析 三、文档版面理解3.1、位置嵌入3.2、表格数据提取 四、智能文档处理技术新解决方案 人类撰写文档是为了记录和保存信息…

网络空间安全专业未来的发展前景以及薪资待遇如何?

不管是考虑未来报读专业的准大学生&#xff0c;还是初入职场的实习生&#xff0c;亦或是想要跳槽转岗的职场人&#xff0c;当我们开始选择一份工作时&#xff0c;本质上都在考虑以下三个问题&#xff1a; 这份工作的收入水平如何&#xff1b;这份工作有没有发展前景&#xff1…

学习系统编程No.27【深入信号处理】

引言&#xff1a; 北京时间&#xff1a;2023/6/27/21:43&#xff0c;刚刚更新完这个星期的第一篇博客&#xff0c;现在刚好趁热打铁&#xff0c;看看写到11点左右&#xff0c;该篇博客能完成多少&#xff0c;并且今天和我预想的一样&#xff0c;通过早睡&#xff0c;成功在7点…

Bean的作用域和生命周期

Bean的作用域和生命周期 &#x1f50e;前置引入 Lombok 相关依赖下载 Lombok 插件 &#x1f50e;Bean的6种作用域对Bean作用域的解释singleton — 单例作用域prototype — 原型作用域request — 请求作用域session — 会话作用域application — 全局作用域websocket — HTTP We…

selenium元素定位---ElementClickInterceptedException(元素点击交互异常)解决方法

目录 前言&#xff1a; 1、异常原因 2、解决方法&#xff1a; 前言&#xff1a; 当使用Selenium进行元素定位和交互时&#xff0c;可能会遇到ElementClickInterceptedException&#xff08;元素点击交互异常&#xff09;的异常。这通常是由于页面上存在其他元素或弹出窗口遮…

MySql进阶篇(附面试快速答法)

文章目录 1、慢查询1.1、如何定位慢查询呢&#xff1f;小总结面试快速答法 1.2、SQL语句执行很慢, 如何分析呢&#xff1f;小总结面试快速答法 2、存储引擎2.1、MySQL体系结构2.2、存储引擎特点小总结 3、索引3.1、什么是索引&#xff1f;小总结面试快速答法 3.2、聚集索引和非…

GO 多线程工具使用和分析

GO 多线程工具使用和分析 Go 语言中的 sync 包提供了一些用于同步和互斥访问共享资源的原语&#xff0c;使用这些可以避免多个goroutine同时访问共享资源时出现的问题&#xff0c;他们有&#xff1a; 互斥锁读写锁condWaitGroupmaponcepoolatomic 本文介绍它们的使用方式 互…

Linux常用命令——findfs命令

在线Linux命令查询工具 findfs 标签或UUID查找文件系统 补充说明 findfs命令依据卷标&#xff08;Label&#xff09;和UUID查找文件系统所对应的设备文件。findfs命令会搜索整个磁盘&#xff0c;看是否有匹配的标签或者UUID没有&#xff0c;如果有则打印到标注输出上。find…

【Opencv】PIL Opencv 向图片写入文字并旋转文字,Opencv图片旋转不截断,Opencv图片旋转不裁剪

文章目录 失真Pillow的实现Opencv的实现不裁剪的旋转图像旋转文字并贴图C的图片透视变换 失真 刚性变换&#xff1a; 只有物体的位置(平移变换)和朝向(旋转变换)发生改变&#xff0c;而形状不变&#xff0c;得到的变换称为刚性变换。刚性变换是最一般的变换。 使用透视变换&a…

基于matlab多运动目标跟踪监测算法实现(附源码)

一、前言 此示例演示如何对来自固定摄像机的视频中的移动对象执行自动检测和基于运动的跟踪。 二、介绍 移动物体检测和基于运动的跟踪是许多计算机视觉应用的重要组成部分&#xff0c;包括活动识别、交通监控和汽车安全。基于运动的对象跟踪问题可以分为两部分&#xff1a; 检…

【人工智能与机器学习】决策树ID3及其python实现

文章目录 1 决策树算法1.1 特征选择1.2 熵&#xff08;entropy&#xff09;1.3 信息增益 2 ID3算法的python实现总结 1 决策树算法 决策树&#xff08;Decision Tree)是一类常见的机器学习方法&#xff0c;是一种非常常用的分类方法&#xff0c;它是一种监督学习。常见的决策树…

ChatGPT实战:高考志愿填报

近期&#xff0c;随着各地陆续发布高考成绩&#xff0c;高考志愿填报市场随之升温&#xff0c;“高报师”再次成为“香饽饽”。填报志愿对中学生来说太难&#xff0c;在一个懵懂的年纪做这样一个决策&#xff0c;份量是比较重的。当普通人没很多的信息做参考的时候&#xff0c;…