webrtc-sdp编码信息协商

简要

        webrtc中采用sdp协议进行会话协商,sdp协商是通信数据的开始,理解sdp协议及webrtc对sdp的处理非常重要,本文简单剖析了webrtc源码对于p2p双方编解码器不同时如何进行协商的过程,sdp协议是offer/answer模型,展示如下图。sdp协议对应的API:CreateOffer、CreateAnswer、SetLocalDescription、SetRemoteDescription。

webrtc-sdp编码信息协商_第1张图片

一、 API

CreateOffer

        CreateOffer主要完成两件事:创建证书,将本地编解码器信息等设置到sdp结构中。

        获取本地编码器:GetSupportedFormats(),对于是否支持vp9和h264,在编译时可以添加参数:rtc_libvpx_build_vp9=true/false rtc_use_h264=true/false 进行设置,webrtc默认支持vp8编解码器。GetSupportedFormats 代码段如下:

std::vector InternalEncoderFactory::GetSupportedFormats() const {

  std::vector supported_codecs;
  supported_codecs.push_back(SdpVideoFormat(cricket::kVp8CodecName));
  for (const webrtc::SdpVideoFormat& format : webrtc::SupportedVP9Codecs())
    supported_codecs.push_back(format);
  for (const webrtc::SdpVideoFormat& format : webrtc::SupportedH264Codecs())
    supported_codecs.push_back(format);
  return supported_codecs;
}

CreateOffer设置编码器信息代码段如下:

