首页 > 编程语言 >车载视频JT1078协议视频接入(C++)

车载视频JT1078协议视频接入(C++)

时间:2024-01-19 14:59:01浏览次数:28  
标签:std 视频 buffer uint8 C++ second videoIter jtHeader JT1078

把之前做的JT1078协议车载视频接入进行文档整理如下:

-----------------------------------------------------------------------------------------------

一。背景;

平台能够通过jt808协议接入车辆GPS定位信息的基础上,扩展车载视频JT1078协议的接入。实现车辆位置信息和视频信息的可视化。该功能可广泛应用于工厂、园区、物流运输等行业,实现对客运、货运、出租车等营运车辆的实时监控。

二。技术架构:

整体架构采用自研视频流媒体平台。通过JT808协议进行信令管理,包括车辆注册、车辆鉴权、位置信息上报、请求视频、关闭视频等信令交互,实现车辆的基本信息注册和位置信息接入,视频控制指令的下发。通过JT1078流媒体服务解析车载终端上传的RTP视频流数据(包含H264视频和AAC音频),将解析后的音视频数据封装为rtmp格式推送到流媒体服务器。zlmediakit流媒体服务器实现rtmp转为flv或者hls等前端页面可以直接播放的视频流格式。

目前只实现了实时视频的接入。历史视频、云台控制、语音对讲等功能在JT1078协议中有对应的规范,后续可扩展实现。

三。模块描述:

1.JT808信令服务:在原有的JT808服务之上扩展了对9101和9102消息类型的支持,能够实现对实时视频的传输控制指令。

2.JT1078流媒体服务:启动TCP服务,接收车载终端上传的RTP视频流数据,根据JT1078协议对RTP数据包的H264视频帧进行解析,将视频帧封装为rtmp协议后推送到流媒体服务器。暂时只支持H264视频帧,未支持H265视频、音频。

3.zlmediakit流媒体服务器:开源的流媒体服务器,实现多种视频协议的接入和输出,能够提供前端页面可以直接播放的flv或hls格式的视频流。

4.视频流管理后台:云视频的管理后台,主要功能为视频设备管理、视频流管理、播放控制、云台控制、第三方视频平台接入等功能。

四。调试工具:

1.一个好用的车载终端模拟工具:CamClient。既可以模拟808的注册和GPS定位上报,也可以通过配置RTSP视频流来模拟车载视频推送。

2.报文解析工具: https://jttools.smallchi.cn/jt808,可以用来分析或组装报文。

3.TCP Server模拟工具。在JT808服务端模拟器上发送9101请求视频流命令:7E 91 01 00 11 01 80 21 54 36 03 00 00 09 31 32 37 2E 30 2E 30 2E 31 2A 1C 00 00 01 01 00 54 7E。设备端会将视频流推送到指定的流媒体服务127.0.0.1:10780端口

五。视频流RTP报文解析,关键代码:

1.JT1078Server.h

#include <memory>
#include <string>
#include <mutex>
#include <unordered_map>
#include "net/TcpServer.h"
#include "net/EventLoop.h"

