源碼地址: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,所以是最後一個分片。