关于RTP时间戳以及播放器对时间戳的处理

2024-04-19 15:08
文章标签 rtp 处理 时间 播放器

本文主要是介绍关于RTP时间戳以及播放器对时间戳的处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

    首先,了解时间戳几个基本概念:

    时间戳单位:时间戳计算的单位不是秒之类的单位,而是由采样频率所代替的单位,这样做的目的就是为了是时间戳单位更为精准。比如说一个音频的采样频率为8000Hz,那么我们可以把时间戳单位设为1 / 8000。
    时间戳增量:相邻两帧之间的时间差(以时间戳单位为基准)。
    采样频率: 有些地方也叫时钟频率,即 每秒钟抽取样本的次数,例如音频的采样率一般为8000Hz
    帧率:      每秒传输或者显示帧数,例如25f/s
    
    再看看RTP时间戳课本中的定义:

    RTP包头的第2个32Bit即为RTP包的时间戳,Time Stamp ,占32位。
    时间戳反映了RTP分组中的数据的第一个字节的采样时刻。在一次会话开始时的时间戳初值也是随机选择的。即使是没有信号发送时,时间戳的数值也要随时间不断的增加。接收端使用时间戳可准确知道应当在什么时间还原哪一个数据块,从而消除传输中的抖动。时间戳还可用来使视频应用中声音和图像同步。
    在RTP协议中并没有规定时间戳的粒度,这取决于有效载荷的类型。因此RTP的时间戳又称为媒体时间戳,以强调这种时间戳的粒度取决于信号的类型。例如,对于8kHz采样的话音信号,若每隔20ms构成一个数据块,则一个数据块中包含有160个样本(0.02×8000=160)。因此每发送一个RTP分组,其时间戳的值就增加160。

   关于时间戳,归纳为以下几个重要的点:

    首先,时间戳就是一个值,用来反映某个数据块的产生(采集)时间点的,后采集的数据块的时间戳一般大于先采集的数据块的。为什么说一般呢?因为时间戳是一个32位无符号整型,如果一直递增到了一定时间数值就会回滚,所以时间戳只是对数据块先后顺序的一个参考。
    第二,在实时流传输中,数据采集后立刻传递到RTP模块进行发送,那么,其实,数据块的采集时间戳就直接作为RTP包的时间戳(要转换为某个时钟频率)。
    第三,如果用RTP来传输固定的文件,则这个时间戳就是视频文件中每一帧携带的时间戳,但是也需要转换为对应RTP通道的时钟频率。
    第四,时间戳的单位采用的是采样频率的倒数,例如采样频率为8000Hz时,时间戳的单位为1 / 8000 ,在Jrtplib库中,有设置时间戳单位的函数接口,而ORTP库中根据负载类型直接给定了时间戳的单位(音频负载1/8000,视频负载1/90000)
    第五,时间戳增量是指相邻两帧(不一定是相邻两个RTP包)之间的时间间隔。

    如果采样频率为90000Hz,则由上面讨论可知,时间戳单位为1/90000,我们就假设1s钟被划分了90000个时间块,那么,如果每秒发送25帧,那么,每一个帧的发送占多少个时间块呢?当然是 90000/25 = 3600。因此,我们根据定义“时间戳增量是发送第二个RTP包相距发送第一个RTP包时的时间间隔”,故时间戳增量应该为3600。

    下面说一下播放器如何对编码器发送的RTP包的时间戳做处理。这里的编码器是指发送RTP包的服务器,一般是指网络摄像机或RTSP服务器。

    播放器本地建立了一个系统时钟,这个时钟一般根据系统的CPU时间来计算出来,当播放开始,时钟的时间为0。时间戳有分绝对时间和相对时间,收到的RTP包的时间戳为绝对时间,播放器需要把这个RTP时间戳转为播放器本地的相对时间戳,相对时间戳以0为开始,时间戳决定了一帧播放或渲染的时刻。当播放开始时,时钟开始运行,时间戳一直增加,播放器会用系统时钟的时间跟视频帧的时间戳(相对时间戳)进行对比,根据它们的大小关系决定是否马上渲染这帧还是要等待。但是,编码器发过来的时间戳只是一个参考值,有可能是不准的,为什么呢?编码器打时间戳基于的时钟跟播放器的本地时钟是可能不一样的,不同的编码器有不同的打时间戳算法,我只说一下常见的做法,一般地,编码器打时间戳是基于一个简单的规则:每发一个视频帧,PTS就递增一个固定的时间增量。根据TS流和Rtp协议的规定,视频的时钟频率是90000,也就是时间单位是1/90000秒。如果帧率是25帧,则相邻两帧的PTS增量就是3600,也就是每发一个帧,时间戳就增加3600,但是实际上两帧的时间增量应该根据它们实际经过的时间,而不是一个固定的值。实际上,编码器每秒采集图像的帧率并不是绝对固定的(取决于物理时钟的精度以及CPU处理的速度),有一定误差,平均采集帧率是25,但可能编码器某1秒采集了25帧,某一秒采集了24帧,假如都是按固定增量的方式递增时间戳,过一段时间后,编码器的时间戳跟播放器的时钟的时间就有很大差异,可能导致播放不流畅,或者缓冲了很多帧延时加大了。
   所以,解码器不能盲目相信编码器的时间戳,发现时间戳跟本地的时间差异太远需要尽快调整,应该实现一个动态适应的机制:当发现接收缓冲区里的帧数比较多时,要加快渲染速度;当缓冲区的帧数在一个比较低的水平(1-4),则稳定渲染速度。

 

这篇关于关于RTP时间戳以及播放器对时间戳的处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/917886

相关文章

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

Python实现自动化接收与处理手机验证码

《Python实现自动化接收与处理手机验证码》在移动互联网时代,短信验证码已成为身份验证、账号注册等环节的重要安全手段,本文将介绍如何利用Python实现验证码的自动接收,识别与转发,需要的可以参考下... 目录引言一、准备工作1.1 硬件与软件需求1.2 环境配置二、核心功能实现2.1 短信监听与获取2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

Python使用date模块进行日期处理的终极指南

《Python使用date模块进行日期处理的终极指南》在处理与时间相关的数据时,Python的date模块是开发者最趁手的工具之一,本文将用通俗的语言,结合真实案例,带您掌握date模块的六大核心功能... 目录引言一、date模块的核心功能1.1 日期表示1.2 日期计算1.3 日期比较二、六大常用方法详

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

Java使用多线程处理未知任务数的方案介绍

《Java使用多线程处理未知任务数的方案介绍》这篇文章主要为大家详细介绍了Java如何使用多线程实现处理未知任务数,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 知道任务个数,你可以定义好线程数规则,生成线程数去跑代码说明:1.虚拟线程池:使用 Executors.newVir