WebRTC CreateOffer源码剖析

一. 前言

        在这篇博客中我们介绍了 SDP 协议相关的内容,WebRTC 是按会话描述,媒体描述(媒体信息,网络描述,安全描述,服务质量)对 SDP 描述字段进行分类的,本文将以 CreateOffer 为例并从源码角度分析 WebRTC 是如何生成 SDP 的。

二. WebRTC SDP类结构说明

        如上是 WebRTC SDP 类关系图,每个 SDP 在 WebRTC 源码中对应一个 SessionDescription 结构,其中包含 ContentInfos,TransportInfos,ContentGroups 等重要类型成员。

class SessionDescription {
  // ...
private:
  ContentInfos contents_;
  TransportInfos transport_infos_;
  ContentGroups content_groups_;
  bool msid_supported_ = true;
  int msid_signaling_ = kMsidSignalingSsrcAttribute;
  bool extmap_allow_mixed_ = false;
};

        ContentInfos (vector<ContentInfo>) 成员包含了 SDP 中的 m= 媒体描述行中的内容信息,一个 ContentInfo 对应一个 m= 媒体描述行中的内容描述信息,ContentInfo 中重要的成员为 std::unique_ptr<MediaContentDescription> description_,MediaContentDescription 包含了 rtcp_mux_,rtcp_reduced_size_,direction_,rtp_header_extensions_ 等成员,分别对应 SDP 文本的 a=rtcp-mux,a=rtcp-rsize,a=sendrecv/sendonly/recvonly,a=extmap:num rtp_header_extension 等设置。 

class RTC_EXPORT ContentInfo {
  // ...
  std::string name;
  MediaProtocolType type;
  bool rejected = false;
  bool bundle_only = false;

 private:
  friend class SessionDescription;
  std::unique_ptr<MediaContentDescription> description_;
};
class MediaContentDescription {
 // ...
 protected:
  bool rtcp_mux_ = false;
  bool rtcp_reduced_size_ = false;
  bool remote_estimate_ = false;
  int bandwidth_ = kAutoBandwidth;
  std::string protocol_;
  std::vector<CryptoParams> cryptos_;
  std::vector<webrtc::RtpExtension> rtp_header_extensions_;
  bool rtp_header_extensions_set_ = false;
  StreamParamsVec send_streams_;
  bool conference_mode_ = false;
  webrtc::RtpTransceiverDirection direction_ =
      webrtc::RtpTransceiverDirection::kSendRecv;
  rtc::SocketAddress connection_address_;
  // Mixed one- and two-byte header not included in offer on media level or
  // session level, but we will respond that we support it. The plan is to add
  // it to our offer on session level. See todo in SessionDescription.
  ExtmapAllowMixed extmap_allow_mixed_enum_ = kNo;

  SimulcastDescription simulcast_;
  std::vector<RidDescription> receive_rids_;

  absl::optional<std::string> alt_protocol_;
};

         TransportInfos (vector<TransportInfo>) 成员包含了 SDP 中 m= 媒体描述行中的通道信息,其中重要的成员为 TransportDescription description,TransportDescription 包含了 ice_ufrag,ice_pwd,ice_mode,connection_role,identity_fingerprint 等成员,分别对应 SDP 文本的 a=ice-ufrag,a=ice-pwd,a=ice-lite,a=setup:actpass/active/passive...,a=fingerprint:hash_algorithm hash_value 等设置。

struct TransportInfo {
  TransportInfo() {}

  TransportInfo(const std::string& content_name,
                const TransportDescription& description)
      : content_name(content_name), description(description) {}

  std::string content_name;
  TransportDescription description;
};
struct TransportDescription {
  // ...  
  std::vector<std::string> transport_options;
  std::string ice_ufrag;
  std::string ice_pwd;
  IceMode ice_mode;
  ConnectionRole connection_role;

  std::unique_ptr<rtc::SSLFingerprint> identity_fingerprint;
  absl::optional<OpaqueTransportParameters> opaque_parameters;
};

        ContentGroups (vector<ContentGroup>) 成员包含了音视频媒体流的绑定信息。

class ContentGroup {
 // ...
 private:
  std::string semantics_;
  ContentNames content_names_;
};

