定义和作用:
pacing 用于udp,相当于tcp的拥塞窗口,根据码率发送字节数.
Pacing按照节奏(一般是5ms)保证报文匀速地发送到网络中,可以避免短时间大量报文造成网络拥塞
根据码率探测结果发送,为什么还存在burst问题呢?
网上的comment:
"针对TCP的问题,我们的协议做了很多优化.首先是Pacing发送,Pacing发送我理解就是均匀的发送,在早期TCP的发送方式,可能一刻间交给网络去传输该RTT内要发送的所有包,然后RTT会越来越大,因此它会有排队, RTT进而变得更大,这样会发送更多,最终会导致路由器排队撑不过来,就会导致丢包,网速抖动。
更好的传输方式是均匀的发,一个RTT是40毫秒,我发40个包,这里每一毫秒发一个包,然后一毫秒之后再发另外一个包,这样对路由器非常均匀,就可以不会哪样频繁地丢包,把网络利用上去"
腾讯云PCDN:从P2P到万物互联服务框架 - 知乎
下游输出:经过调度后输出要发送的报文(入参PacketRouter)。PacedSender
其参数PacketRouter
,可以将pacing后的rtp报文路由到不同模块去做实际发送。PacedSender
,支持Module方式的周期调度(如5ms)以及自定义ProcessThread
的调度方式(可以动态调度);
pacing模块由两个部分组成:
InsertPacket
Process
PacedSender 发送速率设置
PacedSender 包缓存队列
发包间隔 5ms 计算
线程处理方法 process()
发包方法SendPacket()
media_budget //为了平滑。5ms定时发送,根据码率有个预算发送量。保证5ms发送的数据量都是一样的。如果包比较大超过预算,则bytes_remaining_是负值。下个5ms则不发包.
pace实际不发包,仅仅是提供bytes_remaining_给rtp module,真正发包的是rtp module.
paced_sender_->InsertPacket
--PacedSender::InsertPacket
-- // 封装 packet 包,放到list中 packets_//PacketQueue::Push
packets_->Push(PacketQueue::Packet( ssrc, sequence_number...));
void PacedSender::Process() {
......
size_t queue_size_bytes = packets_->SizeInBytes();
int64_t avg_time_left_ms = std::max<int64_t>(1, queue_time_limit - packets_->AverageQueueTimeMs());
// 1 根据包大小/包在queue中单的平均时长,计算发送队列中的所有的这些数据需要的最小码率
int min_bitrate_needed_kbps =
static_cast<int>(queue_size_bytes * 8 / avg_time_left_ms);
if (min_bitrate_needed_kbps > target_bitrate_kbps)
target_bitrate_kbps = min_bitrate_needed_kbps;
//2 更新发送预算量bytes_remaining_。最大是500ms发送量
media_budget_->set_target_rate_kbps(target_bitrate_kbps);
// 3 更新30ms这么长时间可以发送多少字节数(上限和下限)
UpdateBudgetWithElapsedTime(elapsed_time_ms);
}
//=========probe for next time====
bool is_probing = prober_->IsProbing();
PacedPacketInfo pacing_info;
size_t bytes_sent = 0;
size_t recommended_probe_size = 0;
if (is_probing) {
// 若当前间隔探测器有效,使用BitrateProber探测
pacing_info = prober_->CurrentCluster();//pace_info
// 4 推荐的最小探测间隔是2ms, 发送2ms的数据
recommended_probe_size = prober_->RecommendedMinProbeSize();
}
//=====下面是循环发送packet数据=============================================
while (!packets_->Empty() && !paused_) {
// 从packets_队列中取出一个待发送RTP包(此时仅取出但不从队列中移除)
const PacketQueue::Packet& packet = packets_->BeginPop();
//===4 SendPacket内部实现1 PacketRouter::TimeToSendPacket 2 发包后会 update: bytes_remaining_*/
if (SendPacket(packet, pacing_info)) {//packet 有优先级
...
}
}
// 6通过已发送数据字节数bytes_sent和当前时间来更新bitrate probe以计算出下次
// Process方法调用时间点
if (is_probing) {
probing_send_failure_ = bytes_sent == 0;
if (!probing_send_failure_)
prober_->ProbeSent(clock_->TimeInMilliseconds(), bytes_sent);
}
}
int64_t PacedSender::TimeUntilNextProcess() {
rtc::CritScope cs(&critsect_);
// 当前时间 减去 上一次 process()方法调用的时间,得出时间间隔
int64_t elapsed_time_us =
clock_->TimeInMicroseconds() - time_last_process_us_;
int64_t elapsed_time_ms = (elapsed_time_us + 500) / 1000
if (prober_->IsProbing()) {//使用探测
//BitrateProber::TimeUntilNextProbe//下次探测时间-now等于时间间隔
int64_t ret = prober_->TimeUntilNextProbe(clock_->TimeInMilliseconds());
}
//没有使用探测: kMinPacketLimitMs:5ms,即 process() 方法 5m 处理一次
return std::max<int64_t>(kMinPacketLimitMs - elapsed_time_ms, 0);
}
PacedSender基本工作原理:
IntervalBudget
就是一段时间内的发送码率预算。
类 IntervalBudget中主要两个变量,发送速率(target_rate_kbps_),允许向外发送的字节(bytes_remaining_)。
每次发包前会更新media_budget
中预算bytes_remaining_
的大小,而每次发送时间(<= 5ms)内最多发送 bytes_remaining_
字节数,从而达到限制和平滑带宽的目的.PacedSender
中 padding
发送的原理和此类似。
notes:每次预算发送bytes_remaining_
,why会出现多发呢?
通过Process方法的不断被调用来SendPacket;
通过media_budget模块来计算本次理应发送的字节数,即预算.数据包发送后根据本次实际发送的字节数和理应发送的字节数来判断下次是否应该允许继续发送?
通过BitrateProbe模块来探测每次SendPacket后要wait多久?
在Process内部分2部分,发送数据前和发送数据后.
1) 发送前要依当前带宽和本轮Process和上次Process的时间间隔值来更新media_budget预算,以决定是否允许本轮发送数据。
2) 发送成功后要调用BitrateProber的ProbeSent方法来更新下次Process调用等待时间间隔。
在PacedSender.cc中最重要的就是Process和TimeUntilNextProcess方法,Process是主发送线程,其由外部其他逻辑驱动进行定时调用. 每次调用Process的时间间隔是从TimeUntilNextProcess方法中获取的。
此间隔值依带宽变化和本轮探测中已发送数据包的字节数来估算??
整体流程
1. pace 模块 在 modules\pacing 中
2. 看下paced_sender.cc,这个是pace.h Pacer 接口的实现,主要有如下几个接口
3. PacedSender::InsertPacket, 这里没有把真正的数据insert packet 中,而是把数据信息插入
4. 看下Push 动作, RoundRobinPacketQueue::Push
4. PacedSender::Process(),从取出要要发送的packet 信息,然后发送到socket 中
5. RoundRobinPacketQueue::BeginPop
6. PacedSender::SendPacket 这里层层传递后调用 RTPSender::TimeToSendPacket
2.1、PacedSender 发送速率设置
GCC 估测的带宽只会通过 PacedSender::SetEstimatedBitrate方法设置到 PacedSender 中, pacing_bitrate_kbps_ 为 PacedSender 发送媒体包的速率,为GCC估测带宽 乘以了固定系数 kDefaultPaceMultiplier(2.5)
2.2、PacedSender 包缓存队列(输入) PacedSender::InsertPacket
该方法由 rtp_sender 模块调用,将封装好的视频rtp包的信息,如 ssrc, sequence_number等封装成Packet数据结构存储到队列中,并未缓存真正的媒体数据。
发包时,PacedSender 会通过这些信息,在rtp_sender中的缓存队列中找到对应的媒体包数据,最终发送到到网络上。
2.3、发包间隔 5ms 计算:PacedSender::TimeUntilNextProcess
使用bitrateProber探测,BitrateProber::TimeUntilNextProbe方法来计算.
没有开启探测,则使用默认值5ms一次探测
2.4、线程处理方法 process() //pacesedner继承于module
Process方法是PacedSender的主发送线程,在Process方法内没有while循环和wait机制
,其是由其他模块在外部驱动的。
具体可参考call/rtp_transport_controller_send.cc 中有关PacedSender是如何作为一个module注册到process thread中的,以及是如何在ProcessThread中对各module的Process方法进行驱动的。因为PacedSender继承于Pacer,而Pacer类又继承于module.
调用Process的模块每次都会等待一个时间间隔,此时间间隔是PacedSender::TimeUntilNextProcess由BitrateProbe模块来探测出来的.
Process方法的主要功能就是SendPacket及更新预算
2.5、发包方法SendPacket()
Process由外部调用,SendPacket()由Process的while发包循环调用,发包后会调用 UpdateBudgetWithBytesSent(packet.bytes) 从 media_budget_ 减去packet.bytes长度的发包预算, 当发包循环走几次之后,media_budget中的预算长度被消耗完,
即 <= 0,此时media_budget_->bytes_remaining方法会做 max(0, bytes_remaining_) 处理,即返回0 而发包前会判断 media_budget_->bytes_remaining() == 0 ,满足条件就return false不发了。
2.6、media_budget简介
media_budget
是在 PacedSender
中封装的一个类
线程池每5ms(kMinPacketLimitMs)进入PacedSender::Process处理一次。
假设发送的速率设定为200KB/s,假设一个数据包长度为1500Byte。
那么每隔5ms可以向外发送1000byte的数据(200k*5/1000),但是实际上pacing模块按照数据包(1500Byte)为单位向网络中发送数据包。
只要bytes_remaining_>0。上个时间间隔若"多发"了字节,需要在本次时间间隔内"少发”(bytes_remaining_ = bytes_remaining_ + bytes)。
bytes_remaining_<0的情况是,从queue中取出这个包,超过了发送量,所以bytes_remaining_为负数,这样下次就不发了.
1 设置目标发送码率
IntervalBudget::set_target_rate_kbps
- 在
IntervalBudget
中根据目标码率配合时间窗口,进行每相隔指定的时间范围内应该发送预算多少字节。
2)增加发送预算
当bytes_remaining_ < 0 说明上次发多了。
void IntervalBudget::IncreaseBudget(int64_t delta_time_ms) {
int64_t bytes = target_rate_kbps_ * delta_time_ms / 8;
if (bytes_remaining_ < 0 || can_build_up_underuse_) {
// We overused last interval, compensate this interval.
bytes_remaining_ = std::min(bytes_remaining_ + bytes, max_bytes_in_budget_);
} else {
// If we underused last interval we can't use it this interval.
bytes_remaining_ = std::min(bytes, max_bytes_in_budget_);
}
}
3)调整剩余预算
void IntervalBudget::UseBudget(size_t bytes) {
bytes_remaining_ = std::max(bytes_remaining_ - static_cast<int>(bytes),
-max_bytes_in_budget_);
}
- 每成功发送一包数据后需要调用该函数对使用预算进行更新。
- 这里假设在发送数据前调用了
set_target_rate_kbps(240)
,IncreaseBudget(30)
,也就是目标发送码率240kbps,发送时间为30ms.经上述的计算得出,预算该30ms内可以发送900字节的数据. - 假设第一次发送数据发送了200字节,那么发送成功后调用该函数,算出剩余的预算还有900-200=700字节。
4)获取剩余预算
size_t IntervalBudget::bytes_remaining() const {
return rtc::saturated_cast<size_t>(std::max<int64_t>(0, bytes_remaining_));
}
发送
Pacing根据是否有budget剩余决定是否可以发送,如果有的话从RoundRobinQueue
中取最高优先级的报文发送,通过这种方式可以控制发送码率为设置的码率。
- 设置目标码率:
IntervalBudget::set_target_rate_kbps
- 获取需要发送的报文:
GetPendingPacket
,先判断budget是否足够,足够则调用RoundRobinPacketQueue::Pop
得到需要发送的报文 - 随着时间流逝(如设置拥塞窗口、插入报文、发送报文时检测),增加窗口
UpdateBudgetWithElapsedTime
->IntervalBudget::IncreaseBudget
。 - 报文发送完成后,需要减少budget:
UpdateBudgetWithSentData
->IntervalBudget::UseBudget
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,Pacer 网络报文平滑策略
作用:等于UDP上实现的滑动窗口,控制发送量。
视频帧可能分别封装在多个RTP报文,若这个视频帧RTP报文同时发送到网络,必然会导致网络瞬间拥塞.PACER 就是实现把RTP同一时刻产生的若干包,周期性的发送,防止上行流量激增导致拥塞
让视频数据按照评估码率均匀的分布在各个时间片里发送。
削峰填谷; 将报文平滑地发送给接收端。在弱网环境尤其重要。
根据 estimator 计算的码率,来计算发包频率
因为帧大小不同,如果编码出来就发,造成有的时候发的多,有的时候发的少。
所以编码出来的帧,都放入pacer queue。由queue定时发送固定数量,这样每秒发送的数据量是一样。避免了上面的错误.
PACER模块定量计算发送网络报文数据量,相当于cache等待发送。
会引起延迟?应该结合码率一起看。
5ms发送一次,40ms发送8次,I frame仍然没有发完会报卡
如果不定时,立刻全发。发生网络拥塞,丢包。也是报卡
上面是网络能力不足,如果网络能力足够那么直接发就完了。
200k i帧,40ms发送,5M/s=40Mb/s.那么网速小于这个值,这种发送防止过度拥塞.
这个有必要吗?写到socket,tcp会根据网速发送的。socket有可能不会一次发完。因为这个是udp所以自己根据码率发送?
budget:
是个评估单位时间内可以发送多少数据量的一个机制,因为pacer是会根据pace timer定时来触发发送检查.Budget会根据评估出来的参考码率计算这次定时事件能发送多少字节,可以表示为:
delta time是上次检查时间点和这次检查时间点的时间差。
target bitrate是pacer的参考码率,是由estimator根据网络状态评估出来的。
remain_bytes每次触发发包时会减去发送报文的长度size,如果remain_bytes > 0,继续从pace queue中取下一个报文进行发送,直到remain_bytes <=0 或者 pace queue没有更多的报文。
2 什么时候发:有两种模式kPeriodic,kDynamic.kPeriodic模式下,固定每隔5ms调用一次发送报文
如果pacer queue没有更多待发送的报文,但media_budget_计算出还可以发送更多的数据,这个时候pacer会进行padding报文补充。
WebRTC中pacer的流程比较清晰,分为三步:
1)如果一帧图像被编码和RTP切分打包后,先会将RTP报文存在待发送的队列中,并将报文元数据(packet id, size, timestamp, 重传标示)送到pacer queue进行排队等待发送,插入队列的元数据会进行优先级排序。
2)pacer timer会触发一个定时任务事件来计算budget,budget会算出当前时间片网络可以发送多少数据,然后从pacer queue当中取出报文元数据进行网络发送。
3)如果pacer queue没有更多待发送的报文,但budget却还可以发送更多的数据,这个时候pacer会进行padding报文补充。
其中2) 进一步说明:
发多少:
1) PacingController::ProcessPackets被定时触发后,会计算当前时间和上次被调用时间的时间差
2) 将时间差参数传入media_budget_,media_budget_算出当前时间片网络可以发送多少数据,
3) 从pacer queue当中取出报文元数据进行网络发送。
media_budget_根据评估出来的参考码率计算这次定时事件能发送多少字节的公式如下:
delta time:上次检查时间点和这次检查时间点的时间差。
target bitrate:pacer的参考码率,是由estimator根据网络状态评估出来的。
remain_bytes:每次触发发包时会减去发送报文的长度size,如果remain_bytes > 0,继续从pace queue中取下一个报文进行发送,直到remain_bytes <=0 或者 pace queue没有更多的报文。
设置发送速率 PacedSender::SetEstimatedBitrate(bitrate_bps)
包缓存队列;发包线程按一定间隔发包。
实现 :pace queue是一个基于优先级排序的多维链表,它并不是一个先进先出的fifo,而是一个按优先级排序的list
PacingController::NextSendTime,PacingController::ProcessPackets是PACER模块两个核心函数.
PacingController::NextSendTime在控制发送节奏上,有两种模式kPeriodic、kDynamic
2.1、PacedSender 发送速率设置
2.2、PacedSender 包缓存队列
2.3、发包间隔 5ms 计算
2.4、线程处理方法 process()
2.5、发包方法SendPacket()
2.6、media_budget简介
webrtc QOS方法十(pacer实现)_CrystalShaw的博客-CSDN博客_webrtc平滑发送
发送调节器 PacedSender代码走读_好记性不如写博客!-CSDN博客