流媒体通信中RTP/RTCP在项目中的应用
一 概述:
本文档描述RTC通信中RTP/RTCP的应用以及当前项目中的使用策略。
二 RTP/RTCP协议简介
2.1 协议标准
RTP 由 IETF(http://www.ietf.org/)定义在 RFC 3550和3551中。
RTP被定义为传输音频、视频、模拟数据等实时数据的传输协议,与传统的注的高可靠的数据传输的运输层协议相比,它更加侧重的数据传输的实时性,此协议提供的服务包括数据顺序号、时间标记、传输控制等。
RTP通常与辅助控制协议RTCP一起工作,RTP只负责实时数据的传输,RTCP负责对RTP的通信和会话进行带外管理(如流量控制、拥塞控制、会话源管理等)。
2.2 RTP/RTCP协议层次和封装
RTP位于传输层(通常是UDP)之上,应用程序之下,实时语音、视频数据经过模数转换和压缩编码处理后,先送给RTP封装成为RTP数据单元,RTP数据单元被封装为UDP数据报,然后再向下递交给IP封装为IP数据包。
RTP分组只包含RTP数据,而控制是由另一个配套协议RTCP提供。RTP在端口号1025到65535之间选择一个未使用的偶数UDP端口号,而在同一次会话中的RTCP则使用下一个奇数UDP端口号。
RTP通常和RTCP一起工作,在RTP会话期间,各参与者周期的发送RTCP消息。RTCP消息含有已发送数据的丢包统计和网络拥塞等信息,服务器可以利用这些信息动态的改变传输速率,甚至改变净荷的类型。RTCP消息也被封装为UDP数据报进行传输。
2.3 RTP Header头解析
前12字节是固定的,CSRC可以有多个或者0个。
- V:RTP协议的版本号,占2位,当前协议版本号为2
- P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分
- X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头
- CC:CSRC计数器,占4位,指示CSRC标识符个数
- M:标志,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
- PT(payload type):有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是用来区分音频流和视频流,这样便于客户端进行解析。
- 序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包。当出现网络抖动的情况可以用来对数据进行重新排序。序列号的初始值是随机的,同时音频包和视频包的sequence是分别计数的。
- 时戳(Timestamp):占32位,必须使用90kHZ时钟频率(程序中的90000)。时戳反映了该RTP报文的第一个八位组的采样时刻。接受者使用时戳来计算延迟和延迟抖动,并进行同步控制。可以根据RTP包的时间戳来获得数据包的时序。
- 同步信源(SSRC)标识符:占32位,用于标识同步信源。同步信源是指产生媒体流的信源,他通过RTP报头中的一个32为数字SSRC标识符来标识,而不依赖网络地址,接收者将根据SSRC标识符来区分不同的信源,进行RTP报文的分组。
- 提供信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个CSRC。每个CSRC标识了包含在RTP报文有效载荷中的所有提供信源。
提供信源用来标识对一个RTP混合器产生的新包有贡献的所有RTP包的源。是指当混合器接收到一个或多个同步信源的RTP报文后,经过混合处理产生一个新的组合RTP报文,并把混合器作为组合RTP报文的SSRC,将原来所有的SSRC都作为CSRC传送给接收者,是接受者知道组成组合报文的各个SSRC。
2.4 H264数据RTP打包发送
1)、H264数据打包流程:
从H264编码器读出一帧数据(一个完整的H264帧)通常SPS,PPS大小于MTU可以用一个包发送,I帧或者P帧数据大小大于MTU(通常为1500),需要分包发送。
2)、H264数据结构H264帧由多个NALU组成和起始码(start code),NALU之间有一个称为“起始码”字段分开,就是下图所示的黑色部分,起始码有两种格式:0x000001和0x00000001;有了起始码,我们就可以将H264帧里面的每个NALU取出来进行处理了。一个NALU由Header和RBSP两部分构成,不同的Header通常有如下定义SPS->0x67,PPS->0x68,I-Frame->0x65,RBSP就是编码后的具体分片数据。
3)、NALU打包到RTP
上图展示了如何将一个NALU打包到RTP的payload;上图的左边的打包流程对应的场景是“NALU的长度 <= MTU”,直接将NALU的header拷贝到H264 RTP Payload Header上,将NALU的RBSP拷贝到H264 RTP Payload Content上。
上图右边的打包流程对应的场景是“NALU的长度 > MTU”,要将NALU的RBSP进行分片,以保证打包后的RTP报文长度不大于MTU,H264 RTP Payload Header由FU-identity + FU Header组成;FU-identity字段和NALU header字段的格式一样,其最低的5bits表示payload的类型(不同的取值表示RTP的类型不同,见下表)我们在项目中使用过是28/FU-A;FU payload就是NALU的RBSP(一部分);另外,属于同一H264帧的所有RTP头的时间戳都要打成相同的,接收端根据时间戳来判断哪些包是属于同一个H264帧的。
0 | reserved |
---|---|
1-23 | NALU unit |
24 | STAP-A |
25 | STAP-B |
26 | MTAP16 |
27 | MTAP24 |
28 | FU-A |
29 | FU-B |
30-31 | reserved |
实际项目中通过wireshark抓包,可以看到具体的H264数据RTP打包截图信息如下:
2.5 RTCP协议
RTCP协议处理机根据定义了五种类型的报文:
它们完成接收、分析、产生和发送控制报文的功能。
RTCP可以说是控制交通的协议,它提供了:
1)SR发送者报告分组:用来使发送端周期的向所有接收端用多播方式进行报告。内容包括:
该RTP流的SSRC;该RTP流中最新产生的RTP分组的时间戳和绝对时钟时间(或称墙上时间:wall clock time);该RTP流包含的分组数;该RTP流包含的字节数。
绝对时钟时间是必要的。因为RTP要求每一种媒体使用一个流。有了绝对时钟时间就可以进行图形和声音的同步。
2)RR接收者报告分组:用来使接收端周期性的向所有的点用多播方式进行报告。内容包括
所接收到的RTP流的SSRC;该RTP流的分组丢失率;在该RTP流中的最后一个RTP分组的序号;分组到达时间间隔的抖动等。
发送RR分组有两个目的。第一,可以使所有的接收端和发送端了解当前网络的状态。
第二,可以使所有发送RTCP分组的站点自适应的调整自己发送RTCP分组的速率,RTCP分组的通信量不超过网络中的数据分组的通信量的5%,而接收端分组报告分组的通信量又应小于所有RTCP分组的通信量的75%。
3)SDES源描述分组:给出会话中参加者的描述,包括参加者的规范名(CNAME)
4)BYE分组:关闭一个数据流。
5)APP分组:应用程序能够定义新的分组类型。
整个RTCP协议类型:
2.6 RTCP的发送
不同类型的RTCP信息包可堆叠,不需要插入任何分隔符就可以将多个RTCP包连接起来形成一个RTCP组合包,然后由低层协议用单一包发送出去。由于需要低层协议提供整体长度来决定组合包的结尾,在组合包中没有单个RTCP包的显式计数,组合包中每个RTCP包可独立处理,而不需要按照包组合的先后顺序处理。在组合包中有以下几条强制约束:
1)、只要带宽允许,在SR包或RR包中的接收统计应该经常发送,因此每个周期发送的组合RTCP 包中应包含报告包。
2)、每个组合包中都应该包含SDES CNAME,因为新接收者需要通过接收CNAME来识别源,并与媒体联系进行同步。
3)、组合包前面是包类型数量,其增长应该受到限制。
4)、RTCP传输间隔上,由于RTP设计成允许应用自动扩展,每个会话参与者周期性地向所有其他参与者发送RTCP控制信息包,如每个参与者以固定速率发送接收报告,控制流量将随参与者数量线性增长。由于网络资源有限,相应的数据包就要减少,直接影响用户关心的数据传输。为了限制控制信息的流量,RTCP控制信息包速率必须按比例下降,目前我们采用点对点的方式不存在带宽的问题,可以采用定期发送的策略。
三 项目中RTCP的使用策略
Soure端的流程图:
3.1 Phone to Pad
1)、Phone to Pad中使用标准的RTP/RTCP方案均采用UDP方式传输,RTCP控制中主要以Pad端发送RR(201),SDES(202),以及扩展的组包失败需要I帧请求(210),乱序重组事件(211)。
2)、Pad侧RR和SDES组包一起每5s发送一次,其他的事件(210,211)出现就发送,其中I帧请求间隔建议大于1s,否则会造成持续的大数据产生,加重网络负担。
3)、Pad侧RTP乱序重组事件可定义为网络的抖动导致的乱序,乱序次数可以反映当前网络环境,以此当做网络环境的判断,Phone侧每5s作为一个统计周期,对接收过来的乱序(211)事件做次数统计,以此来动态的降低码率帧率,目前采用的是码率快降(当前码率的0.6)缓升(当前码率的1.1)策略,帧率目前不做变动。
3.2 Phone to PC (TCP传输)
1)、Phone to PC中使用的非标准的RTP组包,直接采用拼接PTS+SIZE头传输的裸流,以及RTCP方案均采用TCP方式传输,RTCP控制中主要以PC端发送RR(201),SDES(202),以及扩展的编码失败需要I帧请求(210),接收数据时间过长,无数据渲染(211)。
2)、PC侧RR和SDES组包一起每5s发送一次,其他的事件(210,211)出现就发送,其中I帧请求间隔建议大于1s,否则会造成持续的大数据产生,加重网络负担。
3)、PC侧可以根据当前帧率的PTS时间间隔T0,排除编码问题前提下网络的延迟直接反应在渲染端就会出现卡顿现象,PC端根据两秒内渲染间隔T1,当T1>2*T0时则认为当前出现了网络拥塞,PC端立刻发送拥塞 (211)事件,Phone侧每5s作为一个统计周期,对接收过来的拥塞 (211)事件做次数统计,以此来动态的降低码率帧率,目前采用的是码率快降(当前码率的0.6)缓升(当前码率的1.1)策略,最大8M,最小600KB,帧率采用快降(当前帧率的0.5)缓升1.2最大60FPS,最低5FPS。
4)、由于PC端目前的网卡性能差异不可控,需要考虑极端情况下出现的延迟,由于TCP无法保障弱网下的数据时延,故在发送侧动态的检测发送数据的队列大小,实时检测还未发送出去的数据多少(以此标准判断当前网络的拥塞延迟)适时的按照第五点提到的策略来动态控制帧率和码率,来达到降低时延的目的。
3.3 Phone to PC (UDP传输)
1)、Phone to PC中使用标准的RTP/RTCP方案均采用UDP方式传输,RTCP控制中主要以Pad端发送RR(201),SDES(202),以及扩展的组包失败需要I帧请求(210),乱序重组事件(211)。
2)、Pad侧RR和SDES组包一起每5s发送一次,其他的事件(210,211)出现就发送,其中I帧请求间隔建议大于1s,否则会造成持续的大数据产生,加重网络负担。
3)、Pad侧RTP乱序重组事件可定义为网络的抖动导致的乱序,乱序次数可以反映当前网络环境,以此当做网络环境的判断,Phone侧每5s作为一个统计周期,对接收过来的乱序(211)事件做次数统计,以此来动态的降低码率帧率,目前采用的是码率快降(当前码率的0.6)缓升(当前码率的1.1)策略,码率最低600K,最高8M,帧率目前不做变动,帧率的变动无法减少数据量的产生,直接降低用户的体验。
3.4 Phone to PC的计划
1)、后续计划需要引入pc端的网络实时检测来反映网络的平均延迟,通过RTCP扩展命令,来动态的设置Phone端编码的帧率和码率。
2)、当前测试已经发现p2p情况下某些老旧pc已经出现比较大的延迟,需要针对弱网下的方案进行优化处理,需要加入UDP+RTP/RTCP方案解决弱网问题,PC端需要适时提醒用户网络出现拥塞问题,进行网络连接优化(同频段wifi连接),优先使用TCP方案,出现卡顿后弹窗切换成UDP方案,确保部分老旧pc的使用。
3)、由于PC非自研产品,有必要引入通用的RTSP协商交互协议,PC端弹框优化后可动态的配置视频的帧率码率,音频的采样率等参数,确保多媒体的交互可以根据pc的性能(编解码能力,网卡延迟以及数据吞度量能力)动态的配置音视频参数。
流媒体通信中RTP/RTCP在项目中的应用的更多相关文章
- Intellij IDEA 中如何查看maven项目中所有jar包的依赖关系图(转载)
Intellij IDEA 中如何查看maven项目中所有jar包的依赖关系图 2017年04月05日 10:53:13 李学凯 阅读数:104997更多 所属专栏: Intellij Idea ...
- 解决tomcat下面部署多个项目log4j的日志输出会集中输出到一个项目中的问题
在一次项目上线后,发现了一个奇怪的问题,经过对源码的阅读调试终于解决,具体经过是这样的: 问题描述:tomcat7下面部署多个项目,log4j的日志输出会集中输出到一个项目中,就算配置了日志文件的绝对 ...
- Android IOS WebRTC 音视频开发总结(八十六)-- WebRTC中RTP/RTCP协议实现分析
本文主要介绍WebRTC中的RTP/RTCP协议,作者:weizhenwei ,文章最早发表在编风网,微信ID:befoio 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID ...
- vue 项目中的坑 在项目中遇到 持续更新ing
1.vue2.0 不支持 v-html 后绑定的内容使用过滤,可是有时候过滤必须使用-----------解决:通过methods中定义方法 然后 v-html='myMethods(string)' ...
- 在eclipse中如何在大量项目中查找指定文件
在eclipse中如果希望在大量的项目中寻找指定的文件可不是一件轻松的事,还好eclipse提供了强大的搜索功能. 我们可以通过通配符或正则表达式来设定查寻条件,下面是操作示例: ctrl+h 打开搜 ...
- 在eclipse中如何在大量项目中查找指定文件(转载)
转载:http://blog.csdn.net/inowcome/article/details/6699227 在eclipse中如果希望在大量的项目中寻找指定的文件可不是一件轻松的事,还好ecli ...
- Intellij IDEA 中如何查看maven项目中所有jar包的依赖关系图
Maven 组件界面介绍 如上图标注 1 所示,为常用的 Maven 工具栏,其中最常用的有: 第一个按钮:Reimport All Maven Projects 表示根据 pom.xml 重新载入项 ...
- IDEA中的maven web 项目中如何设置自己的本地仓库
我们在创建maven项目的时候如何不使用系统指定的本地仓库,而使用自己设置的仓库呢,这里小女子就来进行讲解一下吧! 讲解一:你要想找到settings.xml你就要自己我去官网上去下载apache-m ...
- TP v5中环境变量在项目中的应用
环境变量,顾名思义就是在不同的系统环境,同一个变量的值可以有所不同. 如开发环境.测试环境与正式环境下,数据库配置.静态资源文件Url前缀.缓存.各种key等配置都不相同,对于提交到仓库中的代码,理论 ...
- (网页)SQLserver中在上线的项目中遇到科学计数法怎么办?
遇到这个问题,首先上线的数据能清除吗?显然是不能的. 1.首先要去找这些科学计数法的数字是哪里来的. 2.怎么在不改变数据的情况下去操作这张表.可以使用convert()转一下Decimal.
随机推荐
- 如何快速提高英飞凌单片机编译器 TASKING TriCore Eclipse IDE 编译速度
1.前言 使用英飞凌单片机编译器 TASKING TriCore Eclipse IDE 开发编译时,想必感受最深刻的就是编译速度,那是非常慢了,如果是部分修改的源文件编译还好,不用等太久,而如果选择 ...
- 深入浅出Java多线程(八):volatile
引言 大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第八篇内容:volatile.大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!! 在当今的软件开发领域,多线程编程 ...
- .NET Core开发实战(第3课:.NET Core的现状、未来以及环境搭建)--学习笔记
03 | .NET Core的现状.未来以及环境搭建 .NET Core的现状 .NET Core 的应用场景:桌面端.Web端.云端.移动端.游戏.IOT 和 AI 云端指的是 .NET Core ...
- 《ASP.NET Core 微服务实战》-- 读书笔记(第7章)
第 7 章 开发 ASP.NET Core Web 应用 ASP.NET Core 基础 在本章,我们将从一个命令行应用开始,并且在不借助任何模板,脚手架和向导的情况下,最终得到一个功能完整的 Web ...
- SATA 学习笔记——Link Layer 8b/10b 编码解析
一.故事前传 我们上回说到Link layer的结构,link layer的作用大致可以包括以下几点: Frame flow control CRC的生成与检测 对数据与控制字符的Scrmable/D ...
- MyBatis Interceptor
MyBatis的拦截器可以用于在以下对象及方法中拦截修改: Executor (update, query, flushStatements, commit, rollback, getTransac ...
- 【framework】View添加过程
1 前言 WMS启动流程 中介绍了 WindowManagerService 的启动流程,本文将介绍 View 的添加流程,按照进程分为以下2步: 应用进程:介绍从 WindowManagerImpl ...
- UML类图入门实战
介绍 UML--Unified modeling language UML (统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果. UML 本身是一 ...
- pexpect模块(替代subprocess)
https://blog.csdn.net/pcn01/article/details/104993742/
- 【C# .Net】List循环add,出现数据相同现象? 引发对引用类型和值类型的底层逻辑的思考。
赶项目时发现了一个问题,定义一个引用对象,如果在循环外定义对象,在循环内list.add(object).最后的结果却是所有的对象值都是一样的,即每add一次,都会把之前的数据覆盖. 解决方法:把对象 ...