近期因为需要修改一下WebRTC源码里的sdp信息,主要是音频和视频部分,所以看了一下native部分的实现,这里简单记录一下分析过程,主要是根据sdp字符串查找video codec的来源。注意本文基于Windows平台的WebRTC M76分支,不代表过去和未来的WebRTC版本。
额外说一句,熟悉WebRTC代码的朋友肯定知道WebRTC的VideoEngine及相关工厂类,就大约知道video codec来自哪里了。我记录的目的是从sdp字符串构造作为入口,来反推到VideoEngine以及相关的视频工厂类。
生成sdp的关键文件:
\pc\webrtc_sdp.h
\pc\webrtc_sdp.cc
sdp的生成,入口可以看 SdpSerialize() 这个函数。
以我的Windows上的Chrome浏览器举例,从WebRTC samples 网站随便找个例子创建一个1v1的连接,然后从 chrome://webrtc-internals 中可以看到local description。
支持的video codecs如下:
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123 119 114 115 116
这些codecs来自函数 BuildMediaDescription(…) 中:
if (media_type == cricket::MEDIA_TYPE_VIDEO) {
const VideoContentDescription* video_desc = media_desc->as_video();
for (const cricket::VideoCodec& codec : video_desc->codecs()) {
fmt.append(" ");
fmt.append(rtc::ToString(codec.id));
}
}
for循环执行完毕后,fmt的内容就是:
96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123 119 114 115 116
后面的代码:
InitLine(kLineTypeMedia, type, &os);
os << " " << port << " " << media_desc->protocol() << fmt;
AddLine(os.str(), message);
执行后就得到了:
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123 119 114 115 116
再往后的代码就不一一说明了。最终,构成了以下内容:
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123 119 114 115 116
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:MNjp
a=ice-pwd:YRUMfBg2bIA5bfls9QxJlUJn
a=ice-options:trickle
a=fingerprint:sha-256 49:BF:88:7B:43:86:66:BC:45:36:CF:1B:02:D4:85:AD:84:7C:6C:C7:59:C8:0C:63:BE:26:B3:EF:72:18:46:78
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:13 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:12 urn:3gpp:video-orientation
a=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendonly
a=msid:592b262d-b8ab-49fd-a9b5-f4dfa3a99cc6 c3a57b51-7c44-43d3-b7fa-18b07fd26867
a=rtcp-mux
a=rtcp-rsize
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 VP9/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 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/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 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=102
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=0;profile-level-id=42001f
a=rtpmap:121 rtx/90000
a=fmtp:121 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=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:124 H264/90000
a=rtcp-fb:124 goog-remb
a=rtcp-fb:124 transport-cc
a=rtcp-fb:124 ccm fir
a=rtcp-fb:124 nack
a=rtcp-fb:124 nack pli
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032
a=rtpmap:120 rtx/90000
a=fmtp:120 apt=124
a=rtpmap:123 H264/90000
a=rtcp-fb:123 goog-remb
a=rtcp-fb:123 transport-cc
a=rtcp-fb:123 ccm fir
a=rtcp-fb:123 nack
a=rtcp-fb:123 nack pli
a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032
a=rtpmap:119 rtx/90000
a=fmtp:119 apt=123
a=rtpmap:114 red/90000
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 ulpfec/90000
a=ssrc-group:FID 1338493566 430727286
a=ssrc:1338493566 cname:gqMMdwzksN5NmiIq
a=ssrc:1338493566 msid:592b262d-b8ab-49fd-a9b5-f4dfa3a99cc6 c3a57b51-7c44-43d3-b7fa-18b07fd26867
a=ssrc:1338493566 mslabel:592b262d-b8ab-49fd-a9b5-f4dfa3a99cc6
a=ssrc:1338493566 label:c3a57b51-7c44-43d3-b7fa-18b07fd26867
a=ssrc:430727286 cname:gqMMdwzksN5NmiIq
a=ssrc:430727286 msid:592b262d-b8ab-49fd-a9b5-f4dfa3a99cc6 c3a57b51-7c44-43d3-b7fa-18b07fd26867
a=ssrc:430727286 mslabel:592b262d-b8ab-49fd-a9b5-f4dfa3a99cc6
a=ssrc:430727286 label:c3a57b51-7c44-43d3-b7fa-18b07fd26867
OK,看到了96~116这21个数字,它对应的是21个video codec id,那么问题来了,这21种codecs来自哪里?
先从构造video sdp信息的地方顺藤摸瓜:
- 每一个codec都对应一个VideoCodec,
- VideoContentDescription->codecs()可以得到21个VideoCodec的集合,
- ContentInfo->media_description->as_video 可以得到VideoContentDescription,
- ContentInfo来自SdpSerialize函数参数JsepSessionDescription->contents集合,
- contents()里面所有的内容要看JsepSessionDescription这个类,对应的文件是\pc\jsep_session_description.cc
在SdpSerialize()的入口处打一个断点,看一下JsepSessionDescription.description()的内容,会发现contents里包含2个元素,一个对应audio,一个对应video(21种codecs),如下图:
顺便说一句,为什么有21种辣么多的codecs,视频编码不就那么几个么,原来是不同的profile/level对应了不同的codecs信息,拿VP9举例:
rtx和H264可能就更多了,好几坨,和VP9同理,这里就不截图了。
OK,我们通过查看JsepSessionDescription.description()可以知道,description的内容是JsepSessionDescription构造函数传过来的。那么就要继续往上看,JsepSessionDescription对象的持有者是谁了。
最后发现是\webrtc\pc\webrtc_session_description_factory.cc中的WebRtcSessionDescriptionFactory::InternalCreateOffer()函数中构造了JsepSessionDescription:
auto offer = absl::make_unique<JsepSessionDescription>(
SdpType::kOffer, std::move(desc), session_id_,
rtc::ToString(session_version_++));
第二个参数 desc 来自于上方的:
std::unique_ptr<cricket::SessionDescription> desc =
session_desc_factory_.CreateOffer(
request.options, pc_->local_description()
? pc_->local_description()->description()
: nullptr);
来到 media_session.cc 的 MediaSessionDescriptionFactory::CreateOffer() 方法,发现是调用了 GetCodecsForOffer() 函数获得了上面那21种video codecs。
继续来到 MediaSessionDescriptionFactory::GetCodecsForOffer() 函数里,看到如下调用:
MergeCodecs<VideoCodec>(video_codecs_, video_codecs, &used_pltypes);
MergeCodecs会将video_codecs_(MediaSessionDescriptionFactory的成员变量)填充到video_codecs,所以,继续看video_codecs_的内容来源。
在MediaSessionDescriptionFactory的构造函数中,我们看到了:
channel_manager->GetSupportedVideoCodecs(&video_codecs_);
实现如下:
void ChannelManager::GetSupportedVideoCodecs(
std::vector<VideoCodec>* codecs) const {
if (!media_engine_) {
return;
}
codecs->clear();
std::vector<VideoCodec> video_codecs = media_engine_->video().codecs();
for (const auto& video_codec : video_codecs) {
if (!enable_rtx_ &&
absl::EqualsIgnoreCase(kRtxCodecName, video_codec.name)) {
continue;
}
codecs->push_back(video_codec);
}
}
video codecs来自这一句:
media_engine_->video().codecs();
继续。(注:这一句比较关键,下面会再次提到)
media_engine_是谁持有的呢?
ChannelManager(third_party\webrtc\pc\channel_manager.cc)的构造函数中对media_engine_进行了赋值,那么就需要看ChannelManager对象的持有者。
通过查找,PeerConnectionFactory(\pc\peer_connection_factory.cc)是ChannelManager的持有者和创造者,代码位于PeerConnectionFactory::Initialize()中的这一句:
channel_manager_ = absl::make_unique<cricket::ChannelManager>(
std::move(media_engine_), absl::make_unique<cricket::RtpDataEngine>(),
worker_thread_, network_thread_);
继续往上找,依次来到:
CreateModularPeerConnectionFactory() : \pc\peer_connection_factory.cc
然后到 Chromium 的代码了:
InitializeSignalingThread() : \chromium\src\content\renderer\media\webrtc\peer_connection_dependency_factory.cc
在InitializeSignalingThread()中看到了这一句:
pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps));
CreateMediaEngine() 函数位于 \media\engine\webrtc_media_engine.cc,我们看到:
auto video_engine = absl::make_unique<WebRtcVideoEngine>(
std::move(dependencies.video_encoder_factory),
std::move(dependencies.video_decoder_factory));
WebRTCVideoEngine的源码是:\media\engine\webrtc_video_engine.cc
我们看到WebRTCVideoEngine的构造函数中并没有填充codecs信息,是什么时候填充的呢?通过调试,我们发现调用关系是这样的:
- PeerConnectionFactory::CreatePeerConnection() : \pc\peer_connection_factory.cc
- PeerConnection::Initialize() : \pc\peer_connection.cc
- WebRtcSessionDescriptionFactory::ctor() : \pc\webrtc_session_description_factory.cc
- MediaSessionDescriptionFactory::ctor() : \pc\media_session.cc
- ChannelManager::GetSupportedVideoCodecs()
上面我们其实已经看到了ChannelManager::GetSupportedVideoCodecs()这个方法,它是这样获取codes的:
std::vector<VideoCodec> video_codecs = media_engine_->video().codecs();
codecs()函数的实现对应的就是:
std::vector<VideoCodec> WebRtcVideoEngine::codecs() const {
return AssignPayloadTypesAndDefaultCodecs(encoder_factory_.get());
}
注意在WebRtcVideoEngine里有两个 AssignPayloadTypesAndDefaultCodecs() 的实现,参数不同。上面的调用其实会调用encoder_factory->GetSupportedFormats()来得到一个webrtc::SdpVideoFormat的vector,传给另外一个AssignPayloadTypesAndDefaultCodecs()实现。
std::vector<VideoCodec> AssignPayloadTypesAndDefaultCodecs(
const webrtc::VideoEncoderFactory* encoder_factory) {
return encoder_factory ? AssignPayloadTypesAndDefaultCodecs(
encoder_factory->GetSupportedFormats())
: std::vector<VideoCodec>();
}
可以看到,调用 encoder_factory->GetSupportedFormats() 来获取编码器工厂类支持的 webrtc::SdpVideoFormat 数组,从这些数组元素中得到对应的VideoCodec。
那么encoder_factory是谁呢? 在上面代码 CreateMediaEngine() 的时候我们看到,encoder_factory是在 PeerConnectionDependencyFactory::InitializeSignalingThread() 中传入的,代码片段如下:
void PeerConnectionDependencyFactory::InitializeSignalingThread(
media::GpuVideoAcceleratorFactories* gpu_factories,
base::WaitableEvent* event) {
// 省略
std::unique_ptr<webrtc::VideoEncoderFactory> webrtc_encoder_factory =
CreateWebrtcVideoEncoderFactory(gpu_factories);
std::unique_ptr<webrtc::VideoDecoderFactory> webrtc_decoder_factory =
CreateWebrtcVideoDecoderFactory(gpu_factories);
// Enable Multiplex codec in SDP optionally.
if (base::FeatureList::IsEnabled(features::kWebRtcMultiplexCodec)) {
webrtc_encoder_factory = std::make_unique<webrtc::MultiplexEncoderFactory>(
std::move(webrtc_encoder_factory));
webrtc_decoder_factory = std::make_unique<webrtc::MultiplexDecoderFactory>(
std::move(webrtc_decoder_factory));
}
//省略
cricket::MediaEngineDependencies media_deps;
media_deps.task_queue_factory = pcf_deps.task_queue_factory.get();
media_deps.adm = audio_device_.get();
media_deps.audio_encoder_factory = CreateWebrtcAudioEncoderFactory();
media_deps.audio_decoder_factory = CreateWebrtcAudioDecoderFactory();
media_deps.video_encoder_factory = std::move(webrtc_encoder_factory);
media_deps.video_decoder_factory = std::move(webrtc_decoder_factory);
media_deps.audio_processing = webrtc::AudioProcessingBuilder().Create();
pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps));
}
所以,VideoEncoderFactory主要可能来自2处:第一是 CreateWebrtcVideoEncoderFactory() ,第二是如果设置 WebRTC-MultiplexCodec 为enable(默认是disable),则会转成 MultiplexDecoderFactory。至于什么是 multiplex codec,我没有实际使用经验,不瞎喷,感兴趣的可以看一下 \media\engine\multiplex_codec_factory.h 中的大段注释。
然后CreateWebrtcVideoEncoderFactory()顺着来到:
RTCVideoEncoderFactory::RTCVideoEncoderFactory(
media::GpuVideoAcceleratorFactories* gpu_factories)
: gpu_factories_(gpu_factories) {
const media::VideoEncodeAccelerator::SupportedProfiles& profiles =
gpu_factories_->GetVideoEncodeAcceleratorSupportedProfiles();
for (const auto& profile : profiles) {
base::Optional<webrtc::SdpVideoFormat> format = VEAToWebRTCFormat(profile);
if (format) {
supported_formats_.push_back(std::move(*format));
profiles_.push_back(profile.profile);
}
}
}
上面代码中看到, 先通过 GpuVideoAcceleratorFactories::GetVideoEncodeAcceleratorSupportedProfiles() 获取所有的 profile,然后再通过函数 VEAToWebRTCFormat() “翻译”成webrtc::SdpVideoFormat。
现在基本上已经就找到了真相来源了,我们一开始从sdp中看到的那21种video codecs,原来是来自这里!
不过这里有个问题就是,我的电脑上 GpuVideoAcceleratorFactories::IsGpuVideoAcceleratorEnabled() 是返回 true的,假如返回false的话,那么此时的VideoEncoderFactory就不再是RTCVideoEncoderFactory,而是一个没有具体实现的对象,传入EncoderAdapter (content\renderer\media\webrtc\video_codec_factory.cc )。也就是说,此时硬编码工厂类没有实现,EncoderAdapter 内部会采用软编码工厂类来执行后续的编码器创建工作。
顺带着说一句,EncoderAdapter会维护着硬编码工厂(RTCVideoEncoderFactory)和软编码工厂(InternalEncoderFactory)两个对象,根据电脑上硬编码支持的情况、浏览器开关等因素,来决定使用硬还是软。
假如电脑不支持硬编码,那么就需要看 \webrtc\media\engine\internal_encoder_factory.cc 里的 GetSupportedFormats() 实现了,代码如下:
std::vector<SdpVideoFormat> InternalEncoderFactory::GetSupportedFormats()
const {
std::vector<SdpVideoFormat> 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;
}
VP8是一定支持的,VP9和H264的情况,还需要对应看下webrtc::SupportedVP9Codecs()(vp9.cc和webrtc::SupportedH264Codecs()(h264.cc)这两个文件里的实现了,具体内容就不再列出了。
所以,在不同的电脑上,支持的video codec的数量是不同的。
至此,一切水落石出,原来sdp中那一坨video codecs的源头在视频编码工厂类里,整个查找过程像剥洋葱一样,十分有意思。