webrtc拉流花屏

srs4.0支持rtmpwebrtc之间的互相转化. 使用obs推流到srs上,然后通过其官方带的demo页面播放,效果还不错,但是播放一会视频就花屏了. 一开始我猜想应该是srs实现上的问题. 后面就放下了一段时间, 直到最近,测试某云服务的webrtc直播功能.

现在的各个云厂家直播服务基本都支持了webrtc sfu功能,且支持rtmp推流,webrtc观看, rtmp作为传统的直播推流方式,各种硬件编码器,导播工具都广泛支持,依然是主流的推流方式,rtmp协议简单,基于tcp, 在这种推流方式下, 使用webrtc观看,一般是对视频编码有一定要求,比如无B帧的H264编码,这样可以不再额外做视频转码,因为视频转码消耗太大.音频由于两种协议编码格式不兼容, 一般都是要进行转码,好在音频转码消耗远小于视频,一般云厂商都会内置. 这样的直播方式,使用webrtc观看虽然无法发挥出动态码率的作用,但是webrtc观看的超低延时, 仍然让很多直播产品都争相加入.

接着说我在某些云厂商的webrtc观看功能看到了什么,那就是和之前srs上看到的一样的问题. 场景还是obs推流,webrtc观看功能,然后看到了花屏. 云厂商们开发的SFU是如何实现的,我不得而知,但是不太可能都是基于srs实现的,毕竟srs这个功能也没有出来很久. 这就不得不让人好奇了,因为webrtc解码前是有完整帧判断的, 对h264来说,只要中间出现丢帧,之后属于同一GoP的帧都将会被丢弃, 这样是不会出现花屏的.

花屏分析

首先排除上行的问题,因为使用rtmp拉流方式同一时间的码流画面是正常的,只有webrtc拉流会偶尔出现花屏.

再从下行链路分析,先抓个包看看, 这里payload 125h264的RTP流:

从抓包上看,丢包乱序现象比较严重,我一查线路,本地是联通网,部署srs的机器是电信网, 这个应该是丢包乱序的原因, 花屏也应该和丢包乱序有关.

我将本地网络也换成了电信网,再次测试抓包,基本没有任何丢包乱序,花屏也消失了,这也证实了上面的猜测.

乱序和丢包固然可能造成webrtc观看卡顿, 但是不应该会花屏,进一步分析需要更详细的信息. webrtc在chrome中是强制加密的,走DTLS协议, srs有个功能是关闭webrtc加密, 关闭加密可以在sdp中协商,但是只有chromium或者chrome canary版支持. 关闭加密后wireshark能分析出更多的信息,常用在调试中.

srs关闭加密功能在播放url参数中增加

1
dtls=false&encrypt=false

启动chrome打开详细日志:

1
--enable-logging=stderr --v=1 > log.txt 2>&1

再次复现花屏现象,查看日志,发现一些warning日志:

1
2
3
4
5
6
[113676:112964:0625/094129.237:WARNING:packet_buffer.cc(304)] Received H.264-IDR frame (SPS: 0, PPS: 0). Treating as key frame since WebRTC-SpsPpsIdrIsH264Keyframe is disabled

[113676:112964:0625/094131.213:WARNING:rtp_seq_num_only_ref_finder.cc(65)] Generic frame with packet range [59288, 59296] has no GoP, dropping frame.
[113676:112964:0625/094131.213:WARNING:rtp_seq_num_only_ref_finder.cc(65)] Generic frame with packet range [59280, 59287] has no GoP, dropping frame.
[113676:112964:0625/094131.213:WARNING:rtp_seq_num_only_ref_finder.cc(65)] Generic frame with packet range [59271, 59279] has no GoP, dropping frame.
[113676:112964:0625/094131.213:WARNING:rtp_seq_num_only_ref_finder.cc(65)] Generic frame with packet range [59262, 59270] has no GoP, dropping frame.

日志里有dropping frame, 确实存在丢帧现象, 还有个警告Treating as key frame since WebRTC-SpsPpsIdrIsH264Keyframe is disabled.

WebRTC-SpsPpsIdrIsH264Keyframe是chrome的一个field trial, 默认为false. 这个选项的意思是只有sps pps idr在一起,才会被当作关键帧. webrtc中关键帧判断会影响参考关系, 进而影响完整帧和丢帧逻辑. 一般来说chrome端webrtc的rtp封装h264都会在关键帧前插入sps pps.

再来看去除加密的抓包信息,可以分析出h264格式,需要在wireshark中TelephonyRTP选项中指定一下payload.

看到wireshark解密后的数据流,对应花屏日志,应该能看出端倪了. 抓包里红色部分应该是对应日志警告Treating...的内容. 可以看到sequence 58784这个包前面丢包很严重, 它的解析信息如下:

这个RTP包是个single nal的封装, nal type为5,并且有marker标记,h264封装中表示是完整一帧的结束包. 那么这是一个h264关键帧了? 这个结论不完全正确,仔细看它的前面, 这个包前面有很多IDR-Slice,但是都没有marker标记,原因是因为该h264码流是多slice编码的,一帧是由多个slice组成,所以sequence 58784虽然是single nal封装, 并且有marker标记,但它只是关键帧中的最后一个slice,它无法完整代表关键帧, 它的first_mb_in_slice字段也能表明这不是一帧的开始的slice.

分析到这里,大概的原因也就清晰了,这里chrome webrtc把这一个slice当成了keyframe,而实际它并不完整,因为前面有大量丢包,一旦chrome将它确定为关键帧,那么后续的完整P帧也将 会因为参考关系完整而被送入解码器,这一帧本身也会被送入解码,这样就会造成花屏了.

这恐怕也是chrome webrtcWebRTC-SpsPpsIdrIsH264Keyframe选项出现的原因. 如果这个选项enable, 那么只有挨着sps pps的那个idr slice, 并且直到marker标记的RTP包, 才算做一个完整的keyframe,这样就不会影响解码前的完整帧和参考关系判断,也就不会出现花屏了.

规避

知道花屏的原因了,可以通过chrome增加上述参数验证:

1
Chrome --force-fieldtrials="WebRTC-SpsPpsIdrIsH264Keyframe/Enabled/"

加上这个参数启动后再次测试,果然不再出现花屏现象了, 但是乱序丢包依然还是会造成卡顿的.

当然chrome的参数并不由直播发起方控制,要想在直播源处避免这个问题,那么最好禁用多slice编码.

obs使用x264编码,可以通过设置threads=1参数禁止多slice. 这样推出去的h264码流每帧都是单slice的.