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

从RTP包中解析H264数据

程序员文章站 2022-07-14 18:48:11
...

 

for (;;) {
	RawPacketWithRoom rawPacketWithRoom = arrayBlockingQueue.take();
	RawPacket rtpPacket = rawPacketWithRoom.getRtpPacket();
	
	if (rtpPacket.getPayloadType() == 107) {												//以下处理仅针对H264码流
		ByteBuffer bb = null;														//存放RTP解析后的NALU的数据
		
		byte[] rtpPayload = rtpPacket.getPayload();
		byte fu_indicator = rtpPayload[0];
		byte fu_header = rtpPayload[1];
		byte nalu_type = (byte) (fu_indicator & 0x1f);		
		  
		classLogger.error("=======nalu_type========" + nalu_type);
		if (nalu_type == 0x1C) {  //FU-A										//分片封包模式
			  byte start_flag = (byte) (fu_header & 0x80);
			  byte end_flag = (byte) (fu_header & 0x40);
			  byte nalu_header = (byte) ((fu_indicator & 0xe0) | (fu_header & 0x1f));	//根据fu_indicator和fu_header来重构出nalu_header
			  if (start_flag != 0) {											//第一个分片
				  bb = ByteBuffer.allocate(rtpPayload.length + 3);
				  bb.put(new byte[]{0x0, 0x0, 0x0, 0x1});
				  bb.put(nalu_header);
				  byte[] dest = new byte[rtpPayload.length-2];
				  System.arraycopy(rtpPayload, 2, dest, 0, rtpPayload.length-2);
				  bb.put(dest);
			  } else if (end_flag != 0) {										//最后一个分片
				  bb = ByteBuffer.allocate(rtpPayload.length-2);
				  byte[] dest = new byte[rtpPayload.length-2];
				  System.arraycopy(rtpPayload, 2, dest, 0, rtpPayload.length-2);
				  bb.put(dest);
			  } else {															//中间分片
				  bb = ByteBuffer.allocate(rtpPayload.length-2);
				  byte[] dest = new byte[rtpPayload.length-2];
				  System.arraycopy(rtpPayload, 2, dest, 0, rtpPayload.length-2);
				  bb.put(dest);
			  }
		 } else if (nalu_type == 0x18) {  //STAP-A								//组合封包模式
			bb = ByteBuffer.allocate(rtpPayload.length);
			int srcOffset = 1;										//第一个字节是STAP-A头,跳过
			while ((rtpPayload.length - srcOffset) > 2)				//循环解析RTP,将组合后的NALU取出来,再加上起始码
			{	
				bb.put(new byte[]{0x0, 0x0, 0x0, 0x1});				//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(dest);
				srcOffset += size;
			}
			
		 } else if (nalu_type == 0x1) {											//单一NAL 单元模式
			  bb = ByteBuffer.allocate(rtpPayload.length + 4);					//将整个rtpPayload一起放进去
			  bb.put(new byte[]{0x0, 0x0, 0x0, 0x1});
			  bb.put(rtpPayload);
		 } else {
			 classLogger.debug("Unsupport nalu type!");
		 }
	
		File file = new File(rtpPacket.getSSRCAsLong() + ".264");			//写入mp4文件
		fileOutputStream = new FileOutputStream(file, true);
		fileOutputStream.write(bb.array());
	}
	
	File file = new File(rawPacketWithRoom.getRoomName() + "." + rtpPacket.getSSRCAsLong() + ".264");			//写入mp4文件
	fileOutputStream = new FileOutputStream(file, true);
	fileOutputStream.write(rtpPacket.getPayload());
	
}

其中,RawPacket来自于依赖

<dependency>
      <groupId>org.jitsi</groupId>
      <artifactId>libjitsi</artifactId>
      <version>1.0-20190405.175243-382</version>
</dependency>

首先,看一下RTP payload的类型有哪些:

从RTP包中解析H264数据

可以看出,现有的类型中不存在H264格式,因为它属于动态类型的,需要根据SDP协议来确定,这里假定类型是107。

 

