- /* 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(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;
}