欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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命令这里就不说了,拿到下发的数据在上传视频流的时候需要

Java实现部标JTT1078实时音视频传输指令——视频流负载包(RTP)传输

  • 解析到数据后就连接上服务器即可。

三、获取视频流数据并编码成H.264视频格式

我这里是在Android平台实现的可以看下我这篇文章Android采集摄像头的视频流数据并使用MediaCodec编码为H264格式

四、将获取到的H.264视频流的每一帧数据进行RTP包封装,来看下协议 5.5.3 :

Java实现部标JTT1078实时音视频传输指令——视频流负载包(RTP)传输

五、提取出一些重要的信息

  • 表中定义的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项目地址