OpenRTP 乱序排包和差分抖动计算

news/2024/10/20 8:08:07/

OpenRTP 开源地址

OpenRTP 开源地址

在这里插入图片描述
暂时使用h264 + aac 的音频去测试,点开配置去选择

1 音视频同步问题

先要解决一个音视频同步问题,否则包排不排序都不对,这是因为视频时间戳不一定能够对上音频,为什么呢?因为大部分摄像头不一定能够达到采样的帧率,而音频大部分时间都是用回调来进行,是比较正确的,时间戳递增可以准确无误,但视频一旦帧率没有够上,结果递增时间却是确定的,就会造成两者时间戳不同步,一般像摄像头这种设备,无法够上足够的帧率,所以做法有两种。

1 插帧服务
2 保证帧率
3 修改时间戳
按照绝对时间来修改时间戳是可以的

static uint32_t convertToRTPTimestamp(/*struct timeval tv*/)
{timeval tv;gettimeofday(&tv, NULL);UINT32 timestampIncrement = (90000 * tv.tv_sec);timestampIncrement += (UINT32)((2.0 * 90000 * tv.tv_usec + 1000000.0) / 2000000);//UINT32 const rtpTimestamp =  timestampIncrement;  return timestampIncrement;
}

这样视频时间戳和音频时间戳就可以同步,下面把rtp包的乱序进行排序,基础udp协议是不一定序号连续还有可能重复包发送,所以要解决这个问题,当然,和排序无关,udp丢包是不可避免的。

2 udp 乱序排包

udp 接收一般是乱序的,如何来进行包排序呢

定义数据结构

struct Packet
{uint16_t s = 0;uint32_t t = 0;void* real_rtp = NULL;int timesleep = 10;long long receiveTime = 0;long long sendTime = 0;Packet(uint16_t seq, uint32_t ts){s = seq;t = ts;}void SetCurrentTime(int64_t timere, int64_t timese){receiveTime = timere;sendTime = timese;}
};

其中real_rtp 存放RTP包,从中获取rtp 时间戳等

由于时间戳是32位,而rtp协议的sequnce num 是16位无符号证书,所以无法按照正常直接的比较来进行排序,需要回环计算,为了插入和删除方便,定义双缓冲,一个缓冲为queue队列,输出给应用,一个缓冲为链表, 方便排序

class c_jitter
{//源端口uint16_t v_port = 0;std::list<Packet*> v_packets;//如果时间戳大于最后1个包1秒,将前面所有的包全部播放掉,插入包 返回//最后1个包的seq 为 s1   当前包为s2   // 这个包减去最后一个包得到值 s  //  if s > 65000  往前继续找     //          等于 删除//     s > 0 < 535  插入//否则删除
public:uint16_t out_seq = 0; //出去的包是1...................
}

其中out_seq 为出去的sequence num 记录,如果再次进来的包排序小于这个sequence num,则必须直接放弃

3 排序算法

3.1 排序基础

