从RTP包中解析H264数据

 

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的类型有哪些:

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

 

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

 

  • 分片封包模式:

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

其格式如下:

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

可以看出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 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 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); 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值