webrtc中采用sdp协议进行会话协商,sdp协商是通信数据的开始,理解sdp协议及webrtc对sdp的处理非常重要,本文简单剖析了webrtc源码对于p2p双方编解码器不同时如何进行协商的过程,sdp协议是offer/answer模型,展示如下图。sdp协议对应的API:CreateOffer、CreateAnswer、SetLocalDescription、SetRemoteDescription。
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;
}
}
对于Caller端 CreateOffer成功后调用SetLocalDescription,SetLocalDescription 主要完成了两个步骤
以上两个步骤为接收对方SDP做准备工作。
对于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的认识还很粗浅,若有不足之处,欢迎指导赐教。