namespace xop
{
enum JT1078_SUBMARK
{
    eAtomic,
    eFirst,
    eLast,
    eIntermediate,
    eUnSupportSubMark,
};

enum JT1078_CODEC_TYPE
{
    eG711A,
    eG711U,
    eAAC,
    eAdpcm,
    eH264,
    eH265,
    eUnSupportCodingType,
};

enum JT1078_STREAM_TYPE
{
    eVideoI,
    eVideoP,
    eVideoB,
    eAudio,
    ePassthrough,
    eUnSupportDataType,
};

struct JT1078_RTP_HEADER
{
    uint32_t            DWFramHeadMark;           //帧头标识
    uint8_t                V2 : 2;                     //固定为2
    uint8_t                P1 : 1;                     //固定为0
    uint8_t                X1 : 1;                     //RTP头是否需要扩展位,固定为0
    uint8_t                CC4 : 4;                    //固定为1
    uint8_t                M1 : 1;                     //标志位,确定是否是完整数据帧的边界
    uint8_t                PT7 : 7;                    //负载类型
    uint16_t            WdPackageSequence;        //RTP数据包序号每发送一个RTP数据包序列号加1
    uint8_t                BCDSIMCardNumber[6];      //SIM卡号
    uint8_t                Bt1LogicChannelNumber;    //逻辑通道号
    uint8_t                DataType4 : 4;              //数据类型
    uint8_t                SubpackageHandleMark4 : 4;  //分包处理标记
    uint64_t            Bt8timeStamp;             //时间戳
    uint16_t            WdLastIFrameInterval;     //与上一帧的时间间隔
    uint16_t            WdLastFrameInterval;      //与上一帧的时间间隔
    uint16_t            WdBodyLen;                //数据体长度
    JT1078_CODEC_TYPE    CodecType;
    JT1078_STREAM_TYPE    StreamType;
    JT1078_SUBMARK        SubMark;
    std::string            SimCode;
};

struct JT1078_VIDEO_CHANNEL
{
    std::string simCode = "";
    int channel = 0;
    int mH264Len = 0;
    bool mIsComplete = false;
    SOCKET clientSocket = 0;
    char* H264Data = nullptr;
};

class JT1078Server : public TcpServer
{
public:
    typedef std::function<void(char* frame, int size)> VideoFrameCallback;
    static std::shared_ptr<JT1078Server> Create(xop::EventLoop* loop);
    std::string AddToken(std::string simCode, int channel);
    void SetFrameCallBack(const VideoFrameCallback& cb) { video_cb_ = cb; };
    ~JT1078Server();
private:
    JT1078Server(xop::EventLoop* loop);
    bool ParseJT1078(BufferReader& buffer, SOCKET sockfd);
    virtual TcpConnection::Ptr OnConnect(SOCKET sockfd);
    int ParseRtpHead(std::vector<uint8_t> buffer, JT1078_RTP_HEADER& jtHeader);
    int BcdToString(std::vector<uint8_t> const& in, std::string* out);
    uint8_t BcdToHex(uint8_t const& src);
    uint64_t ByteToU64BigEnd(uint8_t* ByteBuf);
private:
    std::mutex mutex_;
    VideoFrameCallback video_cb_ = nullptr;
    std::unordered_map<std::string, std::shared_ptr<JT1078_VIDEO_CHANNEL>> video_channel_map_;
};
}

2.JT1078Server.cpp

#include "JT1078Server.h"
using namespace xop;
using namespace std;
#define H264_BUFF_LEN 1024000

std::shared_ptr<JT1078Server> JT1078Server::Create(EventLoop* loop)
{
    std::shared_ptr<JT1078Server> server(new JT1078Server(loop));
    return server;
}

std::string xop::JT1078Server::AddToken(std::string simCode, int channel)
{
    std::string token = simCode + "_" + std::to_string(channel);
    std::lock_guard<std::mutex> locker(mutex_);
    if (video_channel_map_.find(token) != video_channel_map_.end()) {
        return token;
    }
    std::shared_ptr<JT1078_VIDEO_CHANNEL> videoChannel(new JT1078_VIDEO_CHANNEL());
    videoChannel->simCode = simCode;
    videoChannel->channel = channel;
    videoChannel->mH264Len = 0;
    videoChannel->mIsComplete = false;
    videoChannel->clientSocket = 0;
    videoChannel->H264Data = new char[H264_BUFF_LEN];
    memset(videoChannel->H264Data, 0, H264_BUFF_LEN);
    video_channel_map_.emplace(token, videoChannel);
    return token;
}

JT1078Server::JT1078Server(EventLoop* loop) : 
    TcpServer(loop) 
{
}

JT1078Server::~JT1078Server()
{
}

