简介
实时语音、视频数据传输的标准协议RTP( Real-time Transport Protocol)和RTCP(RTP Control Ptotocol)
RTP标准定义了两个子协议,RTP和RTCP
数据传输协议RTP,用于实时传输数据。该协议提供的信息包括:时间戳(用于同步)、序列号(用于丢包和重排序检测)、以及负载格式(用于说明数据的编码格式)。 控制协议RTCP,用于QoS反馈和同步媒体流。相对于RTP来说,RTCP所占的带宽非常小,通常只有5%。
为什么要使用RTP 一提到流媒体传输、一谈到什么视频监控、视频会议、语音电话(VOIP),都离不开RTP协议的应用,但当大家都根据经验或者别人的应用而选择RTP协议的时候, 那么为什么我们要使用RTP来进行流媒体的传输呢?为什么我们一定要用RTP?难道TCP、UDP或者其他的网络协议不能达到我们的要求么? 像TCP这样的可靠传输协议,通过超时和重传机制来保证传输数据流中的每一个bit的正确性,但这样会使得无论从协议的实现还是传输的过程都变得非常的复杂。 而且,当传输过程中有数据丢失的时候,由于对数据丢失的检测(超时检测)和重传,会数据流的传输被迫暂停和延时。
或许你会说,我们可以利用客户端构造一个足够大的缓冲区来保证显示的正常,这种方法对于从网络播放音视频来说是可以接受的, 但是对于一些需要实时交互的场合(如视频聊天、视频会议等),如果这种缓冲超过了200ms,将会产生难以接受的实时性体验。
那为什么RTP可以解决上述问题呢
RTP协议是一种基于UDP的传输协议,RTP本身并不能为按顺序传送数据包提供可靠的传送机制,也不提供流量控制或拥塞控制,它依靠RTCP提供这些服务。 这样,对于那些丢失的数据包,不存在由于超时检测而带来的延时,同时,对于那些丢弃的包,也可以由上层根据其重要性来选择性的重传。 比如,对于I帧、P帧、B帧数据,由于其重要性依次降低,故在网络状况不好的情况下,可以考虑在B帧丢失甚至P帧丢失的情况下不进行重传, 这样,在客户端方面,虽然可能会有短暂的不清晰画面,但却保证了实时性的体验和要求。
RTP解析
RTP的工作机制: 当应用程序建立一个RTP会话时,应用程序将确定一对目的传输地址。 目的传输地址由一个网络地址和一对端口组成,有两个端口:一个给RTP包,一个给RTCP包,使得RTP/RTCP数据能够正确发送。 RTP数据发向偶数的UDP端口,而对应的控制信号RTCP数据发向相邻的奇数UDP端口(偶数的UDP端口+1),这样就构成一个UDP端口对。
RTP的发送过程如下,接收过程则相反。
RTP协议从上层接收流媒体信息码流(如H.264),封装成RTP数据包;RTCP从上层接收控制信息,封装成RTCP控制包. RTP将RTP 数据包发往UDP端口对中偶数端口;RTCP将RTCP控制包发往UDP端口对中的奇数端口。 RTP分组只包含RTP数据,而控制是由RTCP协议提供。 RTP在1025到65535之间选择一个未使用的偶数UDP端口号,而在同一次会话中的RTCP则使用下一个奇数UDP端口号。 端口号5004和5005分别用作RTP和RTCP的默认端口号。 RTP分组的首部格式如图2所示,其中前12个字节是必须的。
应用层的一部分 从应用开发者的角度看,RTP 应当是应用层的一部分。 在应用的发送端,开发者必须编写用 RTP 封装分组的程序代码,然后把 RTP 分组交给 UDP 插口接口。 在接收端,RTP 分组通过 UDP 插口接口进入应用层后,还要利用开发者编写的程序代码从 RTP 分组中把应用数据块提取出来。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| V=2 | P| X| CC | M| PT | sequence number |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| timestmp |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| SSRC |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| CSRC |
| .... |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| payload(video, audio) |
| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | padding | count |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
RTP报文头的内容
版本号(V):2比特,用来标志使用的RTP版本。
填充位(P):1比特,如果该位置位,则该RTP包的尾部就包含附加的填充字节。
扩展位(X): 1比特,如果该位置位的话,RTP固定头部后面就跟有一个扩展头部,一般默认为0,很少有场景会用到。
CSRC计数器(CC):4比特,含有固定头部后面跟着的CSRC的数目,是为了计算后面有多少个CSRC,四位说明则最大支持15个CSRC,一般默认为0。
标记位(M): 1比特,特别对于视频而言就是一帧的结束,视频帧比较大, 需要通过多个NALU(网络抽象层单元(Network Abstract Layer Unit))来传输, 当看到M位为1时就认为是这个I帧的结束, 由于音频帧比较小,一个RTP包就是一个音频帧,所以该位直接置1。 当该值为1时,表示该数据包是一帧数据的最后一个数据包。
载荷类型(PayloadType): 7比特,标识了RTP载荷的类型。
序列号(SN):16位,用于标识发送者发送的RTP报文序列号,每发送一个RTP包,则这里就增加1,当达到最大值后,则重新从0开始。 刚才说了一般RTP协议是承载协议是UDP,UDP是不可靠传输协议。 那我们如何保证接收端收的数据是正确的呢,就是通过这个字段进行重新排序,所以接收端一般收到RTP数据第一件事就是排序。 特别注意两点: a. 这个序列号的初始值可以为0但是也可以为其它随机值,只要符合+1就行; b. 发送端的音频和视频都是通过RTP传输的,但是他们是分别计数的,所用的序列号是不同的。 时间戳(Timestamp): 占32位四字节,这个单位要注意是采样率倒数,不是真实的时间,一般要根据采样率进行换算。 这里反应的RTP报文第一个八位组的采样时刻,目的是为了接收端计算延迟、抖动和音视频同步。 需要说明的是,一个视频帧的时间戳是相同的,但是一个视频帧数据量很大可能需要多个RTP包传输,这样就存在多个RTP包时间戳相同的情况, 音频帧数据小,不存在音频帧跨RTP的情况,所以不存在这个问题。
同步源标识符(SSRC):占32位四字节,用于标识同步信号源,这个值只要保证在一路音视频会话里面值不相同即可。该标识符是随机选取的 RFC1889推荐了MD5随机算法。该值的作用就是在会话中标识RTP负载流的身份,给一个唯一标记值。
CSRC list(贡献源列表): 同样是32位,四字节。一个RTP头最多可以含有0-15个,如果是1对1的流媒体传输,这个字段就不用处理,直接忽略该字段。 但是混流和混音时,则需要把各方的RTP同步信号源列出来,这样接收端就能正确指出交谈双方的身份。 用来标志对一个RTP混合器产生的新包有贡献的所有RTP包的源,由混合器将这些有贡献的SSRC标识符插入表中。
荷载内容(payload(video, audio)): 实际上的音视频实际数据。
RTP的会话过程
当应用程序建立一个RTP会话时,应用程序将确定一对目的传输地址。 目的传输地址由一个网络地址和一对端口组成,有两个端口:一个给RTP包,一个给RTCP包,使得RTP/RTCP数据能够正确发送。 RTP数据发向偶数的UDP端口,而对应的控制信号RTCP数据发向相邻的奇数UDP端口(偶数的UDP端口+1),这样就构成一个UDP端口对。
RTP的发送过成如下,接收过程相反 RTP协议从上层接收流媒体信息码流(如H.263),封装成RTP数据包;RTCP从上层接收控制信息,封装成RTCP控制包。 RTP将RTP 数据包发往UDP端口对中偶数端口;RTCP将RTCP控制包发往UDP端口对中的接收端口。 RTP的profile机制 RTP为具体的应用提供了非常大的灵活性,它将传输协议与具体的应用环境、具体的控制策略分开,传输协议本身只提供完成实时传输的机制,开发者可以根据不同的应用环境,自主选择合适的配置环境、以及合适的控制策略。
这里所说的控制策略指的是你可以根据自己特定的应用需求,来实现特定的一些RTCP控制算法,比如前面提到的丢包的检测算法、丢包的重传策略、一些视频会议应用中的控制方案等等(这些策略我可能将在后续的文章中进行描述)。
对于上面说的合适的配置环境,主要是指RTP的相关配置和负载格式的定义。RTP协议为了广泛地支持各种多媒体格式(如 H.264, MPEG-4, MJPEG, MPEG),没有在协议中体现出具体的应用配置,而是通过profile配置文件以及负载类型格式说明文件的形式来提供。
说明:如果应用程序不使用专有的方案来提供有效载荷类型(payload type)、顺序号或者时间戳,而是使用标准的RTP协议,应用程序就更容易与其他的网络应用程序配合运行,这是大家都希望的事情。例如,如果有两个不同的公司都在开发因特网电话软件,他们都把RTP合并到他们的产品中,这样就有希望:使用不同公司电话软件的用户之间能够进行通信。
RTCP的封装
RTCP的主要功能: 服务质量的监视与反馈、媒体间的同步,以及多播组中成员的标识。 在RTP会话期间,各参与者周期性地传送RTCP包。RTCP包中含有已发送的数据包的数量、丢失的数据包的数量等统计资料, 因此,各参与者可以利用这些信息动态地改变传输速率,甚至改变有效载荷类型。 RTP和RTCP配合使用,它们能以有效的反馈和最小的开销使传输效率最佳化,因而特别适合传送网上的实时数据。
RTCP也是用UDP来传送的,但RTCP封装的仅仅是一些控制信息,因而分组很短, 所以可以将多个RTCP分组封装在一个UDP包中。
根据所携带的控制信息不同RTCP信息包可分为RR(接收者报告包)、SR(源报告包)、SEDS(源描述包)、BYE(离开申明)和APP(特殊应用包)五类5类:
SR包(SenderReport):发送者报告, 当前活动发送者发送、接收统计;所谓发送端是指发出RTP数据报的应用程序或者终端,发送端同时也可以是接收端。 由三部分组成,也可能还有第四个特定设置扩展部分。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| V=2 | P| RC | PT=SR=200 | length |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| SSRC of sender |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NTP |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NTP |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| RTP |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| sender packet count |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| sender octet count |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| SSRC_1(SSRC of first source) |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|fraction lost(两次丢失) | cumulative number of pacquets lost(所有的丢包) |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| extented highest sequence number received |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| interarriaval jitter |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| last SR (LSR) |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| delay since last SR(DLSR) |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
第一部分为头:8个字节,如下:
V:2位,标识RTP版本,需要注意的是RTP数据包中的版本号与RTCP数据包的中的版本号是一致的。
填充P:1位,如设置于此位,RTCP包结尾包含一些附加填充八位组,它们不属于控制信息。 最后一个字节填充表示应忽略多少个填充。有些加密算法需要填充,块大小固定。 在组合RTCP包内,填充仅在最后一个包中需要,因为组合包加密成一个整体。
接收报告计数(RC):5位,包含在包内的接收报告块数目,该字段0值为有效的,但没有实际意义。
包类型(PT):8位,包含常数200标识此包为RTCP的SR包。
长度length:16位,其中存放的是该SR包以32比特为单位的总长度减一(减去一个header)。 使用4个字节为1组,长度共有几个4个字节的组,然后用该长度减去1,即为RTCP包中的长度。 例如:假设RTCP数据包的长度为32个字节,32/4=8,总共有8组4个字节,8-1=7,此时RTCP数据包中length的值为7。
SSRC:32位,同步源标识,与对应rtp包中的ssrc一样。
第二部分为发送者信息:20个字节,如下:
NTP(Network time protocol)时标:64位,表示报告发送时的时钟时间, 这样它就可用于合并从其他发送者到那些接收者的接收报告返回的时标。 SR包发送时的绝对时间值,NTP的作用是同步不同的RTP媒体流。 Ntp把当前时间(自1970.1.1以来的秒数)分为整数部分N和小数部分X Ntp高位=整数部分N + 2208988800UL (其中2208988800UL表示自1900.1.1到1970.1.1的秒数,70年) Ntp低位=小数部分 X 4294967296 (其中4294967296为2^32,相当于左移32位)
RTP时标:32位,与上述NTP时标同一时间有关,与RTP数据包中的RTP时间戳具有相同的单位和随机初始值。 以sample为单位,如音频8000HZ,一个packet为20ms,则两个rtp时间戳的间隔为160。 从rtp时间戳换算成ms的公式为: rtp时间戳 x 1000/samplerate。
发送者包计数:32位,自开始发送起,直到SR包产生时刻,发送者发送RTP数据包总数。如改变SSRC标识符,此计数重置。
发送者八位组计数:32位,发送者在RTP数据包中发送的载荷八位组总数(不包括头部和填充)。 从发送开始,直到产生SR包,如发送者改变SSRC标识,重置此计数。这部分可用于估计载荷数据平均速率。
第三部分包含接收报告块,大小不固定。 每个接收报告块传送单个同步源接收RTP包的统计。 发生冲突,当源改变SSRC标识时,接收者并不继续统计。这些统计包括:
SSRC_n(源标识):32位,接收报告块中信息所属源的SSRC标识。
丢失部分:8位,前一个SR或RR包发送以来所丢失的源SSRC_n的RTP数据包中一部分,定义成所丢失包的数目。
丢失包累积数:24位,自接收以来所丢失的源SSRC_n的RTP数据包总数,定义成小于实际所接收包的数量,该数量包括迟到或复制的包。 因此,迟到的包不计为丢失,如有复制,此数量可能为负数。
收到已扩展的最高系列号:32位,低16位包含从SSRC_n源RTP数据包中收到的最高系列号,最重要的16位以系列号循环相应计数扩展系列号。 如开始时间明显不同,同一连接内不同接收者将对系列号产生不同扩展。
间隔抖动:32位,RTP数据包接受时间的统计方差估计,以时标为单位,是一个无符号整数。
上次SR时标(LSR):32位,取最近从SSRC_n收到的SR包中的NTP时间戳的中间32比特,如还没有收到SR,此段设为零。
上次SR以来的延时(DLSR):32位,延迟以1/65536秒为单位,上次从SSRC_n收到SR包到发送本报告的延时,如还没有收到SR,此段设为零。 RR包(ReceiverReport):接收者报告,非活动发送者接收统计,仅作为接收者(只接收rtp包)发送出去的包。 所谓接收者是指仅接收但不发送RTP数据报的应用程序或者终端。包类型包含常数201,并删除发送者信息的20个字节。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| V=2 | P| RC | PT=SR=201 | length |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| SSRC of sender |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| SSRC_1(SSRC of first source) |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|fraction lost(两次丢失) | cumulative number of pacquets lost(所有的丢包) |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| extented highest sequence number received |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| interarriaval jitter |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| last SR (LSR) |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| delay since last SR(DLSR) |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
各个字段解释如下:
SSRC_n(32bit): 发送端的信源标识符,与发送端的SSRC一致。
丢包数(8bit):前一个SR或RR包发送后,到当前的SR包或RR包的间隔内,来自源(共享源SSRC标识)发送的数据包的丢失个数。
丢包率,它是定义一个rr发送间隔中rtp报文的丢失率。
它是一个8bits的数据段,计算方法为,loss fraction = lost rate x 256.
举例,丢包率为25%,该字段为 25% * 256=64
累积丢包数(24bit): 自开始接受源(共享源SSRC标识)发送的数据开始,累积丢失的数据包的个数。
理论计算方式, packet lost = 期待得到报文数量 - 实际收到报文的数量
实际计算方式, packet lost = 期待收到最新sequence - 第一次收到报文的sequence
#需要注意:
1. sequence为16位,容易翻转
可以通过计算extend_sequence来区分翻转的sequence。
2. packet lost可能为负数
因为有rtp报文的重传,如果重传次数多,可能造成负数
3. 该值的测量周期是整个会话
测量周期是整个会话,而不是RR的发送间隔
扩展包序号(32bit):低16位为当前接收到的来自源的(共享源SSRC标识)数据包的最大序列号。高16位表示RT包序列号的循环计数。 我们都知道RTP数据包中,表示序列号的长度为2个字节,即最大的RTP序列号为65536(2的16次方),如果序列号超了65536,假设为65537, 这个时候RTCP在扩展包序号中对其说明,如果没有超过65536,则高16位为0,如果超过65536,则为1,如果再来一圈,则为3。
做个不恰当的比喻,我们跑步一圈为65536,高16位表示我们当前正在跑第几圈,从0开始计数。
计算方法,extended_seq_num = seq_num + (65536 * wrap_around_count)
例如:100 + 65536*2 (即2左移到高16位)
其中:
seq_num为当前接收到的来自源的数据包的最大序列号;
wrap_around_count为sequence翻转的次数。
间隔抖动(32bit):RTP数据包间隔时间的统计估计,以时间戳为单位,用无符号整数表示。
这里的延时抖动指的是rtp报文发送方的网络传输时间的变量估计值。 计算单位是基于时间戳的基础单位,也是32位无符号整形。 因为rtp的发送和接收方没有时间同步系统,所以不大可能能准确的测量网络传送时间。
传输时间= |rtp的时间戳-rtp接受者本地时间| 因为没有发送和接收方的时间同步机制,所以这里关心的不是传输时间,是两次接收到rtp报文传输时间的对比,计算公式如下: D(i,j)=(Rj-Ri)-(Sj-Si)=(Rj-Sj)-(Ri-Si) R是接收到的时间戳,Rj是packet j接收到的时间戳,Ri是packet i接收到的时间戳。 S是rtp报文的时间戳,Sj是packet j的rtp时间戳,Si是packet i的rtp时间戳。 而Jitter的计算公式如下: J(i) = J(i-1) + (|D(i-1,i)| - J(i-1))/16
LSR(32bit): last SR timestamp,表示上一个SR数据包的NTP时间戳,由于NTP时间戳为64bit,LSR为32bit,LSR取上一个SR的NTP时间戳的中间32位: 如上一个SR数据包的NTP时间戳为0x 00 01 7d 6e 3b 64 5a 1c,则LSR为0x 7d 6e 3b 64。
DLSR(32bit):发送当前RR包的时间与上一个SR之间的时间间隔,以1/65536为单位, 例如,本次RR包与上一次SR的时间间隔为264ms,则本字段的值为0.264*65536=17301.54。 最后通过LSR,DLSR可以计算RTT:
RTT: Round-Trip Time,发送者计算的发送来回时间。 发送者可以通过RR报文中的LSR和DLSR来计算RTT。
计算方法: 发送者用接收到RR报文的当前时间-RR报文的LSR,得到发送SR和接收到RR所花费的网络延时。
再进行: (接收到RR报文的当前时间-RR报文的LSR) - RR中的DLSR,也就是去除了在RTP接收者方本地的SR接收和RR发送的延时,这样就得到了RTT。
RTT = 接收到RR报文的当前时间-RR报文的LSR - RR中的DLSR
经验表明,如果RTT大于300ms,这样的质量网络通话无法完成。 只能通过降低比特率,降低打包延时或应用好的错误修复机制来完成。
RTCP传输间隔
由于RTP设计成允许应用自动扩展,可从几个人的小规模系统扩展成上千人的大规模系统。 由于每个对话成员定期发送RTCP信息包,随着参加者不断增加,RTCP信息包频繁发送将占用过多的网络资源, 为了防止拥塞,必须限制RTCP信息包的流量,控制信息所占带宽一般不超过可用带宽的 5%, 因此就需要调整 RTCP包的发送速率。由于任意两个RTP终端之间都互发 RTCP包,因此终端的总数很容易估计出来, 应用程序根据参加者总数就可以调整RTCP包的发送速率
RTP/RTCP的不足之处 RTP与RTCP相结合虽然保证了实时数据的传输,但也有自己的缺点。 最显著的是当有许多用户一起加入会话进程的时候,由于每个参与者都周期发送RTCP信息包,导致RTCP包泛滥。
SDP
会话描述协议,Session Description Protocal
SDP包括以下一些方面:
(1)会话的名称和目的
(2)会话存活时间
(3)包含在会话中的媒体信息,包括:
媒体类型(video,audio, etc)
传输协议(RTP/UDP/IP,H.320, etc)
媒体格式(H.261video, MPEG video, etc)
多播或远端(单播)地址和端口
(4)为接收媒体而需的信息(addresses, ports, formats and so on)
(5)使用的带宽信息
(6)可信赖的接洽信息(Contact information)
说白了就是通信双方用SDP来描述通信过程中的采用什么协议 (用户TCP还是UDP),双方ip与port,发的数据是什么类型(视频还是音频)等等信息。 字段很多,但是实际当中只需要重点关注几个即可。
SDP主要分为三大类:会话层、时间层、媒体层。
实例
# 协议版本
v=0
# 会话源,<用户名><会话id><版本><网络类型><地址类型><地址>
o=64010600002020000001 0 0 IN IP4 172.20.16.3
# 会话的名称
s=Play
# 媒体连接信息,<网络类型><地址类型><地址>
c=IN IP4 172.20.16.3
# 会话的开始时间和接收时间
t=0 0
# 媒体信息
m=video 6000 RTP/AVP 96 98 97
# 媒体流的方向
a=recvonly
# 对媒体字段的详细解析
a=rtpmap:96 PS/90000
a=rtpmap:98 H264/90000
a=rtpmap:97 MPEG4/90000
1 会话层 1.1 重点关注c字段,Connection Data 重点关注c字段,Connection Data c=* (connection information - notrequired if included in all media)表示媒体连接信息。 格式: c=(networktype) (address type) (connection address) network type:网络类型,一般为"IN",表示"internet" address type:地址类型,一般为IP4。 connection address:应用程序必须处理域名和ip地址两种情形。 单播时,为域名或ip地址,推荐使用域名, 多播,为ip地址,且ip后面必须有TTL(取值范围是0-255), 地址和TTL决定了多播包被传播的范围。 例如: c=IN IP4 224.2.1.1/127 c字段的ip参数:如果是发送方,表明是从这个ip往出发,如果是接收方,表明在这个ip接受数据。
1.2 重点关注s字段,Session Name 只有一个s字段。表明会话的名称。 在gb28181中有play实时开流、playback回放、download下载、talk语音对讲。
2 时间层 RepeatTimesand Time Zones 只有一个字段t,是必须要的。 t=(start time) (stop time) 描述了会话的开始时间和结束时间。 GB28181中,实时都设置为0,录像回放时,为录像开始时间,结束时间。
3 媒体层
3.1 重点关注m字段,Media Announcements 一个会话描述包括几个媒体描述。一个媒体描述以"m=“开始到下一个"m=“结束。 m=(media) (port) (transport) (fmt list)
media:表示媒体类型。 有"audio”, “video”,“application”(例白板信息), “data”(不向用户显示的数据) 和"control”(描述额外的控制通道)。
port:媒体流发往传输层的端口。 取决于c=行规定的网络类型和接下来的传送层协议:对UDP为1024-65535;对于RTP,偶数端口被用来传输数据,奇数端口用来传输RTCP包。
transport:传输协议,与c=行的地址类型有关。 两种: RTP/AVP,表示Real time Transport Protocol using the Audio/Video profile carried over UDP, UDP。
fmt list:媒体格式。 对于音频和视频就是在RTP Audio/Video Profile定义的负载类型(payload type)。 但第一个为缺省值,分为静态绑定和动态绑定:静态绑定即媒体编码方式与RTP负载类型有确定的一一对应关系,动态绑定即媒体编码方式(如时钟频率,音频信道数等)没有完全确定,需要进一步的属性说明(用rtpmap)。
3.2 重点关注a字段,zero or more media attributelines a字段多用来补充扩展信息用,其中的rtpmap是来对m字段中的fmt list进一步说明的。 GB28181中对a字段进行了扩展。
GB28181 SDP的规定 在媒体描述层的字段里面加了一个y字段、f字段
y字段是存放 SSRC,十进制字符串,共10位,第一位0为实时,1为历史。2-6位为SIP监控域ID的4-8位,7-10位作为域内媒体流标识,是一个与当前域内产生的媒体流SSRC值后4位不重复的十进制数。
f字段是媒体描述 f=v/编码格式/分辨率/帧率/码率类型/码率大小 a/编码格式/码率大小/采样率
a字段除了和一般SDP都有的rtpmap之外还加了其他参数。
a=rtpmap:<负载类型> 编码名/速率 a=rtpmap:96 PS/90000 a=rtpmap:98 H264/90000 a=rtpmap:97 MPEG4/90000
如果请求某媒体流的方向为sendonly,那么响应中对应媒体的方向必须为recvonly; 如果请求某媒体流的方向为recvonly,那么响应中对应媒体的方向必须为sendonly; 如果请求某媒体流的方向为sendrecv,那么响应中对应媒体的方向可以sendrecv/sendonly/recvonly/inactive中的一种; 如果请求某媒体流的方向为inactive,那么响应中对应媒体的方向必须为inactive
RTP + H264
1、h264码流传输
I帧: 参考帧或者关键帧,可以理解为是一帧完整画面,解码时只需要本帧数据就解码成一幅完整的图片,数据量比较大。 P帧: 差别帧,只有与前面的帧或I帧的差别数据,需要依赖前面已编码图象作为参考图象,才能解码成一幅完整的图片,数据量小。 B帧: 双向差别帧,也就是B帧记录的是本帧与前后帧的差别,需要依赖前后帧和I帧才能解码出一幅完整的图片,数据量小。
由于I帧比较大,已经超出mtu最大1500,所以需要拆包分片传输。 这里说的拆包发送不是指发送超过1500的数据包时tcp的分段传输或者upd的ip分片传输,而是指rtp协议本身对264的拆包。 rtp打包后将数据交给tcp或者upd进行传输的时候就已经控制在1500以内。 这样可以提高传输效率,避免一个I帧万一丢失就会造成花屏或者重传造成延时卡顿等等问题。
2、H264在网络传输的单元:NALU
header 部分可以判断类型等信息,从右往左的低5个bit位。
SPS: 0x67 header & 0x1F = 7 I Frame: 0x65 header & 0x1F = 5 PPS: 0x68 header & 0x1F = 8 P Frame: 0x41 header & 0x1F = 1 SEI: 0x66 header & 0x1F = 6
3、h264的RTP打包
单NALU:
P帧或者B帧比较小的包,直接将NALU打包成RTP包进行传输
RTP header(12bytes) + NALU header (1byte) + NALU payload
多NALU:
特别小的包几个NALU放在一个RTP包中
FUs(Fragment Units):
帧长度超过MTU的,就必须要拆包组成RTP包了,有FU-A,FU-B。
对于FU这种方式的,RTP包构成为:RTP header (12bytes)+ FU Indicator (1byte) + FU header(1 byte) + NALU payload
NALU头不见了,如何判断类型,实际上NALU头被分散填充到FU indicator和FU header里面了
如果bit位按照从左到右编号0-7来算, nalu头中0-2前三个bit放在FU indicator的0-2前三个bit中, 后3-7五个bit放入FU header的后3-7五个中。 还原的时候也可以从FU的indicator和header中得到,也就是:
NALU header = (FU indicator & 0xe0) | (FU header & 0x1F) 取FU indicator前三和FU header后五
headerStart[1] = (headerStart[0]&0xE0)|(headerStart[1]&0x1F);
因此查看I帧p帧类型,遇到FU分片的,直接看第二个字节,即Fu header后五位,这个跟直接看NALU头并无差异,一般有:0x85,0x05,0x45等等。 对于不是FU分片的,则因为NALU头是存在的,直接看就行了。
多个RTP包如何还原组合成回一个完整的I或者P帧 在FU header中有标记为判断 照旧从左到右,Fu header前两个bit表示start和end标记,start为1表示一个I或P帧分片开始,end为1表示一个I或P帧分片结束
如何查看是一个I帧分片开始
看第一个字节FU Indicator,照旧从左到右,Fu Indicator前三个bit是NALU头的前三个bit,后五位为类型FU-A:28(11100),FU-B:29(11101)。
RTP抓包看下来整个payload是0x7c开头
如果不是FU分片,第一字节就是NALU头,如:0x67,0x68,0x41等。
RTP包中接收的264包是不含有0x00,0x00,0x00,0x01头的,这部分是rtp接收以后,另外再加上去的,解码的时候再做判断的。
实现
processSpecialHeader(BufferedPacket* packet, unsigned& resultSpecialHeaderSize) {
unsigned char* headerStart = packet->data();
unsigned packetSize = packet->dataSize();
unsigned numBytesToSkip;
// Check the 'nal_unit_type' for special 'aggregation' or 'fragmentation' packets:
if (packetSize < 1) return False;
fCurPacketNALUnitType = (headerStart[0]&0x1F); //FU Indicator后五位即NALU类型 0x1F = 0001 1111
switch (fCurPacketNALUnitType) {
case 24: { // STAP-A
numBytesToSkip = 1; // discard the type byte
break;
}
case 25: case 26: case 27: { // STAP-B, MTAP16, or MTAP24
numBytesToSkip = 3; // discard the type byte, and the initial DON
break;
}
case 28: case 29: { // // FU-A or FU-B
// For these NALUs, the first two bytes are the FU indicator and the FU header.
// If the start bit is set, we reconstruct the original NAL header into byte 1:
if (packetSize < 2) return False;
unsigned char startBit = headerStart[1]&0x80; //FU Header start标记位 0x80= 1000 0000
unsigned char endBit = headerStart[1]&0x40; //FU Header End标记位 0x40= 0100 0000
if (startBit) {
fCurrentPacketBeginsFrame = True;
headerStart[1] = (headerStart[0]&0xE0)|(headerStart[1]&0x1F); //还原NALU头
numBytesToSkip = 1;
} else {
// The start bit is not set, so we skip both the FU indicator and header:
fCurrentPacketBeginsFrame = False;
numBytesToSkip = 2;
}
fCurrentPacketCompletesFrame = (endBit != 0);
break;
}
default: {
// This packet contains one complete NAL unit:
fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame = True; //默认没有分片,完整的NALU
numBytesToSkip = 0;
break;
}
}
resultSpecialHeaderSize = numBytesToSkip;
return True;
}
ps流
PS流传输格式预览
1、视频关键帧的封装 RTP + PS header + PS system header + PS system Map + PES header +h264 data
2、视频非关键帧的封装 RTP +PS header + PES header + h264 data
3、音频帧的封装: RTP + PES header + G711
实例:
rtp over udp
数据解析,这是个I帧
rtp header 80 60 00 00 00 00 00 00 00 00 04 00
——12byte固定长度
ps header 00 00 01 ba …
——00 00 01 ba 44 f0 4f 69 64 01 02 5f 03 fe ff ff 00 01 11 0c 前14byte是固定的, 第14byte 0xfe & 0x07 = 0x0e 也就是后面拓展6byte
ps system header 00 00 01 bb …
——随后的 00 12是长度,也就是ps system header长度 = 4 + 18 byte
ps system map 00 00 01 bc …
——同样随后的 00 5e是长度,也就是ps system map的长度 = 4 + 94 byte
pes header 00 00 01 e0/c0 …
——e0是视频,c0是音频,同样随后的00 1e是长度,也就是pes header的长度 4+30 byte,剩下的就是ps payload数据
ps payload SPS 00 00 00 01 67 …
ps payload PPS 00 00 00 01 68 …
ps payload I 00 00 00 01 65 …
ps payload P 00 00 00 01 61/41 …
rtp over tcp
rtp over tcp (len)05 84 (rtp header)80 60 6d ee 00 b5 62 60 00 00 a5 f6 (ps header)00 00 01 ba 44 76 55 85 74 01 02 5f 03 fe ff ff 00 00 86 24 (pes header)00 00 01 e0 21 ba 8c 80 0a 21 1d 95 61 5d ff ff ff ff f8 (ps payload P)00 00 00 01 61 e0 40 00 59 13 ff 01 23 44 a1 02 38 33 0f 99 df 89 95 01 9e 6d 31 00 2a 8f 05 a5 fb 96 67 38 b8 7f c5 73 bb 25 b6 96 3d 0c 15 0e a4 ed 95 30 6b 43 35 51 9a 04 a1 89 26 6a 6a fc 64 c6 44 37 2a 32 6d 16 12 41 83 53 42 d7 66 e3 51 6b 8e bc 8f 40 73 2a 22 9d a0 d7 b9 c1 ed f8 a5 14 91 2d 8e 90 07 0e b4 2e 4a 0e cb 03 4b 73 f4 1a 49 0a d3 1f bb 72 c5 28 13 b7 9b 35 a0 18 3a c0 91 73 99 1d 4c dd 3b fd eb ce 8e 73 79 34 8a 05 6d 98 d6 a9 20 d3 43 44 d9 b3 cd be 5b f6 74 86 f4 67 26 2f a1 be fb 5c 2c aa 81 4d 51 85 06 c7 65 82 52 47 05 5b ae 76 93
数据解析,这是个P帧
len 05 84
rtp header 80 60 6d ee 00 b5 62 60 00 00 a5 f6
ps header 00 00 01 ba …
pes header 00 00 01 e0 …
ps payload P 00 00 00 01 61/41 …
RTP over TCP模式比RTP over UDP模式多了长度字段,但是通过长度信息组包,组成完整的TCP包,完整的TCP包去掉长度信息就是RTP包。 然后由于tcp底层会作拆包和粘包的优化处理,所以应用层需要特殊处理,最好用jrtplib的tcp模式,jrtplib库已经处理好了拆包和粘包。
接收,解析流程 tcp—>rtp—>ps—>h264
封包流程:
1、封好的ps buf,按FU-A的格式,每隔1400 byte切一片,每片往前添加个12 byte的rtp header,最后一个切片置位Marker,往外发就行。
2、rtp over tcp模式下,rtp heade前还得加2 byte长度,所以rtp over tcp头是14 byte, rtp over udp头是12byte.
3.PSH头部分析
PSH:Program Stream pack Header,是PS包的包头,主要包含系统时间信息。
若当前码流类型为音频流则可选择是否包含PSH头部。 若当前码流类型为视频流,且为当前帧的第1个NALU,则包含PSH头部。 若为I帧,PSH头部长度为44个字节。若为P帧,PSH头部长度为20个字节。 PSH头部主要包含时间戳,最大比特率,帧号信息。
RTP头 80 60 6d ee 00 b5 62 60 00 00 a5 f6s
#define RTP_HDR_LEN 12
static int gb28181_make_rtp_header(char *pData, int marker_flag, unsigned short cseq, long long curpts, unsigned int ssrc) {
bits_buffer_s bitsBuffer;
if (pData == NULL)
return -1;
bitsBuffer.i_size = RTP_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, RTP_HDR_SIZE);
bits_write(&bitsBuffer, 2, RTP_VERSION); /* rtp version 版本号,固定为2 */
bits_write(&bitsBuffer, 1, 0); /* rtp padding */
bits_write(&bitsBuffer, 1, 0); /* rtp extension */
bits_write(&bitsBuffer, 4, 0); /* rtp CSRC count */
bits_write(&bitsBuffer, 1, (marker_flag)); /* rtp marker 结束标志位,一帧图像的最后一包RTP置1*/
bits_write(&bitsBuffer, 7, 96); /* rtp payload type,96代表PS*/
bits_write(&bitsBuffer, 16, (cseq)); /* rtp sequence */
bits_write(&bitsBuffer, 32, (curpts)); /* rtp timestamp */
bits_write(&bitsBuffer, 32, (ssrc)); /* rtp SSRC */
return 0;
}
PSH头部标志
0X 00 00 01 BA …
#define PS_HDR_LEN 14
static int gb28181_make_ps_header(char *pData, unsigned long long s64Scr)
{
unsigned long long lScrExt = (s64Scr) % 100;
s64Scr = s64Scr / 100;
bits_buffer_s bitsBuffer;
bitsBuffer.i_size = PS_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, PS_HDR_LEN);
bits_write(&bitsBuffer, 32, 0x000001BA); /*start codes 起始码*/
bits_write(&bitsBuffer, 2, 1); /*marker bits '01b'*/
bits_write(&bitsBuffer, 3, (s64Scr>>30)&0x07); /*System clock [32..30]*/
bits_write(&bitsBuffer, 1, 1); /*marker bit*/
bits_write(&bitsBuffer, 15, (s64Scr>>15)&0x7FFF); /*System clock [29..15]*/
bits_write(&bitsBuffer, 1, 1); /*marker bit*/
bits_write(&bitsBuffer, 15, s64Scr & 0x7fff); /*System clock [14..0]*/
bits_write(&bitsBuffer, 1, 1); /*marker bit*/
bits_write(&bitsBuffer, 9, 0); /*SCR extension*/
bits_write(&bitsBuffer, 1, 1); /*marker bit*/
bits_write(&bitsBuffer, 22, (255)&0x3fffff); /*bit rate(n units of 50 bytes per second.)*/
bits_write(&bitsBuffer, 2, 3); /*marker bits '11'*/
bits_write(&bitsBuffer, 5, 0x1f); /*reserved(reserved for future use)*/
bits_write(&bitsBuffer, 3, 0); /*stuffing length*/
return 0;
}
SYS附加信息头部
0X 00 00 01 BB …
#define SYS_HDR_LEN 18
static int gb28181_make_sys_header(char *pData) {
bits_buffer_s bitsBuffer;
bitsBuffer.i_size = SYS_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, SYS_HDR_LEN);
/*system header*/
bits_write( &bitsBuffer, 32, 0x000001BB); /*start code*/
bits_write( &bitsBuffer, 16, SYS_HDR_LEN-6); /* 减6,是因为start code加上length这两位,占了6个字节*/
bits_write( &bitsBuffer, 1, 1); /*marker_bit*/
bits_write( &bitsBuffer, 22, 50000); /*rate_bound*/
bits_write( &bitsBuffer, 1, 1); /*marker_bit*/
bits_write( &bitsBuffer, 6, 1); /*audio_bound*/
bits_write( &bitsBuffer, 1, 0); /*fixed_flag */
bits_write( &bitsBuffer, 1, 1); /*CSPS_flag */
bits_write( &bitsBuffer, 1, 1); /*system_audio_lock_flag*/
bits_write( &bitsBuffer, 1, 1); /*system_video_lock_flag*/
bits_write( &bitsBuffer, 1, 1); /*marker_bit*/
bits_write( &bitsBuffer, 5, 1); /*video_bound*/
bits_write( &bitsBuffer, 1, 0); /*dif from mpeg1*/
bits_write( &bitsBuffer, 7, 0x7F); /*reserver*/
/*audio stream bound*/
bits_write( &bitsBuffer, 8, 0xC0); /*stream_id 音频的流id*/
bits_write( &bitsBuffer, 2, 3); /*marker_bit */
bits_write( &bitsBuffer, 1, 0); /*PSTD_buffer_bound_scale*/
bits_write( &bitsBuffer, 13, 512); /*PSTD_buffer_size_bound*/
/*video stream bound*/
bits_write( &bitsBuffer, 8, 0xE0); /*stream_id 视频的流id*/
bits_write( &bitsBuffer, 2, 3); /*marker_bit */
bits_write( &bitsBuffer, 1, 1); /*PSTD_buffer_bound_scale*/
bits_write( &bitsBuffer, 13, 2048); /*PSTD_buffer_size_bound*/
return 0;
}
PSM头部分析 PS system Map 节目映射流(PSM)
当前为音频流/私有数据流的关键帧需要加PSM头部。 当前为视频流且为I帧的第一个NALU时需要加PSM头部。 主要包含BASIC信息、DEVICE信息、加密信息、视频流信息、音频流信息、私有数据信息。 视频流信息分为VIDEO信息、VIDEO_CLIP信息、TIMING_HRD信息长度。 音频流信息分为AUDIO信息。
0x 00 00 01 BC PSM头部标志:
static int gb28181_make_psm_header(char *pData) {
bits_buffer_s bitsBuffer;
bitsBuffer.i_size = PSM_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, PSM_HDR_LEN);//24Bytes
bits_write(&bitsBuffer, 24,0x000001); /*start code*/
bits_write(&bitsBuffer, 8, 0xBC); /*map stream id*/
bits_write(&bitsBuffer, 16,18); /*program stream map length*/
bits_write(&bitsBuffer, 1, 1); /*current next indicator */
bits_write(&bitsBuffer, 2, 3); /*reserved*/
bits_write(&bitsBuffer, 5, 0); /*program stream map version*/
bits_write(&bitsBuffer, 7, 0x7F); /*reserved */
bits_write(&bitsBuffer, 1, 1); /*marker bit */
bits_write(&bitsBuffer, 16,0); /*programe stream info length*/
bits_write(&bitsBuffer, 16, 8); /*elementary stream map length is*/
/*video*/
bits_write(&bitsBuffer, 8, 0x1B); /*stream_type 视频编码格式H.264*/
bits_write(&bitsBuffer, 8, 0xE0); /*elementary_stream_id*/
bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length */
/*audio*/
bits_write(&bitsBuffer, 8, 0x90); /*stream_type 音频编码格式G711*/
bits_write(&bitsBuffer, 8, 0xC0); /*elementary_stream_id*/
bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length is*/
/*crc (2e b9 0f 3d)*/
bits_write(&bitsBuffer, 8, 0x45); /*crc (24~31) bits*/
bits_write(&bitsBuffer, 8, 0xBD); /*crc (16~23) bits*/
bits_write(&bitsBuffer, 8, 0xDC); /*crc (8~15) bits*/
bits_write(&bitsBuffer, 8, 0xF4); /*crc (0~7) bits*/
return 0;
}
PES包分析
视频流/音频流/私有数据流都包含若干PES包。每个PES包由PES头部和码流数据两部分组成。 PES头部第4个字节用于判断码流类型,视频流为0xe0,音频流为0xc0,私有数据流为0xbd。
对于视频流,每帧视频流分为若干NALU,每个NALU分为若干个段,每个段需加一个PES头部。 第一个NALU的第一段的PES头部中可包含pts信息和user_data信息。
对于音频流/私有数据流,每帧数据分为若干段,每段需加一个PES头部。 第一段的PES头部中可包含pts信息和user_data信息。
00 00 01 E0 PES头部标志,表示当前码流为视频流。 00 00 01 C0 PES头部标志,表示当前码流为音频流。 00 00 01 BD PES头部标志,表示当前码流为私有数据。
#define PES_HDR_LEN 19
static int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, unsigned long long pts, unsigned long long dts) {
bits_buffer_s bitsBuffer;
bitsBuffer.i_size = PES_HDR_LEN;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char *)(pData);
memset(bitsBuffer.p_data, 0, PES_HDR_LEN);
/*system header*/
bits_write( &bitsBuffer, 24,0x000001); /*start code*/
bits_write( &bitsBuffer, 8, (stream_id)); /*streamID*/
bits_write( &bitsBuffer, 16,(payload_len)+13); /*packet_len pes剩余头部以及后面的es长度之和,比如SPS长度+13*/
bits_write( &bitsBuffer, 2, 2 ); /*'10'*/
bits_write( &bitsBuffer, 2, 0 ); /*scrambling_control*/
bits_write( &bitsBuffer, 1, 1 ); /*priority*/
bits_write( &bitsBuffer, 1, 1 ); /*data_alignment_indicator*/
bits_write( &bitsBuffer, 1, 0 ); /*copyright*/
bits_write( &bitsBuffer, 1, 0 ); /*original_or_copy*/
bits_write( &bitsBuffer, 1, 1 ); /*PTS_flag 是否有PTS*/
bits_write( &bitsBuffer, 1, 1 ); /*DTS_flag 是否有DTS信息*/
bits_write( &bitsBuffer, 1, 0 ); /*ESCR_flag*/
bits_write( &bitsBuffer, 1, 0 ); /*ES_rate_flag*/
bits_write( &bitsBuffer, 1, 0 ); /*DSM_trick_mode_flag*/
bits_write( &bitsBuffer, 1, 0 ); /*additional_copy_info_flag*/
bits_write( &bitsBuffer, 1, 0 ); /*PES_CRC_flag*/
bits_write( &bitsBuffer, 1, 0 ); /*PES_extension_flag*/
bits_write( &bitsBuffer, 8, 10); /*header_data_length*/
/*PTS,DTS*/
bits_write( &bitsBuffer, 4, 3 ); /*'0011'*/
bits_write( &bitsBuffer, 3, ((pts)>>30)&0x07 ); /*PTS[32..30]*/
bits_write( &bitsBuffer, 1, 1 );
bits_write( &bitsBuffer, 15,((pts)>>15)&0x7FFF); /*PTS[29..15]*/
bits_write( &bitsBuffer, 1, 1 );
bits_write( &bitsBuffer, 15,(pts)&0x7FFF); /*PTS[14..0]*/
bits_write( &bitsBuffer, 1, 1 );
bits_write( &bitsBuffer, 4, 1 ); /*'0001'*/
bits_write( &bitsBuffer, 3, ((dts)>>30)&0x07 ); /*DTS[32..30]*/
bits_write( &bitsBuffer, 1, 1 );
bits_write( &bitsBuffer, 15,((dts)>>15)&0x7FFF); /*DTS[29..15]*/
bits_write( &bitsBuffer, 1, 1 );
bits_write( &bitsBuffer, 15,(dts)&0x7FFF); /*DTS[14..0]*/
bits_write( &bitsBuffer, 1, 1 );
return 0;
}
注: 它记录了帧的时间戳,DTS可以不填,如果填写要和PTS保持一致,且同一帧数据的PTS要也要一样(即SPS、PPS、IDR的PES要一致)