三. WebRTC生成SDP

        WebRTC 生成 SDP 的步骤:获取本地媒体,创建 PeerConnection 对等连接,通过 AddTrack 添加往 PeerConnection 添加媒体轨信息,调用 createOffer 产生 offer SDP(如果是 answer SDP 则调用 createAnswer)。

CreatePeerConnection

        WebRTC 在生成 SDP 时会携带支持的音视频媒体类型,例如 a=rtpmap:111 opus/48000/2,a=rtpmap:103 ISAC/16000,a=rtpmap:9 G722/8000,那么它是如何收集编解码器信息的呢?

peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
    nullptr /* network_thread */, nullptr /* worker_thread */,
    nullptr /* signaling_thread */, nullptr /* default_adm */,
    webrtc::CreateBuiltinAudioEncoderFactory(),
    webrtc::CreateBuiltinAudioDecoderFactory(),
    webrtc::CreateBuiltinVideoEncoderFactory(),
    webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,
    nullptr /* audio_processing */);

        首先创建 PeerConnectionFactory 会传入音视频编解码器工厂,以创建音频编码器工厂 CreateBuiltinAudioEncoderFactory 说明,它调用 CreateAudioEncoderFactory 可变模板参数方法,将该版本支持的编码器类型传入(如下 Opus,Isac,G722,Ilbc,G711...)。

rtc::scoped_refptr<AudioEncoderFactory> CreateBuiltinAudioEncoderFactory() {
  return CreateAudioEncoderFactory<

#if WEBRTC_USE_BUILTIN_OPUS
      AudioEncoderOpus, NotAdvertised<AudioEncoderMultiChannelOpus>,
#endif

      AudioEncoderIsac, AudioEncoderG722,

#if WEBRTC_USE_BUILTIN_ILBC
      AudioEncoderIlbc,
#endif

      AudioEncoderG711, NotAdvertised<AudioEncoderL16>>();
}
template <typename... Ts>
rtc::scoped_refptr<AudioEncoderFactory> CreateAudioEncoderFactory() {
  // There's no technical reason we couldn't allow zero template parameters,
  // but such a factory couldn't create any encoders, and callers can do this
  // by mistake by simply forgetting the <> altogether. So we forbid it in
  // order to prevent caller foot-shooting.
  static_assert(sizeof...(Ts) >= 1,
                "Caller must give at least one template parameter");

  return rtc::scoped_refptr<AudioEncoderFactory>(
      new rtc::RefCountedObject<
          audio_encoder_factory_template_impl::AudioEncoderFactoryT<Ts...>>());
}
template <typename... Ts>
class AudioEncoderFactoryT : public AudioEncoderFactory {
 public:
  std::vector<AudioCodecSpec> GetSupportedEncoders() override {
    std::vector<AudioCodecSpec> specs;
    Helper<Ts...>::AppendSupportedEncoders(&specs);
    return specs;
  }

  absl::optional<AudioCodecInfo> QueryAudioEncoder(
      const SdpAudioFormat& format) override {
    return Helper<Ts...>::QueryAudioEncoder(format);
  }

  std::unique_ptr<AudioEncoder> MakeAudioEncoder(
      int payload_type,
      const SdpAudioFormat& format,
      absl::optional<AudioCodecPairId> codec_pair_id) override {
    return Helper<Ts...>::MakeAudioEncoder(payload_type, format, codec_pair_id);
  }
};

        CreateAudioEncoderFactory 会返回 rtc::scoped_refptr<AudioEncoderFactory> 对象,CreateAudioDecoderFactory,CreateVideoEncoderFactory,CreateVideoDecoderFactory 也会返回对应的音视频编解码工厂,它们被赋值给 media_dependencies 用于创建 PeerConnectionFactory。

cricket::MediaEngineDependencies media_dependencies;
media_dependencies.task_queue_factory = dependencies.task_queue_factory.get();
media_dependencies.adm = std::move(default_adm);
media_dependencies.audio_encoder_factory = std::move(audio_encoder_factory);
media_dependencies.audio_decoder_factory = std::move(audio_decoder_factory);
 if (audio_processing) {
  media_dependencies.audio_processing = std::move(audio_processing);
 } else {
  media_dependencies.audio_processing = AudioProcessingBuilder().Create();
 }
