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);