一、背景介绍
WebRTC实现的冗余方式有三种:UlpFEC(rfc5109)、FlexFEC(rfc8627)、inbandFEC(opus音频使用)。
UlpFEC和FlexFEC实现的原理都是,将一组M个报文进行异或,生成N(N就是FEC的冗余度)个FEC报文,打包出去。这组报文任意丢其中的N个,都可以通过这组(M-N)个报文+FEC冗余包恢复回来,增大FEC冗余报文的保护范围。例如下面示意图:D为媒体包,R为冗余包,该图所示的冗余度为2。
1、发送端打包示意图
2、网络丢包示意图
3、报文恢复示意图
二、FlexFec VS UlpFEC
媒体包打包格式 | FEC SSRC | FEC Sequence | |
UlpFEC | rfc2198+rfc5109 | 同媒体报文SSRC | 与媒体报文共享Sequence |
FlexFEC | rfc8627 | 单独SSRC | 使用独立的Sequence |
1、WebRTC H264在封装上只有一层Sequence,依靠Sequence连续来完判断是否是完整视频帧,没有更多信息的封装,导致如果FEC报文数据在一帧的包中间,这样的判断逻辑就会有问题。而VPX,除了Sequence,还有PictureId等比较多的边界信息和类型信息,可以很好的解码FEC包。所以WebRTC默认只有在VPX时才启用UlpFEC。
MaybeCreateFecGenerator
->ShouldDisableRedAndUlpfec
->PayloadTypeSupportsSkippingFecPackets
默认只有VPX支持UlpFEC冗余编码。
bool PayloadTypeSupportsSkippingFecPackets(const std::string& payload_name,
const WebRtcKeyValueConfig& trials) {
const VideoCodecType codecType = PayloadStringToCodecType(payload_name);
if (codecType == kVideoCodecVP8 || codecType == kVideoCodecVP9) {
return true;
}
if (codecType == kVideoCodecGeneric &&
absl::StartsWith(trials.Lookup("WebRTC-GenericPictureId"), "Enabled")) {
return true;
}
return false;
}
2、实际上UlpFEC的SSRC同媒体报文的SSRC,且Sequence和媒体报文是共享的,这样会导致在开启UlpFEC时,无法区分媒体报文还是FEC报文,在开启NACK时使FEC冗余包也会NACK重传,会导致一定程度上的带宽浪费。
所以目前大家都比较优选FlexFEC冗余编码,比较少使用UlpFEC冗余编码。
三、FlexFEC原理
1、冗余模式
按照rfc8627协议建议,FlexFEC可以在1D行、列、2D数组异或,三种编码模式。
1)1D行异或模式
2)1D列异或模式
3)2D数组异或模式
但是Webrtc源码仅实现了MaskRandom、MaskBursty或1D列异或冗余模式。
具体细节可参考PacketMaskTable::LookUp函数实现
2、RTP包协议
1)完整RTP报文格式
2)FEC Header定义
webrtc并没有完全遵循RFC协议,而是自己自定义一套协议。该格式在flexfec_header_reader_writer.h头文件定义。
mask掩码长度说明
// Size (in bytes) of packet masks, given number of K bits set.
constexpr size_t kFlexfecPacketMaskSizes[] = {2, 6, 14};
掩码用来标识改FEC冗余报文保护报文的列表。保护序列号列表的计算公式为:
SN base_i + mask对应为1的bit位置序号。 参见ForwardErrorCorrection::InsertFecPacket函数计算FEC冗余报文的保护队列序列号:
3、SDP协商
上面的示例可以看到,媒体视频rtp报文的pt值是100,flexfec冗余报文的PT值时110,媒体视频RTP报文的ssrc是1234,flexfec冗余报文的ssrc是2345。
对应webrtc的实现代码如下:
1、PT值确定GetPayloadTypesAndDefaultCodecs
2、ssrc的确认 AddFecFrSsrc
四、代码实现
1、FlexFEC编码实现
1)FlexFEC生成和push pacer队列调用栈
2)FlexFEC报文生成流程
UlpfecGenerator::AddPacketAndGenerateFec函数走读说明:
目前webrtc限制,仅支持48bit的掩码(因为Kbit需要占位,48个报文打冗余的话,maskhead就是14字节),代码里面有一处不足,就是单帧视频报文数大于48的话,后续报文不会push到media_packets_队列,也就不会参与冗余。
WebRTC这样设计是考虑到UlpFEC不允许在一帧的包中间发送FEC报文,否则会影响接收端对帧的完整性判断。但是目前FlexFEC使用单独的SSRC和Sequence参数,这个问题可以天然解决。但是WebRTC源码没有同步更新设计。导致会出现概率情况下一帧报文中的部分报文不能进行FEC保护。
2、FlexFEC解码实现