写点什么

webrtc simulcast 开启

用户头像
糖米唐爹
关注
发布于: 3 小时前
webrtc simulcast 开启

备注: 基于 webrtc89 版本, codec 选择的是 H264!!!

一,概述

Simulcast is a technique by which a WebRTC client encodes the same video stream twice in different resolutions and bitrates and sending these to a router who then decides who receives which of the streams.


上文摘自webrtcglossary,目前 webrtc 已经支持 h264 Simulcast 功能,发送端将一路视频源编码成多道不同分辨率的流并发送到服务器,这样接收端就可以根据需要订阅不同的流,好处是可以节省了服务器转码的消耗资源,但是当发送端资源受限,多道流势必会相互抢占资源,影响视频的质量.

二,simulcast 开启

webrtc 89 版本开启 simulcast 相对简单,仅需在 createoffer 设置 RTCOfferAnswerOptions num_simulcast_layers 参数就可以了,示例:

const int kMaxVideoSimucastLayersNum = 2;int RTCPeerConnection::createOffer(){  	webrtc::PeerConnectionInterface::RTCOfferAnswerOptions option;  	option.num_simulcast_layers = kMaxVideoSimucastLayersNum;  	mWebrtcPeerConnection->CreateOffer(csdObserver, option);}
复制代码

当调用 peerconnection 开始创建 offer,根据 num_simulcast_layers 的值生成对应个数的 ssrc,看下 simulcast sdp 的例子:

v=0......//两道流的ssrca=ssrc-group:SIM 687580381 1424746405 // ssrc 与 nack ssrca=ssrc-group:FID 687580381 3320499586 a=ssrc-group:FID 1424746405 2590647556a=ssrc:687580381 cname:uC7imfOfwk+q50+Ia=ssrc:687580381 msid:1079508994 video-defaulta=ssrc:687580381 mslabel:1079508994a=ssrc:687580381 label:video-defaulta=ssrc:1424746405 cname:uC7imfOfwk+q50+Ia=ssrc:1424746405 msid:1079508994 video-defaulta=ssrc:1424746405 mslabel:1079508994a=ssrc:1424746405 label:video-defaulta=ssrc:3320499586 cname:uC7imfOfwk+q50+Ia=ssrc:3320499586 msid:1079508994 video-defaulta=ssrc:3320499586 mslabel:1079508994a=ssrc:3320499586 label:video-defaulta=ssrc:2590647556 cname:uC7imfOfwk+q50+Ia=ssrc:2590647556 msid:1079508994 video-defaulta=ssrc:2590647556 mslabel:1079508994a=ssrc:2590647556 label:video-default
复制代码


三,编码器开启 simulcast 功能

当 offer 创建成功,其实编码器其实还是不知道这些信息的,需要调用 SetLocalDescription 把相关的参数设置到编码器的,流程如下

  1. 调用 SetLocalDescription, offer 中的信息被提取,填充 WebRtcVideoSendStream 成员 parameters_,rtp_parameters_,看下 WebRtcVideoSendStream 的声明:

 class WebRtcVideoSendStream{ pubic:        webrtc::VideoEncoderConfig CreateVideoEncoderConfig(const VideoCodec& codec) const; private:        VideoSendStreamParameters parameters_;        webrtc::RtpParameters rtp_parameters_;        webrtc::VideoSendStream* stream_; };
复制代码
  1. 收到远端 answer 消息时,调用 SetRemoteDescription,提取 answer ,然后再结合第一步 offer 中的信息由 VideoStreamEncoder 创建 VideoEncoderConfig 对象 .


  1. 完成 1,2 两步后,视频数据已经开始采集了,当首帧数据上来 VideoStreamEncoder 会被缓存到 last_frame_info_。


  1. 创建 layers,这一步的主要作用是根据 VideoEncoderConfig 和 last_frame_info_生成 layers,并填充各个 layer 的 width,height,max_qp,max_bitrate_bps,target_bitrate_bps,min_bitrate_bps 信息,为了后面的带宽分配做准备,layer 的 width/height 是根据 last_frame_info_的宽高/2 递减,其中 max_bitrate_bps,target_bitrate_bps,min_bitrate_bps 是查表所得,见表 kSimulcastFormats,

