从H265文件中读取文件并封装成RTP

源码地址: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<byte[]> 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<byte[]> 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<byte[]> 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<byte[]> 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<byte[]> 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<byte[]> 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<byte[]> rtpList = new ArrayList<byte[]>();				//封装后的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封装的过程类似,就不重复介绍了,仅仅贴一下代码。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值