大家好,又见面了,我是你们的朋友全栈君。
一 rtp概述
RTP全名是Real-time Transport Protocol(实时传输协议)。它是IETF提出的一个标准,对应的RFC文档为RFC3550。RFC3550不仅定义了RTP,而且定义了配套的相关协议RTCP(Real-time Transport Control Protocol,即实时传输控制协议)。RTP用来为IP网上的语音、图像、传真等多种需要实时传输的多媒体数据提供端到端的实时传输服务。RTP为Internet上端到端的实时传输提供时间信息和流同步,但并不保证服务质量,服务质量由RTCP来提供。
RTP的头部格式
前12字节是固定的,CSRC可以有多个或者0个。
1)V:RTP协议的版本号,占2位,当前协议版本号为2
2)P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分
3)X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头
4)CC:CSRC计数器,占4位,指示CSRC标识符个数
5)M:标志,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
6)PT(payload type):有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是用来区分音频流和视频流,这样便于客户端进行解析。
7)序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包。当出现网络抖动的情况可以用来对数据进行重新排序。序列号的初始值是随机的,同时音频包和视频包的sequence是分别计数的。
8)时戳(Timestamp):占32位,必须使用90kHZ时钟频率(程序中的90000)。时戳反映了该RTP报文的第一个八位组的采样时刻。接受者使用时戳来计算延迟和延迟抖动,并进行同步控制。可以根据RTP包的时间戳来获得数据包的时序。
9)同步信源(SSRC)标识符:占32位,用于标识同步信源。同步信源是指产生媒体流的信源,他通过RTP报头中的一个32为数字SSRC标识符来标识,而不依赖网络地址,接收者将根据SSRC标识符来区分不同的信源,进行RTP报文的分组。
10)提供信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个CSRC。每个CSRC标识了包含在RTP报文有效载荷中的所有提供信源。
提供信源用来标识对一个RTP混合器产生的新包有贡献的所有RTP包的源。是指当混合器接收到一个或多个同步信源的RTP报文后,经过混合处理产生一个新的组合RTP报文,并把混合器作为组合RTP报文的SSRC,将原来所有的SSRC都作为CSRC传送给接收者,是接受者知道组成组合报文的各个SSRC。
二 rtp码流
rtp载荷h264媒体流:rtp协议头和h264码流
rtp头后是rtp载荷,rtp载荷第一个字节格式和NALU头一样。
SODB(String Of Data Bits):最原始的编码数据, 长度不一定是8的倍数,此时需要对齐.
RBSP:在SODB后添加结尾比特(RBSP trailing bits 一个bit“1”)若干比特“0”,以便字节对齐。
EBSP:在 RBSP 的基础上增加了防止伪起始码字节(0X03)
NALU是对RBSP的封装,而RTP是对NALU的封装。
F和NRI也跟NALU头一样,只有Type有些不一样:拓展24 – 31
0 没有定义; 1-23 NAL单元 单个 NAL 单元包.
24 STAP-A 单一时间的组合包; 25 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包; 27 MTAP24 多个时间的组合包
28 FU-A 分片的单元; 29 FU-B 分片的单元; 30-31 没有定义
1) 单个NAL单元包:荷载中只包含一个NAL单元。NAL头类型域等于原始 NAL单元(NALU)类型,即Type在范围1到23之间。
对于NALU(NAL单元)的长度小于MTU大小的包,一般采用单一NAL单元模式
定义在此的NAL单元包必须只包含一个。RTP序号必须符合NAL单元的解码顺序。这种情况下,NAL单元的第一字节和RTP荷载头第一个字节重合。如上图所示。
对于一个原始H264的NALU单元常由[start code] [NALU Header] [NALU Payload]三部分组成,其中start code用于标识这是一个NALU单元的开始,必须是“00 00 00 01”或“00 00 01”,NALU头仅一个字节,其后都是NALU单元载荷。
打包时去除“00 00 01”或“00 00 00 01”的开始码,把其他数据封装成RTP包即可。
如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F … ]
这是一个序列参数集 NAL 单元。 [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 载荷.
封装成 RTP 包将如下:
[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F … ]
即只要去掉 4 个字节的开始码就可以了.
2)组合包:本类型用于聚合多个NAL单元到单个RTP荷载中。本包有四种版本,单时间聚合包类型A(STAP-A)单时间聚合包类型B(STAP-B),多时间聚合包类型(MTAP)16位位移(MTAP16),多时间聚合包类型(MTAP)24位位移(MTAP24)。赋予STAP-A,STAP-B,MTAP16,MTAP24的NAL单元类型号(Type)分别是24 25 26 27
当NALU的长度特别小时,可以把几个NALU单元封在一个RTP包中。这种模式下,有多个NALU头和NALU载荷。
3)分片包:用于分片单个NAL单元到多个RTP包。现存两个版本FU-A,FU-B,用NAL单元类型(Type)28 29标识
常用的打包时的分包规则:如果小于MTU采用单个NAL单元包,如果大于MTU就采用FUs分片方式
当NALU的长度超过MTU时,就必须对NALU单元进行分片封包,也称为Fragmentation Units (FUs)NAL单元的一个分片由整数个连续NAL单元字节组成。每个NAL单元字节必须正好是该NAL单元一个分片的一部分。相同NAL单元的分片必须使用递增的RTP序号连续顺序发送(第一和最后分片之间没有其他的RTP包)。同时,NAL单元必须按照RTP顺序号的顺序装配。STAPs,MTAPs不可以被分片。 FUs不可以嵌套。 即, 一个FU 不可以包含另一个FU。
S(开始位): 1 bit, 当设置成1,指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
E(结束位): 1 bit, 当设置成1,指示分片NAL单元的结束,即,荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的 FU荷载不是分片NAL单元的最后分片,结束位设置为0。
R(保留位): 1 bit, 保留位必须设置为0,接收者必须忽略该位。
Type(类型):5 bit, 是NAL Header中的Type。
打包时,原始的NAL头的前三位为FU indicator的前三位,原始的NAL头的后五位(Type)为FU header的后五位(Type)。
取一段rtp码流分析如下:
80 60 01 0f 00 0e 10 00 00 00 00 00 7c 85 88 82 €`……….|???
00 0a 7f ca 94 05 3b7f 3e 7f fe 14 2b 27 26 f8 …??.;.>.?.+’&?
89 88 dd 85 62 e1 6dfc 33 01 38 1a 10 35 f2 14 ????b?m?3.8..5?.
84 6e 21 24 8f 72 62f0 51 7e 10 5f 0d 42 71 12 ?n!$?rb?Q~._.Bq.
17 65 62 a1 f1 44 dc df 4b 4a 38 aa 96 b7 dd 24 .eb??D??KJ8????$
前12字节是RTP Header,7c是FU indicator,85是FU Header
FU indicator(0x7C)和FU Header(0x85)二进制 0111 1100 1000 0101
按顺序解析如下:
0 是F
11 是NRI
11100 是FU Type,这里是28,即FU-A
1 是S,Start,说明是分片的第一包
0 是E,End,如果是分片的最后一包,设置为1,这里不是
0 是R,Remain,保留位,总是0
00101 是NAL Type,这里是5,说明是关键帧
打包时,FU indicator的F、NRI是NAL Header中的F、NRI,Type是28(FU-A);FU Header的S、E、R分别按照分片起始位置设置,Type是NAL Header中的Type。
解包时,取FU indicator的前三位和FU Header的后五位,即0110 0101(0x65)为NAL类型。
4)SPS/PPS
一个frame分割成多个slice,一个slice分割成多个宏块。一个Slice编码之后被打包进一个NAL单元。不过NAL单元除了容纳Slice编码的码流外,还可以容纳其他数据,比如序列参数集SPS。
NALU头由一个字节组成:forbidden_zero_bit(1bit) + nal_ref_idc(2bit) + nal_unit_type(5bit)
forbidden_zero_bit: 禁止位,初始为0,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元。
nal_ref_idc:nal重要性指示,标志该NAL单元的重要性,值越大,越重要,解码器在解码处理不过来的时候,可以丢掉重要性为0的NALU。
nal_unit_type:NALU常用类型取值如下表所示:
5 IDR图像中的片
6 补充增强信息单元(SEI)
7 SPS
8 PPS
9 分割符
10 序列结束符
11 流结束符
12 填充数据
H.264码流第一个 NALU是 SPS(序列参数集Sequence Parameter Set)
H.264码流第二个 NALU是 PPS(图像参数集Picture Parameter Set)
SPS和PPS都是特殊的NALU。一个MP4文件只有一个SPS,但是有很多PPS,SPS必须在所有NALU的最开头。
H.264码流第三个 NALU 是 IDR(即时解码器刷新),第一个I帧称为IDR帧,IDR帧的作用是立刻刷新,使错误不致传播,从IDR帧开始,重新算一个新的序列开始编码。解码器收到IDR帧时,将所有的参考帧队列丢弃,把所有的PPS和SPS参数进行更新。
H.264编码时,在每个NAL前添加起始码 0x000001,解码器在码流中检测到起始码,当前NAL结束。为了防止NAL内部出现0x000001的数据,h.264又提出’防止竞争 emulation prevention”机制,在编码完一个NAL时,如果检测出有连续两个0x00字节,就在后面插入一个0x03。当解码器在NAL内部检测到0x000003的数据,就把0x03抛弃,恢复原始数据。
0x000000 ———–> 0x00000300
0x000001 ———–> 0x00000301
0x000002 ———–> 0x00000302
0x000003 ———–> 0x00000303
总的来说,H264的码流的打包方式有两种,一种为annex-b byte stream format 的格式,这个是绝大部分编码器的默认输出格式,就是每个帧的开头的3~4个字节是H264的start_code,0x00000001或者0x000001;另一种是原始的NAL打包格式,就是开始的若干字节(1,2,4字节)是NAL的长度,而不是start_code,此时必须借助某个全局的数据来获得编 码器的profile,level,PPS,SPS等信息才可以解码。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/153953.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...