bool JT1078Server::ParseJT1078(BufferReader& buffer, SOCKET sockfd)
{
    int nBuffLen = buffer.ReadableBytes();
    printf("接收到 %d 条数据\n", nBuffLen);
    //uint8_t* packetData = (uint8_t*)buffer.Peek();
    uint8_t* packetData = new uint8_t[nBuffLen];
    memcpy(packetData, buffer.Peek(), nBuffLen);
    buffer.RetrieveAll();
    int index = 0;
    int headPos = 0;
    bool bFirst = true;
    int nFirst = 0;
    while (index < nBuffLen)
    {
        bool bFindHeader = false;
        while (true)
        {
            if ((index + 4) > nBuffLen) {
                break;
            }
            if ((packetData[index] == 0x30) && (packetData[index + 1] == 0x31) && (packetData[index + 2] == 0x63) && (packetData[index + 3] == 0x64)) {
                bFindHeader = true;
                headPos = index;
                break;
            }
            else {
                index++;
            }
        }
        
        if (!bFindHeader)
        {
            printf("未查找到包头\n");
            break;
        }

        if (bFirst && headPos > 0)
        {
            nFirst = headPos;
        }
        else {
            nFirst = 0;
        }
        bFirst = false;

        JT1078_RTP_HEADER jtRtpHeader;
        vector<uint8_t> headData;
        for (int index = 0; index < 34; index++)
        {
            headData.push_back(packetData[headPos + index]);
        }
        int ret = ParseRtpHead(headData, jtRtpHeader);
        if (ret < 0)
        {
            printf("解析报文头失败\n");
            index++;
            continue;
        }

        int headLength = 30;
        int temp = 0;
        if (jtRtpHeader.StreamType == eVideoI || jtRtpHeader.StreamType == eVideoP || jtRtpHeader.StreamType == eVideoB)
        {
            temp = headLength;
        }
        else if (jtRtpHeader.StreamType == eAudio)
        {
            temp = headLength - 8;
        }
        else if (jtRtpHeader.StreamType == ePassthrough)
        {
            temp = headLength - 8 - 2 - 2;
        }

        index = headPos + temp + jtRtpHeader.WdBodyLen;

        std::string token = jtRtpHeader.SimCode + "_" + std::to_string(jtRtpHeader.Bt1LogicChannelNumber);
        mutex_.lock();
        auto videoIter = video_channel_map_.find(token);
        if (videoIter == video_channel_map_.end()) {
            printf("未配置的通道:%s_%d\n", jtRtpHeader.SimCode.c_str(), jtRtpHeader.Bt1LogicChannelNumber);
            mutex_.unlock();
            continue;
        }
        if (jtRtpHeader.CodecType == eH264)
        {
            int railDateLen = jtRtpHeader.WdBodyLen;
            if (index > nBuffLen && jtRtpHeader.WdBodyLen > 0)
            {
                // I帧时,有可能视频分包。视频流长度超过当前包总长度
                railDateLen = nBuffLen - headPos - headLength;
            }
            if (railDateLen <= 0)
            {
                // 当前包中已无可以视频数据
                mutex_.unlock();
                continue;
            }
            if (nFirst > 0 && videoIter->second->clientSocket == sockfd)
            {
                //LOG_ERROR("**** nFirst: " << nFirst);
                memcpy(videoIter->second->H264Data + videoIter->second->mH264Len, (char*)packetData, nFirst);
                videoIter->second->mH264Len += nFirst;
            }
            videoIter->second->clientSocket = sockfd;

            // --- 解决数据分包问题
            if (jtRtpHeader.SubMark == eAtomic) {
                memset(videoIter->second->H264Data, 0, H264_BUFF_LEN);
                memcpy(videoIter->second->H264Data, (char*)packetData + headPos + headLength, railDateLen);
                videoIter->second->mH264Len = railDateLen;
                videoIter->second->mIsComplete = true;
            }
            else if (jtRtpHeader.SubMark == eFirst)
            {
                memset(videoIter->second->H264Data, 0, H264_BUFF_LEN);
                memcpy(videoIter->second->H264Data, (char*)packetData + headPos + headLength, railDateLen);
                videoIter->second->mH264Len = railDateLen;
                videoIter->second->mIsComplete = false;
            }
            else if (jtRtpHeader.SubMark == eIntermediate)
            {
                memcpy(videoIter->second->H264Data + videoIter->second->mH264Len, (char*)packetData + headPos + headLength, railDateLen);
                videoIter->second->mH264Len += railDateLen;
                videoIter->second->mIsComplete = false;
            }
            else if (jtRtpHeader.SubMark == eLast)
            {
                memcpy(videoIter->second->H264Data + videoIter->second->mH264Len, (char*)packetData + headPos + headLength, railDateLen);
                videoIter->second->mH264Len += railDateLen;
                videoIter->second->mIsComplete = true;
            }
            if (videoIter->second->mIsComplete)
            {
                std::cout << "----------------------- 保存H264,数据长度:" << videoIter->second->mH264Len << std::endl;
                if (video_cb_)
                {
                    video_cb_(videoIter->second->H264Data, videoIter->second->mH264Len);
                }
            }
        }
        mutex_.unlock();
    }
    delete[]packetData;
    return true;
}

