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

news2024/10/20 15:11:20

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.coloradmin.cn/o/2219370.html

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

相关文章

前端考试总结

1HTML标签 h标题标签 块级标签 独占一行 p段落标签 同上 br换行标签 单标签 img图片标签 内联标签:不独占一行(src图片地址 alt图片的替代文字 title鼠标悬停提示文字) a超链接标签 同上 (href跳转路径 target属性{_blank新窗口打开 _self在当前窗口打开}) 列表标签(ul无…

诺贝尔物理学奖与机器学习、神经网络:一场跨时代的融合与展望

诺贝尔物理学奖与机器学习、神经网络&#xff1a;一场跨时代的融合与展望 机器学习与神经网络的崛起 机器学习与神经网络的发展前景 机器学习和神经网络的研究与传统物理学的关系 总结 2024年&#xff0c;诺贝尔物理学奖颁给了机器学习与神经网络&#xff0c;这一具有里程碑…

JAVA就业笔记5——第二阶段(2)

课程须知 A类知识&#xff1a;工作和面试常用&#xff0c;代码必须要手敲&#xff0c;需要掌握。 B类知识&#xff1a;面试会问道&#xff0c;工作不常用&#xff0c;代码不需要手敲&#xff0c;理解能正确表达即可。 C类知识&#xff1a;工作和面试不常用&#xff0c;代码不…

房屋租赁管理系统|基于java和小程序的房屋租赁管理系统小程序设计与实现(源码+数据库+文档)

房屋租赁管理系统小程序 目录 基于java和小程序的房屋租赁管理系统小程序设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设…

asp.net core _ViewStart.cshtml 和 _ViewImports.cshtml

_ViewStart.cshtml asp.net mvc 就出现了 》》/Views/ViewStart.cshtml _ViewStart.cshtml 是默认模板&#xff0c;当页面没有指定 Layout 时&#xff0c;会自动调用此默认模板‌&#xff0c;如果要取消 在当页面设定 &#xff08;如下&#xff09;&#xff0c;则表示 当前页面…

线下陪玩导游系统软件源码,家政预约服务源码(h5+小程序+app)

游戏陪玩系统源码陪玩小程序源码搭建基于PHP&#xff0b;MySQL陪玩系统app源码陪玩系统定制开发服务、成品陪玩系统源码 系统基于Nginx或者Apache PHP7.3 数据库mysql5.6 前端为uniapp-vue2.0 后端为thinkphp6 有域名授权加密&#xff0c;其他开源可二开 演示源码下载 开…

Collection 单列集合 List Set

集合概念 集合是一种特殊类 ,这些类可以存储任意类对象,并且长度可变, 这些集合类都位于java.util中,使用的话必须导包 按照存储结构可以分为两大类 单列集合 Collection 双列集合 Map 两种 区别如下 Collection 单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两…

包子凑数

类似完全背包求方案数&#xff0c;再加上点数论知识&#xff0c;裴蜀定理。 #include<bits/stdc.h> using namespace std; #define int long long #define endl \n const int N300000; bool f[N]; int a[120]; signed main() {ios::sync_with_stdio(0);cin.tie(0);cout.…

华为ICT大赛2024-2025网络赛道考试分析

华为ICT大赛2024-2025正在报名中&#xff0c;网络赛道的同学如何备考&#xff0c;了解考试内容呢&#xff1f; 一、考试概况 华为ICT大赛分为4个赛段&#xff0c;分别为省赛初赛、省赛复赛、中国总决赛&#xff0c;全球总决赛。其中对应的能力级别分别如下&#xff1a; 省赛…

PHP爬虫API:获取商品详情的新利器

为什么选择PHP爬虫API 灵活的数据处理&#xff1a;PHP强大的数据处理能力&#xff0c;使得从API获取的数据可以被快速地处理和分析。丰富的库支持&#xff1a;PHP拥有如cURL、Guzzle等库&#xff0c;这些库简化了HTTP请求的发送和响应的接收。易于集成&#xff1a;PHP作为服务…

无人机搭载激光雷达在地形测绘中的多元应用

一、高精度地形测量 无人机激光雷达能够发射激光脉冲并接收其回波&#xff0c;通过精确计算激光脉冲的往返时间来确定目标物的距离。这一特性使得无人机激光雷达在地形测绘中能够实现高精度的三维地形测量。通过快速获取大量地形数据&#xff0c;可以生成高精度的数字高程模型…

pytorh学习笔记——cifar10(一)生成数据

CIFAR&#xff08;Canadian Institute For Advanced Research&#xff09;是一个用于图像识别研究的数据集。CIFAR数据集包含多个子数据集&#xff0c;最常用的是CIFAR-10和CIFAR-100。 CIFAR-10数据集包含60000张32x32彩色图像&#xff0c;分为10个类别&#xff0c;每…

SpringCloud无介绍快使用,单机Eureka服务注册中心cloud-eureka-server7001搭建(十)

TOC 问题背景 从零开始学springcloud微服务项目 注意事项&#xff1a; 约定 > 配置 > 编码IDEA版本2021.1这个项目&#xff0c;我分了很多篇章&#xff0c;每篇文章一个操作步骤&#xff0c;目的是显得更简单明了controller调service&#xff0c;service调dao项目源码以及…

Python学习的自我理解和想法(17)

学的是b站的课程&#xff08;千锋教育&#xff09;&#xff0c;跟老师写程序&#xff0c;不是自创的代码&#xff01; 今天是学Python的第17天&#xff0c;学的内容是面向对象设计。开学了&#xff0c;时间不多&#xff0c;写得不多&#xff0c;见谅。 目录 1.面向对象入门 …

基于PHP+MySQL+Vue的网上订餐系统

摘要 本文介绍了一个基于PHPMySQLVue技术的网上订餐系统。该系统旨在为用户提供便捷的在线订餐服务&#xff0c;同时提高餐厅的运营效率。系统后端采用PHP语言开发&#xff0c;利用MySQL数据库进行数据存储与管理&#xff0c;实现了用户注册登录、菜品浏览、购物车管理、订单提…

es kibana .logstash离线集群安装

es离线集群安装 下载对应的版本一般看你客户端引用的是什么版本我这里下载的是7.6.2 官方下载地址&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch 源码安装-环境准备&#xff1a;在etc/hosts文件添加3台主机 node-001 192.168.1.81 node-002 19…

图像中的数值计算

目录 图像读取与形状图像数据展示图像数据操作超出范围的像素值处理 图像读取与形状 使用cv2.imread函数读取图像文件。图像的形状通过shape属性获取&#xff0c;格式为(高度, 宽度, 颜色通道数)。 import cv2img1 cv2.imread(bg.jpg) img2 cv2.imread(fish.jpg)print(img1…

微信小程序:miniprogram-ci自动打包工具使用介绍以及支持配置环境变量、jekins打包、taro、uni-app三方工具

微信小程序&#xff1a;miniprogram-ci自动打包工具使用介绍以及支持配置环境变量、jekins打包、taro、uni-app三方工具 背景介绍 一直都是本地电脑运行微信开发者工具打包上传。多项目中新老版本对node版本要求不一致&#xff0c;老是切来切去。而且同一个人开发上传需要打包…

求最大公约数(c语言)

先看题&#x1f447; 我这里介绍的方法&#xff1a;辗转相除法&#xff1a; 最大公约数&#xff1a; 最大公约数是指同时能整除俩个或更多整数的最大正整数。 欧几里得算法就是求最大公约数的算法 求最大公约数涉及到一个数学原理的转换: 俩个数的最大公约数等于其中一个数和…

关于我、重生到500年前凭借C语言改变世界科技vlog.7——数组函数实践

文章目录 扫雷游戏专题1.扫雷游戏的设计分析1.1 棋盘1.2 文件 2.扫雷游戏的实现3.扫雷游戏的扩展 希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 扫雷游戏专题 掌握了前面的数组与函数的知识&#xff0c;我们可以制作一款大多电脑上都有的简易…