欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

H.265 RTP打包发送以及RTSP抓包分析

程序员文章站 2022-07-14 18:45:49
...

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

H.265 RTP打包发送以及RTSP抓包分析

SPS帧是以42开头, (42 >> 1) & 0x3f = 33

H.265 RTP打包发送以及RTSP抓包分析

PPS帧是以44开头, (44 >> 1) & 0x3f = 34

H.265 RTP打包发送以及RTSP抓包分析

SEI帧是以4E开头 (4E >> 1) & 0x3f = 39

H.265 RTP打包发送以及RTSP抓包分析

切片分包发送,Fragmentation unit (FU)

分包的首包
H.265 RTP打包发送以及RTSP抓包分析

分包的中间包
H.265 RTP打包发送以及RTSP抓包分析

分包的尾包
H.265 RTP打包发送以及RTSP抓包分析

这里再来回顾一下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

参考文档

: h.265的rtp打包解包标准.

相关标签: 多媒体Multimedia