流媒体学习之路(mediasoup)——GCC的Feedback信令回复(8)
——
我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost
目标:可以让大家熟悉各类Qos能力、带宽估计能力,提供每个环节关键参数调节接口并实现一个json全配置,提供全面的可视化算法观察能力。
欢迎大家使用
——
文章目录
前面停了很久没有更新mediasoup的介绍文章,最近继续深入地分析几个比较有意思的类。本文把目光集中到GCC的反馈处理类——TransportCongestionControlServer中。它依附于Producer做上行gcc反馈,虽然只是给上行发送端记录feedback的小功能,但是仍然需要解析一下它的思想。
一、简述
首先,我们可以知道mediasoup中有很多的feedback类型(但是为啥我单独拎出来说这个呢?诶正好我在看,哈哈。后续别的咱再说):
准确来说,这个应该是传输的feedback。它的payload是有自己定义的结构的,但头部与正常的RTCP是一致的。
1.1 Feedback包结构
直接看这个feedback的结构:
/* 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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 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(反馈状态包数):这个统计了整个反馈中携带状态信息的计数,注意只是当前反馈包所携带的信息的计数,与后面的fb pkt. count 不同。这个的作用在于更方便地去取出状态数据。
3.reference time(参考时间):整个feedback信息记录的状态都可以根据这个参考时间计算出时间戳。
4.fb pkt. count(反馈的总包数):该包与上述的状态包计数不同,是一个累计值。它可以用于计算整体的feedback的丢包率。
5.packet chunk(包状态记录块):这个记录的是每个包的到达状态,可以为:未到达 00、正常到的 01、来慢了 10。为了压缩整个传输的数据包大小,分别设计了两种压缩模式——Run length chunk(行程长度编码数据块)与Status vector chunk(状态矢量编码数据块),packet chunk的第一bit标识chunk类型。
6.recv delta(到达时间间隔):以250us(0.25ms)为单位,表示RTP包到达时间与前面一个RTP包到达时间的间隔,对于记录的第一个RTP包,该包的时间间隔是相对reference time的。
1.2 数据内容
上述的几个内容还有两个比较值得展开说的——packet chunk和recv delta。
1.2.1 packet chunk
别的文章都已经介绍了无数遍了,packet chunk的两种编码模式就是为了更有效地节省数据空间,因此对整个接收状态进行了压缩。packet chunk里的每一块空间都是一个独立的接收状态展示。
其中第一bit为0的叫做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个包连续未到达
当第一个bit为1时,表示为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个包可表示。
1.2.2 Receive Delta
以250us(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字节长度能够覆盖很大范围了
以上说明总结起来就是:对于收到的RTP包在TransportFeedback RTCP receive delta列表中通过时间间隔记录到达时间,如果与前面包时间间隔小,那么使用1字节表示,否则2字节,超过最大取值范围,就另起新RTCP包了。
对于"Packet received, small delta"状态的包来说,receive delta最大值63.75ms,那么一秒时间跨度最少能标识1000/63.75~=16个包。由于receive delta为250us的倍数,所以一秒时间跨度最多能标识4000个包。
packet chunk以及receive delta的使用是为了尽可能减小RTCP包大小。packet chunk用到了不同编码方式,对于收到的RTP包才添加到达时间信息,而且是通过时间间隔的方式记录到达时间。
以上关于格式的内容参考了该大佬的文章:https://blog.jianchihu.net/webrtc-research-transport-cc-rtp-rtcp.html
二、代码分析
介绍完整体的包格式,我们下面开始对mediasoup的TransportCongestionControlServer类做具体的分析。
2.1 怎么开启transport-cc feedback?
首先我们发送rtp数据的时候需要开启transport-cc拓展头,只有开启了transport-cc的拓展头我们才能采集它的序号信息。因为在传输中,我们可能存在视频包、重传包、音频包等等,如果要把所有的带宽都算出来,那么这些包的拓展头序号是要连续的,这样就是一条独立的链路去做带宽估计,不受别的混乱序号的影响。
那么在mediasoup中怎么设置transport-cc的拓展头呢(该拓展头的字段如果是协商来的就不需要太担心,但假设你使用了自己自定义的id,那么需要和mediasoup对其id)?
上图的位置就是js层修改它id的地方,但是还不够,下层也要记得改:
这样你就可以自己自定义transport-cc的拓展字段id了。
2.2 类解析
下面展示了头文件,这个类除了具备feedback的能力之外,还有一个老掉牙的rembserver能力。rembserver其实是旧的接收端带宽估计的内容,目前已被移除了,但是待会还是简单介绍一下(之后会写一篇文章特意介绍 gcc——仅限发送端估计)。
// 头文件:#include "RTC/TransportCongestionControlServer.hpp"
namespace RTC
{
class TransportCongestionControlServer : public webrtc::RemoteBitrateEstimator::Listener,
public Timer::Listener
{
public:
class Listener
{
public:
virtual ~Listener() = default;
public:
virtual void OnTransportCongestionControlServerSendRtcpPacket(
RTC::TransportCongestionControlServer* tccServer, RTC::RTCP::Packet* packet) = 0;
};
public:
TransportCongestionControlServer(
RTC::TransportCongestionControlServer::Listener* listener,
RTC::BweType bweType,
size_t maxRtcpPacketLen);
virtual ~TransportCongestionControlServer();
public:
RTC::BweType GetBweType() const
{
return this->bweType;
}
void TransportConnected();
void TransportDisconnected();
uint32_t GetAvailableBitrate() const
{
switch (this->bweType)
{
case RTC::BweType::REMB:
return this->rembServer->GetAvailableBitrate();
default:
return 0u;
}
}
double GetPacketLoss() const;
void IncomingPacket(uint64_t nowMs, const RTC::RtpPacket* packet);
void SetMaxIncomingBitrate(uint32_t bitrate);
private:
void SendTransportCcFeedback();
void MaySendLimitationRembFeedback();
void UpdatePacketLoss(double packetLoss);
/* Pure virtual methods inherited from webrtc::RemoteBitrateEstimator::Listener. */
public:
void OnRembServerAvailableBitrate(
const webrtc::RemoteBitrateEstimator* remoteBitrateEstimator,
const std::vector<uint32_t>& ssrcs,
uint32_t availableBitrate) override;
/* Pure virtual methods inherited from Timer::Listener. */
public:
void OnTimer(Timer* timer) override;
private:
// Passed by argument.
Listener* listener{ nullptr }; // 监听上层
// Allocated by this.
Timer* transportCcFeedbackSendPeriodicTimer{ nullptr }; // 发送定时器
std::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> transportCcFeedbackPacket; // feedback 产生函数
webrtc::RemoteBitrateEstimatorAbsSendTime* rembServer{ nullptr }; // remb服务
// Others.
RTC::BweType bweType;
size_t maxRtcpPacketLen{ 0u }; // 最大rtcp的包长度
uint8_t transportCcFeedbackPacketCount{ 0u }; // feedback包的计数
uint32_t transportCcFeedbackSenderSsrc{ 0u }; // 发送的ssrc,在这里没啥用直接给的 0
uint32_t transportCcFeedbackMediaSsrc{ 0u }; // 媒体的ssrc,比较重要,在发送端接到feedback的时候会根据它来计算
uint32_t maxIncomingBitrate{ 0u }; // 最大进入码率
uint64_t limitationRembSentAtMs{ 0u }; // 最大限制码率
uint8_t unlimitedRembCounter{ 0u }; // 未限制计数
std::deque<double> packetLossHistory; // 丢包历史队列
double packetLoss{ 0 }; // 丢包率
};
} // namespace RTC
上述头文件中对于feedback比较重要的函数为:IncomingPacket、SendTransportCcFeedback 和 OnRembServerAvailableBitrate。这三个函数基本涵盖了整个tcc-feedback主流程。
IncomingPacket
首先IncomingPacket函数分成两个方式去处理进入的rtp包。当使用发送端估计时 BweType = TRANSPORT_CC,就进入到feedback生成的内容中;当使用接收端估计时 BweType = REMB,就进入到接收端带宽估计部分。
// transportCcFeedbackPacket 指针是一个继承了 webrtc中 RTCP 包的类,直接使用该类指针记录当前所有接包的信息就可以发送出去,后续的包重置即可进行下一轮的feedback记录。
void TransportCongestionControlServer::IncomingPacket(uint64_t nowMs, const RTC::RtpPacket* packet)
{
MS_TRACE();
switch (this->bweType)
{
case RTC::BweType::TRANSPORT_CC:
{
uint16_t wideSeqNumber;
// 获取不到seq退出
if (!packet->ReadTransportWideCc01(wideSeqNumber))
break;
// Update the RTCP media SSRC of the ongoing Transport-CC Feedback packet.
// 对senderssrc无需求,设置为0
this->transportCcFeedbackSenderSsrc = 0u;
// 需要mediassrc来区分包,取出备用
this->transportCcFeedbackMediaSsrc = packet->GetSsrc();
// 把待发送包的ssrc做记录
this->transportCcFeedbackPacket->SetSenderSsrc(0u);
this->transportCcFeedbackPacket->SetMediaSsrc(packet->GetSsrc());
// Provide the feedback packet with the RTP packet info. If it fails,
// send current feedback and add the packet info to a new one.
// 放入feedback类中
auto result =
this->transportCcFeedbackPacket->AddPacket(wideSeqNumber, nowMs, this->maxRtcpPacketLen);
switch (result)
{
// 成功则检测是否当前 feedback报文已满载? 满了就发送包
case RTC::RTCP::FeedbackRtpTransportPacket::AddPacketResult::SUCCESS:
{
// If the feedback packet is full, send it now.
if (this->transportCcFeedbackPacket->IsFull())
{
MS_DEBUG_DEV("transport-cc feedback packet is full, sending feedback now");
SendTransportCcFeedback();
}
break;
}
// 在放入的过程中满了就重置包等下一轮发送
case RTC::RTCP::FeedbackRtpTransportPacket::AddPacketResult::MAX_SIZE_EXCEEDED:
{
// Send ongoing feedback packet and add the new packet info to the
// regenerated one.
SendTransportCcFeedback();
this->transportCcFeedbackPacket->AddPacket(wideSeqNumber, nowMs, this->maxRtcpPacketLen);
break;
}
// 失败则直接重置,有可能前面的信息已失效
case RTC::RTCP::FeedbackRtpTransportPacket::AddPacketResult::FATAL:
{
// Create a new feedback packet.
this->transportCcFeedbackPacket.reset(new RTC::RTCP::FeedbackRtpTransportPacket(
this->transportCcFeedbackSenderSsrc, this->transportCcFeedbackMediaSsrc));
// Use current packet count.
// NOTE: Do not increment it since the previous ongoing feedback
// packet was not sent.
this->transportCcFeedbackPacket->SetFeedbackPacketCount(
this->transportCcFeedbackPacketCount);
break;
}
}
// 检测是否需要发送remb报文
MaySendLimitationRembFeedback();
break;
}
case RTC::BweType::REMB:
{
uint32_t absSendTime;
// 存在发送的时间则开始接收端计算否则返回
if (!packet->ReadAbsSendTime(absSendTime))
break;
// NOTE: nowMs is uint64_t but we need to "convert" it to int64_t before
// we give it to libwebrtc lib (althought this is implicit in the
// conversion so it would be converted within the method call).
auto nowMsInt64 = static_cast<int64_t>(nowMs);
// 放入接收端估计逻辑中
this->rembServer->IncomingPacket(nowMsInt64, packet->GetPayloadLength(), *packet, absSendTime);
break;
}
}
}
/* ----------------- 补充个额外的类:FeedbackRtpTransportPacket ----------------- */
// 这个类继承了 FeedbackRtpPacket,它的add函数中有个细节。
// 它使用的时间戳 this->latestTimestamp 记录时会对精度做一个调整,避免第一个包造成的误差影响后续的统计。
class FeedbackRtpTransportPacket : public FeedbackRtpPacket;
...
FeedbackRtpTransportPacket::AddPacketResult FeedbackRtpTransportPacket::AddPacket(
uint16_t sequenceNumber, uint64_t timestamp, size_t maxRtcpPacketLen)
{
...
// Let's see if we must set our base.
if (this->latestTimestamp == 0u)
{
this->baseSequenceNumber = sequenceNumber + 1;
this->referenceTime = static_cast<int32_t>((timestamp & 0x1FFFFFC0) / 64);
this->latestSequenceNumber = sequenceNumber;
this->latestTimestamp = (timestamp >> 6) * 64; // IMPORTANT: Loose precision. 除64 再乘 64 相当于把后面的精度去掉
return AddPacketResult::SUCCESS;
}
...
}
/* ----------------- 补充个额外的类:FeedbackRtpTransportPacket ----------------- */
SendTransportCcFeedback
该函数是feedback中的发送环节,很简单:
inline void TransportCongestionControlServer::SendTransportCcFeedback()
{
MS_TRACE();
// mediasoup发送包之前会对该数据进行可序化,这样就相当于让这个类变成了一个可发送的数据格式了
// 比如当前的feedback包还是一个正常的类,在外部可序化之后给空间做了限制,这样发送时就可序化了。
// 这里检测是否可序化过了,可序化过的包就不应该再继续操作而是直接反回
if (!this->transportCcFeedbackPacket->IsSerializable())
return;
// 取出序列号和时间
auto latestWideSeqNumber = this->transportCcFeedbackPacket->GetLatestSequenceNumber();
auto latestTimestamp = this->transportCcFeedbackPacket->GetLatestTimestamp();
// Notify the listener.
// 发送出去
this->listener->OnTransportCongestionControlServerSendRtcpPacket(
this, this->transportCcFeedbackPacket.get());
// Create a new feedback packet.
// 重新创建新的feedback包
this->transportCcFeedbackPacket.reset(new RTC::RTCP::FeedbackRtpTransportPacket(
this->transportCcFeedbackSenderSsrc, this->transportCcFeedbackMediaSsrc));
// Increment packet count.
// 设置当前已记录的feedback包数量
this->transportCcFeedbackPacket->SetFeedbackPacketCount(++this->transportCcFeedbackPacketCount);
// Pass the latest packet info (if any) as pre base for the new feedback packet.
// 最后把上一个包的时间先放入feedback里这样后续可以根据它做参考继续计算
if (latestTimestamp > 0u)
{
MS_WARN_TAG(rtcp, "frq test reset feedback packet add packet latestWideSeqNumber:%lld, latestTimestamp:%lld",
latestWideSeqNumber, latestTimestamp);
this->transportCcFeedbackPacket->AddPacket(
latestWideSeqNumber, latestTimestamp, this->maxRtcpPacketLen);
}
}
/* ----------------- 在这里插入OnTimer函数说一下间隔问题 ----------------- */
// 初始间隔100ms发送一个
static constexpr uint64_t TransportCcFeedbackSendInterval{ 100u }; // In ms.
inline void TransportCongestionControlServer::OnTimer(Timer* timer)
{
MS_TRACE();
if (timer == this->transportCcFeedbackSendPeriodicTimer)
SendTransportCcFeedback();
}
/* ----------------- 在这里插入OnTimer函数说一下间隔问题 ----------------- */
OnRembServerAvailableBitrate(remb回调函数)
OnRembServerAvailableBitrate这个函数是rembserver的一个观察者回调函数,当计算完成后会触发回调:
/* ----------------- 在这里插入讲一下 remb 发送 ----------------- */
// 当 BweType = REMB 时,进入到IncomingPacket 函数的数据就会进入到 rembserver 这个对象中进行接收端带宽估计。
// 在头文件中我们可以看到该类还继承了 webrtc::RemoteBitrateEstimator::Listener 监听者,意味着当计算出remb的码率后会触发一个回调
inline void TransportCongestionControlServer::OnRembServerAvailableBitrate(
const webrtc::RemoteBitrateEstimator* /*rembServer*/,
const std::vector<uint32_t>& ssrcs,
uint32_t availableBitrate)
{
MS_TRACE();
// Limit announced bitrate if requested via API.
// 取出接收码率与估计码率的最小值
if (this->maxIncomingBitrate != 0u)
availableBitrate = std::min(availableBitrate, this->maxIncomingBitrate);
#if MS_LOG_DEV_LEVEL == 3
std::ostringstream ssrcsStream;
if (!ssrcs.empty())
{
std::copy(ssrcs.begin(), ssrcs.end() - 1, std::ostream_iterator<uint32_t>(ssrcsStream, ","));
ssrcsStream << ssrcs.back();
}
MS_DEBUG_DEV(
"sending RTCP REMB packet [bitrate:%" PRIu32 ", ssrcs:%s]",
availableBitrate,
ssrcsStream.str().c_str());
#endif
// 封包
RTC::RTCP::FeedbackPsRembPacket packet(0u, 0u);
packet.SetBitrate(availableBitrate);
packet.SetSsrcs(ssrcs);
packet.Serialize(RTC::RTCP::Buffer);
// Notify the listener.
// 发送
this->listener->OnTransportCongestionControlServerSendRtcpPacket(this, &packet);
}
// 另外在设置最大进入码率的函数中也会触发发送的逻辑
void TransportCongestionControlServer::SetMaxIncomingBitrate(uint32_t bitrate)
{
MS_TRACE();
auto previousMaxIncomingBitrate = this->maxIncomingBitrate;
this->maxIncomingBitrate = bitrate;
if (previousMaxIncomingBitrate != 0u && this->maxIncomingBitrate == 0u)
{
// This is to ensure that we send N REMB packets with bitrate 0 (unlimited).
this->unlimitedRembCounter = UnlimitedRembNumPackets;
MaySendLimitationRembFeedback();
}
}
/* ----------------- 在这里插入讲一下 remb 发送 ----------------- */
三、结语
本文讲了mediasoup的拥塞控制接收端类,讲得稍微细一些。其实主要就两个功能:产生发送端带宽估计的feedback报文、产生接收端估计的remb报文(已废弃)。remb和transport-cc两个在mediasoup中无法同时使用,所以这俩只有一个会在工作时运行,之后我们分析一下webrtc的gcc算法和发送控制类——TransportCongestionControlClient。