当前位置: 首页 > news >正文

PeerConnection深度解析一:CreateOffer

源码路径:

  • api/peer_connection_interface.h

  • pc/peer_connection.cc

  • pc/sdp_offer_answer.cc

  • pc/webrtc_session_description_factory.hhttps://chromium.googlesource.com/external/webrtc  main 分支

对应规范:W3C WebRTC 1.0 §4.4.1

createOffer 在整个信令流程中有什么作用或者说为什么需要调用这个函数,刚开始了解 webrtc的人都会有这个疑问?

想象你要发起一场视频会议,CreateOffer 就是起草一份会议邀请函的过程。

这份"邀请函"里写了什么

  1. 我支持的视频格式:H.264、VP8、VP9我支持的音频格式:Opus、G711

  2. 我的网络入口地址:(等 ICE 收集)

  3. 我的身份证明:(DTLS 证书指纹)

  4. 我们用同一条通道传音视频吗:是(BUNDLE)

  5. 我打算:既发送也接收(sendrecv)

这就是 SDP Offer 的本质——一份本端能力的自我声明。

简单来说CreateOffer 干了四件事:

  1. 排队, 确保不和其他信令操作撞车

  2. 收集能力, 遍历所有 Track/Transceiver我要发什么?收什么?

  3. 等证书, DTLS 证书没好就排队 证书好了才能写入指纹

  4. 拼SDP, 把所有信息组装成文本通过 OnSuccess 回调返回

下面我们一起详细了解下CreateOffer。

1、W3C 规范怎么定义 CreateOffer

W3C 规范对 createOffer() 的定义非常简洁:

生成一个 SDP Offer,包含本端支持的媒体能力配置,用于发起一次新的 WebRTC 会话,或者修改一个已建立的会话。