media_dependencies.audio_mixer = std::move(audio_mixer);
media_dependencies.video_encoder_factory = std::move(video_encoder_factory);
media_dependencies.video_decoder_factory = std::move(video_decoder_factory);
dependencies.media_engine =
    cricket::CreateMediaEngine(std::move(media_dependencies));
return CreateModularPeerConnectionFactory(std::move(dependencies));

        PeerConnectionFactory 创建后再调用 PeerConnectionFactory::Initialize 创建 ChannelManager 并进行初始化,ChannelManager::Init 会进行媒体引擎初始化,它会调用音视频编解码工厂类的 GetSupportedEncoders/GetSupportedDecoders 获取我们传入的编解码器类型所支持的编解码器参数。 

void WebRtcVoiceEngine::Init() {
  // ...
  // Load our audio codec lists.
  RTC_LOG(LS_VERBOSE) << "Supported send codecs in order of preference:";
  send_codecs_ = CollectCodecs(encoder_factory_->GetSupportedEncoders());
  for (const AudioCodec& codec : send_codecs_) {
    RTC_LOG(LS_VERBOSE) << ToString(codec);
  }

  RTC_LOG(LS_VERBOSE) << "Supported recv codecs in order of preference:";
  recv_codecs_ = CollectCodecs(decoder_factory_->GetSupportedDecoders());
  for (const AudioCodec& codec : recv_codecs_) {
    RTC_LOG(LS_VERBOSE) << ToString(codec);
  }
  // ...
}

        GetSupportedEncoders 会调用 AppendSupportedEncoders 将支持的编码器参数传入,如下是 OPUS 对应的 SdpAudioFormat 和 AudioCodecInfo 信息。

void AudioEncoderOpusImpl::AppendSupportedEncoders(
    std::vector<AudioCodecSpec>* specs) {
  const SdpAudioFormat fmt = {"opus",
                              kRtpTimestampRateHz,
                              2,
                              {{"minptime", "10"}, {"useinbandfec", "1"}}};
  const AudioCodecInfo info = QueryAudioEncoder(*SdpToConfig(fmt));
  specs->push_back({fmt, info});
}

         PeerConnectionFactory 初始化完成后可以通过 CreatePeerConnection 创建 PeerConnection,在 PeerConnection::Initialize 中会创建 WebRtcSessionDescriptionFactory 对象,后续 createOffer 将通过该对象构造出 SDP。

bool PeerConnection::Initialize(
    const PeerConnectionInterface::RTCConfiguration& configuration,
    PeerConnectionDependencies dependencies) {
  // ...

  webrtc_session_desc_factory_.reset(new WebRtcSessionDescriptionFactory(
      signaling_thread(), channel_manager(), this, session_id(),
      std::move(dependencies.cert_generator), certificate, &ssrc_generator_));
  webrtc_session_desc_factory_->SignalCertificateReady.connect(
      this, &PeerConnection::OnCertificateReady);

  if (options.disable_encryption) {
    webrtc_session_desc_factory_->SetSdesPolicy(cricket::SEC_DISABLED);
  }

  webrtc_session_desc_factory_->set_enable_encrypted_rtp_header_extensions(
      GetCryptoOptions().srtp.enable_encrypted_rtp_header_extensions);
  webrtc_session_desc_factory_->set_is_unified_plan(IsUnifiedPlan());
  
  // ...
}

         上述描述的流程总结图如下。

CreateOffer

        当上层调用 PeerConnection::CreateOffer 后则开始生成 offer SDP,它会调用 PeerConnection::DoCreateOffer,DoCreateOffer 再调用 WebRtcSessionDescriptionFactory::CreateOffer 进行 offer SDP 的创建。

