從RTP包中解析H265數據

源碼地址:https://github.com/zhouyinfei/rtsp-netty-server

 

首先上代碼:

//rtp拆包成nalu h265
public static byte[] rtpToNaluH265Pack(RawPacket rtpPacket){
	//h265碼流處理
//		if (rtpPacket.getPayloadType() == 96) {												//以下處理僅針對H265碼流
	ByteBuffer bb = null;														//存放RTP解析後的NALU的數據
	
	byte[] rtpPayload = rtpPacket.getPayload();
	byte fu_header0 = rtpPayload[0];							
	byte nalu_type = (byte) ((fu_header0>>1) & 0x3f);			//獲取NALU TYPE
	  
//			System.out.println("nalu_type=" + nalu_type);
	if (nalu_type == 49) {  										//分片封包模式
		byte fu_header2 = rtpPayload[2];
		byte start_flag = (byte) (fu_header2 & 0x80);				//是否起始片
		byte end_flag = (byte) (fu_header2 & 0x40);					//是否結束片
		nalu_type = (byte) (fu_header2&0x3F);						//nalu type
		byte nalu_header0 = (byte) (nalu_type<<1);					
		byte nalu_header1 = 0x01;										//固定值
		if (start_flag != 0) {											//第一個分片
			bb = ByteBuffer.allocate(rtpPayload.length + 3);
			bb.put(new byte[]{0x0, 0x0, 0x0, 0x1});
			bb.put(nalu_header0);
			bb.put(nalu_header1);
			byte[] dest = new byte[rtpPayload.length-3];
			System.arraycopy(rtpPayload, 3, dest, 0, rtpPayload.length-3);
			bb.put(dest);
		} else if (end_flag != 0) {										//最後一個分片
			bb = ByteBuffer.allocate(rtpPayload.length-3);
			byte[] dest = new byte[rtpPayload.length-3];
			System.arraycopy(rtpPayload, 3, dest, 0, rtpPayload.length-3);
			bb.put(dest);
		} else {															//中間分片
			bb = ByteBuffer.allocate(rtpPayload.length-3);
			byte[] dest = new byte[rtpPayload.length-3];
			System.arraycopy(rtpPayload, 3, dest, 0, rtpPayload.length-3);
			bb.put(dest);
		}
	} else if (nalu_type == 48) {  								//組合封包模式
		int srcOffset = 2;										//第一個字節是STAP-A頭,跳過
		int bufferLen = 0;
		//先計算需要的ByteBuffer長度,再將內容放進去
		while ((rtpPayload.length - srcOffset) > 2)				//循環解析RTP,將組合後的NALU取出來,再加上起始碼
		{	
			int size = 0;										//NALU的長度,2個字節
			size |= rtpPayload[srcOffset] << 8;						
			size |= rtpPayload[srcOffset + 1];

			srcOffset += 2;										//將NALU header和NALU payload一起放進去,然後進入下一個循環
			bufferLen += (4+size);
			srcOffset += size;
		}
		
		srcOffset = 2;
		bb = ByteBuffer.allocate(bufferLen);
		while ((rtpPayload.length - srcOffset) > 2)				//循環解析RTP,將組合後的NALU取出來,再加上起始碼
		{	
			int size = 0;										//NALU的長度,2個字節
			size |= rtpPayload[srcOffset] << 8;						
			size |= rtpPayload[srcOffset + 1];

			srcOffset += 2;										//將NALU header和NALU payload一起放進去,然後進入下一個循環
			byte[] dest = new byte[size];
			System.arraycopy(rtpPayload, srcOffset, dest, 0, size);
			
			bb.put(new byte[]{0x0, 0x0, 0x0, 0x1});				//NALU的起始碼
			bb.put(dest);
			
			srcOffset += size;
		}
		
	 } else if (nalu_type == 1 || nalu_type == 19 || nalu_type == 32 || nalu_type == 33 ||
			 nalu_type == 34 || nalu_type == 39) {											//單一NAL 單元模式
		  bb = ByteBuffer.allocate(rtpPayload.length + 4);					//將整個rtpPayload一起放進去
		  bb.put(new byte[]{0x0, 0x0, 0x0, 0x1});
		  bb.put(rtpPayload);
	 } else {
		 log.debug("rtpToNaluH265Pack-----Unsupport nalu type!");
	 }
	
	if (bb != null) {
		return bb.array();
	}
//		}
	return null;
}

首先是H265的格式,參考:

https://blog.csdn.net/weixin_42226021/article/details/88936803

https://blog.csdn.net/g0415shenw/article/details/81609261

 

這裏主要需要關注H265封包成RTP的部分:

(1)、一個NALU打包成一個RTP包,只需要在一個12字節的RTP包頭後添加去掉開始碼的NALU即可
(這種模式在一個NALU的大小小於MTU時使用)。
(2)、一個NALU打包成幾個RTP包(FUs模式),在12個字節的RTP頭後面有兩個字節的PayloadHdr和一個字節的FU
header。PayloadHdr的值等於NALU頭的type位改爲49(十進制)後的值,FU header第1位標記RTP包是否爲NALU的第一片,第2位標記RTP包是否爲NALU的最後一片。後6位是NALU頭的type位。

從上面說明大概可知,H265的封包模式如下:

1、單一單元模式

一個RTP包只包含一個NALU

2、分片封包

FU header還包括了是否是第一片或者最後一片的標識。

3、組合封包模式

 

這篇博客沒有說明組合封包模式,但是根據 實驗發現,組合封包模式的時候,type的值是48。

 

 

下面舉例子對第二、三種情況進行說明。

1、組合封包例子

例如如下的H265封裝後的RTP包

前2個字節是PayloadHdr,內容是:60 01,其格式如下:

所以要獲取1-6(從0開始數)位上的內容,結果是48,說明是組合封包模式。

接下來2個字節是NALU的長度00 17,標識了NALU的長度是23。

依次類推,第二個NALU的長度是00 22,也就是34,然後後面的34個字節是第二個NALU的內容。

 

2、分片封包例子

下面是分片封包的例子

前2個字節是62 01,根據格式獲取到type的值是49,所以可知是分片封包類型。

接下來的1個字節是FU header,值是93。

1001 0011

其中後6位是NALU type,可以知道NALU type是19,屬於IDR類型。而第一位是1,所以它是第一個分片。

看一下最後一個分片的格式:

第3字節是53,二進制格式爲:0101 0011, NALU type是19, 第二位是1,所以是最後一個分片。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章