H.265 RTP打包发送以及RTSP抓包分析
文章目录
1.原始码流H264/H265中NAL unit Header简介
原始码流都是由一个个的 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:表示原始字节序列的有效载荷。
NAL unit Header
下面对比一下原始码流的H264和H265的NAL unit header。其中H264的Nal Unit头是一个字节,H265变成两个字节,具体每个位表示如下:
H264 NAL Header
--------------------
|0|1|2|3|4|5|6|7|
--------------------
|F|N_R| Type |
F:forbidden_zero_bit | N_R:nal_reference_idc | type:nal_unit_type |
---|---|---|
1 bit | 2 bits | 5 bits |
一般为0,为 1 时表示此单元出现错误,解码器会丢弃该数据,按丢包处理。 | VCL可以表征参考帧属性,参考帧非0,非参考帧0,Non-VCL 表征解码时的可丢弃与否,如SPS PPS不可丢弃 为1,SEI可丢弃为0 | 当前NAL的类型 |
解析H264 type方式:
//定义header[4]为去除掉“00 00 00 01”之后的那个字节
type = header[4] & 0x1F
H.264常用的type类型
type | type value |
---|---|
sps | 7 |
pps | 8 |
sei(增强信息帧,可以没有) | 6 |
常用I帧 | 5 |
常用P帧 | 1 |
H265 NAL Header
---------------------------------------
|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
---------------------------------------
|F| Type | LayerId |Tid|
F:forbidden_zero_bit | Type:nal_unit_type | LayerId:nuh_layer_id | TID:nuh_temporal_id_plus1 |
---|---|---|---|
1 bit | 6 bits | 6 bits | 3 bits |
H.265要求是0,是1的话指示语法违规等. | vps是32, sps是33, pps是34, 前缀sei是39. IDR是19和20. | 编码层级信息 现在是0,将来可能扩展用. |
时间分级信息 |
解析H265 type方式:
一般情况下我们都会解析nal_unit_type的值,用以区分到底是什么类型的帧。
//定义header[4]为去除掉“00 00 00 01”之后的那个字节
type = (header[4] & 0x7E) >> 1;
//或者
type = (header[4] >> 1) & 0x3f;
H.264常用的type类型
type | type value |
---|---|
vps | 32 |
sps | 33 |
pps | 34 |
SEI | 39 |
IDR | 19或者20 |
常用P帧,后置图像帧 | 1 |
2.H.265 RTP 打包原理
如果NALU对应的Slice为一帧的开始,则用4字节表示,即0x00 00 00 01;否则用3字节表示,0x00 00 01。下面都以4个字节为一帧来描述。 RTP打包就是前四个字节0x00 00 00 01去掉,添加上PayloadHdr,然后发送出去。
根据标准文档分为四个方式打包:
单个NAL包:Single NAL unit packet
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PayloadHdr | DONL (conditional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| NAL unit payload data |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
在SDP中sprop-max-don-diff = 0时,DONL可以省略;当 0<sprop-max-don-diff<=32767时,DONL不能省略。
去掉原始码流中的“00 00 00 01”,在头部添加tcp(12 bits + 2bits(length))或者udp(12bits)的头,然后发送出去。
聚合包:Aggregation packet (AP)
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PayloadHdr (Type=48) | NALU 1 Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 HDR | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 1 Data |
| . . . |
| |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| . . . | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 HDR | |
+-+-+-+-+-+-+-+-+ NALU 2 Data |
| . . . |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
这种方式通常是用来合成vps sps pps sei包为一个聚合包AP,然后发送出去。
这里的PayloadHdr指的就是H265原始码流的NAL unit header只不过需要把type设置为48.
/*H.265 NAL unit header
---------------------------------------
|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
---------------------------------------
|F| Type | LayerId |Tid|
*/
//H.265 NAL unit header是由两个字节组成,以byte[] APData为数据包
private byte[] AggregationPacket(byte[] sps, byte[] pps, byte[] vps) {
byte[] APData = new byte[sps.length + pps.length + vps.length + 8];
APData[0] = 48 << 1;
APData[1] = 1;
// Write NALU 1 size into the array (NALU 1 is the vps).
APData[2] = (byte) (vps.length >> 8);
APData[3] = (byte) (vps.length & 0xFF);
// Write NALU 1 size into the array (NALU 2 is the sps).
APData[vps.length + 4] = (byte) (sps.length >> 8);
APData[vps.length + 5] = (byte) (sps.length & 0xFF);
// Write NALU 2 size into the array (NALU 3 is the pps).
APData[vps.length + sps.length + 6] = (byte) (pps.length >> 8);
APData[vps.length + sps.length + 7] = (byte) (pps.length & 0xFF);
// Write NALU 1 into the array, then write NALU 2 into the array,then write NALU 3 into the array.
System.arraycopy(vps, 0, stapA, 4, vps.length);
System.arraycopy(sps, 0, stapA, 6 + vps.length, sps.length);
System.arraycopy(pps, 0, stapA, 8 + vps.length + sps.length, pps.length);
return APData;
}
通常情况下,解码器一般都是支持这种解码的,但是我在连接海康NVR时出现解不出来这个聚合包,最终只能以单个包的情况发送这些配置帧。
分片单元:Fragmentation unit (FU)
1.The structure of an FU
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PayloadHdr (Type=49) | FU header | DONL (cond) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
| DONL (cond) | |
|-+-+-+-+-+-+-+-+ |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2.H.265 PayloadHdr
---------------------------------------
|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
---------------------------------------
|F| Type | LayerId |Tid|
3.The structure of FU header
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E| FuType |
+---------------+
* S = variable
* E = variable
* FuType = NAL unit type
* 第一個包: S=1,E=0;
* 中間包: S=0,E=0
* 最後一包: S=0,E=1
单个NAL包是指size <1500-RTP_HEADER_LENGTH - bits(一般情况为1500,有些可能更小是1300)时,采用单个NAL包发送。当大于这个值时候通常采用FU的发送方式,RTP切片,分为首包,中间包,尾包数据。
将PayloadHdr中type设置为49,设置起始位置S和结束位置E
//Set PayloadHdr (16bit type=49)
header[0] = 49 << 1;
header[1] = 1;
// Set FU header
// +---------------+
// |0|1|2|3|4|5|6|7|
// +-+-+-+-+-+-+-+-+
// |S|E| FuType |
// +---------------+
header[2] = (byte) type; // FU header type
header[2] += 0x80; // Start bit
//........
//
//rtpHeaderLenth = 12; //tcp or udp 的头
//.......
while(sum<naluDatalength){
//第一包数据的数据头
buffer = socket.requestBuffer();
buffer[rtpHeaderLenth] = header[0];
buffer[rtpHeaderLenth + 1] = header[1];
buffer[rtpHeaderLenth + 2] = header[2];
//length = 发送一包的数据长度
//最后一包的数据头
sum += length;
if (sum >= naluLength) {
buffer[rtphl + 2] += 0x40;
socket.markNextPacket();
}
senddata()
//中间包的数据头
buffer[rtphl + 2] += 0x40;
}
PACI carrying RTP packet(不常用,没深入了解)
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PayloadHdr (Type=50) |A| cType | PHSsize |F0..2|Y|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Header Extension Structure (PHES) |
|=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=|
| |
| PACI payload: NAL unit |
| . . . |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
实际应用
实际中其实就用到三种格式:
1.一个nal单元打包到一个rtp包中。
2.nal单元比较大,分片打包在多个rtp中.
3.将VPS PPS SPS SEI帧合成一个rtp包中。
有些情况第三种方式可能解不出来,通常前两种播放器都是必须支持的。
3.播放器VLC播放rtsp请求时,Wireshark抓包分析
分析配置帧,single NAL unit packet
接收到的第一包数据按顺序含有:VPS,SPS,PPS,SEI这些帧数据,属于直接发送的。
VPS帧是以40开头 , (0x40 >> 1) & 0x3f = 32
SPS帧是以42开头, (42 >> 1) & 0x3f = 33
PPS帧是以44开头, (44 >> 1) & 0x3f = 34
SEI帧是以4E开头 (4E >> 1) & 0x3f = 39
切片分包发送,Fragmentation unit (FU)
分包的首包
分包的中间包
分包的尾包
这里再来回顾一下FU header这一个单元的定义:
* the FU header
*
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |S|E| FuType |
* +---------------+
*
* S = variable
* E = variable
* FuType = NAL unit type
* 第一個包: S=1,E=0;
* 中間包: S=0,E=0
* 最後一包: S=0,E=1
从抓取的分包图片可以看到前两个字节为 62 01,其中第三个字节分别为:
93,==》1001 0011 S:1 E:0 FyType10011
13,==》0001 0011 S:0 E:0 FyType10011
53,==》0101 0011 S:0 E:1 FyType10011
FyType为10011转换十进制为19,所以实际的Nal type类型19,在H265编码中为NAL_IDR_W_RADL 帧.
前面我们分析原理上面说过,H265切片的PayloadHdr是2 bits,FU值为1 bit,所以62 和 01的值如下得到,其中PayloadHdr为header[0] + header[1],FU的值为header[2]一般H265帧的type:19,20,1等I帧或者P帧
header[0] = 49 << 1; //62
header[1] = 1; //01
header[2] = (byte) type; // H265帧的type:19,20,1等I帧或者P帧
//下面分析修改6和7位分别区分首包,中间包,和尾包
header[2] += 0x80; //Start bit 10+type
header[2] = (byte) (header[2] & 0x7F); //中间包 00+type
header[2] += 0x40; //尾包 01+type
参考文档
上一篇: 将NALU封装成RTP包