void PeerConnection::DoCreateOffer(
    const RTCOfferAnswerOptions& options,
    rtc::scoped_refptr<CreateSessionDescriptionObserver> observer) {
  // ...

  cricket::MediaSessionOptions session_options;
  GetOptionsForOffer(options, &session_options);
  webrtc_session_desc_factory_->CreateOffer(observer, options, session_options);
}

         WebRtcSessionDescriptionFactory::CreateOffer 会往 create_session_description_request_ 塞入创建 offer 类型 SDP 的请求,当工作线程完成证书的创建后会回调 WebRtcSessionDescriptionFactory::SetCertificate,取出请求并执行 WebRtcSessionDescriptionFactory::InternalCreateOffer 创建 offer SDP。

 

         WebRtcSessionDescriptionFactory::InternalCreateOffer 调用 MediaSessionDescriptionFactory::CreateOffer。

 

         MediaSessionDescriptionFactory::CreateOffer 会添加音视频的 SDP 信息,以 AddAudioContentForOffer 为例说明,

 

        添加完媒体信息后又调用 AddTransportOffer 创建通道相关信息。 

 

         上述描述的流程总结图如下。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: WebRTC(Web实时通信)是一个开源项目,用于实现浏览器之间的实时通信。它提供了一系列的API,可以在Web浏览器中实现音频、视频和数据的共享。我们可以通过CSDN网站找到WebRTC的源代码。 在CSDN网站上,可以通过搜索引擎或直接访问相关的代码仓库,例如GitHub,来获取WebRTC的源代码。在代码仓库中,我们可以找到所有的源代码文件和相关的文档,以便我们深入了解和研究WebRTC的实现。 WebRTC的源代码主要由C++和JavaScript编写。C++部分包含了底层音视频处理的功能,例如编码和解码、网络传输和流媒体处理等。JavaScript部分则负责在浏览器中调用和使用WebRTC的API,并处理与用户界面的交互。 WebRTC的源代码非常庞大且涉及复杂的技术,包括音视频编解码、网络传输、数据通信等。因此,理解和使用WebRTC的源代码需要具备一定的编程和网络知识。 通过研究WebRTC的源代码,我们可以深入了解实时通信技术的内部机制,并根据实际需求进行定制和优化。同时,我们也可以利用WebRTC的源代码作为学习和参考,开发自己的实时通信应用程序。 总之,通过在CSDN上获取WebRTC的源代码,我们可以深入了解WebRTC的实现,并利用它构建强大的实时通信应用程序。但是,请注意,理解和使用WebRTC的源代码需要一定的学习和实践。 ### 回答2: WebRTC是一个开源项目,用于实现浏览器之间的实时音视频通信。该项目的源码可以在GitHub上找到,并且在CSDN社区也有相关的文章和教程。 在CSDN上搜索"Webrtc源码"可以找到很多相关的资源。这些资源包括了Webrtc的基本概念解释、架构设计、源码解析以及开发应用的示例等内容。通过阅读这些文章,可以了解Webrtc的整体架构、通信流程以及关键模块的工作原理。 在学习Webrtc源码的过程中,建议先理解WebRTC的基本概念和术语,如信令服务器、ICE协议、SDP等。然后,可以重点关注核心模块,如音视频采集、媒体传输、音视频编解码等。此外,还可以深入研究网络传输协议、媒体处理等相关技术。 CSDN上的相关文章和教程能够提供对Webrtc源码解析和开发指导,同时也是讨论和交流的平台。在学习过程中,可以参考这些文章,并结合源码进行实际的操作和实践。 总而言之,Webrtc源码可以通过CSDN找到相关资源,并通过阅读相应的文章和教程对源码进行理解和学习,借助这些资源和社区的支持,我们可以更好地掌握Webrtc的实现原理和开发应用。 ### 回答3: WebRTC(Web实时通信)是一个开放源代码项目,旨在提供实时音视频通信的能力。关于WebRTC源码,可以在CSDN等技术社区找到相关资源。 在CSDN上,可以找到很多关于WebRTC源码的学习资料和教程。首先,可以通过搜索关键词“WebRTC源码”来获取一些源码解析文章,这些文章会帮助我们深入了解WebRTC的实现原理和基本架构。 此外,CSDN上也有一些专门讨论WebRTC的论坛和社区,这些社区中的会员可以分享彼此的学习经验、提供问题解答等。在这些社区中,我们可以找到许多WebRTC开发者和爱好者,他们可以提供有关源码的深入分析和讨论。 在CSDN还可以找到一些WebRTC相关的开源项目,例如一些基于WebRTC的音频、视频通信的实现。这些开源项目往往提供了完整的源代码和文档,可以作为参考和学习的资料。 总之,CSDN是一个很好的资源平台,提供了关于WebRTC源码的丰富信息。通过CSDN,我们可以找到相关的源码解析文章、讨论社区和开源项目,这些资源都能帮助我们更好地理解和学习WebRTC源码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

椛茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值