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机制浅析的更多相关文章

  1. odoo开发笔记 -- odoo web机制浅析

    http://blog.csdn.net/M0relia/article/details/39025947

  2. InnoDB的锁机制浅析(三)—幻读

    文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁) Inno ...

  3. typecho流程原理和插件机制浅析(第一弹)

    typecho流程原理和插件机制浅析(第一弹) 兜兜 393 2014年03月28日 发布 推荐 5 推荐 收藏 24 收藏,3.5k 浏览 虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终 ...

  4. [Firefly引擎][学习笔记三][已完结]所需模块封装

    原地址:http://www.9miao.com/question-15-54671.html 学习笔记一传送门学习笔记二传送门 学习笔记三导读:        笔记三主要就是各个模块的封装了,这里贴 ...

  5. 构建高性能WEB站点笔记三

    构建高性能WEB站点笔记三 第10章 分布式缓存 10.1数据库的前端缓存区 文件系统内核缓冲区,位于物理内存的内核地址空间,除了使用O_DIRECT标记打开的文件以外,所有对磁盘文件的读写操作都要经 ...

  6. java学习笔记09--反射机制

    java学习笔记09--反射机制 什么是反射: 反射是java语言的一个特性,它允许程序在运行时来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来 ...

  7. VSTO学习笔记(三) 开发Office 2010 64位COM加载项

    原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...

  8. 学习笔记(三)--->《Java 8编程官方参考教程(第9版).pdf》:第十章到十二章学习笔记

    回到顶部 注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.若有违本之,则本人不负法律责任.违法 ...

  9. InnoDB的锁机制浅析(五)—死锁场景(Insert死锁)

    可能的死锁场景 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意 ...

  10. InnoDB的锁机制浅析(四)—不同SQL的加锁状况

    不同SQL的加锁状况 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/ ...

随机推荐

  1. 通过flask完成web实时播放视频

    def gen(): r = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB, password=PASS_WORD) ...

  2. react常见bug - 查询条件变化,但page未重置为1

    问题1.多次触发请求,且存在潜在的竞态问题 const [page, setPage] = useState(1); const [keyword, setKeyword] = useState('' ...

  3. Educational Codeforces Round 2 个人总结A-E

    Educational Codeforces Round 2 A. Extract Numbers 简单的模拟 bool check(string op) { if(op.size()==1& ...

  4. python3GUI--用Tk开发一款恶搞蓝屏软件(附源码)

    目录 一.准备工作 1.Tkinter 2.科普-电脑蓝屏 二.预览 1.蓝屏-win10.win11(中文版) 2.蓝屏-win10.win11(英文版) 3.Windows其他版本(XP.Win7 ...

  5. springboot 项目启动后访问不论什么请求的是spring的注册页面Please sign in Username || springboot禁用security

    解决方法: 1.在启动类上添加注解@EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class}) 2.或者:@SpringB ...

  6. SpringBoot - Lombok使用详解4(@Data、@Value、@NonNull、@Cleanup)

    六.Lombok 注解详解(4) 8,@Data (1)@Data 是一个复合注解,用在类上,使用后会生成:默认的无参构造函数.所有属性的 getter.所有非 final 属性的 setter 方法 ...

  7. curl curl请求

    $params=[ "a"=>"test", "b"=>"测试" ]; $header=[ "Co ...

  8. scrcpy

    捕获配置 缩小尺寸 有时,以较低的清晰度镜像 Android 设备以提高性能很有用. 将宽度和高度限制为某个值(例如 1024): scrcpy --max-size 1024 scrcpy -m 1 ...

  9. linux-minicom

    q:只能发送不能接收 a:串口设置,硬件流控制=>NO

  10. windows远程linux桌面

    TigerVNC是一个高性能.平台无关的虚拟网络计算(Virtual Network Computing,VNC)实现,是一个客户端/服务器应用程序,允许用户在远程机器上启动并与图形应用程序进行交互. ...