做直播,音视频通讯。经常需要通过rtp协议封装音视频数据来发送。网上找到的基本都是c或c++版本的,没有JAVA版本的。就算千辛万苦找到一篇java版本的,要么不能用,要么就是一些片段,要么有封包没解包。
很是蛋疼,本人也是这样,刚开始不太熟悉rtp协议,不太明白怎么封包组包,痛苦了几天,终于搞出来了,分享给有需要的朋友,希望对你们有所帮助。
直接看代码吧。不多说了。
首先看看关键类:
package com.imsdk.socket.udp.codec; import android.os.SystemClock; import android.util.Log; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.util.Random; import java.util.concurrent.Semaphore; public class RtspPacketEncode { private static final String TAG = "RtspPacketEncode"; //------------视频转换数据监听----------- public interface H264ToRtpLinsener { void h264ToRtpResponse(byte[] out, int len); } private H264ToRtpLinsener h264ToRtpLinsener; //执行回调 private void exceuteH264ToRtpLinsener(byte[] out, int len) { if (this.h264ToRtpLinsener != null) { h264ToRtpLinsener.h264ToRtpResponse(out, len); } } // -------视频-------- private int framerate = 10; private byte[] sendbuf = new byte[1500]; private int packageSize = 1400; private int seq_num = 0; private int timestamp_increse = (int) (90000.0 / framerate);//framerate是帧率 private int ts_current = 0; private int bytes = 0; // -------视频END-------- public RtspPacketEncode(H264ToRtpLinsener h264ToRtpLinsener) { this.h264ToRtpLinsener = h264ToRtpLinsener; } /** * 一帧一帧的RTP封包 * * @param r * @return */ public void h264ToRtp(byte[] r, int h264len) throws Exception { CalculateUtil.memset(sendbuf, 0, 1500); sendbuf[1] = (byte) (sendbuf[1] | 96); // 负载类型号96,其值为:01100000 sendbuf[0] = (byte) (sendbuf[0] | 0x80); // 版本号,此版本固定为2 sendbuf[1] = (byte) (sendbuf[1] & 254); //标志位,由具体协议规定其值,其值为:01100000 sendbuf[11] = 10;//随机指定10,并在本RTP回话中全局唯一,java默认采用网络字节序号 不用转换(同源标识符的最后一个字节) if (h264len <= packageSize) { sendbuf[1] = (byte) (sendbuf[1] | 0x80); // 设置rtp M位为1,其值为:11100000,分包的最后一片,M位(第一位)为0,后7位是十进制的96,表示负载类型 sendbuf[3] = (byte) seq_num++; System.arraycopy(CalculateUtil.intToByte(seq_num++), 0, sendbuf, 2, 2);//send[2]和send[3]为序列号,共两位 { // java默认的网络字节序是大端字节序(无论在什么平台上),因为windows为小字节序,所以必须倒序 /**参考: * http://blog.csdn.net/u011068702/article/details/51857557 * http://cpjsjxy.iteye.com/blog/1591261 */ byte temp = 0; temp = sendbuf[3]; sendbuf[3] = sendbuf[2]; sendbuf[2] = temp; } // FU-A HEADER, 并将这个HEADER填入sendbuf[12] sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x80)) << 7); sendbuf[12] = (byte) (sendbuf[12] | ((byte) ((r[0] & 0x60) >> 5)) << 5); sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x1f))); // 同理将sendbuf[13]赋给nalu_payload //NALU头已经写到sendbuf[12]中,接下来则存放的是NAL的第一个字节之后的数据。所以从r的第二个字节开始复制 System.arraycopy(r, 1, sendbuf, 13, h264len - 1); ts_current = ts_current + timestamp_increse; System.arraycopy(CalculateUtil.intToByte(ts_current), 0, sendbuf, 4, 4);//序列号接下来是时间戳,4个字节,存储后也需要倒序 { byte temp = 0; temp = sendbuf[4]; sendbuf[4] = sendbuf[7]; sendbuf[7] = temp; temp = sendbuf[5]; sendbuf[5] = sendbuf[6]; sendbuf[6] = temp; } bytes = h264len + 12;//获sendbuf的长度,为nalu的长度(包含nalu头但取出起始前缀,加上rtp_header固定长度12个字节) //client.send(new DatagramPacket(sendbuf, bytes, addr, port/*9200*/)); //send(sendbuf,bytes); exceuteH264ToRtpLinsener(sendbuf, bytes); } else if (h264len > packageSize) { int k = 0, l = 0; k = h264len / packageSize; l = h264len % packageSize; int t = 0; ts_current = ts_current + timestamp_increse; System.arraycopy(CalculateUtil.intToByte(ts_current), 0, sendbuf, 4, 4);//时间戳,并且倒序 { byte temp = 0; temp = sendbuf[4]; sendbuf[4] = sendbuf[7]; sendbuf[7] = temp; temp = sendbuf[5]; sendbuf[5] = sendbuf[6]; sendbuf[6] = temp; } while (t <= k) { System.arraycopy(CalculateUtil.intToByte(seq_num++), 0, sendbuf, 2, 2);//序列号,并且倒序 { byte temp = 0; temp = sendbuf[3]; sendbuf[3] = sendbuf[2]; sendbuf[2] = temp; } if (t == 0) {//分包的第一片 sendbuf[1] = (byte) (sendbuf[1] & 0x7F);//其值为:01100000,不是最后一片,M位(第一位)设为0 //FU indicator,一个字节,紧接在RTP header之后,包括F,NRI,header sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x80)) << 7);//禁止位,为0 sendbuf[12] = (byte) (sendbuf[12] | ((byte) ((r[0] & 0x60) >> 5)) << 5);//NRI,表示包的重要性 sendbuf[12] = (byte) (sendbuf[12] | (byte) (28));//TYPE,表示此FU-A包为什么类型,一般此处为28 //FU header,一个字节,S,E,R,TYPE sendbuf[13] = (byte) (sendbuf[13] & 0xBF);//E=0,表示是否为最后一个包,是则为1 sendbuf[13] = (byte) (sendbuf[13] & 0xDF);//R=0,保留位,必须设置为0 sendbuf[13] = (byte) (sendbuf[13] | 0x80);//S=1,表示是否为第一个包,是则为1 sendbuf[13] = (byte) (sendbuf[13] | ((byte) (r[0] & 0x1f)));//TYPE,即NALU头对应的TYPE //将除去NALU头剩下的NALU数据写入sendbuf的第14个字节之后。前14个字节包括:12字节的RTP Header,FU indicator,FU header System.arraycopy(r, 1, sendbuf, 14, packageSize); //client.send(new DatagramPacket(sendbuf, packageSize + 14, addr, port/*9200*/)); exceuteH264ToRtpLinsener(sendbuf, packageSize + 14); t++; } else if (t == k) {//分片的最后一片 sendbuf[1] = (byte) (sendbuf[1] | 0x80); sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x80)) << 7); sendbuf[12] = (byte) (sendbuf[12] | ((byte) ((r[0] & 0x60) >> 5)) << 5); sendbuf[12] = (byte) (sendbuf[12] | (byte) (28)); sendbuf[13] = (byte) (sendbuf[13] & 0xDF); //R=0,保留位必须设为0 sendbuf[13] = (byte) (sendbuf[13] & 0x7F); //S=0,不是第一个包 sendbuf[13] = (byte) (sendbuf[13] | 0x40); //E=1,是最后一个包 sendbuf[13] = (byte) (sendbuf[13] | ((byte) (r[0] & 0x1f)));//NALU头对应的type if (0 != l) {//如果不能整除,则有剩下的包,执行此代码。如果包大小恰好是1400的倍数,不执行此代码。 System.arraycopy(r, t * packageSize + 1, sendbuf, 14, l - 1);//l-1,不包含NALU头 bytes = l - 1 + 14; //bytes=l-1+14; //client.send(new DatagramPacket(sendbuf, bytes, addr, port/*9200*/)); //send(sendbuf,bytes); exceuteH264ToRtpLinsener(sendbuf, bytes); }//pl t++; } else if (t < k && 0 != t) {//既不是第一片,又不是最后一片的包 sendbuf[1] = (byte) (sendbuf[1] & 0x7F); //M=0,其值为:01100000,不是最后一片,M位(第一位)设为0. sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x80)) << 7); sendbuf[12] = (byte) (sendbuf[12] | ((byte) ((r[0] & 0x60) >> 5)) << 5); sendbuf[12] = (byte) (sendbuf[12] | (byte) (28)); sendbuf[13] = (byte) (sendbuf[13] & 0xDF); //R=0,保留位必须设为0 sendbuf[13] = (byte) (sendbuf[13] & 0x7F); //S=0,不是第一个包 sendbuf[13] = (byte) (sendbuf[13] & 0xBF); //E=0,不是最后一个包 sendbuf[13] = (byte) (sendbuf[13] | ((byte) (r[0] & 0x1f)));//NALU头对应的type System.arraycopy(r, t * packageSize + 1, sendbuf, 14, packageSize);//不包含NALU头 //client.send(new DatagramPacket(sendbuf, packageSize + 14, addr, port/*9200*/)); //send(sendbuf,1414); exceuteH264ToRtpLinsener(sendbuf, packageSize + 14); t++; } } } } }
计算类:
package com.imsdk.socket.udp.codec; /** * 计算类 * * @author kokJuis */ public class CalculateUtil { /** * 注释:int到字节数组的转换! * * @param number * @return */ public static byte[] intToByte(int number) { int temp = number; byte[] b = new byte[4]; for (int i = 0; i < b.length; i++) { b[i] = new Integer(temp & 0xff).byteValue();// 将最低位保存在最低位 temp = temp >> 8; // 向右移8位 } return b; } public static int byteToInt(byte b) { //Java 总是把 byte 当做有符处理;我们可以通过将其和 0xFF 进行二进制与得到它的无符值 return b & 0xFF; } //byte 数组与 int 的相互转换 public static int byteArrayToInt(byte[] b) { return b[3] & 0xFF | (b[2] & 0xFF) << 8 | (b[1] & 0xFF) << 16 | (b[0] & 0xFF) << 24; } public static byte[] intToByteArray(int a) { return new byte[] { (byte) ((a >> 24) & 0xFF), (byte) ((a >> 16) & 0xFF), (byte) ((a >> 8) & 0xFF), (byte) (a & 0xFF) }; } // 清空buf的值 public static void memset(byte[] buf, int value, int size) { for (int i = 0; i < size; i++) { buf[i] = (byte) value; } } public static void dump(NALU_t n) { System.out.println("len: " + n.len + " nal_unit_type:" + n.nal_unit_type); } // 判断是否为0x000001,如果是返回1 public static int FindStartCode2(byte[] Buf, int off) { if (Buf[0 + off] != 0 || Buf[1 + off] != 0 || Buf[2 + off] != 1) return 0; else return 1; } // 判断是否为0x00000001,如果是返回1 public static int FindStartCode3(byte[] Buf, int off) { if (Buf[0 + off] != 0 || Buf[1 + off] != 0 || Buf[2 + off] != 0 || Buf[3 + off] != 1) return 0; else return 1; } }
使用的话,实现监听就可以了:
@Override public void h264ToRtpResponse(byte[] out, int len) { //h264转rtp监听 if (out != null) { Log.v(TAG, "---发送数据---" + len); netSendTask.pushBuf(out, len); } } rtspPacketEncode.h264ToRtp(h264, ret);
组包类:
package com.imsdk.socket.udp.codec; public class RtspPacketDecode { private byte[] h264Buffer; private int h264Len = 0; private int h264Pos = 0; private static final byte[] start_code = {0, 0, 0, 1}; // h264 start code //传入视频的分辨率 public RtspPacketDecode(int width, int height) { h264Buffer = new byte[getYuvBuffer(width, height)]; } /** * RTP解包H264 * * @param rtpData * @return */ public byte[] rtp2h264(byte[] rtpData, int rtpLen) { int fu_header_len = 12; // FU-Header长度为12字节 int extension = (rtpData[0] & (1 << 4)); // X: 扩展为是否为1 if (extension > 0) { // 计算扩展头的长度 int extLen = (rtpData[12] << 24) + (rtpData[13] << 16) + (rtpData[14] << 8) + rtpData[15]; fu_header_len += (extLen + 1) * 4; } // 解析FU-indicator byte indicatorType = (byte) (CalculateUtil.byteToInt(rtpData[fu_header_len]) & 0x1f); // 取出low 5 bit 则为FU-indicator type byte nri = (byte) ((CalculateUtil.byteToInt(rtpData[fu_header_len]) >> 5) & 0x03); // 取出h2bit and h3bit byte f = (byte) (CalculateUtil.byteToInt(rtpData[fu_header_len]) >> 7); // 取出h1bit byte h264_nal_header; byte fu_header; if (indicatorType == 28) { // FU-A fu_header = rtpData[fu_header_len + 1]; byte s = (byte) (rtpData[fu_header_len + 1] & 0x80); byte e = (byte) (rtpData[fu_header_len + 1] & 0x40); if (e == 64) { // end of fu-a //ZOLogUtil.d("RtpParser", "end of fu-a.....;;;"); byte[] temp = new byte[rtpLen - (fu_header_len + 2)]; System.arraycopy(rtpData, fu_header_len + 2, temp, 0, temp.length); writeData2Buffer(temp, temp.length); if (h264Pos >= 0) { h264Pos = -1; if (h264Len > 0) { byte[] h264Data = new byte[h264Len]; System.arraycopy(h264Buffer, 0, h264Data, 0, h264Len); h264Len = 0; return h264Data; } } } else if (s == -128) { // start of fu-a h264Pos = 0; // 指针归0 writeData2Buffer(start_code, 4); // 写入H264起始码 h264_nal_header = (byte) ((fu_header & 0x1f) | (nri << 5) | (f << 7)); writeData2Buffer(new byte[]{h264_nal_header}, 1); byte[] temp = new byte[rtpLen - (fu_header_len + 2)]; System.arraycopy(rtpData, fu_header_len + 2, temp, 0, temp.length); // 负载数据 writeData2Buffer(temp, temp.length); } else { byte[] temp = new byte[rtpLen - (fu_header_len + 2)]; System.arraycopy(rtpData, fu_header_len + 2, temp, 0, temp.length); writeData2Buffer(temp, temp.length); } } else { // nalu h264Pos = 0; writeData2Buffer(start_code, 4); byte[] temp = new byte[rtpLen - fu_header_len]; System.arraycopy(rtpData, fu_header_len, temp, 0, temp.length); writeData2Buffer(temp, temp.length); if (h264Pos >= 0) { h264Pos = -1; if (h264Len > 0) { byte[] h264Data = new byte[h264Len]; System.arraycopy(h264Buffer, 0, h264Data, 0, h264Len); h264Len = 0; return h264Data; } } } return null; } private void writeData2Buffer(byte[] data, int len) { if (h264Pos >= 0) { System.arraycopy(data, 0, h264Buffer, h264Pos, len); h264Pos += len; h264Len += len; } } //计算h264大小 public int getYuvBuffer(int width, int height) { // stride = ALIGN(width, 16) int stride = (int) Math.ceil(width / 16.0) * 16; // y_size = stride * height int y_size = stride * height; // c_stride = ALIGN(stride/2, 16) int c_stride = (int) Math.ceil(width / 32.0) * 16; // c_size = c_stride * height/2 int c_size = c_stride * height / 2; // size = y_size + c_size * 2 return y_size + c_size * 2; } }
使用:
byte[] tmp = rtspPacketDecode.rtp2h264(out,len);
以上这篇java、android可用的rtp封包解包h264案例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持小牛知识库。
我知道使用Wireshark和VLC保存RTP h264流是可能的。但为了学习更多关于视频流,我正在尝试自己做。有几个相关的问题有助于阅读这个主题: 如何处理原始UDP数据包,以便directshow源筛选器中的解码器筛选器对其进行解码 如何将H.264 UDP数据包转换为可播放的媒体流或文件(碎片整理) 以这些为背景,我目前的工作如下: 我可以通过UTP接收RTP数据包。 我按照上面问题中的讨论
2)当我接收到一个数据包,并且它是“fragment_type==28”(不仅仅是这样,而是让我们认为我真的得到了一个片段),这意味着我有一个IDR片段。 3)每个报文都有一个由发信方生成的序列号,该序列号按如下顺序排列:如果报文a的序列号为20,则发信方发送的下一个报文为21,以此类推。 现在让我们来回答我的问题: A)如果我有一个IDR要重建,我如何知道什么数据包属于这个IDR?让我举一个例子
我阅读了H.264视频RFC的RTP有效载荷格式,如果我在视频流中发现有sps和pps数据包(元数据),然后是Idr(完整图像),然后在上一个Idr到当前状态之间改变数据包,再从一开始。 我知道每个包装h264数据的rtp包报头都有序列号。 我不明白的是,对于更改的数据包(在Idr数据包之间),他们如何知道与它们相关的每个Idr? 在h264报头/数据中是否有写入它们与哪个rtp序列号或h264序
我目前正在尝试从RTP流解析H264数据,然后将其发送到MediaCodec以呈现在Android的SurfaceView上。 但是,我不确定如何: 根据RTP数据包正确构建H264切片 将H264切片组装成切片后发送到媒体编解码器
我正在为android编写一个rtp视频流,它从android本地套接字读取h264编码的数据并将其打包。问题是我做到了,但我在客户端(Voip)中不断收到黑帧。 通信方式如下:Android- 有几件事我还不明白: 1) Android的mediarecorder给了我一个原始的h264流,我怎么知道NAL何时根据该流开始/结束?它没有任何0x000001模式,但它有一个0x0000(我假设它是
我需要创建一个android应用程序来显示实时电视源。据网关供应商介绍,该应用程序应该可以播放来自DVB网关多播的实时视频流,它可以流式输出UDP或RTP。我在我的电脑上设置了vlc来输出UDP和RTP,并试图让android播放器显示它们,结果手指断了。过了一会儿,我发现android只支持HTTP/S和RTSP实时流。我尝试了所有的FFMPEG解决方案和不同的媒体播放器,但都没有成功。我不是视