从WebRTC SDP实现源码中寻找video codec的来源

近期因为需要修改一下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),如下图:
SdpSerialize入口打断点
顺便说一句,为什么有21种辣么多的codecs,视频编码不就那么几个么,原来是不同的profile/level对应了不同的codecs信息,拿VP9举例:
VP9不同的profile-id
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的源头在视频编码工厂类里,整个查找过程像剥洋葱一样,十分有意思。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值