constexpr const SimulcastFormat kSimulcastFormats[] = {    {1920, 1080, 3, webrtc::DataRate::KilobitsPerSec(5000),     webrtc::DataRate::KilobitsPerSec(4000),     webrtc::DataRate::KilobitsPerSec(800)},    {1280, 720, 3, webrtc::DataRate::KilobitsPerSec(2500),     webrtc::DataRate::KilobitsPerSec(2500),     webrtc::DataRate::KilobitsPerSec(600)},    {960, 540, 3, webrtc::DataRate::KilobitsPerSec(1200),     webrtc::DataRate::KilobitsPerSec(1200),     webrtc::DataRate::KilobitsPerSec(350)},    {640, 360, 2, webrtc::DataRate::KilobitsPerSec(700),     webrtc::DataRate::KilobitsPerSec(500),     webrtc::DataRate::KilobitsPerSec(150)},    {480, 270, 2, webrtc::DataRate::KilobitsPerSec(450),     webrtc::DataRate::KilobitsPerSec(350),     webrtc::DataRate::KilobitsPerSec(150)},    {320, 180, 1, webrtc::DataRate::KilobitsPerSec(200),     webrtc::DataRate::KilobitsPerSec(150),     webrtc::DataRate::KilobitsPerSec(30)},    {0, 0, 1, webrtc::DataRate::KilobitsPerSec(200),     webrtc::DataRate::KilobitsPerSec(150),     webrtc::DataRate::KilobitsPerSec(30)}};
复制代码


  1. 用 layers 填充 codec,这一步没有什么好说的,layers 转换成 codec 对象。

  2. 设置编码器

4,5,6 的实现在 VideoStreamEncoder::ReconfigureEncoder

