webrtc QOS笔记三 Nack机制浅析
nack源码浅析
Video Nack
机制概述
- nack的机制非常简洁,收到非连续的packet seq 会将丢包的seq插入自身nack_list缓存, 之后立即发送一次那组丢包的seq重传请求, 之后如果超时仍然没有收到重传回来的seq, 就通过定时任务继续发送.
nack 三个缓存list
- nack_list_ : 用于记录已丢包的信息,seq 即为list key
- keyframe_list_ : 记录关键帧序列号,可用于后面清理比关键帧老的过旧nack,
- recovered_list_ : 用于记录从RTX或FEC恢复过来的包,
nack 两种发送方式:
1.kSeqNumOnly : 开启nack模块后,nack会检查接收到packet的序列号,如果序列号连续性中断即认为是丢包了,如下例子,上一次最新收到的包序列号为38,当前新收到的序列号为41,那么[39,40]就判定为是丢掉了,会立刻发送这组[39,40]的nack重传请求
newest_seq_num_:36 seq_num:37 is_keyframe:0 is_recovered: 0
newest_seq_num_:37 seq_num:38 is_keyframe:0 is_recovered: 0
newest_seq_num_:38 seq_num:41 is_keyframe:0 is_recovered: 0
newest_seq_num_:41 seq_num:42 is_keyframe:0 is_recovered: 0
newest_seq_num_:42 seq_num:43 is_keyframe:0 is_recovered: 0
2.kTimeOnly : nack 模块创建后会启动一个定时任务,默认周期kUpdateInterval(20ms), 这个周期任务会调用GetNackBatch(kTimeOnly)从nack_list里面获取满足发送条件的seq,批量发送nack重传请求.
repeating_task_ = RepeatingTaskHandle::DelayedStart(
TaskQueueBase::Current(), kUpdateInterval,
[this]() {
std::vector<uint16_t> nack_batch = GetNackBatch(kTimeOnly);
if (!nack_batch.empty()) {
nack_sender_->SendNack(nack_batch, false);
}
});
3.kSeqNumOnly模式是在接收packet的时候触发一次,并且只发送一次,即第一次,之后如果仍然没有收到重传回来的包就通过kTimeOnly定时任务方式继续请求重传.
nack模块源码简析
nack模块
- nack模块位于在RTX和FEC 和 jitter buffer之间,经过Call模块将RTP包分发到RtpVideoStreamReceiver模块,当模块RtpVideoStreamReceiver每次对rtp包进行处理的时候都会调用NackModule::OnReceivedPacket()主动驱动NackModule模块.
nack list
- insert :
- insert : AddPacketsToNack()会判断包的连续性,相应的丢包序列如果不在recover list里面就会插入
- erase :
- 1.序列号距离当前收到的序列号过旧的包kMaxPacketAge(10000)
- 2.nack_list 大小 + 即将插入的nack 序列数量如果超过kMaxNackPackets(1000) 就会清理掉关键帧之前的nack,循环直至size 小于1000 或者 已经到了最新关键帧
- 3.如果经步骤2 nack_list大小仍然超过了nackkMaxNackPackets(1000) 会全部清理掉,并重新请求关键帧
- 4.收到乱序的包, 可能是抖动过来的 或者 后面恢复过来的包.
- 5.发送超过10次仍然没有收到重传回来的包.
keyFrame list & recovered list
- insert :
- 只需要判断是否是关键帧或恢复过来的包即可插入
- erase:
- 三个缓存都相同的删除点,清理序列号距离当前收到的序列号过旧的包kMaxPacketAge(10000) 例如[6,7,...,100007],6即被清理.
nack 发送的策略
前面提的两种发送处理在NackModule2::GetNackBatch()里面,一处在创建模块的时候就会启动的定时任务里定时调用,一处在NackModule2:: OnReceivedPacket()检查完包连续性后就会立即调用.
NackModule2::GetNackBatch(kSeqNumOnly) : kSeqNumOnly 根据序列号判断是否发送nack
仅在第一次发送的时会用到序列号方式,延迟发送时间为kDefaultSendNackDelayMs(0ms), 所以基本是入nack_lisk后就立即发送了(可以作为优化点之一后面提), 发送后会更新nack_list[seq].send_at_time = now(), 供后续定时任务判断是否超时
NackModule2::GetNackBatch(kTimeOnly) :kTimeOnly 根据时间判断是否发送nack,在没有打开补偿配置的情况下间隔为一个rtt时间,rtt会动态更新(默认频率1000ms), 初始值为kDefaultRttMs(100ms), 再次发送的时间 resend_delay 默认为一个rtt 时间,即一个rtt时间后没有收到重传回来的nack,就继续发送, 实验阶段增加了补偿配置,可以动态延长resend_delay 延迟, 可以作为改进方案之一, 后面有提.
nack 模块的几个重要常量
- nack 模块的几个重要常量
const int kMaxPacketAge = 10000; //三个缓存list包序列的生存长度
const int kMaxNackPackets = 1000; // nack_list 存储 packets 的最大大小
const int kDefaultRttMs = 100; // 默认rtt 时间
const int kMaxNackRetries = 10; // 最大重试次数
const int kDefaultSendNackDelayMs = 0; // 延迟发送nack时间
static constexpr TimeDelta kUpdateInterval = TimeDelta::Millis(20); // 定时发送nack任务的周期
改进参考
经过调研和一小部分数据测试,发现nack有几个可能的优化点
配置一个合适的发送延迟
收到正常顺序外的包,原生机制默认是直接就返送nack的, 当前版本支持了NACK延时发送机制,通过控制NACK延时发送的时间间隔,避免固定延时网络下无必要的重传请求。比如,如果kDefaultSendNackDelayMs=20ms,如果因为网络的固有延时,造成某些数据包迟到了10ms,而此时没有NACK延时发送机制的话,这些包都会被认为丢了,从而对这些包请求重传。但是如果有20ms的NACK延时发送,这些包就不会被计算为丢失,从而避免了没有必要的重传请求,避免了资源浪费
[023:217](nack_module2.cc:182): OnReceivedPacket:seq_num|newest_seq_num_|is_keyframe|is_recovered|loss_ratio|recover_ratio: 25402|25399|0|0|36.21%|0.00%|0|0|903
[023:218](nack_module2.cc:182): OnReceivedPacket:seq_num|newest_seq_num_|is_keyframe|is_recovered|loss_ratio|recover_ratio: 25401|25402|0|0|36.24%|0.00%|0|0|905
[023:219](rtp_video_stream_receiver2.cc:745): RtpVideoStreamReceiver2::RequestPacketRetransmit:SendNack:sn:size():2:|25400|25401|
重发补偿
改进2和上面情况其实类似,
如下日志, 序列33156 刚发送完第二次nack请求(69:466ms), 仅30ms之差收到了重传包(069:496ms),之后又收到了一次重传包(69:843ms),浪费网络资源.
可以配置重传补偿,每次重传时间增加%25,原生机制自带,需要打开配置。
[069:326](video_receive_stream2.cc:581):VideoReceiveStream2:OnRttUpdate|avg_rtt_ms|max_rtt_ms: 377ms|407ms
[069:466](rtp_video_stream_receiver2.cc:742):RtpVideoStreamReceiver2::RequestPacketRetransmit:SendNack:sn:size():1:|33156|
[069:496](rtx_receive_stream.cc:70):RtxReceiveStream::OnRtpPacket:recovered:sq:33156
[069:497](nack_module2.cc:181):OnReceivedPacket:seq_num|newest_seq_num_|is_keyframe|is_recovered|loss_ratio|recover_ratio|dup_recover_ratio:33156|33167|0|1|29.90%|98.80%|30.37%
----
[069:843](rtx_receive_stream.cc:70):RtxReceiveStream::OnRtpPacket:recovered:sq:33156
----
//测试发现在 200ms delay %30丢包下 这类重复重传包的情况也有高达%30左右比率
//这个case尝试增加这个改进后,重传比率降低到了 %21左右
----
[327:764](nack_module2.cc:182):OnReceivedPacket:seq_num|newest_seq_num_|is_keyframe|is_recovered|loss_ratio|recover_ratio|dup_recover_ratio: 23891|23894|0|1|30.93%|97.13%|%21.88
Audio Nack
Audio Nack 默认不打开,可通过配置feedback参数打开
codec.AddFeedbackParam(
FeedbackParam(kRtcpFbParamNack, kParamValueEmpty));
具体实现在NackTracker中, 机制大同小异
可改进点 :
固定丢包场景,高丢包等场景,可重置RTT校验策略,增强nack重传效果,配合控制neteq buffer 低水位高度,实验测试可以做到80-90%抗接收丢包.
SRS Nack
机制与webrtc 大同小异, 调用栈如下:
默认nack_list 大小 audio 66 video 666 定时默认20ms
if (is_audio) {
rtp_queue_ = new SrsRtpRingBuffer(100);
nack_receiver_ = new SrsRtpNackForReceiver(rtp_queue_, 100 * 2 / 3);
} else {
rtp_queue_ = new SrsRtpRingBuffer(1000);
nack_receiver_ = new SrsRtpNackForReceiver(rtp_queue_, 1000 * 2 / 3);
}
----
SrsRtcConnectionNackTimer::SrsRtcConnectionNackTimer(SrsRtcConnection* p) : p_(p)
{
_srs_hybrid->timer20ms()->subscribe(this);
}
默认初始常量
SrsNackOption::SrsNackOption()
{
max_count = 15;
max_alive_time = 1000 * SRS_UTIME_MILLISECONDS;
first_nack_interval = 10 * SRS_UTIME_MILLISECONDS;
nack_interval = 50 * SRS_UTIME_MILLISECONDS;
max_nack_interval = 500 * SRS_UTIME_MILLISECONDS;
min_nack_interval = 20 * SRS_UTIME_MILLISECONDS;
nack_check_interval = 20 * SRS_UTIME_MILLISECONDS;
}
webrtc QOS笔记三 Nack机制浅析的更多相关文章
- odoo开发笔记 -- odoo web机制浅析
http://blog.csdn.net/M0relia/article/details/39025947
- InnoDB的锁机制浅析(三)—幻读
文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁) Inno ...
- typecho流程原理和插件机制浅析(第一弹)
typecho流程原理和插件机制浅析(第一弹) 兜兜 393 2014年03月28日 发布 推荐 5 推荐 收藏 24 收藏,3.5k 浏览 虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终 ...
- [Firefly引擎][学习笔记三][已完结]所需模块封装
原地址:http://www.9miao.com/question-15-54671.html 学习笔记一传送门学习笔记二传送门 学习笔记三导读: 笔记三主要就是各个模块的封装了,这里贴 ...
- 构建高性能WEB站点笔记三
构建高性能WEB站点笔记三 第10章 分布式缓存 10.1数据库的前端缓存区 文件系统内核缓冲区,位于物理内存的内核地址空间,除了使用O_DIRECT标记打开的文件以外,所有对磁盘文件的读写操作都要经 ...
- java学习笔记09--反射机制
java学习笔记09--反射机制 什么是反射: 反射是java语言的一个特性,它允许程序在运行时来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来 ...
- VSTO学习笔记(三) 开发Office 2010 64位COM加载项
原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...
- 学习笔记(三)--->《Java 8编程官方参考教程(第9版).pdf》:第十章到十二章学习笔记
回到顶部 注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.若有违本之,则本人不负法律责任.违法 ...
- InnoDB的锁机制浅析(五)—死锁场景(Insert死锁)
可能的死锁场景 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意 ...
- InnoDB的锁机制浅析(四)—不同SQL的加锁状况
不同SQL的加锁状况 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/ ...
随机推荐
- react实现某个元素节点截图(html2canvas)
1.安装所需插件:html2canvas: 2.引入插件: import html2canvas from 'html2canvas'; 3.使用: const creatImg = () => ...
- Ignore warnings
import warnings warnings.filterwarnings("ignore")
- html-list
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- PTA1002 写出这个数 (20 分)
1002 写出这个数 (20 分) 读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字. 输入格式: 每个测试输入包含 1 个测试用例,即给出自然数 n 的值.这里保证 n 小于 1 ...
- Navicat15激活
1.下载Navicat for MySQL 15 https://www.navicat.com.cn/download/navicat-for-mysql 2.下载激活工具 注意:因某些限制,下载链 ...
- node.js 数据模拟
Node: js在服务端的一个运行环境 node框架:express koa egg (本文采用express) express: 是基于node的一个web框架 restful api:是目前流 ...
- js实现指定dom节点滚动到可视窗口
const rollDom = document.getElementById('domId') // 获取想要滚动的dom节点 rollDom.scrollIntoView({ block: 'ce ...
- Ubuntu常用环境配置
gcc sudo apt-get install build-essential update-alternatives 管理python环境 # 添加版本 # update-alternatives ...
- 【Unity】阅读LuaFramework_UGUI的一种方法
写在前面 我第一次接触到LuaFramework_UGUI是在一个工作项目中,当时也是第一次知道toLua.但我刚开始了解LuaFramework_UGUI时十分混乱,甚至将LuaFramework_ ...
- vite + vue安装 注意事项
一.要求node版本必须>12.0.0 1.node 如何升级 · 执行npm cache clean -f 清除缓冲 · npm install -g n 安装 n 模块 n模块用于管理 n ...