原始码流都是由一个个的 NALU(Network Abstract Layer)网络抽象层 连续组成,其中NALU=[StartCode] + [NALU Header] + [NALU Payload]组成,其中
StartCode:表示一个NALU的开始,一般情况下是以4字节“00 00 00 01”或者3字节“00 00 01”,一般4字节居多。
NALU Header:表示一组视频编码的头部信息,具体下面分析。
Payload:表示原始字节序列的有效载荷。
下面对比一下原始码流的H264和H265的NAL unit header。其中H264的Nal Unit头是一个字节,具体每个位表示如下:
--------------------
|0|1|2|3|4|5|6|7|
--------------------
|F|NRI| Type | Payload
F:forbidden_zero_bit | NRI:nal_reference_idc | type:nal_unit_type |
---|---|---|
1 bit | 2 bits | 5 bits |
H.264 规范中规定了这一位必须为 0 | VCL可以表征参考帧属性,参考帧非0,非参考帧0,Non-VCL 表征解码时的可丢弃与否,如SPS PPS不可丢弃 为1,SEI可丢弃为0 | 当前NAL的类型 |
VCL: Video Coding Layer
//定义header[4]为去除掉“00 00 00 01”之后的那个字节
int type = header[4] & 0x1F
type | type value |
---|---|
sps | 7 |
pps | 8 |
sei(增强信息帧,可以没有) | 6 |
常用I帧 | 5 |
常用P帧 | 1 |
这里说明一下,有些编码是没有SEI帧的,SEI这个增强信息帧中一般填入一些,人脸识别的坐标等信息,也可以自定义一些信息。一般的流都是按照sps,pps,I ,p,I,P……的顺序发送的,其中流媒体发送的时候为了能够在任何点都能够解析编码,通常会在I帧前添加sps和pps两帧的单独包或者聚合包,之后再发送I帧。
RTP包发送的时候,就是将原始码流中的“00 00 00 01”去掉,按照封装格式封装,然后为每一个封装添加RTP header,再发送出去。下面我们介绍下其格式:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| contributing source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
完整的的header如上所示,来个表格介绍下都表示的含义:
位类型 | 位数 | 解释 |
---|---|---|
V | 2 bit | Version版本号,通常为2,二进制位“10” |
P | 1 bit | Padding填充标志,如果P=1,一般会在报文后边添加不是有效载荷的多个八位数组 |
X | 1 bit | 扩展标志 |
CC | 4 bits | CSRC计数器,指示CSRC 标识符的个数 |
M | 1 bit | Marker bit标志位,一般为1时,表示一帧的最后一个包 |
PT | 7 bits | Payload type效荷载类型,一般用来区分视频和音频的,例如视频为96,音频为97 |
SN | 16 bits | Sequence number序列号 |
timestamp | 32 bits | 时间戳,必须使用90 kHz 时钟频率。反映了该RTP报文的第一个 八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。 |
SSRC | 32 bits | 同步信源,是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。 |
多个CSRC | 每个32 bits | 特约信源,此处有上面的CC决定数目,CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。 |
其中CC 很多情况下为0,即没有CSRC信息,所以通常情况下RTP header是由12个字节组成的。
如果使用UDP传输的话,直接使用这个RTP header;但是在tcp传输的时候会在header前在添加四个字节:
[magic number]+[channel number]+[data length]
说明一下
magic number | channel number | data length |
---|---|---|
1个字节 | 1个字节 | 2个字节 |
就是一个“$”符号 | 00–视频RTP,01–视频RTCP 02–音频RTP,03–音频RTCP |
RTP header+NAL unit的总长度 |
我们来抓取一帧数据来说明一下,如下图:
其中Payload是从67开始,就是数据的封装,具体怎么封装这个载荷的呢,下面就来介绍一下。
这里我们先说下Type的类型,RTP中增加了H264中23中以外的格式,先来看下这个header头部
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
这个Type类型用于区分封装的类型,H.264的帧类型1-23之间,之后RTP又有新增,如下:
Type Packet Type name
-------------------------------------------------
0 undefined
1-23 NAL unit Single NAL unit packet per H.264
24 STAP-A Single-time aggregation packet
25 STAP-B Single-time aggregation packet
26 MTAP16 Multi-time aggregation packet
27 MTAP24 Multi-time aggregation packet
28 FU-A Fragmentation unit
29 FU-B Fragmentation unit
30-31 undefined
这里有些类型分为A型和B型,区别在于是否含有(DON, DONB, DOND)信息,含有的为B型,不含有的为A型。我们一般情况下用到的都是A型,不含有(DON, DONB, DOND)信息的,是我们主要分析的对象。
所以从封装标准文档中看,分为三种格式分别为:Single NAL unit,aggregation packet,Fragmentation unit。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a Single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
这种很简单,一个 RTP 包仅由一个完整的 NALU 组成. 这种情况下 RTP NAL 头类型字段和原始的 H.264的
NALU 头类型字段是一样的,即在范围1到23之间。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Data |
: |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 Data |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
多时间聚合包分为两种:MTAP16和MTAP24,区别在于每个NALU TS offset的长度一个是16位,一个是24位。
MTAP16格式
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|MTAP16 NAL HDR | decoding order number base | NALU 1 Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Size | NALU 1 DOND | NALU 1 TS offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 HDR | NALU 1 DATA |
+-+-+-+-+-+-+-+-+ +
: |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 SIZE | NALU 2 DOND |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 TS offset | NALU 2 HDR | NALU 2 DATA |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
MTAP24格式
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|MTAP24 NAL HDR | decoding order number base | NALU 1 Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Size | NALU 1 DOND | NALU 1 TS offs |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|NALU 1 TS offs | NALU 1 HDR | NALU 1 DATA |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
: |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 SIZE | NALU 2 DOND |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 TS offset | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 DATA |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
聚合包中,常用的打包类型为单时间聚合包STAP-A,通常用于将sps,pps,sei帧聚合为一个RTP包发送,而多时间聚合包不常用,一般自己制作rtspserver时也不会采用这种方式。
其中STAP-A NAL HDR一般是将Type设置为24,再拼装sps,pps,sei帧,代码如下:
private byte[] saveStapA(byte[] sps, byte[] pps) {
byte[] stapA = new byte[sps.length + pps.length + 5];
// STAP-A NAL header is 24
stapA[0] = 24;
// Write NALU 1 size into the array (NALU 1 is the SPS).
stapA[1] = (byte) (sps.length >> 8);
stapA[2] = (byte) (sps.length & 0xFF);
// Write NALU 2 size into the array (NALU 2 is the PPS).
stapA[sps.length + 3] = (byte) (pps.length >> 8);
stapA[sps.length + 4] = (byte) (pps.length & 0xFF);
// Write NALU 1 into the array, then write NALU 2 into the array.
System.arraycopy(sps, 0, stapA, 3, sps.length);
System.arraycopy(pps, 0, stapA, 5 + sps.length, pps.length);
return stapA;
}
FU-A 的格式如下
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
下面是FU indicator,FU Indicator其实和NAL头部几乎一样,就是nal类型变成了分片包的类型28,原先的nal类型保存到了FU Header中的Type。
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
//这里的type为28
FU header
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
//这里的type为原始码流h.264de NAL类型
* 第一個包: S=1,E=0;
* 中間包: S=0,E=0
* 最後一包: S=0,E=1
类型 | S start,为1表示分片包的第一包 |
E end,为1时表示分片包的最后一包 |
R 保留,一直为0 |
---|---|---|---|
起始包 | 1 | 0 | 0 |
中间包 | 0 | 0 | 0 |
最后包 | 0 | 1 | 0 |
这些内容填充好之后,就和payload数据合成一个RTP包,然后发送出去。
sps,pps,sei帧这些都是采用single NAL unit发送,所以解析type=Payload[0]&0x1F
67&0x1F = 7,此为sps帧
这个第一个字节为0x7C(二进制 0111 1100),定义如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
//这里的type为28
解析得到:
type= 0x7C&0x1F = 0x1C = 28,即分片type类型
NRI = 0x7C&0x60 = 0x60,二进制(0110 0000)
第二个字节为0x85(二进制 1000 0101),定义如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
//这里的type为原始码流h.264de NAL类型
* 第一個包: S=1,E=0;
* 中間包: S=0,E=0
* 最後一包: S=0,E=1
NALType=0x85&0x1F = 0x05 = 5 ,即为I帧
根据二进制位数可知,S= 1, E=0 , R=0,所以为起开始包
中间包的FU indicator,应该与首包相同,都为0x7C,
第二字节为0x05(二进制 0000 0101),即type=0x05=5
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
//这里的type为原始码流h.264de NAL类型
第一個包: S=1,E=0;
中間包: S=0,E=0
最後一包: S=0,E=1
而且S=0,E=0,R=0,所以为中间包
尾包的FU indicator,应该与首包相同,都为0x7C,
第二字节为 0x45(二进制位 0100 0101)
type = 0x45 & 0x1F = 0x05 = 5
S=0,E=1,R=0,所以为最后一包数据。
H264 RTP封包标准.