VideoStreamEncoder::ReconfigureEncoder(){		//1,创建layers   std::vector<VideoStream> streams =      encoder_config_.video_stream_factory->CreateEncoderStreams(          last_frame_info_->width, last_frame_info_->height, encoder_config_);    //2,填充codec   VideoCodec codec;  if (!VideoCodecInitializer::SetupCodec(encoder_config_, streams, &codec)) {  }    //3,设置到编码器   if (encoder_->InitEncode(            &send_codec_,            VideoEncoder::Settings(settings_.capabilities, number_of_cores_,                                   max_data_payload_length)) != 0)     }
复制代码

经过上述的步骤,编码器可能已经有视频流已经出来,但是有可能没有实现我们想要的功能,这因为当总带宽不足时,webrtc 只会编出小流。这涉及了 simulast 的带宽分配相关,见第四节。


四,simulast 带宽分配



上图是新评估出的带宽如何设置到编码器的流程图,其实当这个带宽值传递到 VideoStreamEncoder 之前,已经减去了 fec,nack 和视频封包所占用带宽,具体处理逻辑在 RtpVideoSender::OnBitrateUpdated 中。


第三节创建 layers 时,每个 layer max_bitrate_bps,target_bitrate_bps,min_bitrate_bps 都会根据 kSimulcastFormats 填充一个默认值,下面讲下 gcc 评估出的带宽分配给每个 simulcast 的 layer 实现逻辑。核心函数有两个,我们结合代码直接分析。


  • DistributeAllocationToSimulcastLayers


void SimulcastRateAllocator::DistributeAllocationToSimulcastLayers(    DataRate total_bitrate,    DataRate stable_bitrate,    VideoBitrateAllocation* allocated_bitrates) {  DataRate left_in_total_allocation = total_bitrate;  DataRate left_in_stable_allocation = stable_bitrate;	//1, 按照每个layer 默认的maxBitrate 进行从小到大排序  std::vector<size_t> layer_index(codec_.numberOfSimulcastStreams);  std::iota(layer_index.begin(), layer_index.end(), 0);  std::stable_sort(layer_index.begin(), layer_index.end(),                   [this](size_t a, size_t b) {                     return std::tie(codec_.simulcastStream[a].maxBitrate) <                            std::tie(codec_.simulcastStream[b].maxBitrate);                   });
...... //2, 开始为每个layer 分配带宽 for (; active_layer < codec_.numberOfSimulcastStreams; ++active_layer) { // 3, 获取每个layer 对象 const SpatialLayer& stream = codec_.simulcastStream[layer_index[active_layer]]; DataRate min_bitrate = DataRate::KilobitsPerSec(stream.minBitrate); DataRate target_bitrate = DataRate::KilobitsPerSec(stream.targetBitrate); ...... // 4, 判断剩余带宽是否满足当前layer 最小需求,如何不满足,编码器则不会编出对应的流, // 这就是上面我提到的,为什么只会编出小流的原因。 if (left_in_stable_allocation < min_bitrate) { allocated_bitrates->set_bw_limited(true); break; }
top_active_layer = layer_index[active_layer]; stream_enabled_[layer_index[active_layer]] = true; DataRate layer_rate = std::min(left_in_total_allocation, target_bitrate); // 5, 设置目标码率 allocated_bitrates->SetBitrate(layer_index[active_layer], 0, layer_rate.bps()); ...... // 6, 计算剩余带宽 left_in_total_allocation -= layer_rate; left_in_stable_allocation -= std::min(left_in_stable_allocation, target_bitrate); }
// 7,继续分配剩余带宽,逻辑简单不做解释 if (left_in_total_allocation > DataRate::Zero()) { const SpatialLayer& stream = codec_.simulcastStream[top_active_layer]; DataRate initial_layer_rate = DataRate::BitsPerSec( allocated_bitrates->GetSpatialLayerSum(top_active_layer)); DataRate additional_allocation = std::min( left_in_total_allocation, DataRate::KilobitsPerSec(stream.maxBitrate) - initial_layer_rate); allocated_bitrates->SetBitrate( top_active_layer, 0, (initial_layer_rate + additional_allocation).bps()); }}
复制代码


  • DistributeAllocationToTemporalLayers


simulcast 每层流都是独立的, 这样又可以按照 Temporal Scalability 继续分多层, 这个函数的功能就是为每个 Temporal 层按照下面的规则继续分配带宽.

static const float    kLayerRateAllocation[kMaxTemporalStreams][kMaxTemporalStreams] = {        {1.0f, 1.0f, 1.0f, 1.0f},  // 1 layer        {0.6f, 1.0f, 1.0f, 1.0f},  // 2 layers {60%, 40%}        {0.4f, 0.6f, 1.0f, 1.0f},  // 3 layers {40%, 20%, 40%}        {0.25f, 0.4f, 0.6f, 1.0f}  // 4 layers {25%, 15%, 20%, 40%}};
复制代码


五, Simulcast 的 layer 分辨率设置


上面提到 webrtc 创建 layers 时,layer 的宽高/2 递减,当不能满足我们的的需求时,可能就需要应用层主动来做调整了,具体的流程,

1,获取到 VideoRtpSender 对象,该类最终会从 WebRtcVideoSendStream 取出 rtp_parameters_,我们可以设置视频下调比例(scale_resolution_down_by),带宽分配优先级(bitrate_priority),最大码率等各种信息。

2,根据需要填充该结构体,代码示例:

    //rtpParameters 其实就是上面6.1  rtp_parameters_     webrtc::RtpParameters rtpParameters = mVideoSenderMap[tag].sender->GetParameters();    webrtc::RtpEncodingParameters encodingParameters;    rtpParameters.encodings.push_back(encodingParameters);    	// update rtpParameters		.......    mVideoSenderMap[tag].sender->SetParameters(rtpParameters);
复制代码


六,禁止 simulcast 层数变化


simulcast 层数会随着视频的分辨率调整而改变,具体有以下几种,

  • 开启 cpu 自适应功能

  • 网络变化

  • qp 改变

当 simulcast 层数不断发生变化,势必会引起 server 端转发策略设计的复杂性,我们可以通过在 webrtc::field_trial 添加"WebRTC-LegacySimulcastLayerLimit/Disabled"就可以把它关闭,见函数:

size_t LimitSimulcastLayerCount(int width,                                int height,                                size_t need_layers,                                size_t layer_count,                                const webrtc::WebRtcKeyValueConfig& trials) {  if (!absl::StartsWith(trials.Lookup(kUseLegacySimulcastLayerLimitFieldTrial),                        "Disabled")) {    size_t adaptive_layer_count = std::max(        need_layers,        kSimulcastFormats[FindSimulcastFormatIndex(width, height)].max_layers);    if (layer_count > adaptive_layer_count) {      layer_count = adaptive_layer_count;    }  }  return layer_count;}
复制代码


七,simulcast 做成产品级功能的思考


webrtc 现在确实支持 h264 simulcast 功能,但是现在主流都是些 SFU 架构, 我们不仅要考虑发送端分辨率自适应的情况,还要考虑接受端的情况, 总结下把 simulcast 做成产品级的功能需要解决的问题,

  1. 如何设计服务器转发策略.

  2. 同时开启分辨率自适应功能, 发送端,接收端与服务端如何做信息同步.


用户头像

糖米唐爹

关注

还未添加个人签名 2020.08.12 加入

还未添加个人简介

评论

发布
暂无评论
webrtc simulcast 开启