当我们上行推Simulcast流的时候,会发现Simulcast层数会时不时变化,本来是大小流(大小两个分辨率)两层,但是变为只有一层小流了,后面又恢复为两层了,如果我们服务端支持Simulcast的话,这就成为一个棘手问题。
例如我们团队的WebRTC产品,客户端默认会启用Simulcast,上行推送两个不同分辨率的流到服务端,服务端下行会依据带宽估计值,自动选择某个分辨率的流给订阅客户端。假设此时下行带宽足够,服务端会推送大流。但是对于上行客户端来说,因为某种原因,Simulcast层数减少为一层,只剩一路小流了,如果没有相应处理,此时订阅客户端就接收不到视频流了。
Simulcast层数为何变化
WebRTC中如果送到编码器的采集视频帧分辨率发生变化,会触发ReconfigureEncoder
也就是重置编码器操作,然后Simulcast层数也会重新计算。在WebRTC中,Simulcast层数与采集视频帧分辨率的关系由一张表决定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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)}}; |
对于1920x1080的采集分辨率,允许的最大Simulcast层数为3层,对于640x360的采集分辨率,允许的最大层数为2层。所以当采集视频分辨率从1920x1080变为640x360时,Simulcast层数就会发生变化。
下面简单说下几种造成采集视频帧分辨率变化的几种情况:
1)带宽估计值不够;
2)编码视频的QP值在设定的阈值范围外;
3)通过编码延时计算得到的CPU负载发生变化。
发生如上情况时,WebRTC会调整采集的原始视频帧分辨率或者帧率,至于是调整采集分辨率还是帧率,依据我们采取的策略,WebRTC中目前存在四种策略:
1)BALANCED
,平衡模式;
2)MAINTAIN_FRAMERATE
,保分辨率模式,也就是清晰模式;
3)MAINTAIN_RESOLUTION
,保帧率模式,也就是流畅模式;
4)DISABLED
,禁用模式。
例如对于摄像头视频流,默认是流畅模式,我们举两个例子说明下。
1)如果分配的带宽估计值满足不了当前编码器分辨率的码率设置时,就会触发降低采集视频帧分辨率的操作;
2)如果检测到CPU负载较高,也会触发降低采集视频帧分辨率的操作。这种现象在移动端设备较为明显,会看到视频刚开始很清晰,后面就模糊了(假设带宽足够情况下)。
当然了,对于屏幕共享流,默认是清晰模式,只会触发降低帧率的操作,看起来视频会变卡。
如何防止Simulcast层数变化
这里我们先看下启用Simulcast时,采集分辨率改变后,重置编码器的相关代码流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
VideoStreamEncoder::OnFrame ↓ VideoStreamEncoder::MaybeEncodeVideoFrame ↓ VideoStreamEncoder::ReconfigureEncoder ↓ EncoderStreamFactory::CreateEncoderStreams ↓ EncoderStreamFactory::CreateSimulcastOrConfereceModeScreenshareStreams ↓ GetSimulcastConfig ↓ LimitSimulcastLayerCount |
在LimitSimulcastLayerCount
中会根据前面提到的表内容,改变Simulcast层数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int LimitSimulcastLayerCount(int width, int height, int layer_count) { if (!webrtc::field_trial::IsDisabled( kUseLegacySimulcastLayerLimitFieldTrial)) { // 根据table,得到当前分辨率允许的simulcast最大层数 int adaptive_layer_count = kSimulcastFormats[FindSimulcastFormatIndex(width, height)].max_layers; if (layer_count > adaptive_layer_count) { RTC_LOG(LS_WARNING) << "Reducing simulcast layer count from " << layer_count << " to " << adaptive_layer_count; layer_count = adaptive_layer_count; } } return layer_count; } |
可知,通过在webrtc::field_trial
设置禁用kUseLegacySimulcastLayerLimitFieldTrial
即可保持当前Simulcast层数,避免了大流消失的麻烦。
总结
本文分析了Simulcast层数变化的原因,以及如何防止变化。其中介绍了WebRTC的清晰模式,流畅模式等概念,后续文章会对这些进行详细分析。
如何觉得好,请点赞、打赏、收藏三连哦,这样我才有更大动力写出更多底层分析文章。
文章评论