从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,所以是最后一个分片。
上一篇: H264帧 通过 RTP 打包
下一篇: 【鲲鹏来了】鲲鹏迁移过程案例分享