此外,我们还需要知道H264的几种封包格式,主要包括3种:分片封包模式、组合封包模式、单一NAL 单元模式。

 

  • 分片封包模式:

而当NALU 的长度超过MTU 时,就必须对NALU 单元进行分片封包.也称为Fragmentation Units (FUs)。

其格式如下:

从RTP包中解析H264数据

而其中,FUindicator和FU headerhas的格式如下:

从RTP包中解析H264数据

可以看出nalu_header需要根据fu_indicator和fu_header来重构出来的,即代码:

byte nalu_header = (byte) ((fu_indicator & 0xe0) | (fu_header & 0x1f));

而从上图中可以看到,fu_header中包含了起始位和结束位的标志,所以再分成3中情况:

       a、起始分片:首先要添加NALU的的起始码00 00 00 01,然后添加NALU头部nalu_header,然后是payload内容。

       b、结束分片、中间分片:处理方式都一样,就是将payload放入buffer中即可。

即如下代码:

byte nalu_header = (byte) ((fu_indicator & 0xe0) | (fu_header & 0x1f));	//根据fu_indicator和fu_header来重构出nalu_header
if (start_flag != 0) {											//第一个分片
  bb = ByteBuffer.allocate(rtpPayload.length + 3);
  bb.put(new byte[]{0x0, 0x0, 0x0, 0x1});
  bb.put(nalu_header);
  byte[] dest = new byte[rtpPayload.length-2];
  System.arraycopy(rtpPayload, 2, dest, 0, rtpPayload.length-2);
  bb.put(dest);
} else if (end_flag != 0) {										//最后一个分片
  bb = ByteBuffer.allocate(rtpPayload.length-2);
  byte[] dest = new byte[rtpPayload.length-2];
  System.arraycopy(rtpPayload, 2, dest, 0, rtpPayload.length-2);
  bb.put(dest);
} else {															//中间分片
  bb = ByteBuffer.allocate(rtpPayload.length-2);
  byte[] dest = new byte[rtpPayload.length-2];
  System.arraycopy(rtpPayload, 2, dest, 0, rtpPayload.length-2);
  bb.put(dest);
}
  • 组合封包模式

当NALU 的长度特别小时,可以把几个NALU 单元封在一个RTP 包中。

格式如下:

从RTP包中解析H264数据

[RTP header]+[STAP-A头(1字节,低5位为24)] +

[第1个nalu长度(2字节)] + [第1个nalu header] + [第1个nalu payload]+

[第2个nalu长度(2字节)] + [第2个nalu header] + [第2个nalu payload]+

[第N个nalu长度(2字节)] + [第N个nalu header] + [第N个nalu payload]

 这种情况下,除了STAP-A头外,循环解析RTP,将组合后的NALU取出来,再加上起始码

while ((rtpPayload.length - srcOffset) > 2)                //循环解析RTP,将组合后的NALU取出来,再加上起始码
  {    bb.put(new byte[]{0x0, 0x0, 0x0, 0x1});                //NALU的起始码

 然后取出每个NALU的长度字段

int size = 0;                                        //NALU的长度,2个字节
size |= rtpPayload[srcOffset] << 8;                        
size |= rtpPayload[srcOffset + 1];

 将NALU header和NALU payload一起放进去,然后进入下一个循环

srcOffset += 2;                                       
byte[] dest = new byte[size];
System.arraycopy(rtpPayload, srcOffset, dest, 0, size);
bb.put(dest);
srcOffset += size;

  •  单一NAL 单元模式

对于NALU 的长度小于MTU 大小的包,一般采用单一NAL 单元模式.

格式如下:

从RTP包中解析H264数据

[RTP header] + [nalu header] + [nalu payload]

此时,只需要加上起始码00 00 00 01,然后将整个rtpPayload一起放进去即可。

bb = ByteBuffer.allocate(rtpPayload.length + 4);                    
bb.put(new byte[]{0x0, 0x0, 0x0, 0x1});
bb.put(rtpPayload); 

 

相关标签: 网络协议