Java实现部标JTT1078实时音视频传输指令——视频流负载包(RTP)传输
程序员文章站
2022-03-22 23:24:22
...
一、 说一说实现的思路
- 1.接收平台方下发的0x9101命令(实时音视频传输请求)
- 2.解析下发的0x9101命令,拿到音视频上传数据的服务器IP和端口号
- 3.开始获取设备摄像头的每一帧视频数据,这里需要注意的是:视频数据支持的格式有:H.264,H.265,AVS,SVAC;详细的可以查看JTT1078协议的(表12)
- 4.将每一帧的数据封装成(表19)音视频流及透传数据传输协议负载包格式
二、解析平台下发的0x9101命令这里就不说了,拿到下发的数据在上传视频流的时候需要
- 解析到数据后就连接上服务器即可。
三、获取视频流数据并编码成H.264视频格式
我这里是在Android平台实现的可以看下我这篇文章Android采集摄像头的视频流数据并使用MediaCodec编码为H264格式
四、将获取到的H.264视频流的每一帧数据进行RTP包封装,来看下协议 5.5.3 :
五、提取出一些重要的信息
- 表中定义的bit位按照大端模式填写:也就是将bit按字符串从左到右排列然后在转成byte
- 字段PT(负载类型,见表19):这处是文档写错了,应该是见(表12)
- 字段数据类型:I/P/B帧的判断,这个就大家可以参考下这位博主的H264码流的I/P/B帧NALU判断
简单说下:一帧H264的视频流数据一般为0x00 0x00 0x00 0x01 或 0x00 0x00 0x01开头,所以0x01后面的这个字节就是NALU类型 - 数据体长度不超过950个byte:如果一帧数据大于了950byte就需要分包了
下面就来看代码的具体实现了
//实时视频数据包序号
private static int RTP_VIDEO_COUNT = 0;
//计算这一I帧距离上一I帧的间隔
private static long LAST_I_FRAME_TIME;
//计算这一帧与上一帧的间隔
private static long LAST_FRAME_TIME;
/**
* 打包实时视频RTP包
*
* @param data H.264一帧的视频数据
* @param phone SIM卡号
* @param liveClient 与服务器的连接
*/
public static synchronized void videoLive(byte[] data, int channelNum, String phone, LiveClient liveClient) {
List<byte[]> dataList = new ArrayList<>();
//每个包的大小
double everyPkgSize = 950.d;
int length = data.length;
if (data.length > everyPkgSize) {
//分包的总包数
long totalPkg = Math.round(Math.ceil(length / everyPkgSize));
for (int i = 1; i <= totalPkg; i++) {
int end = (int) (i * everyPkgSize);
if (end >= length) {
end = length;
}
byte[] bytes = Arrays.copyOfRange(data, (int) ((i - 1) * everyPkgSize), end);
dataList.add(bytes);
}
} else {
dataList.add(data);
}
for (int i = 0; i < dataList.size(); i++) {
byte[] pkgData = dataList.get(i);
ByteBuf buffer = Unpooled.buffer();
buffer.writeBytes(new byte[]{0x30, 0x31, 0x63, 0x64});
// V P X CC
String vpxcc = "10 0 0 0001".replace(" ", "");
buffer.writeByte(Integer.parseInt(vpxcc, 2));
// M PT
String mpt = "0 1100010".replace(" ", "");
buffer.writeByte(Integer.parseInt(mpt, 2));
//包序号
buffer.writeBytes(ByteUtil.int2Word(RTP_VIDEO_COUNT));
//SIM
buffer.writeBytes(ByteUtil.string2Bcd(phone));
//逻辑通道号
buffer.writeByte(channelNum);
String dataType = "";
//取h264的第5个字节,即NALU类型
byte NALU = data[4];
if ((NALU & 0x1F) == 5) {
//这是I帧
dataType = "0000";
LAST_I_FRAME_TIME = System.currentTimeMillis();
} else {
dataType = "0001";
}
//分包标记
if (dataList.size() == 1) {
//不分包
dataType += "0000";
} else if (i == 0) {
//第一个包
dataType += "0001";
} else if (i == dataList.size() - 1) {
//最后一个包
dataType += "0010";
} else {
//中间包
dataType += "0011";
}
//数据类型 分分包标记
buffer.writeByte(Integer.parseInt(dataType, 2));
//时间戳
buffer.writeBytes(ByteUtil.long2Bytes(System.currentTimeMillis()));
//Last I Frame Interval
long difIFrame = System.currentTimeMillis() - LAST_I_FRAME_TIME;
buffer.writeBytes(ByteUtil.int2Word(difIFrame));
//Last Frame Interval
long difFrame = System.currentTimeMillis() - LAST_FRAME_TIME;
buffer.writeBytes(ByteUtil.int2Word(difFrame));
//数据体长度
buffer.writeBytes(ByteUtil.int2Word(pkgData.length));
//数据体
buffer.writeBytes(pkgData);
//发送数据
if (liveClient != null) {
liveClient.sendData(ByteBufUtil.toArray(buffer));
}
RTP_VIDEO_COUNT++;
}
LAST_FRAME_TIME = System.currentTimeMillis();
}
实在没找到Java这边怎么判断B/P帧,所以我这里只判断了是不是I帧,如果不是则都认为是P帧
- 第一步就是判断一帧数据是否要分包,如果需要分包则将分好的每一个包添加至集合中
- 第二步把对应的字段数据填入
- 第三步计算 Last I Frame Interval(这一I帧距离上一I帧的间隔),Last Frame Interval(这一帧距离上一帧的间隔)的时间间隔
- 第四步直接发送就可以了
整个协议的重点就是获取视频流数据并编码H.264,判断每一帧数据是那一帧(I/B/P),计算帧间隔;这样就搞定了视频流上传了
最后附上:使用Netty封装的 部标JTT808,JTT1078,渝标协议 数据上传Android端示例GitHub项目地址
上一篇: RTP-H264码流
下一篇: 浅谈PHP中use关键字的3种使用方法