TcpConnection::Ptr JT1078Server::OnConnect(SOCKET sockfd)
{
    auto conn = std::make_shared<TcpConnection>(event_loop_->GetTaskScheduler().get(), sockfd);
    conn->SetReadCallback([this, sockfd](xop::TcpConnection::Ptr conn, xop::BufferReader& buffer) {
        return this->ParseJT1078(buffer, sockfd);
        });
    return conn;
}

int xop::JT1078Server::ParseRtpHead(std::vector<uint8_t> buffer, JT1078_RTP_HEADER& jtHeader)
{
    if (buffer.size() < 30)
    {
        printf("帧长度不足,丢弃\n");
        return -1;
    }
    
    if (buffer[0] != 0x30 || buffer[1] != 0x31 || buffer[2] != 0x63 || buffer[3] != 0x64)
    {
        printf("帧头错误,丢弃\n");
        return -2;
    }

    jtHeader.V2 = (buffer[4] >> 6) & 0x03;
    jtHeader.P1 = (buffer[4] >> 5) & 0x01;
    jtHeader.X1 = (buffer[4] >> 4) & 0x01;
    jtHeader.CC4 = buffer[4] & 0x0F;
    jtHeader.M1 = (buffer[5] >> 7) & 0x01;
    jtHeader.PT7 = buffer[5] & 0x7F;
    
    switch (jtHeader.PT7)
    {
    case 6:
        jtHeader.CodecType = eG711A;
        break;
    case 7:
        jtHeader.CodecType = eG711U;
        break;
    case 19:
        jtHeader.CodecType = eAdpcm;
        break;
    case 98:
        jtHeader.CodecType = eH264;
        break;
    case 99:
        jtHeader.CodecType = eH265;
        break;
    default:
        printf("不支持的编码类型\n");
        return -3;
    }

    jtHeader.WdPackageSequence = (buffer[6] << 8) + buffer[7];
    memcpy(jtHeader.BCDSIMCardNumber, &buffer[0] + 8, 6);
    string simString;
    vector<uint8_t> simCode;
    for (int i = 0; i < 6; i++)
    {
        simCode.push_back(jtHeader.BCDSIMCardNumber[i]);
    }
    BcdToString(simCode, &simString);
    jtHeader.SimCode = "0" + simString;

    jtHeader.Bt1LogicChannelNumber = buffer[14];
    jtHeader.DataType4 = (buffer[15] >> 4) & 0x0F;
    switch (jtHeader.DataType4)
    {
    case 0x00://I
        jtHeader.StreamType = eVideoI;
        break;
    case 0x01://P
        jtHeader.StreamType = eVideoP;
        break;
    case 0x02://B
        jtHeader.StreamType = eVideoB;
        break;
    case 0x03://音频
        jtHeader.StreamType = eAudio;
        break;
    case 0x04://透传
        jtHeader.StreamType = ePassthrough;
        break;
    default:
        printf("不支持的流类型\n");
        return -4;
    }

    jtHeader.SubpackageHandleMark4 = buffer[15] & 0x0F;
    switch (jtHeader.SubpackageHandleMark4)
    {
    case 0x00:
        jtHeader.SubMark = eAtomic; 
        break;
    case 0x01:
        jtHeader.SubMark = eFirst;
        break;
    case 0x02:
        jtHeader.SubMark = eLast; 
        break;
    case 0x03:
        jtHeader.SubMark = eIntermediate; 
        break;
    default:
        printf("不支持的分包处理标识\n");
        return -5;
    }

    if (jtHeader.StreamType == eVideoI || jtHeader.StreamType == eVideoP || jtHeader.StreamType == eVideoB)
    {
        jtHeader.Bt8timeStamp = ByteToU64BigEnd(&buffer[0]+ 16);
        std::cout << "time:" << jtHeader.Bt8timeStamp << std::endl;
        jtHeader.WdLastIFrameInterval = (buffer[24] << 8) + buffer[25];
        jtHeader.WdLastFrameInterval = (buffer[26] << 8) + buffer[27];
        jtHeader.WdBodyLen = (buffer[28] << 8) + buffer[29];
    }
    if (jtHeader.StreamType == eAudio)
    {
        jtHeader.Bt8timeStamp = ByteToU64BigEnd(&buffer[0] + 16);
        jtHeader.WdBodyLen = (buffer[24] << 8) + buffer[25];
    }
    if (jtHeader.StreamType == ePassthrough)
    {
        jtHeader.WdLastIFrameInterval = (buffer[24] << 8) + buffer[25];
        jtHeader.WdLastFrameInterval = (buffer[26] << 8) + buffer[27];
        jtHeader.WdBodyLen = (buffer[28] << 8) + buffer[29];
    }
    if (jtHeader.WdBodyLen > 950)
    {
        jtHeader.WdBodyLen = 950;
    }
    return 0;
}

