webrtc中的ulp fec实现分析
作者:互联网
目录
概述
webrtc在90版本使用了ULP FEC来应对网络丢包,ULP是Uneven Level Protection的缩写,意为不均等保护,可以针对数据的重要程度提供不同级别的保护。一般音视频的媒体数据,不同部分的重要程度是不一样的,越靠前的数据越重要,对靠前的数据使用更多的FEC包来保护,靠后的数据使用更少的FEC包来保护,这样就可更充分的利用带宽。
1.rfc5109关于ulpfec的定义
1.1 fec包结构
RTP Header就是标准的RTP头, FEC Header固定为10个字节,FEC Header后面可以跟着多个 Level,每个Level保护着不同的数据。
1.2 fec header结构
E: 保留的扩展标志位,必须设置为0
L:长掩码标志位。当L设置为0时,ulp header的mask长度为16bits,当L设置为1时,ulp header的mask长度为48bits。
P、X、CC、PT的值由此fec包所保护的RTP包的对应值通过XOR运算得到。
SN base: 设置为此fec包所保护的RTP包中的最小序列号。
TS recovery: 由此fec包所保护的RTP包的timestamps通过XOR运算得到。
Length recovery:由此fec包所保护的RTP包的长度通过XOR运算得到。
1.3 ulp header 结构
Protection Length: 此级别所保护的数据的长度。
mask: 保护掩码,长度为2个字节或者6个字节(由fec header 的L标志位决定)。通过mask可以知道此级别保护了哪些RTP包,例如
SN base等于100,mask值等于9b80,对应的二进制为 1001 1011 1000 0000,那么就可以知道第0、3、4、6、7、8个RTP包被此级别所保护,被保护的RTP包的序列号分别是100、103、104、10106、107、108。
2.webrtc中ulpfec的启用
如果采用H264编码,则不会启用ULPFEC功能,见代码call/RtpVideoSender::ShouldDisableRedAndUlpfec
编码生成fec包的个数由保护系数protection_factor和媒体包个数决定。protection_factor是一个由丢包率和有效比率决定的值,计算公式:
protection_factor = kFecRateTable[k];
k = rate_i * 129 + loss_j;
loss_j = 0, 1, .. 128;
rate_i是在某个范围内变化的值。
kFecRateTable是一个静态数组,在modules/video_coding/fec_rate_table.h文件中定义,具体的protection_factor计算过程在modules/video_coding/media_opt_util.cc文件的VCMFecMethod::ProtectionFactor函数。
一般网络畅通的时候不会生成fec包,只有在丢包的情况下protection_factor才不会为0,接下来才会生成fec编码包。
3.掩码表的作用
在ulpfec协议中,一个fec包可以保护多个媒体包,而一个媒体包也可以被多个fec包所保护。假设m个媒体包需要使用k个fec包来保护,那么可以通过一个掩码表来表示媒体包和fec包的关系。ULP Leave Header的mask字段可以是2个字节或者6个字节下面以2个字节为例,假如有12个媒体包需要3个fec包来保护,那么其掩码表packet_masks为: 9b 80 4f 10 3c 60,其中9b 80表示第一个fec包所保护的媒体包,4f 10和 3c 60分别表示第二个、第三个fec包所保护的媒体包。以9b 80为例进行分析,9b 80的二进制表示为:1001 1011 1000 0000,表明此fec包保护了第0、3、4、6、7、8个媒体包,假设基准序列号为27176,那么此fec包所保护的RTP包的序号就分别是27176、27179、27180、27182、27183、27184.
webrtc在modules/rtp_rtcp_source/fec_private_tables_bursty和fec_private_tables_random文件中预定义了两个掩码表kPacketMaskBurstyTbl和kPacketMaskRandomTbl,其中kPacketMaskBurstyTbl用于阵发性或者连续性的网络丢包环境,而kPacketMaskRandomTbl用于随机性的丢包环境。上面举例的掩码表packet_masks就是由kPacketMaskBurstyTbl或者kPacketMaskRandomTbl而得到。
4.webrtc中fec包的生成。
编码生成fec包的流程如下:
下面是EncodeFec函数的实现代码:
int ForwardErrorCorrection::EncodeFec(const PacketList& media_packets,
uint8_t protection_factor,
int num_important_packets,
bool use_unequal_protection,
FecMaskType fec_mask_type,
std::list<Packet*>* fec_packets) {
const size_t num_media_packets = media_packets.size();
// Sanity check arguments.
RTC_DCHECK_GT(num_media_packets, 0);
RTC_DCHECK_GE(num_important_packets, 0);
RTC_DCHECK_LE(num_important_packets, num_media_packets);
RTC_DCHECK(fec_packets->empty());
const size_t max_media_packets = fec_header_writer_->MaxMediaPackets();
if (num_media_packets > max_media_packets) {
RTC_LOG(LS_WARNING) << "Can't protect " << num_media_packets
<< " media packets per frame. Max is "
<< max_media_packets << ".";
return -1;
}
// Error check the media packets.
for (const auto& media_packet : media_packets) {
RTC_DCHECK(media_packet);
if (media_packet->data.size() < kRtpHeaderSize) {
RTC_LOG(LS_WARNING) << "Media packet " << media_packet->data.size()
<< " bytes "
"is smaller than RTP header.";
return -1;
}
// Ensure the FEC packets will fit in a typical MTU.
if (media_packet->data.size() + MaxPacketOverhead() + kTransportOverhead >
IP_PACKET_SIZE) {
RTC_LOG(LS_WARNING) << "Media packet " << media_packet->data.size()
<< " bytes "
"with overhead is larger than "
<< IP_PACKET_SIZE << " bytes.";
}
}
// Prepare generated FEC packets.
int num_fec_packets = NumFecPackets(num_media_packets, protection_factor);
if (num_fec_packets == 0) {
return 0;
}
for (int i = 0; i < num_fec_packets; ++i) {
generated_fec_packets_[i].data.EnsureCapacity(IP_PACKET_SIZE);
memset(generated_fec_packets_[i].data.data(), 0, IP_PACKET_SIZE);
// Use this as a marker for untouched packets.
generated_fec_packets_[i].data.SetSize(0);
fec_packets->push_back(&generated_fec_packets_[i]);
}
internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets);
packet_mask_size_ = internal::PacketMaskSize(num_media_packets);
memset(packet_masks_, 0, num_fec_packets * packet_mask_size_);
internal::GeneratePacketMasks(num_media_packets, num_fec_packets,
num_important_packets, use_unequal_protection,
&mask_table, packet_masks_);
// Adapt packet masks to missing media packets.
int num_mask_bits = InsertZerosInPacketMasks(media_packets, num_fec_packets);
if (num_mask_bits < 0) {
RTC_LOG(LS_INFO) << "Due to sequence number gaps, cannot protect media "
"packets with a single block of FEC packets.";
fec_packets->clear();
return -1;
}
packet_mask_size_ = internal::PacketMaskSize(num_mask_bits);
// Write FEC packets to |generated_fec_packets_|.
GenerateFecPayloads(media_packets, num_fec_packets);
// TODO(brandtr): Generalize this when multistream protection support is
// added.
const uint32_t media_ssrc = ParseSsrc(media_packets.front()->data.data());
const uint16_t seq_num_base =
ParseSequenceNumber(media_packets.front()->data.data());
FinalizeFecHeaders(num_fec_packets, media_ssrc, seq_num_base);
return 0;
}
EncodeFec函数的大概流程就是根据媒体包个数和保护系数protection_factor确定需要需要生成的fec包的个数num_fec_packets,如果num_fec_packets为0则函数返回,否则获取生成fec包所需要的掩码表并存放在packet_masks_变量中,然后调用GenerateFecPayloads生成所有的fec包,这个时候得到的fec包并不是最终的状态,还需要调用FinalizeFecHeaders来调整fec包的头部。
下面来看下GenerateFecPayload函数的实现:
void ForwardErrorCorrection::GenerateFecPayloads(
const PacketList& media_packets,
size_t num_fec_packets) {
RTC_DCHECK(!media_packets.empty());
for (size_t i = 0; i < num_fec_packets; ++i) {
Packet* const fec_packet = &generated_fec_packets_[i];
size_t pkt_mask_idx = i * packet_mask_size_;
const size_t min_packet_mask_size = fec_header_writer_->MinPacketMaskSize(
&packet_masks_[pkt_mask_idx], packet_mask_size_);
const size_t fec_header_size =
fec_header_writer_->FecHeaderSize(min_packet_mask_size);
size_t media_pkt_idx = 0;
auto media_packets_it = media_packets.cbegin();
uint16_t prev_seq_num =
ParseSequenceNumber((*media_packets_it)->data.data());
while (media_packets_it != media_packets.end()) {
Packet* const media_packet = media_packets_it->get();
const uint8_t* media_packet_data = media_packet->data.cdata();
// Should |media_packet| be protected by |fec_packet|?
if (packet_masks_[pkt_mask_idx] & (1 << (7 - media_pkt_idx))) {
size_t media_payload_length =
media_packet->data.size() - kRtpHeaderSize;
bool first_protected_packet = (fec_packet->data.size() == 0);
size_t fec_packet_length = fec_header_size + media_payload_length;
if (fec_packet_length > fec_packet->data.size()) {
// Recall that XORing with zero (which the FEC packets are prefilled
// with) is the identity operator, thus all prior XORs are
// still correct even though we expand the packet length here.
fec_packet->data.SetSize(fec_packet_length);
}
if (first_protected_packet) {
uint8_t* data = fec_packet->data.data();
// Write P, X, CC, M, and PT recovery fields.
// Note that bits 0, 1, and 16 are overwritten in FinalizeFecHeaders.
memcpy(&data[0], &media_packet_data[0], 2);
// Write length recovery field. (This is a temporary location for
// ULPFEC.)
ByteWriter<uint16_t>::WriteBigEndian(&data[2], media_payload_length);
// Write timestamp recovery field.
memcpy(&data[4], &media_packet_data[4], 4);
// Write payload.
if (media_payload_length > 0) {
memcpy(&data[fec_header_size], &media_packet_data[kRtpHeaderSize],
media_payload_length);
}
} else {
XorHeaders(*media_packet, fec_packet);
XorPayloads(*media_packet, media_payload_length, fec_header_size,
fec_packet);
}
}
media_packets_it++;
if (media_packets_it != media_packets.end()) {
uint16_t seq_num =
ParseSequenceNumber((*media_packets_it)->data.data());
media_pkt_idx += static_cast<uint16_t>(seq_num - prev_seq_num);
prev_seq_num = seq_num;
}
pkt_mask_idx += media_pkt_idx / 8;
media_pkt_idx %= 8;
}
RTC_DCHECK_GT(fec_packet->data.size(), 0)
<< "Packet mask is wrong or poorly designed.";
}
}
GenerateFecPayloads函数的处理流程如下:
(1)取fec包列表generated_fec_packets_中的一个包fec_packet,计算此fec包所对应的掩码表的索引 pkt_mask_idx,计算此fec包的头部大小fec_header_size,计算媒体包列表media_packets的第一个包的序号prev_seq_num。
(2)取media_packets_的一个包media_packet进行处理,通过掩码值和media_packet的序列号来判断此media_packet是否是被fec_packet所保护。如果不受保护则转到(5)处理。如果受保护,则判断此media_packet是否此fec_packet保护的第一个包,如果是则转到(3)处理,否则转到(4)处理。
(3)a.将media_packet的头两个字节拷贝fec_packet的头两个字节,因为RTP头部的第一个字节的开始两位是版本事情,而FEC头部第一个字节的开始两位是是E和L,所以可以使用memcpy直接复制,后面调用FinazlizeFecHeaders函数再对E、L位进行修正。b.将media_packet的负载长度临时写到fec_packet的第3、4个字节,后面调用FinazlizeFecHeaders函数再对长度进行修正。c.将media_packet的时间戳写到fec_packet。d.将media_packet的负载数据写到fec_packet。转到(5)处理。
(4)将media_packet与fec_packet的头部进行xor运算,运算结果写到fec_packet的头部。将media_packet与fec_packet的负载数据进行xor运算,运算结果写到fec_packet的负载部分。
(5)取media_packets_的下一个包,转(2)继续处理。遍历media_packets_后结束循环。
(6 )取generated_fec_packets_的下一个包,转(1)继续处理。遍历generated_fec_packets后结束循环,至此所有的fec包基本都构建好了。
接下来再看FinalizeFecHeaders函数的代码:
void ForwardErrorCorrection::FinalizeFecHeaders(size_t num_fec_packets,
uint32_t media_ssrc,
uint16_t seq_num_base) {
for (size_t i = 0; i < num_fec_packets; ++i) {
fec_header_writer_->FinalizeFecHeader(
media_ssrc, seq_num_base, &packet_masks_[i * packet_mask_size_],
packet_mask_size_, &generated_fec_packets_[i]);
}
}
void UlpfecHeaderWriter::FinalizeFecHeader(
uint32_t /* media_ssrc */,
uint16_t seq_num_base,
const uint8_t* packet_mask,
size_t packet_mask_size,
ForwardErrorCorrection::Packet* fec_packet) const {
uint8_t* data = fec_packet->data.data();
// Set E bit to zero.
data[0] &= 0x7f;
// Set L bit based on packet mask size. (Note that the packet mask
// can only take on two discrete values.)
bool l_bit = (packet_mask_size == kUlpfecPacketMaskSizeLBitSet);
if (l_bit) {
data[0] |= 0x40; // Set the L bit.
} else {
RTC_DCHECK_EQ(packet_mask_size, kUlpfecPacketMaskSizeLBitClear);
data[0] &= 0xbf; // Clear the L bit.
}
// Copy length recovery field from temporary location.
memcpy(&data[8], &data[2], 2);
// Write sequence number base.
ByteWriter<uint16_t>::WriteBigEndian(&data[2], seq_num_base);
// Protection length is set to entire packet. (This is not
// required in general.)
const size_t fec_header_size = FecHeaderSize(packet_mask_size);
ByteWriter<uint16_t>::WriteBigEndian(
&data[10], fec_packet->data.size() - fec_header_size);
// Copy the packet mask.
memcpy(&data[12], packet_mask, packet_mask_size);
}
FinalizeFecHeader函数修正了fec头的E和L标志位,同时写入了基准序列号,更正了length recovery字段的值,最后写入ulp level header。ulp level header由保护长度(2个字节)和掩码(2个或者6个字节)组成。由FinilizeFecHeader函数可以看出,虽然ulp fec协议支持在一个fec包里面封装多个保护级别的数据,但webrtc实际上只用到了一个级别。
5.利用fec包恢复丢失的rtp包
fec包的解析就是fec包封装的逆过程,代码就不贴了,可以参见modules/rtc_rtcp/source/forward_error_correction.cc::DecodeFec以及相关函数,下面简要下收到RTP包后恢复丢失的RTP包的处理流程。
(1)判断此RTP包是否是fec包,如果是那么把这个包放到队列received_fec_packets_里面,并通过fec包里面的mask字段解析出此fec包所保护的所有RTP包,把这些被保护的RTP包存放到此fec包下面。
(2)如果此RTP包是媒体包,那么更新received_fec_packets中对应的fec包的保护包接收情况。
(3)尝试恢复丢失的包。遍历received_fec_packets_,如果其中的一个fec包的所保护的RTP包的缺失数packets_missing刚好是1,那么就利用此fec包恢复缺失的RTP包。如果packets_missing为0,证明此fec包所保护的RTP包均已收到,丢弃此fec包。如果packets_missing大于1则处理下一个fec包。
6.Red打包格式
Red(Redundant coding)编码是webrtc中采用的一种编码方式,虽然modules/rtp_rtcp/source/ulpfec_receiver_impl.cc文件中存在red格式的定义:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F| block PT | timestamp offset | block length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
但实际上调试发现webrtc在采用red编码时打包RTP包仅仅是改变了原RTP包的payload类型,同时在原有的payload前增加多一个字节的red_payload。假设原有的RTP的的负载类型是96,而red负载类型是122,那么包结构如下:
原rtp包: RTP Header(PT = 96 ) + payload
red格式rtp包: RTP Header(PT = 122) + red_payload(一字节, 值为96) + payload
payload部分没有任何改变,如果是fec类型的包,那么red_payload = 122, 这样接收端就可以通过red_payload的值来区分此RTP包是普通媒体包还是fec包。
7.fec打包和RTP包加密的先后顺序
fec打包在加密操作之前进行。所有的音视频RTP包、fec类型的RTP包都会被送到pacing模块,再由pacing模块传到pc_network_thread线程进行发送,在调用socket发送数据包之前才调用libsrtp模块进行加密,加密是针对RTP包的payload部分进行。收端接收到RTP包后需要先解密再进行解析。
8.如何区分音频RTP包和视频RTP包
当webrtc使用同一个udp端口来传输音视频数据时,需要能够区分音频、视频RTP包。可以通过RTP包中的SSRC来区分。音频、视频的RTP包的SSRC不同,接收端在得到RTP包的SSRC后,根据SSRC来进行不同的处理。
9.如何区分RTP包和RTCP包
通过负载类型来区分
// For additional details, see http://tools.ietf.org/html/rfc5761.
bool IsRtcpPacket(rtc::ArrayView<const char> packet) {
if (packet.size() < kMinRtcpPacketLen ||
!HasCorrectRtpVersion(
rtc::reinterpret_array_view<const uint8_t>(packet))) {
return false;
}
char pt = packet[1] & 0x7F;
return (63 < pt) && (pt < 96);
}
标签:fec,media,ulp,packets,packet,RTP,webrtc,size 来源: https://blog.csdn.net/weixin_29405665/article/details/107250663