源码地址:https://github.com/zhouyinfei/rtsp-netty-server
首先上代码:
public void playH265(File f){
//播放h265视频文件
BufferedInputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(f));
int buf_size = 64*1024;
byte[] buffer = new byte[buf_size]; //从文件读的字节存入的地方
byte[] nalu; //临时存储一个nalu单元内容
byte[] firstHalfNalu = null; //nalu前半段
byte[] secondHalfNalu = null; //nalu后半段
int len = 0; //每次从文件读的字节数
int state = 0; //状态机,值范围:0、1、2、3、4
int first = 1; //是否是第一个起始码
int cross = 0; //某个nalu是否跨buffer
// int ssrc = RandomUtils.nextInt(); //rtp的ssrc
rtpUtils = new RtpUtils();
while (-1 != (len = in.read(buffer, 0, buf_size))) {
if (isRtspAlive == 0) { //如果rtsp连接中断,则停止发送udp
break;
}
int start = 0; //第一个nalu的起始位置
int offset = 0; //当前循环中的偏移量
while (offset <= len-4) {
if (state == 0) { //没有遗留状态
if (buffer[offset] == 0x00 &&
buffer[offset + 1] == 0x00 &&
buffer[offset + 2] == 0x00 &&
buffer[offset + 3] == 0x01) {
if (cross == 1) { //跨buffer
if (first == 0) { //不是第一个起始码
secondHalfNalu = new byte[offset]; //拿到后半段内容
System.arraycopy(buffer, start, secondHalfNalu, 0, secondHalfNalu.length);
//拼接前半段与后半段内容, 拷贝到新的数组中
int naluSize = firstHalfNalu.length + secondHalfNalu.length;
nalu = new byte[naluSize];
System.arraycopy(firstHalfNalu, 0, nalu, 0, firstHalfNalu.length);
System.arraycopy(secondHalfNalu, 0, nalu, firstHalfNalu.length, secondHalfNalu.length);
List rtpList = rtpUtils.naluH265ToRtpPack(nalu, videoSsrc, fps);
for (byte[] rptPackage : rtpList) {
ByteBuf byteBuf = Unpooled.directBuffer();
byteBuf.writeBytes(rptPackage);
RtspNettyServer.rtpChannel.writeAndFlush(new DatagramPacket(byteBuf, this.dstVideoAddr));
}
}
offset += 4;
state = 0;
first = 0;
start = offset; //当前位置变成新的起始位置
cross = 0; //跨buffer标志位重置成0
} else { //没有跨buffer
if (first == 0) { //不是第一个起始码
int naluSize = offset - start;
nalu = new byte[naluSize];
System.arraycopy(buffer, start, nalu, 0, naluSize);
List rtpList = rtpUtils.naluH265ToRtpPack(nalu, videoSsrc, fps);
for (byte[] rptPackage : rtpList) {
ByteBuf byteBuf = Unpooled.directBuffer();
byteBuf.writeBytes(rptPackage);
RtspNettyServer.rtpChannel.writeAndFlush(new DatagramPacket(byteBuf, this.dstVideoAddr));
}
}
offset += 4;
state = 0;
first = 0;
start = offset; //当前位置变成新的起始位置
}
} else {
state = 0;
offset ++;
}
} else if (state == 1) {
if (buffer[offset] == 0x00 &&
buffer[offset + 1] == 0x00 &&
buffer[offset + 2] == 0x01) {
//拿到两个起始码之间的一个nalu的数据
int naluSize = firstHalfNalu.length - 1;
nalu = new byte[naluSize];
System.arraycopy(firstHalfNalu, 0, nalu, 0, naluSize);
List rtpList = rtpUtils.naluH265ToRtpPack(nalu, videoSsrc, fps);
for (byte[] rptPackage : rtpList) {
ByteBuf byteBuf = Unpooled.directBuffer();
byteBuf.writeBytes(rptPackage);
RtspNettyServer.rtpChannel.writeAndFlush(new DatagramPacket(byteBuf, this.dstVideoAddr));
}
offset += 3;
state = 0;
start = offset; //当前位置变成新的起始位置
} else {
state = 0;
offset ++;
}
} else if (state == 2) {
if (buffer[offset] == 0x00 &&
buffer[offset + 1] == 0x01) {
//拿到两个起始码之间的一个nalu的数据
int naluSize = firstHalfNalu.length - 2;
nalu = new byte[naluSize];
System.arraycopy(firstHalfNalu, 0, nalu, 0, naluSize);
List rtpList = rtpUtils.naluH265ToRtpPack(nalu, videoSsrc, fps);
for (byte[] rptPackage : rtpList) {
ByteBuf byteBuf = Unpooled.directBuffer();
byteBuf.writeBytes(rptPackage);
RtspNettyServer.rtpChannel.writeAndFlush(new DatagramPacket(byteBuf, this.dstVideoAddr));
}
offset += 2;
state = 0;
start = offset; //当前位置变成新的起始位置
} else {
state = 0;
offset ++;
}
} else if (state == 3) {
if (buffer[offset] == 0x01) {
//拿到两个起始码之间的一个nalu的数据
int naluSize = firstHalfNalu.length - 3;
nalu = new byte[naluSize];
System.arraycopy(firstHalfNalu, 0, nalu, 0, naluSize);
List rtpList = rtpUtils.naluH265ToRtpPack(nalu, videoSsrc, fps);
for (byte[] rptPackage : rtpList) {
ByteBuf byteBuf = Unpooled.directBuffer();
byteBuf.writeBytes(rptPackage);
RtspNettyServer.rtpChannel.writeAndFlush(new DatagramPacket(byteBuf, this.dstVideoAddr));
}
offset += 1;
state = 0;
start = offset; //当前位置变成新的起始位置
} else {
state = 0;
offset ++;
}
}
}
//指针指向最后3位时
if (offset == len-3) {
if (buffer[offset] == 0x00 &&
buffer[offset + 1] == 0x00 &&
buffer[offset + 2] == 0x00) {
state = 3;
} else if (buffer[offset + 1] == 0x00 &&
buffer[offset + 2] == 0x00) {
state = 2;
} else if (buffer[offset + 2] == 0x00) {
state = 1;
}
cross = 1; //一定会跨buffer
firstHalfNalu = new byte[offset + 3 - start]; //初始化前半段nalu数组,将前半段内容放进去
System.arraycopy(buffer, start, firstHalfNalu, 0, firstHalfNalu.length);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//nalu h265封装成rtp
public List naluH265ToRtpPack(byte[] nalu, int ssrc, int fps){
byte[] pcData = nalu; //两个起始码(00 00 00 01)之间的NALU数据
int mtu = 1400; //最大传输单元
int iLen = pcData.length; //NALU总长度
ByteBuffer bb = null;
List rtpList = new ArrayList(); //封装后的rtp包集合
if (iLen > mtu) { //超过MTU 分片封包模式
byte start_flag = (byte) 0x80;
byte mid_flag = 0x00;
byte end_flag = 0x40;
byte nalu_type = (byte) ((pcData[0]>>1) & 0x3f); //获取NALU的6bit 帧类型
//组装FU-A帧头数据 3byte
byte fu_header0 = 0x62; //第一字节固定
byte fu_header1 = 0x01; //第二字节固定
byte fu_header2 = nalu_type; //第三字节的低6位是nalu_type
boolean bFirst = true; //是否是第一个分片
boolean mark = false; //是否是最后一个分片
int nOffset = 2; //偏移量,跳过2个字节naluHeader
while (!mark) {
bb = ByteBuffer.allocate(mtu + 3);
if (iLen < nOffset + mtu) { //是否拆分结束, 最后一个分片
mtu = iLen - nOffset;
mark = true;
fu_header2 = (byte) (end_flag + nalu_type);
} else {
if (bFirst == true) { //第一个分片
fu_header2 = (byte) (start_flag + nalu_type);
bFirst = false;
} else { //或者中间分片
fu_header2 = (byte) (mid_flag + nalu_type);
}
}
bb.put(fu_header0);
bb.put(fu_header1);
bb.put(fu_header2);
byte[] dest = new byte[mtu];
System.arraycopy(pcData, nOffset, dest, 0, mtu);
bb.put(dest);
nOffset += mtu;
byte[] rtpPackage = makeH264Rtp(bb.array(), mark, seqNum, (int)System.currentTimeMillis(), ssrc);
rtpList.add(rtpPackage);
seqNum ++;
}
} else { //单一NAL 单元模式, 不使用组合模式。mark始终为true
//从SPS中获取帧率
// byte nalu_type = (byte) (pcData[0] & 0x1f); //获取NALU的5bit 帧类型
// if (nalu_type == 7) { //如果是sps
// SpsInfoStruct info = new SpsInfoStruct();
// SpsUtils.h264ParseSps(nalu, nalu.length, info);
// if (info.fps != 0) { //如果sps中存在帧率信息,则使用。如果没有,则使用默认帧率
// intervel = 1000/info.fps;
// }
// }
//根据rtsp传过来的fps参数
if (fps != 0) {
intervel = 1000/fps;
}
byte[] rtpPackage = makeH264Rtp(pcData, true, seqNum, (int)System.currentTimeMillis(), ssrc);
rtpList.add(rtpPackage);
seqNum ++;
}
try {
Thread.sleep(intervel); //根据帧率延时发送
} catch (InterruptedException e) {
e.printStackTrace();
}
return rtpList;
}
在前面博客《从RTP包中解析H265数据》中讲的是从NALU --> RTP, 这一篇博客讲的是:h265文件--> NALU --> RTP。这些内容与H264封装的过程类似,就不重复介绍了,仅仅贴一下代码。