int xop::JT1078Server::BcdToString(std::vector<uint8_t> const& in, std::string* out)
{
    if (out == nullptr) return -1;
    out->clear();
    size_t pos = 0;
    uint8_t tmp = BcdToHex(in[pos]);
    if (tmp / 10 == 0) {
        out->push_back(tmp % 10 + '0');
        ++pos;
    }
    for (; pos < in.size(); ++pos) {
        tmp = BcdToHex(in[pos]);
        out->push_back(tmp / 10 + '0');
        out->push_back(tmp % 10 + '0');
    }
    return 0;
}

uint8_t xop::JT1078Server::BcdToHex(uint8_t const& src)
{
    uint8_t temp;
    temp = (src >> 4) * 10 + (src & 0x0f);
    return temp;
}

uint64_t xop::JT1078Server::ByteToU64BigEnd(uint8_t* ByteBuf)
{
    uint64_t u64Value = 0;
    for (uint8_t i = 0; i < 8; i++)
    {
        u64Value <<= 8;
        u64Value |= ByteBuf[i];
    }
    return u64Value;
}

 

 

标签:std,视频,buffer,uint8,C++,second,videoIter,jtHeader,JT1078
From: https://www.cnblogs.com/feixiang-energy/p/17972786

相关文章

  • 纯网页语音视频聊天和桌面分享(附源码,PC版+手机版)
    在网页里实现文字聊天是比较容易的,但若要实现视频聊天,就比较麻烦了。本文将实现一个纯网页版的视频聊天和桌面分享的Demo,可直接在浏览器中运行,不需要安装任何插件。一.主要功能及支持平台1.本Demo的主要功能有(1)一对一语音视频聊天。(2)远程桌面观看。(3)当客户端掉线时,会......
  • 【计算机算法设计与分析】罗密欧与朱丽叶的迷宫问题(C++_回溯法)
    文章目录题目描述测试样例算法原理算法实现参考资料题目描述罗密欧与朱丽叶的迷宫。罗密欧与朱丽叶身处一个mxn的迷宫中,如图所示。每一个方恪表示迷宫中的一个房间。这mxn个房间中有一些房间是封闭的。不允任何人进入。在迷宫中任何位置均可沿8个方向进入未封闭的房间。罗密......
  • C++ Qt开发:Charts与数据库组件联动
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍Charts组件与QSql数据库组件的常用方法及灵活运用。在之前的文章中详细介绍了关于QCharts绘图组件......
  • C/C++ 实现动态资源文件释放
    当我们开发Windows应用程序时,通常会涉及到使用资源(Resource)的情况。资源可以包括图标、位图、字符串等,它们以二进制形式嵌入到可执行文件中。在某些情况下,我们可能需要从可执行文件中提取自定义资源并保存为独立的文件。在这篇博客文章中,我们将讨论如何使用C++和WinAPI实现这个目标......
  • C++ 邮件槽ShellCode跨进程传输
    在计算机安全领域,进程间通信(IPC)一直是一个备受关注的话题。在本文中,我们将探讨如何使用Windows邮件槽(Mailslot)实现ShellCode的跨进程传输。邮件槽提供了一种简单而有效的单向通信机制,使得任何进程都能够成为邮件槽服务器,并通过UDP通信向其他进程发送数据。邮件槽是Windows操作系统......
  • C++ 共享内存ShellCode跨进程传输
    在计算机安全领域,ShellCode是一段用于利用系统漏洞或执行特定任务的机器码。为了增加攻击的难度,研究人员经常探索新的传递ShellCode的方式。本文介绍了一种使用共享内存的方法,通过该方法,两个本地进程可以相互传递ShellCode,从而实现一种巧妙的本地传输手段。如果你问我为何在本地了......
  • 如何实现纯网页语音视频聊天和桌面分享?(附源码,PC版+手机版)
    在网页里实现文字聊天是比较容易的,但若要实现视频聊天,就比较麻烦了。本文将实现一个纯网页版的视频聊天和桌面分享的Demo,可直接在浏览器中运行,不需要安装任何插件。一.主要功能及支持平台1.本Demo的主要功能有(1)一对一语音视频聊天。(2)远程桌面观看。(3)当客户端掉线时,会......
  • 安防监控平台LntonAIServer视频汇聚平台明烟明火识别 烟火算法检测告警
    LntonAIServer视频汇聚平台是一款基于人工智能技术的安防监控平台。它能够实时监控和分析视频数据,通过烟火算法检测告警,为我们提供及时、准确的安全信息。无论是在家庭、办公室,还是在公共场所,都能使用LntonAIServer。明烟明火识别是LntonAIServer视频汇聚平台的一大亮......
  • 安防监控平台LntonAIServer视频汇聚平台烟火算法检测
    在这个科技日新月异的时代,我们的生活被各种高科技产品所包围。其中,人工智能技术的出现,更是让我们对未来充满了期待。今天,我要为大家介绍的是一款名为“LntonAIServer”的视频汇聚平台,它采用了一种名为“森林烟火算法”的技术,为我们的环境安全提供了有力的保障。“森......
  • C++简史
    喜欢本篇文章速速点赞评论⭐收藏MISRAC++:2023,MISRA®C++标准的下一个版本,来了!为了帮助您做好准备,我们介绍了Perforce首席技术支持工程师FrankvandenBeuken博士撰写的MISRAC++:2023博客系列的第二部分。在这篇博客中,我们将深入探讨C++的历史、编程语言多年来的发展历......