std::unique_ptr MediaSessionDescriptionFactory::CreateOffer(
    const MediaSessionOptions& session_options,
    const SessionDescription* current_description) const {

  ... 获取本地支持的编解码器....
  AudioCodecs offer_audio_codecs;
  VideoCodecs offer_video_codecs;
  RtpDataCodecs offer_rtp_data_codecs;
  GetCodecsForOffer(current_active_contents, &offer_audio_codecs,
                    &offer_video_codecs, &offer_rtp_data_codecs);

...填充sdp audio/video content....
for (const MediaDescriptionOptions& media_description_options :
       session_options.media_description_options) {
      switch (media_description_options.type) {
      case MEDIA_TYPE_AUDIO:  AddAudioContentForOffer;
      case MEDIA_TYPE_VIDEO:  AddVideoContentForOffer;   
    }
}

SetLocalDescription

        对于Caller端 CreateOffer成功后调用SetLocalDescription,SetLocalDescription 主要完成了两个步骤

  • 设置sdp信息,创建必要数据:ApplyLocalDescription
  • ICE媒体探测:MaybeStartGathering

webrtc-sdp编码信息协商_第2张图片

以上两个步骤为接收对方SDP做准备工作。

 SetRemoteDescription

        对于Caller端,收到Called端的answer,本地调用 SetRemoteDescription,与SetLocalSescription 略有不同:之前SetLocalDescription已经创建的transport、channel 等不会再重新创建,会协商sdp中音视频的编解码器,调用堆栈:

PeerConnection::SetRemoteDescription
VideoChannel::SetRemoteContent_w //(视频:VoiceChannel::SetRemoteContent_w)
WebRtcVideoChannel::WebRtcVideoSendStream::SetSendParameters
WebRtcVideoChannel::GetChangedSendParameters 获取协商编解码器
WebRtcVideoChannel::WebRtcVideoSendStream::SetSendParameters //编解码器发生变化,重新设置发送流的参数
WebRtcVideoChannel::WebRtcVideoSendStream::RecreateWebRtcStream //重新创建stream
VideoSendStream* Call::CreateVideoSendStream 重新创建stream
设置编码器信息到 VideoSendStream、VideoStreamEncoder中,当视频帧需要编码的时候创建编码器 

        GetChangedSendParameters 协商远端SDP中的编码与本地编码器,选择双方都支持的编码器存入negotiated_codecs中,从negotiated_codecs中取出第一个作为发送端的编码器,SelectSendVideoCodecs 是协商编码器接口,协商用了两层for循环,外层循环遍历远端编码器,内层循环遍历本地编码器,这样能保证本地编码器与远端编码器一致。

        协商完编码器后,需要重新设置参数,RecreateWebRtcStream 重新创建媒体流,将编码器参数设置到VideoStreamEncoder中,对于发送端此时还没有视频数据,当收到第一帧数据后才开始根据编码器参数创建真正对应的编码器。

bool WebRtcVideoChannel::GetChangedSendParameters(){
    //获取协商后的编解码器
    std::vector negotiated_codecs =
      SelectSendVideoCodecs(MapCodecs(params.codecs));
      if (negotiated_codecs_ != negotiated_codecs) {
    
    //选择发送通道的编码器:所有协商编码器中的第一个
    if (send_codec_ != negotiated_codecs.front()) {
      changed_params->send_codec = negotiated_codecs.front();
    }
    changed_params->negotiated_codecs = std::move(negotiated_codecs);
  }  
  ...
}

//协商远端与本地的编解码器:先根据远端codes遍历本地codecs,找到相同的编码器,存入vector中返回
std::vector
WebRtcVideoChannel::SelectSendVideoCodecs(){
    std::vector encoders;
  for (const VideoCodecSettings& remote_codec : remote_mapped_codecs) {
    for (auto format_it = sdp_formats.begin();
         format_it != sdp_formats.end();) {
      // For H264, we will limit the encode level to the remote offered level
      // regardless if level asymmetry is allowed or not. This is strictly not
      // following the spec in https://tools.ietf.org/html/rfc6184#section-8.2.2
      // since we should limit the encode level to the lower of local and remote
      // level when level asymmetry is not allowed.
      if (IsSameCodec(format_it->name, format_it->parameters,
                      remote_codec.codec.name, remote_codec.codec.params)) {
        encoders.push_back(remote_codec);

        // To allow the VideoEncoderFactory to keep information about which
        // implementation to instantitate when CreateEncoder is called the two
        // parmeter sets are merged.
        encoders.back().codec.params.insert(format_it->parameters.begin(),
                                            format_it->parameters.end());

        format_it = sdp_formats.erase(format_it);
      } else {
        ++format_it;
      }
    }
}

二、测试

        对于Caller端,先收集本地的编码器信息转存到SDP结构中,通过信令发送给对端,对端协商后发送answer,Caller端收到answer后调用SetRemoteDescription 进行本端协商,找到合适的编码器。 对此做了两个测试:1. 双方编码器信息存在交集  2. 双方编码器信息没有交集,具体测试及结果如下: 

    1. Caller端编码信息包含了Called端编码信息,协商会只用二者交集的编码器。编译两个peerconnection.exe,exe1作为caller端。exe1: 支持vp8,支持h264;exe2:不支持vp8,仅支持h264

Caller端 offer:
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 123 125 122 124 107 108
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 H264/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 H264/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:123 rtx/90000
a=fmtp:123 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=125
a=rtpmap:124 red/90000
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=124
a=rtpmap:108 ulpfec/90000
Called端 answer:
m=video 9 UDP/TLS/RTP/SAVPF 98 99 100 101 127 123 125 122 124 107 108
a=rtpmap:98 H264/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 H264/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:123 rtx/90000
a=fmtp:123 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=125
a=rtpmap:124 red/90000
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=124
a=rtpmap:108 ulpfec/90000

2. Caller端和Called端编码器没有交集:Caller 端 编码器仅支持 vp8,Called 端 编码器仅支持 h264

        在SetRemoteDescription时出错,直接返回,SelectSendVideoCodecs 需要协商远端编码器与本地编解码器进行比较,远端编解码器和本地编解码器没有交集,协商失败,不会继续走CreateAnswer部分。

三、总结

        本文粗略总结了Caller端SDP的关键接口,CreateOffer、SetLocalDescription、SetRemoteDescription 及如何协商编码器,对webrtc的认识还很粗浅,若有不足之处,欢迎指导赐教。

你可能感兴趣的:(webrtc,webrtc,p2p)