一、 说一说实现的思路

  • 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项目地址

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