webrtc源码阅读之examples/peerconnection

news2024/11/23 10:30:34

阅读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(); //设置连接的一些callback
  char 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到server
  if (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()) {  //初始化PeerConnection
    peer_id_ = peer_id;
    peer_connection_->CreateOffer(   //生成Offer
        this, 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 ui
  main_wnd_->SwitchToStreamingUI();
}

2.1.2 CreateOffer

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

peer_connection_->CreateOffer(   //生成Offer
    this, 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 description

  std::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;
    //初始化peerconnection
    if (!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的sdp
    if (!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(    //设置远程sdp
        DummySetSessionDescriptionObserver::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())) { //添加candidate
      RTC_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转换为RGB
    libyuv::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.coloradmin.cn/o/703423.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mysql架构篇--Mysql(M-M) 主从同步

文章目录 前言一、M-M 介绍&#xff1a;二、M-M 搭建&#xff1a;1.Master1&#xff1a;1.1 my.cnf 参数配置&#xff1a;1.2 创建主从同步用户&#xff1a;1.3 开启复制&#xff1a; 2.Master2&#xff1a;2.1 my.cnf 参数配置&#xff1a;2.2 创建主从同步用户&#xff1a;2.…

飞桨携手登临解读软硬一体技术优势,共推AI产业应用落地

众所周知&#xff0c;AI应用落地面临着场景碎片化、开发成本高、算力成本高等诸多难题&#xff0c;这对AI框架与AI芯片都提出了非常高的要求&#xff0c;即既要满足端、边、云多场景的部署需求&#xff0c;还需要支持自动化压缩与高性能推理引擎深度联动。因此充分发挥软硬一体…

通过一个简单的例子理解 Python 中的多线程

关于进程和线程的概念可以看这篇文章: https://blog.csdn.net/ThinkWon/article/details/102021274 什么时候需要多线程编程&#xff1f;简单来说就是一个程序里面有比较耗时间的操作&#xff0c;你希望先让它单独跑着&#xff0c;直接开始进行下一步的操作&#xff08;Python默…

centos中部署调用matlab程序

环境&#xff1a; Java8 MATLAB2018b centos7 在本机环境上运行成功后的代码打包部署到服务器上运行会报错&#xff0c;需要在服务器上安装MATLAB的运行环境。 首先下载跟本机MATLAB环境一样的mcr文件。 打开网址 http://cn.mathworks.com/products/compiler/mcr/ 下载对…

Python调用百度地图API实现路径规划提取真实距离、时间

1.注册百度地图开放平台账号 网址&#xff1a;百度地图开放平台 | 百度地图API SDK | 地图开发 2.打开控制台&#xff0c;创建应用、获取AK 如下图所示 桥重点、敲重点、瞧重点 在使用python调用API端口时&#xff0c;我们需要申请的为服务端应用类别&#xff0c;别申请错了…

JS实现简单拼图游戏

JS实现简单拼图游戏 点击“打乱”按钮开始游戏&#xff1b;按下鼠标左键拖动一块到另一块上松开鼠标左键互换。游戏资源为一张图片 我这里名称为 2.jpg&#xff0c;将其与下面的网页文件放在同一目录中即可。 网页文件源码如下&#xff1a; <!DOCTYPE html> <html&…

Chapter 1: Introduction - Why Program? | Python for Everybody 讲义_Cn

文章目录 Python for Everybody课程简介适合所有人的 Python (Why Program?)为什么要学习写程序&#xff1f;创造力和动力计算机硬件架构了解编程单词和句子与 Python 对话术语&#xff1a;解释器和编译器Writing a program什么是程序&#xff1f;The building blocks of prog…

移远通信联合中国移动研究院等伙伴发布5G RedCap和车载模组白皮书

6月28日&#xff0c;在“2023年上海世界移动通信大会(MWC)”上&#xff0c;中国移动研究院联合移远通信等产业合作伙伴&#xff0c;共同发布了《5G RedCap轻量化通用模组技术要求白皮书》以及《车载模组技术发展白皮书》。 白皮书的发布为5G RedCap和车载领域技术和产业的进一步…

pdf加水印怎么加?一分钟学会!

在办公场景中&#xff0c;我们经常需要向客户或同事发送PDF格式的文件。这时就会面临一个问题&#xff1a;如何为我们的劳动成果打上个人烙印呢?答案是通过添加水印来满足我们的需求。但是&#xff0c;如何给PDF添加水印呢?难道要手动一个个添加吗?这无疑非常费力&#xff0…

整合来自多个Aurora数据库数据,亚马逊云科技为用户提供数据分析一体化融合解决方案

亚马逊云科技近日在沙利文联合头豹研究院发布的《2023年中国数据管理解决方案市场报告》中再次获评中国数据管理解决方案的领导者位置&#xff0c;并在增长指数和创新指数上获得最高评分。亚马逊云科技凭借其独特的数据服务和数据湖组合、全面的无服务器选项、打破数据传输壁垒…

ReentrantReadWriteLock源码

介绍 用一个变量如何维护多种状态 在 ReentrantLock 中&#xff0c;使用 Sync ( 实际是 AQS )的 int 类型的 state 来表示同步状态&#xff0c;表示锁被一个线程重复获取的次数。 但是&#xff0c;读写锁 ReentrantReadWriteLock 内部维护着一对读写锁&#xff0c;如果要用一…

软件自动化测试初学者忠告,错过就后悔了

目录 自动化测试进阶 自动化测试的层次 脚本 1-1 不要在实际项目中使用录制和回放 1-2 不要使用暂停 1-3 在循环中超时退出 1-4 不要将自动化测试完全等同于开发 1-5 不要写复杂的代码 1-6 验证逻辑条件的所有选项 1-7 使用编程规范 1-8 使用静态代码分析器 1-9 随…

Jmeter使用beanshell对接口加密,调用AES代码的jar包,CBC模式,有偏移量。

目录 前言&#xff1a; 这是AES加密代码 beanshell中的脚本 前言&#xff1a; 对接口进行加密是保障数据安全的重要措施之一。在JMeter中&#xff0c;你可以使用BeanShell脚本来实现对接口数据的加密处理。 工作中需要对接口进行AES加密&#xff0c;找开发要来了加密的代码…

PADS Logic的格点推荐设置

使用合适的格点大小能有效的提高我们设计的效率以及所设计文件的美观性。 第一步&#xff1a;执行菜单命令工具-选项&#xff0c;如图1所示 图1 设置选项示意图 第二步&#xff1a;在弹出的选项窗口常规栏中可以找到栅格设置&#xff0c;如图2所示&#xff0c;在设计中通常设置…

真无线蓝牙耳机排行榜10强,十款真无线蓝牙耳机盘点

为了帮助大家在这个充满选择的世界中找到最理想的蓝牙耳机&#xff0c;我们特别为您精心挑选了几款备受赞誉的产品&#xff0c;它们在音质、舒适度、功能和性价比等方面都有出色的表现。在本文中&#xff0c;我们将深入探讨这些蓝牙耳机的特点和优势&#xff0c;帮助您更好地了…

es学习知识汇总

es的索引库就相当于mysql的表 es的文档就相当于mysql的一条数据&#xff08;内容&#xff09; 用代码创建索引库到es 新增文档&#xff08;相当于mysql的一条数据&#xff08;内容&#xff09; 模拟将数据库中的到内容新增到es中 查询文档 注&#xff1a;以下 hotel为索引库名…

如何用迅捷PDF转换器在线压缩PDF文件

大家在工作中传输和保存PDF文件&#xff0c;有时发现PDF文件过大不利于保存&#xff0c;这种情况很多人都会对其进行压缩&#xff0c;但在压缩PDF大小的同时&#xff0c;大家也要注意PDF文件压缩后的质量&#xff0c;那么怎么才能又压缩大小&#xff0c;又能保持文件清晰度呢&a…

GitLab安装及代码管理

一、安装 环境&#xff1a;centos7 1.1、下载rpm安装脚本命令 curl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash 上边命令里的rpm安装脚本地址可能会随时更新&#xff0c;当前需要用的是哪个地址&#xff0c;可以参考…

Java基础-泛型类、泛型方法、泛型接口

泛型的简单使用 集合中存储数据&#xff0c;使用到的泛型 // 如果我们没有给集合指定类型&#xff0c;默认认为所有的数据类型都是Object类型 // 此时可以往集合添加任意的数据类型。 // 带来一个坏处:我们在获取数据的时候&#xff0c;无法使用他的特有行为。 // 此时推出了泛…

新生产力革命下,亚马逊云科技超600种不同计算实例满足算力要求

近日&#xff0c;一年一度的亚马逊云科技中国峰会在上海如期召开。在本次峰会上可以切实地感受到亚马逊云科技的Day one理念&#xff0c;并且也对AI创新做了详细解读。 “AI创新”使算力需求井喷、运维复杂性增加 随着AI大模型的发展&#xff0c;大模型应用规模呈爆发式增长…