但"简洁"的背后,规范要求实现方必须处理相当多的细节:

  • 收集当前所有 RtpTransceiver 的媒体描述

  • 根据 RTCOfferAnswerOptions 决定 m= section 的方向

  • 携带本端的 ICE 凭据(ufrag / pwd)

  • 携带 DTLS 证书指纹(a=fingerprint

  • 处理 ICE Restart 逻辑

  • 整个操作必须串行化,不能与其他信令操作并发

这些细节,在 libwebrtc 里都有对应的实现,我们一层一层来看。


2、接口签名

// api/peer_connection_interface.h // See: https://www.w3.org/TR/webrtc/#idl-def-rtcofferansweroptions // Create a new offer.   // The CreateSessionDescriptionObserver callback will be called when done.   virtual void CreateOffer(CreateSessionDescriptionObserver* observer,                            const RTCOfferAnswerOptions& options) = 0;

两个参数,设计非常清晰:

① CreateSessionDescriptionObserver

结果通过 Observer 异步回调,而不是直接返回。 原因是 SDP 生成可能涉及异步操作(最典型的是 DTLS 证书的异步生成),不能同步返回。回调只有两个接口:

// api/jsep.h class CreateSessionDescriptionObserver {   // 成功:拿到 SDP,下一步调用 SetLocalDescription   virtual void OnSuccess(SessionDescriptionInterface* desc) = 0;   // 失败:收到错误原因   virtual void OnFailure(RTCError error) = 0; };

② RTCOfferAnswerOptions 控制 SDP 生成行为的参数集:

// api/peer_connection_interface.h // See: https://www.w3.org/TR/webrtc/#idl-def-rtcofferansweroptions struct RTCOfferAnswerOptions {   // Plan B 遗留选项:是否在 Offer 中包含音/视频接收方向   // Unified Plan 用户应使用 AddTransceiver,不应设置这两个字段   int offer_to_receive_video = kUndefined;   int offer_to_receive_audio = kUndefined;   // 是否开启语音活动检测(VAD)   bool voice_activity_detection = true;   // 是否强制 ICE Restart(重新生成 ICE 凭据)   bool ice_restart = false;   // 是否开启 BUNDLE(音视频复用同一传输通道,推荐 true)   bool use_rtp_mux = true; };

深度细节:offer_to_receive_audio/video 是 Plan B 语义的遗留字段。 在 Unified Plan 下,媒体方向由 RtpTransceiver::direction 控制, 这两个字段会触发 HandleLegacyOfferOptions 做兼容处理, 内部实际上是在操作 Transceiver 的 direction,而不是直接写 SDP。

3、调用链:一次 CreateOffer 经历了什么

完整调用链如下:

应用层调用     pc->CreateOffer(observer, options)             │             ▼  [Signaling Thread]     PeerConnection::CreateOffer()             │  直接转发给 sdp_handler_             ▼     SdpOfferAnswerHandler::CreateOffer()             │  封装进 OperationsChain 串行队列             ▼     SdpOfferAnswerHandler::DoCreateOffer()             │  前置校验 → GetOptionsForOffer()             ▼     WebRtcSessionDescriptionFactory::CreateOffer()             │  等待 DTLS 证书就绪(可能异步)             ▼     MediaSessionDescriptionFactory::CreateOffer()             │  真正生成 cricket::SessionDescription             ▼     observer->OnSuccess(SessionDescriptionInterface*)

4、第一层:PeerConnection 调用CreateOffer

// pc/peer_connection.cc void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,                                  const RTCOfferAnswerOptions& options) {   RTC_DCHECK_RUN_ON(signaling_thread());   sdp_handler_->CreateOffer(observer, options); }

PeerConnection::CreateOffer 本身什么都不做, 只是做了一个线程断言(必须在 signaling_thread 上调用), 然后把工作全部委托给 SdpOfferAn

http://www.jsqmd.com/news/658776/

相关文章:

  • 对比分析DeerFlow和Hermes的记忆/技能进化系统
  • 别再手动炒股了!清华博士教你用 AI Agent 搭建量化交易系统(附源码)
  • 对话开发者:除了爆款,我们还能拿出什么样来对抗大环境的冷?
  • Fastjson的AutoType:从‘得力助手’到‘安全噩梦’,我们该如何用SafeMode优雅收场?
  • noi-2026年4月14号作业
  • 实操分享:为什么【灵智AI站群】能实现百万收录?亲自测试
  • 手把手拆解记分牌(Scoreboard)硬件:如何用Python模拟一个简单的ILP调度器?
  • 单片机串口通信入门:手把手教你配置TMOD、SCON和SBUF寄存器(附代码)
  • 从“完全或无”到IND-CCA2:公钥加密安全模型的演进与实战解析
  • 解决‘找不到.so文件’:GCC动态链接库编译成功后运行报错的三种终极解决方案
  • 苏州2026年,探秘苏州灌装机工厂的智造新篇章
  • 简单理解:NFC(近场通信)
  • ESP BLE 安全实战:从配对到加密的代码实现与场景解析
  • 从零到一:手把手教你用conda与pip实现开发环境的无缝迁移与国内源加速
  • 从BUUCTF一道RSA难题看e与φ不互素问题的AMM算法实战解析
  • Unity中Dropdown与TMP_Dropdown的OnValueChange事件优化:解决单选项点击无响应问题
  • 从零到一:基于Keil uVision5与LPC17XX的嵌入式工程构建实战
  • Kafka: 一条消息的完整“生命之旅”
  • 基于EOF分析的PDO指数计算与Python实践指南
  • 简单理解:MTK(联发科)、中兴微(中兴微电子)、ASR(翱捷科技)
  • [Simulink实战] 基于STM32的永磁同步电机无传感FOC控制:从模型到代码的完整开发流程
  • 炉石传说HsMod插件:55项功能深度解析与架构实现
  • Joy-Con Toolkit深度解析:开源手柄控制技术的架构与实现
  • 时序抖动:概念、测量与系统设计优化
  • 保姆级避坑指南:Ubuntu 20.04 LTS源码编译Qt 5.15.2全流程
  • 学Simulink——基于Simulink的AUTOSAR架构下电机控制软件组件建模
  • 5分钟快速上手!Umi-OCR免费离线文字识别工具终极指南
  • 图像处理 | 从原理到实战:一网打尽经典边缘检测算子(Roberts, Sobel, Prewitt, Canny)及其Python实现
  • Python调试神器:Pdb命令速查手册
  • python pre-commit-hooks