webrtc QOS笔记 Nack机制浅析

news2024/10/6 0:26:58

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/784400.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10分钟内入门 ArcGIS Pro

本文来源&#xff1a;GIS荟 大家好&#xff0c;这篇文章大概会花费你10分钟的时间&#xff0c;带你入门 ArcGIS Pro 的使用&#xff0c;不过前提是你有 ArcMap 使用经验。 我将从工程文件组织方式、软件界面、常用功能、编辑器、制图这5个维度给大家介绍。 演示使用的 ArcGI…

【SQL应知应会】表分区(一)• Oracle版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 分区表 • Oracle版 前言一、分区表1.什么是表分区…

《电脑城的衰退:时代变迁中的背影》

随着科技的不断进步和电子商务的兴起&#xff0c;电脑城这个曾经火爆的地方正逐渐走向衰退甚至面临消失。对于这一变迁&#xff0c;我认为既有利也有弊。 首先&#xff0c;电脑城的衰退带来了一定的便利。传统的电脑城通常拥有大量的实体店铺&#xff0c;买家必须亲自前往选择…

Qt/C++音视频开发49-多级连保存和推流设计(同时保存到多个文件/推流到多个平台)

一、前言 近期遇到个用户需要多级联的保存和推流&#xff0c;在ffmpegsave多线程保存类中实现这个功能&#xff0c;越简单越好&#xff0c;就是在推流的同时&#xff0c;能够开启自动转储功能&#xff0c;一边推流的同时一边录像保存到本地视频文件。最初设想的一个方案是new两…

LeetCode515. 在每个树行中找最大值

515. 在每个树行中找最大值 文章目录 [515. 在每个树行中找最大值](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/)一、题目二、题解 一、题目 给定一棵二叉树的根节点 root &#xff0c;请找出该二叉树中每一层的最大值。 示例1&#xff1a; 输入: ro…

Nginx配置解析

server {listen 80;server_name example.com;location / {proxy_pass http://backend;}location / 是 Nginx 的一个匹配规则&#xff0c;用于匹配所有请求路径。proxy_pass 指令则用于将匹配到的请求转发给指定的后端服务器。下面是关于 location / 和 proxy_pass 的详细介绍&a…

YARN的设计思想

YARN的设计思想 ​ YARN的基本思想是将资源管理和作业调度/监视功能划分为单独的守护进程。其思想是拥有一个全局ResourceManager (RM)&#xff0c;以及每个应用程序拥有一个ApplicationMaster (AM)。应用程序可以是单个作业&#xff0c;也可以是一组作业。 一个ResourceManag…

【Linux从入门到精通】进程的控制(进程替换)

本篇文章会对进程替换进行讲解。希望本篇文章会对你有所帮助 文章目录 一、进程替换概念 二、进程替换函数 2、1 execl 2、2 execlp 2、3 execv 2、3 execle 2、4 execve 三、总结 &#x1f64b;‍♂️ 作者&#xff1a;Ggggggtm &#x1f64b;‍♂️ &#x1f440; 专栏&…

SpringBoot项目修改Tomcat版本号

SpringBoot项目修改Tomcat版本号 前言如果项目是以jar包形式打包部署如果项目是以war包形式打包部署示例 仰天大笑出门去&#xff0c;我辈岂是蓬蒿人 前言 Springboot项目,默认是使用内嵌Tomcat servlet容器形式打包部署。关于怎么修改默认的版本号&#xff0c;捣鼓了好久终于…

ChatGPT的工作原理:从输入到输出

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

Day 42 算法记录|动态规划 09 (打家劫舍)

打家劫舍 198.打家劫舍213.打家劫舍II337.打家劫舍 III 198.打家劫舍 1.dp[i]&#xff1a;考虑下标i&#xff08;包括i&#xff09;以内的房屋&#xff0c;最多可以偷窃的金额为dp[i]。 2.dp[i] max(dp[i - 2] nums[i], dp[i - 1]); 3.初始化&#xff0c;dp[0] 和 dp[1]&…

2021 年高教社杯全国大学生数学建模竞赛 E 题 中药材的鉴别 第一题

目录 1.数据预处理 1.1 数据基本信息探索 1.2 数据可视化 1.3 异常值处理 2. 数据特征值提取 2.1 数据标准化 2.2 PCA提取特征值 3. 数据聚类鉴别药材种类 3.1 肘部图确定K值 3.2 轮廓系数图确定K值 3.3 数据聚类 3.4 聚类结果可视化 4. 研究不同种类药材…

(10)强化:贪婪模式,捕获组,正则替换,正则分割,反向引用,UBB,断言,委托,Invoke,lambda,Action

一、作业问题 1、问&#xff1a;.net正则表达式默认使用unix的正则表达式模式? 答&#xff1a;在C#和.NET中&#xff0c;默认使用的是基于ECMAScript标准的正则表达式模式&#xff0c;而不是UNIX风 格的正则表达式模式。 …

MATLAB与ROS联合仿真——ROS环境搭建及相关准备工作(下)

本篇文章主要介绍在安装完ROS后&#xff0c;在进行MATLAB与ROS联合仿真之前&#xff0c;需要进行的一些环境搭建以及准备工作&#xff0c;主要分为 创建ROS工作空间及功能包、必备功能包安装、安装Gazebo11、导入实验功能包至工作空间、安装Visual_Studio_Code(选做)、常用便捷…

数据结构————顺序表

1.线性表 &#xff08;1&#xff09;.线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表&#xff0c;链表&#xff0c;栈&#xff0c;队列&#xff0c;字符串... &#xff08;2&#xff09;.线…

如何学习python数据分析?

Python数据分析基础全流程攻略如下&#xff08;适合初学、转岗、无编程基础小白&#xff0c;直接教学&#xff0c;没有额外链接&#xff09; 一、学习 针对数据分析模块&#xff0c;python学习的内容并非全都要学&#xff08;SQL也是如此&#xff09;&#xff0c;即不需要像程…

IT技术岗位应聘的关键技巧与准备方法

面试攻略&#xff1a; # 导入所需的模块 import pandas as pd import numpy as np# 定义一个函数&#xff0c;返回两个数字的和 def add_numbers(num1, num2):return num1 num2# 创建一个DataFrame data {"Name": ["Alice", "Bob", "Char…

新零售数字化商业模式如何建立?新零售数字化营销怎么做?

随着零售行业增速放缓、用户消费结构升级&#xff0c;企业需要需求新的价值增长点进行转型升级&#xff0c;从而为消费者提供更为多元化的消费需求、提升自己的消费体验。在大数据、物联网、5G及区块链等技术兴起的背景下&#xff0c;数字化新零售系统应运而生。 开利网络认为&…

微服务笔记---Nacos集群搭建

微服务笔记---Nacos集群搭建 Nacos集群搭建1.集群结构图2.搭建集群2.1.初始化数据库2.2.下载nacos2.3.配置Nacos2.4.启动2.5.nginx反向代理2.6.优化 Nacos集群搭建 1.集群结构图 官方给出的Nacos集群图&#xff1a; 其中包含3个nacos节点&#xff0c;然后一个负载均衡器代理…

实现Aware接口使用Spring底层组件

实现Aware接口使用Spring底层组件 Aware接口的实现类 基于Component&#xff0c;通过Aware的实现类在容器创建之前将Spring底层的信息获取并使用。 例如&#xff1a; 获取应用上下文对象applicationContext的ApplicationContextAware获取该类的bean对象信息的BeanNameAware…