一、FeedbackRtpTransportPacket
/* RTP Extensions for Transport-wide Congestion Control
* draft-holmer-rmcat-transport-wide-cc-extensions-01
0 1 2 3
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P| FMT=15 | PT=205 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of packet sender |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of media source |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| base sequence number | packet status count | // packet status count: 发送包总数
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| reference time | fb pkt. count |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| packet chunk | packet chunk |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| packet chunk | recv delta | recv delta |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
. .
. .
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| recv delta | recv delta | zero padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
1)、base sequence number:参考seq, 初始统计序号
2)、packet status count:反馈状态包数
3)、reference time:参考时间戳
4)、fb pkt. count:反馈的总包数,累计值(计算丢包率丢包率)
5)、packet chunk:包状态记录块,记录每个包到达的状态,分为
enum Status : uint8_t
{
NotReceived = 0, // 没有收到
SmallDelta, // 正常到达
LargeDelta, // 到达缓慢
Reserved,
None
};
packet chunk的两种压缩模式(通过首bit标识chunk类型、可以理解为字符串压缩算法):
——Run length chunk(行程长度编码数据块)
// 对于包
有一串字符串 aaabbbcddeeeeee,我们怎么尽量简单地去表示它让它长度变短呢?
// 其实可以写为:3a3bc2d6e —— 也就是按数字表示每个字母的数量,不带数字的就为一个。
// 对应到packet chunk里 结构就是:
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|T| S | Run Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// T 就为第一个 bit —— 编码类型
// S 两个bit 表示状态 (00未到达、01正常到达、10来慢了)
// Run Length 就是具体的数据
// 例如下面:
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0 0|0 0 0 0 0 1 1 0 1 1 1 0 1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 第一个bit是0,表示run length编码
// 后面两个bit是00, 表示未到达包状态
// 后面的数据 0 0 0 0 0 1 1 0 1 1 1 0 1 -> 11011101 换算成十进制就是221个包,存在221个包连续未到达
——Status vector chunk(状态矢量编码数据块)
// 有一串字符串 aaabbbcddeeeeee,我们怎么尽量简单地去表示它让它长度变短呢? // 其实可以写为:3a3bc2d6e —— 也就是按数字表示每个字母的数量,不带数字的就为一个。 // 对应到packet chunk里 结构就是: 0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |T|S| symbol list | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // T 就为第一个 bit —— 编码类型 // S 一个bit 表示状态 (注意:只有一位): 0表示后面的数据都按一位来(简单表示到达的状态,所以一般表示0未到达、1已到达)这样后面14个bit可以表示14个包。 // 1表示后面使用两位来表示(例如:00、01、10完全表示接到的状态,00未到达、01正常到达、10慢了)这样后面只能表示7个包。 // Run Length 就是具体的数据 // 例如下面: 0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1|0|0 1 1 1 1 1 0 0 0 1 1 1 0 0| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // 第一个bit是1,表示symbol list编码 // 后面一个bit是0, 表示使用一位表示法 // 后面的数据 01111100011100 ——>表示:第一个包未到达、接着5个包都到达了、随后3个包未到达、三个包到达了、最后两个包未到达 —— 一共14个包可表示。 0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1|1|0 0 1 1 0 1 0 1 0 1 0 0 0 0| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // 第一个bit是1,表示symbol list编码 // 后面一个bit是1, 表示使用两位表示法 // 后面的数据 0 0 1 1 0 1 0 1 0 1 0 0 0 0 ——>表示:第一个包未到达、第二个包 11 到慢了、01连续三个 正常达到、最后两个未到达 —— 一共7个包可表示。
6)、recv delta:到达时间间隔, 以0.25ms为间隔,表示RTP包到达时间与前面一个RTP包到达时间的间隔,对于记录的第一个RTP包,该包的时间间隔是相对reference time的。
如果在packet chunk记录了一个"Packet received, small delta"状态的包,那么就会在receive delta列表中添加一个无符号1字节长度receive delta,无符号1字节取值范围[0,255],由于Receive Delta以0.25ms为单位,故此时Receive Delta取值范围[0, 63.75]ms
如果在packet chunk记录了一个"Packet received, large or negative delta"状态的包,那么就会在receive delta列表中添加一个有符号2字节长度的receive delta,范围[-8192.0, 8191.75] ms
如果时间间隔超过了最大限制,那么就会构建一个新的TransportFeedback RTCP包,由于reference time长度为3字节,所以目前的包中3字节长度能够覆盖很大范围了
二、计算丢包率
1)、通过Transport统计出来当前的丢包率
// 预计发送包数 size_t expected_packets = feedback->GetPacketStatusCount(); // 统计丢包总数 size_t lost_packets = 0; for (const auto& result : feedback->GetPacketResults()) { if (!result.received) lost_packets += 1; } this->UpdatePacketLoss(static_cast<double>(lost_packets) / expected_packets);
2)、通过丢包直方图算法计算丢包率(加权平均)
void TransportCongestionControlClient::UpdatePacketLoss(double packetLoss) { // 把当前的丢包率统计进丢包直方图中 if (this->packetLossHistory.size() == PacketLossHistogramLength) this->packetLossHistory.pop_front(); this->packetLossHistory.push_back(packetLoss); /* * 计算加权平均值 * * 丢包率越靠近权重越大,最初的丢包率权重为1,后续的权重一次+1 * */ size_t weight{ 0 }; size_t samples{ 0 }; double totalPacketLoss{ 0 }; // 丢包率遍历 for (auto packetLossEntry : this->packetLossHistory) { // 权重累加 weight++; // 总计权重累加 samples += weight; // 总计丢包率 totalPacketLoss += weight * packetLossEntry; } // 加权平均丢包率 this->packetLoss = totalPacketLoss / samples; }