排序算法如下,增加port 是因为如果port 源端口变了,实际上以前的所有rtp包都必须放弃,算法的思想是先假定新进来的包是大于最后一个包的seq的,再进行判断16位整数回绕,最后根据是否大于已经出去的seq 数,判决是否放弃还是插入链表

 int sortPacket(int seq, int ts, uint16_t port){if (v_port == 0)v_port = port;if (v_packets.size() > 0) {bool isin = false;auto riter = v_packets.rbegin();while (riter != v_packets.rend()) {Packet* node = *riter;uint16_t s1 = node->s;uint16_t s2 = seq;uint16_t s = s2 - s1;if (s == 0) // 重复包{return -1;}if (s < 535){//插入到这个riter的后面auto nextIt = riter.base();Packet* node = new Packet(seq, ts);v_packets.insert(nextIt, node);isin = true;break;}++riter;}if (!isin){uint16_t s1 = v_packets.front()->s;uint16_t s2 = seq;//uint16_t s = s2 - s1;if (s2 > s1)    // like 65535  1  out is 0  then the 65535 we discard{uint16_t sx1 = s1 - out_seq;uint16_t sx2 = s1 - s2;if (sx1 > sx2){//uint16_t s0 = out_seq -s1;printf("s2>s1 seq in %d seq out is %d\n", s2, out_seq);auto it = v_packets.begin();// 在第一个元素之前插入新元素Packet* node = new Packet(seq, ts);v_packets.insert(it, node);return 0;}}if (s2 < s1) //like  1  3  out is 2         {uint16_t sx1 = s1 - out_seq;uint16_t sx2 = s1 - s2;if (sx2 < sx1){printf("s2<s1 seq in %d out is %d\n", s2, out_seq);auto it = v_packets.begin();// 在第一个元素之前插入新元素Packet* node = new Packet(seq, ts);v_packets.insert(it, node);return 0;}}}return -1;}//是第一个元素auto it = v_packets.begin();// 在第一个元素之前插入新元素Packet* node = new Packet(seq, ts);v_packets.insert(it, node);return 0;}

3.2 测试

void test1()
{uint16_t a1 = 65530;uint16_t a2 = 65535;//uint16_t b = 0;uint16_t b1 = 3;uint16_t b2 = 4;uint16_t b3 = 65531;uint16_t b4 = 7;uint16_t b5 = 6;uint16_t b6 = 65533;uint16_t b7 = 1;uint16_t b8 = 2;uint16_t b9 = 0;c_jitter p;p.addPacket(a1, 0);p.addPacket(a2, 0);p.addPacket(b1, 0);p.addPacket(b2, 0);p.sortPacket(b3, 0, 6000);p.sortPacket(b4, 0, 6000);p.sortPacket(b5, 0, 6000);p.sortPacket(b6, 0, 6000);p.sortPacket(b7, 0, 6000);p.sortPacket(b8, 0, 6000);p.sortPacket(b9, 0, 6000);p.printPacketList();
}

按照 65530 65535 3 4 65531 7 6 65533 1 2 0 排序的结果应该为
65530 65531 65533 65535 0 1 2 3 4 6 7
结果为
在这里插入图片描述
如果出去的包的seq 为 0
插入包为 6 8 7 7 15 3 65535 65534
则因为出去的包为0 ,而最后两个包虽然接近于3 ,但是 小于 0 ,所以必须被丢弃,而 两个7 包也必须丢弃一个

void test3()
{c_jitter p;//p.addPacket(7, 0);p.setout_Seq(0);p.sortPacket(6, 0, 6000);p.sortPacket(8, 0, 6000);p.sortPacket(7, 0, 6000);p.sortPacket(7, 0, 6000);p.sortPacket(15, 0, 6000);p.sortPacket(3, 0, 6000);p.sortPacket(65535, 0, 6000);p.sortPacket(65534, 0, 6000);p.printPacketList();
}

结果为
在这里插入图片描述

4 包抖动

4.1 、数据收集

记录每个数据包的接收时间戳。
可以在接收数据包时,使用系统时间函数获取当前时间并记录下来。

4.2 计算延迟

对于每个接收到的数据包,计算其延迟。
延迟可以通过当前时间减去数据包的发送时间(如果发送时间包含在数据包中或者可以通过其他方式获取)得到。

4.3 计算抖动

首先计算平均延迟。
将所有数据包的延迟相加,然后除以数据包的数量。
对于每个数据包,计算其延迟与平均延迟的差值的绝对值。
这个差值表示该数据包的延迟与平均延迟的偏离程度。
计算抖动值。
抖动可以通过计算所有数据包延迟与平均延迟差值的绝对值的平均值来得到。

    //计算抖动double calculateJitter(const std::list<Packet*>& packets) {int totalDelay = 0;for (const Packet* packet : v_packets) {int delay = packet->receiveTime - packet->sendTime;totalDelay += delay;}double averageDelay = static_cast<double>(totalDelay) / packets.size();double totalDeviation = 0.0;for (const Packet* packet : packets) {int delay = packet->receiveTime - packet->sendTime;double deviation = std::abs(delay - averageDelay);totalDeviation += deviation;}return totalDeviation / packets.size();}
};

程序已经放在开源项目里面,为了增加可用性,后面会加上我们的rtmp server 和 rtspserver,同时使用tcp 和 udp。

4.4 根据抖动调整延时

根据以上的抖动,可以动态去分配延时,尽量让rtp包延时均匀,如果包来的越来越慢,抖动加剧,我们的策略应该适当来进行延时播放。


http://www.ppmy.cn/news/1540472.html

相关文章

力扣3191.使二进制数全变成1

给你一个二进制数组 nums 。 你可以对数组执行以下操作 任意 次&#xff08;也可以 0 次&#xff09;&#xff1a; 选择数组中 任意连续 3 个元素&#xff0c;并将它们 全部反转 。 反转 一个元素指的是将它的值从 0 变 1 &#xff0c;或者从 1 变 0 。 请你返回将 nums 中…

证件照小程序源码,前后端稳定运行

演示&#xff1a;证寸照制作 运行环境: Linux Nginx PHP >5.6 MySQL>5.6 安装步骤: 1.下载源码上传至你的服务器宝塔面板 2.直接添加站点选择源码目录&#xff0c;新建数据库 3.设置代码执行目录为/web 4.在浏览器中输入你的域名&#xff0c;会提示安装&#xff0c;填写…

UE4 材质学习笔记09(雨水水坑着色器/完整雨水着色器)

一.雨水水坑着色器 要用到这样一个噪声贴图&#xff0c;我们要做的就是&#xff0c;做出水坑并让水坑在这种浑浊的噪点中产生&#xff0c;因此水坑将从最暗的斑点生长&#xff0c;然后随着它继续占据越来越亮的像素而生长 现在水坑将从上到下投射到世界空间中&#xff0c;所以…

高级java每日一道面试题-2024年10月15日-JVM篇-说一下JVM的主要组成部分?及其作用?

如果有遗漏,评论区告诉我进行补充 面试官: 说一下JVM的主要组成部分?及其作用? 我回答: Java 虚拟机&#xff08;JVM&#xff09;是 Java 运行时环境的核心组件&#xff0c;它负责执行 Java 字节码。JVM 的主要组成部分及其作用如下&#xff1a; 类加载器子系统 (Class L…

【H2O2|全栈】WPS/Office系列有哪些好用的快捷方式?

目录 WPS/Office 前言 准备工作 Office通用快捷键 PPT快捷键 Excel快捷键 Word快捷键 结束语 WPS/Office 前言 本章节属于前端前置知识&#xff0c;即使不学习前端&#xff0c;在工作中掌握常见的WPS/Office办公技能也是十分重要的。在本篇中&#xff0c;我将会分享常…

数据结构-贪心算法笔记

前言&#xff1a;贪心无套路&#xff0c;狠狠刷就完事 分发饼干 455. 分发饼干 - 力扣&#xff08;LeetCode&#xff09; class Solution {/*** 找出最多有多少个孩子可以得到糖果。** param g 一个数组&#xff0c;表示每个孩子对糖果大小的满意度。* param s 一个数组&…

【移动安全】OWASP MASTG 移动应用程序安全测试指南

OWASP 是 Open Web Application Security Project MASTG 是 Mobile Application Security Testing Guide 移动应用程序安全测试指南 英文网站&#xff1a;https://mas.owasp.org/MASTG/ 中文网站&#xff1a;http://www.owasp.org.cn/OWASP-CHINA/owasp-project/owasp-mobile-…

Java项目-基于springboot框架的校园在线拍卖系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…