1、基本知识
https://blog.csdn.net/zpoison/article/details/86528327
元数据进行网络传输,需要先进行逐层封装和逐层解析的过程,如下图:
协议与上下层关系简图
1.1 以太网帧 MAC帧
MAC帧是数据帧的一种,数据链路层的协议数据单元,包括三部分,帧头和帧尾包含一些必要的控制信息,比如同步信息、地址信息、差错控制信息等;数据部分则包含网络层传下来的数据,比如ip数据包。
MAC层要求定界字符之后的内容要在64字节到1518个字节之间,其中包括14字节的帧头(目标MAC和源MAC,数据长度),4字节的帧尾(FCS,校验值),46-1500数据部分(这里是IP报文数据,IP报文数据通常最大为MTU值1500,最小为仅IP包头40字节)。
通常我们用whireshark抓包TCP时,只会看到mac帧头和数据部分,也就是一个包的数据长度为14+(20+20 ~ 1500),即54 ~ 1514长度。(UDP为14+(20+8 ~ 1500)即42~1514长度)
1.2 IP包
ip地址长度为32bit,普通的IP包头长为20个字节(若有选项数据再增加m大小选项数据)。IP包的数据部分,通常是TCP数据包/UDP数据包。
IP包数据长度为20(+m) + N,由于MTU=1500,所以IP数据长度至多为1480字节。
1.3 UDP包
用户数据报协议UDP(User Datagram Protocol),是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加UDP首部)。
UDP首部占用8个字节。当IP数据包首部为20字节,所有UDP数据包为下图。
此时,UDP的数据长度至多为(1500-20)-8 = 1480 - 8 = 1472字节。
抓包示意如下,显示的Length是网络包长度,1500+14, Len是UDP数据长度。
1.4 TCP包
TCP提供一种面向连接的、可靠的字节流服务。 所以TCP要比UDP可靠的多,UDP是把数据直接发出去,而不管对方是不是在收信。
TCP首部占用20个字节。当IP数据包首部为20字节,所有TCP数据包为下图。
此时,TCP的数据长度至多为(1500-20)-20 = 1480 - 20 = 1460字节。
1.5 RTP/RTCP包
1.5.1 RTP包头
- V:RTP协议的版本号,占2位,当前协议版本号为2
- P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
- X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头
- CC:CSRC计数器,占4位,指示CSRC 标识符的个数
- M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
- PT: 有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是用来区分音频流和视频流的,这样便于客户端进行解析。
- 序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包。同时出现网络抖动的情况可以用来对数据进行重新排序,序列号的初始值是随机的,同时音频包和视频包的sequence是分别记数的。
- 时戳(Timestamp):占32位,必须使用90 kHz 时钟频率。时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。
- 同步信源(SSRC)标识符:占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。
- 特约信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。
注:基本的RTP说明并不定义任何头扩展本身,如果遇到X=1,需要特殊处理,扩展数据在CSRC(若有)后、负载前,格式如下:
以上,rtp包的包头当CC位为0时,表示无CSRC,那么RTP包头长12个字节。因此 :
1)当rtp走tcp时,rtp载荷长度最多为 1460 – 12 = 1448个字节。
2)当rtp走udp时,rtp载荷长度最多为 1472 – 12 = 1460个字节。
1.5.2 RTP封包
载荷第一个字节(或NALU单元) 格式定义如下,包含禁止位F、重要级别NRI、类型Type。
常用的NALU Header对应的type数值:
由于i帧比较大,已经超出mtu最大1500,所以需要拆包分片传输,这里说的拆包发送不是指发送超过1500的数据包时tcp的分段传输或者upd的ip分片传输,而是指rtp协议本身对264的拆包。(rtmp打包就比较简单,由于是基于tcp的协议,大包直接交给tcp去做分段传输,rtmp通过设置合适的trunk size去发送一帧帧数据。)
如果小于MTU采用单个NAL单元包,如果大于MTU就采用FUs分片方式。常用的打包方式就是单个NAL包和FU-A方式,所以我们只解析这两种。
1.5.2.1 单个NALU单元包
P帧或者B帧比较小的包,直接将NALU打包成RTP包进行传输。
RTP header(12bytes) + NALU header (1byte) + NALU payload
1.5.2.2 分片单元FU-A
相同NAL单元的分片必须使用递增的RTP序号连续顺序发送(第一和最后分片之间没有其他的RTP包)。相似,NAL单元必须按照RTP顺序号的顺序装配。
RTP header(12bytes) + FU Indicator(1byte) + FU header(1 byte) + FU payload
FU-A由1字节的分片单元指示(如下图左),1字节的分片单元头(如下图右),
其中分片单元头各字结位如下:
- S: 1 bit 当设置成1,开始位指示分片NAL单元的开始。
当跟随的FU荷载不是分片NALU单元荷载的开始,开始位设为0。 - E: 1 bit 当设置成1, 结束位指示分片NAL单元的结束,即荷载的最后字节也是分片NALU单元的最后一个字节。
当跟随的FU荷载不是分片NAL单元的最后分片,结束位设置为0。 - R: 1 bit 保留位必须设置为0,接收者必须忽略该位
分片开始 SE = 10, 分片中间 SE = 00, 分片结束 SE = 01
这里分片传输方式是无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五个中。
反过来,NALU Header 由FU indicator前三位和FU header后五位组成,即:
NALU header = (FU indicator & 0xe0) | (FU header & 0x1F) 。
1.5.2.3 抓包演示
—>RTP包中接收的264包是不含有0x00,0x00,0x00,0x01头的,这部分是rtp接收以后,另外再加上去的,解码的时候再做判断的。(whireshark不能解析rtp时,右边解码为RTP)。
-
SPS、PPS
RTP数据部分第一个字节0x18,后五位11000, 24, STAP-A单一时间聚合包。
第一个包长度0x0002,数据0x0910, nalu header 0x09,不重要,分隔符
第二个包长度0x001b,nalu header 0x27,F=0, NRI=2, TYPE= SPS
第二个包长度0x0004,nalu header 0x28,F=0, NRI=2, TYPE= PPS -
I帧分片开始
RTP数据部分第一个字节0x3C,后五位11100, 28, FU-A分片
第二个字节分片头0x85, S=1, E=0, 分片开始, 后五位是00101,5, I帧
-
I帧分片
分片指示单元 0x3c 后五位11100, 28, FU-A分片
分片单元头 0x05 S=0, E=0, 分片,后五位是00101,5, I帧
同一个I帧的分片,时间戳相同。
-
I帧结束
这里比较特殊,没有使用分片单元描述结束。
使用的是NALU单元标记结束一个视频帧的结束。