当我们上行推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_RESOLUTION
,保分辨率模式,也就是清晰模式;
3)MAINTAIN_FRAMERATE
,保帧率模式,也就是流畅模式;
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的清晰模式,流畅模式等概念,后续文章会对这些进行详细分析。
文章评论
博主好,感谢分享!
2)MAINTAIN_FRAMERATE,保分辨率模式,也就是清晰模式;
3)MAINTAIN_RESOLUTION,保帧率模式,也就是流畅模式;
这两个从字面意思看,貌似是写反了?
@悠哉嗑瓜子 感谢纠正,已修改
博主您好,请问WebRTC启用simulcast后,
VideoStreamEncoder::OnEncodedImage(video_stream_encoder.cc)是不是应该收到多个编码流呀?我应该成功配置了两个Simulcast流,但是只收到了1280*720的编码流,博主您知道可能是什么原因导致的吗?
以下是我的配置信息:
video_stream_encoder.cc: (line 685): ReconfigureEncoder:
Simulcast streams:
0: 640x360 fps: 60 min_kbps: 150 target_kbps: 500 max_kbps: 700 max_fps: 60 max_qp: 56 num_tl: 3 active: true
1: 1280x720 fps: 60 min_kbps: 600 target_kbps: 2500 max_kbps: 2500 max_fps: 60 max_qp: 56 num_tl: 3 active: true
最后感谢博主的分享!
@Jason 只编码出一路流,你把日志都打开,应该能看出问题
@Jeff,感谢博主的回复。我看了一下日志,应该是硬编码器不支持。
【video_stream_encoder.cc: (line 1278): Encoder settings 】
这里的EncoderInfo::supports_simulcast = 0.
博主您觉得是这个原因吗?
2)另外,请问目前WebRTC如果要支持H264的Simulcast功能,是不是必须得使用OpenH264进行软编?
博主之前也是使用软编实现的吗?(我现在在安卓平台测试,发现WebRTC目前不支持H264软编)
@Jason 恩,你要相信WebRTC日志。安卓上,WebRTC支持H264软编,你照着VP8/VP9的调用接口,自己实现jni接口调用,调用openH264库进行软编:
src\sdk\android\api\org\webrtc\LibvpxVp8Encoder.java
src\sdk\android\src\jni\vp8_codec.cc
你模仿这个VP8接口调用实现openH264编码器调用即可
@Jeff,我用VP8软编试了一下,simulcast是正常的。之前的H264、VP8都是使用的硬编。后面的工作就是实现以及选择支持的硬/软编码器了。感谢!
请问webrtc上如何才能开启simulcast呢
@jwkuang 已邮件回复
@Jeff 你好博主,我也在学习simulcast的过程,读了你的文档大受启发,我发现在UnifiedPlan标准下没办法直接通过设置num_simulcast_layers启用,所以也请帮忙将开启simulcast的逻辑发我一份,谢谢
@Jeff 请问iOS端webrtc上如何才能开启simulcast呢
@jwkuang 能不能把simulcast分享的方法,分享一下。
刚接触这一块,可能问的问题比较小白。想请教的就是simulcast我的理解是多个不同分辨率的流,但是设置里面为什么会有num_temporal_layers设置时域层数,这个不是SVC的概念吗?这是simulcast和svc混用吗?另外如果时域层数大于1,解码端解码h264使用的是ffmpeg能正常解码吗?
@xjm WebRTC里Simulcast主要指不同分辨率的流,WebRTC内部代码是叫做空域分层(跟SVC里的不一样),是通过创建多个编码器实现的。对于每个分辨率的流,如果支持SVC里的时域分层,也会配置每个分辨率各自的时域层数,只能说simulcast用到了SVC技术
@Jeff 非常感谢博主的回复,多谢
@xjm 非常感谢博主的回复,多谢
博主你好,1. 请问web端有什么方法,可以保证编码层数。
2. 通过设置每层的编码maxbitrate能起到什么作用呢
你好,请教一下,我下了个web的simulcast的例子
pc1.addTransceiver(
localStream.getVideoTracks()[0],
{
sendEncodings: [{rid: 'l', active: true}, {rid: 'm', active: true}, {rid: 'h', active: true}],
streams: [localStream]
});
在webrtc-internals里看确实有了3个RTCOutboundRTPVideoStream,但是只有一个在发送数据,怎么才能让3个都发数据呢
你好,博主,能不能将开启simulcast的逻辑发我一份,我发现我无论怎么设置num_simulcast_layers的值,对结果都没有影响
请问下博主,webrtc使用的openh264默认支持svc吗,我看源码里面好像只有vp8和vp9支持开启