zoukankan      html  css  js  c++  java
  • GB28181 下级平台(设备)实现

            本文主要介绍GB28181 下级平台(设备)实现的基本内容,适合初入门同学,老司机可略过。

     首先需要知道GB28181 上、下级关系,比如两个平台A和B,如B需要从A获取视频流,则B是上级平台,A是下级平台。

    另外需要清楚国标下级平台是广义的,复杂的视频平台可以是国标下级平台,支持国标NVR可以称为国标平台,支持国

    标的摄像机也可以称为国标平台。

           国标下级平台概率清楚后,接下来需要了解的是国标下级平台实现的功能。

    1. 注册

           注册功能是国标下级平台向国标上级平台发送Register消息(基于SIP)上下级平台互联,第一个信令是下级平台发起的

    具体的抓包内容是下图所示:

          

                                       图1. 上级平台注册消息截图

           如图1所示,Request-Line 中 34020000002000000001为上级平台id,192.168.1.102为上级平台ip,5060 为上级平台信令

    端口。From及To中的34020000001320000101是下级平台自身的id,192.168.1.106为下级平台ip,5060为下级平台信令端口。

    下级平台上上级平台发送Register 消息如上级平台不回复 下级平台会一直发送Register消息,知道收到200 OK或者未鉴权消息

    ,如收到未鉴权消息,下级平台需要根据接收的参数(例如nonce)以及已知的上级平台密码 通过MD5加密后生成response 等

    信息再次发起注册请求(具体的实现细节可参考GB28181-2016文档)

      基本代码实现(基于PJSip):

           首先初始化库

      bool Init(std::string contact, int logLevel,bool getCatalog,bool publicNet)
      {
        this->getCatalog = getCatalog;
        this->contact = contact;
        this->publicNet = publicNet;
        getPlatformIdFromContact();
        pj_log_set_level(logLevel);
        auto status = pj_init();
    
        status = pjlib_util_init();
    
        pj_caching_pool_init(&cachingPool, &pj_pool_factory_default_policy, 0);
    
        status = pjsip_endpt_create(&cachingPool.factory, nullptr, &endPoint);
    
        status = pjsip_tsx_layer_init_module(endPoint);
    
        status = pjsip_ua_init_module(endPoint, nullptr);
    
        pool = pj_pool_create(&cachingPool.factory, "proxyapp", 4000, 4000, nullptr);
    
        auto pjStr =StrToPjstr(GetAddr());
    
        pj_sockaddr_in pjAddr;
        pjAddr.sin_family = pj_AF_INET();
        pj_inet_aton(pjStr.get(), &pjAddr.sin_addr);
    
        auto port = GetPort();
        pjAddr.sin_port = pj_htons(static_cast<pj_uint16_t>(GetPort()));
        status = pjsip_udp_transport_start(endPoint, &pjAddr, nullptr, 1, nullptr);
        if (status != PJ_SUCCESS) return status == PJ_SUCCESS;
    
        auto realm = StrToPjstr(GetLocalDomain());
        return pjsip_auth_srv_init(pool, &authentication, realm.get(), lookup, 0) == PJ_SUCCESS ? true : false;
      }
    

      

    发送Register消息

     int startRegister()
    {
      pj_status_t status;
        pjsip_regc *regc;
                       
        pj_thread_t *uithread;
     
        pj_thread_desc rtpdesc;
        if (!pj_thread_is_registered())
        {
             pj_thread_register(nullptr, rtpdesc, &uithread);
        }
         //pj_thread_create(context.pool, "register", &registerThreadHandler, nullptr, 0, 0, &registerThread);
       status = pjsip_regc_create(context.endPoint, this, &clientCb, &regc);
        if (status != PJ_SUCCESS)
        {
             return status;
        }
    
        GBPlatform platform;
         platform.Init(registerInfo.platformId, registerInfo.platformAddr, registerInfo.platformPort);
         MediaContext mediaContxt(context.contact);
         GBPlatform localPlatform;
         localPlatform.Init(mediaContxt.GetDeviceId(), mediaContxt.GetplatformIP(), mediaContxt.GetPlatformPort());
    
        auto pjContact = StrToPjstr(platform.GetContact());
         auto pjSipCodecUrl = StrToPjstr(localPlatform.GetSipCodecUrl());
       auto pjContextContact = StrToPjstr(context.contact);
        status = pjsip_regc_init(regc, pjContact.get(), pjSipCodecUrl.get(), pjSipCodecUrl.get(), 1,
                             pjContextContact.get(), registerInfo.expires ? registerInfo.expires : 60);
         if (status != PJ_SUCCESS)
        {
              pjsip_regc_destroy(regc);
              return status;
         }
                       
         pjsip_tx_data *tdata;
        pjsip_regc_register(regc, PJ_TRUE, &tdata);
         status = pjsip_regc_send(regc, tdata);
      }
    

      

      2. 保活

         下级注册成功后(收到上级平台的200 OK消息)便开始发送保活消息(keepalive)保活间隔可以是3-5s 也可以是1分钟,这

    个时间点国标协议没有规定,上下级协商(主要不是通过信令协商,是口头协商)如上级平台一段时间没有收到下级平台保活消

    息 上级平台会认为下级平台已离线。 保活消息如下图所示,其中Device字段中34020000001320000003是下级平台id。正常情况

    下上级平台收到下级平台保活消息后发送200 OK消息。

         

                  图2 保活消息截图

      基本代码实现:

    void keepAlive()
    {
    
       pjsip_tx_data *tdata;
       GBPlatform *platform = new GBPlatform();
       platform->Init(registerInfo.platformId, registerInfo.platformAddr, registerInfo.platformPort);
       auto message = format(
       "<?xml version="1.0" encoding="utf-8" ?>
    "
       "<Notify>
    "
        "<CmdType>Keepalive</CmdType>
    "
       "<SN>12</SN>
    "
        "<DeviceID>%s</DeviceID>
    "
        "<Status>OK</Status>
    "
        "</Notify>
    ", context.platformId
        );
    
        char msg[500] = { 0 };
        message.copy(msg, message.size(), 0);
    
        const pjsip_method method = { PJSIP_OTHER_METHOD, {(char *)"MESSAGE", 7} };
        auto text = StrToPjstr(msg);
    
       auto pjContact = StrToPjstr(context.contact);
       auto pjSipIpUrl = StrToPjstr(platform->GetSipIpUrl());
       auto pjSipCodecUrl = StrToPjstr(platform->GetSipCodecUrl());
    
        pj_status_t  status = pjsip_endpt_create_request(context.endPoint, &method, pjSipIpUrl.get(), pjContact.get(), pjSipCodecUrl.get(), pjContact.get(), nullptr, -1, text.get(), &tdata);
    
        delete platform;
        tdata->msg->body->content_type.type = pj_str("Application");
        tdata->msg->body->content_type.subtype = pj_str("MANSCDP+xml");
        pjsip_endpt_send_request(context.endPoint, tdata, -1, this, on_keepalive_callback);
    }
    

      

            3. 发送Catalog

                下级平台第3个实现的功能是向上级平台推送自身的设备消息,当然前提是上级平台发送了请求设备消息。国标摄像机

         只有一个设备就是它自身,国标NVR可能有多个(几个通过连接了摄像机就几个),国标平台则是该平台管理的摄像机。

          响应Catalog消息内容如下图所示:

                 图3.  发送Catalog消息截图

       如图3所示,Message Body 采用xml格式,消息体中第一个DeviceID 是下级平台自身的Id,DeviceList 中的Num值表示本

    次Catalog消息中含有的设备数目。如果有100个设备,可以每次发一条,发100次,也可以每次发2条,发50次。这个国标

    协议也没有规定。Item节点中DeviceID是真正设备id好,Name是设备的名称Status是设备的状态(是否在线)。

    基本代码实现:

    bool OnReceive(pjsip_rx_data* rdata) override
    {
        if (rdata->msg_info.cseq->method.id != PJSIP_OTHER_METHOD) return false;
    
        CGXmlParser xmlParser(context.GetMessageBody(rdata));
        if (!xmlParser.GetXml())
        {
             return true;
        }
        CGDynamicStruct dynamicStruct;
        dynamicStruct.Set(xmlParser.GetXml());
    
        auto cmd = xmlParser.GetXml()->firstChild()->nodeName();
        auto cmdType = dynamicStruct.Get<std::string>("CmdType");
        if (cmdType != "Catalog") return false;
    
        std::string SN = "";
        std::string PlatformAddr;
        int PlatformPort = 0;
    
        try
        {
            SN = dynamicStruct.Get<std::string>("SN");
        }
        catch (Poco::Exception e)
        {
             std::cout << e.displayText() << std::endl;
        }
         bool registered = false;
         if (!SN.empty())
         {
             auto formtoHdr = (pjsip_fromto_hdr*)pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_FROM, NULL);
             pjsip_sip_uri* frometoUri = (pjsip_sip_uri*)pjsip_uri_get_uri(formtoHdr->uri);
    
             std::string platFormId = context.PjstrTostr(frometoUri->user);
             PlatformAddr = rdata->pkt_info.src_name;
             PlatformPort = rdata->pkt_info.src_port;
             GBPlatform  *platform = new GBPlatform;
             platform->Init(platFormId, PlatformAddr, PlatformPort);
             RegisterStatus  status = CGSuperiorServerSessionGroup::GetInstance().IsServerRegistered(platform->GetPlatformUUID());
            if (status == RegisterStatus::Registered)
            {
                  registered = true;
                  response(rdata, PJSIP_SC_OK, NoHead);
                  std::vector<CGCatalogInfo> catalogs = CG28181MediaInfo::GetCatalogs(context.GetMediaAddrIp(), context.GetMediaAddrPort(), platform->GetPlatformUUID());
                  if (catalogs.empty())
                   {
                         CGCatalogInfo catalog;
                         context.ResponseCatalogInfo(platform, catalog, 0);
                   }
                   else
                    {
                        for (auto it = catalogs.begin(); it != catalogs.end(); it++)
                         {
                              (*it).SerialNumber = SN;
                              context.ResponseCatalogInfo(platform, (*it), 1);
                         }
                     }
              }
                            delete platform;
         }
        if (!registered)
        {
             response(rdata, PJSIP_SC_BAD_REQUEST, NoHead);
        }
         return true;
    }
    

      

    4.  发送视频

         下级平台发送视频跟发送Catalog消息一样,都是被动的,只有在上级平台发送请求视频命令Invite消息后将上级平台指定

    的端口推送视频(这里仅讨论Udp的方式,实际GB28181-2016支持Tcp的方式)这里有3个问题需要清楚第1个是向哪个Ip

    哪个端口推送,这个问题可以从上级平台发送Invite消息中得到答案。Invite消息携带了上级平台接收视频的Ip及Port,如下图4

    所示。第2个需要搞清楚的问题是推什么格式的视频流,答案是rtp +MpegPS流。视频的编码格式一般为H264,也有的是H265,

    封装的流程是相同的:先将裸流(H264 or H265)打包成MpegPS流 再加上12个字节的rtp包就可以了。第3个问题是 发送哪个

    设备的流给上级平台,这个问题相对简单点,上级平台发送过来的Invite消息中的SDP信息已经指定他想要的设备Id。

       

                 图4.  Invite消息截图

      基本代码实现:

    bool OnReceive(pjsip_rx_data* rdata) override
    {
        if (rdata->msg_info.cseq->method.id != PJSIP_INVITE_METHOD) return false;
    
        auto dlg = pjsip_rdata_get_dlg(rdata);
        pjsip_rdata_sdp_info *sdp = pjsip_rdata_get_sdp_info(rdata);
        if (!sdp || !sdp->sdp) return false;
    
        pj_uint32_t startTime = sdp->sdp->time.start;
        pj_uint32_t endTime = sdp->sdp->time.stop;
    
        pjsip_cid_hdr *callIdHdr = (pjsip_cid_hdr*)pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CALL_ID, NULL);
    
        std::string PlatformAddr = rdata->pkt_info.src_name;
        int PlatformPort = rdata->pkt_info.src_port;
        std::string  platFormId = getPlatformIdFormHdr(rdata);
        string platformUUID = platFormId + "@" + PlatformAddr + ":" + std::to_string(PlatformPort);
        std::string ssrc = GetSSRCFromeSdp(sdp);
        bool isRegistered = CGSuperiorServerSessionGroup::GetInstance().IsServerRegistered(platformUUID);
        if (!isRegistered)
        {
              response(rdata, PJSIP_SC_BAD_REQUEST, NoHead);
              std::cout << platformUUID <<" not registered"<<std::endl;
              return true;
         }
    
         pjmedia_sdp_media * media = sdp->sdp->media[0];
         CGTransportType transport = getTransportType(media->desc.transport);
    
         if (transport == CGTransportType::UnknownType)
         {
              response(rdata, PJSIP_SC_BAD_REQUEST, NoHead);
              return true;
          }
    
                        //response(rdata, PJSIP_SC_TRYING, NoHead);
          pjsip_inv_session *inv = context.InviteAnswerTring(rdata);
         pjsip_sip_uri* requestLineUri = (pjsip_sip_uri*)pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
         string deviceId = context.PjstrTostr(requestLineUri->user) + "@" + PlatformAddr + ":" + std::to_string(PlatformPort);;
    
          CGCatalogInfo catalog = CG28181MediaInfo::GetCatalogByDeviceId(context.GetMediaAddrIp(), context.GetMediaAddrPort(), deviceId);
          if (catalog.TransportTypeSend != CGTransportType::BothUdpTcp)
           {
                if (catalog.TransportTypeSend != transport)
                {
                    response(rdata, PJSIP_SC_BAD_REQUEST, NoHead);
                    std::cout << catalog.DeviceID<< " TransportTypeSend incorrect"  << std::endl;
                    return true;
                }
           }
           if (catalog.Status != DeviceOnLine)
           {
              response(rdata, PJSIP_SC_BAD_REQUEST, NoHead);
              std:cout << catalog.DeviceID << " Device not online" << std::endl;
           }
    
           if (inv)
           {
               int port = CG28181MediaInfo::GetTransportBindingPort(context.GetMediaAddrIp(), context.GetMediaAddrPort(), catalog.ProtocolTypeRecv);
                            //pjmedia_sdp_session *newSdp = createRealStreamResponseSDP(sdp->sdp,false,ssrc);
                MediaContext mediaContextLocal;
                mediaContextLocal.SetSSRC(ssrc);
               auto &context = CGSipContext::GetInstance();
               mediaContextLocal.SetDeviceId(context.platformId);
              mediaContextLocal.SetTransportSrcPort(port);
               mediaContextLocal.SetTransportMediaServerAddr(context.GetAddr());
               std::string sdpInfo= createRealStreamUdpSDP(mediaContextLocal);
              context.InviteAnswerOk(inv, sdpInfo);
              MediaContext mediaContext;
              mediaContext.SetDeviceId(context.PjstrTostr(requestLineUri->user));
                            
               mediaContext.SetRecvProtocolType(catalog.ProtocolTypeRecv);
               mediaContext.SetSendProtocolType(catalog.ProtocolTypeSend);
               if (catalog.ProtocolTypeRecv == CGProtocolType::PT_GB28181)
               {
                    mediaContext.SetPlatformIP(catalog.PlatformAddr); 
                    mediaContext.SetPlatformPort(catalog.PlatformPort);
               }
               else
                {
                        mediaContext.SetPlatformIP(PlatformAddr);
                        mediaContext.SetPlatformPort(PlatformPort);
                }
                string receiveAddr = context.PjstrTostr(sdp->sdp->conn->addr);
                int receivePort = media->desc.port;
                mediaContext.SetRecvAddress(receiveAddr);
                mediaContext.SetRecvPort(receivePort);
                mediaContext.SetRequesterId(platformUUID);
                mediaContext.SetTransportSrcPort(port);
                mediaContext.SetSendRtpTransportType(static_cast<RtpTransportType>(transport));
                mediaContext.SetRecvRtpTransportType(static_cast<RtpTransportType>(catalog.TransportTypeRecv));
    
                mediaContext.SetStreamUrl(catalog.Url);
                mediaContext.SetSSRC(ssrc);
                std::shared_ptr<CGInviteSession> inviteSession = make_shared<CGInviteSession>();
                inviteSession->Init(context.PjstrTostr(callIdHdr->id), inv, mediaContext);
                CGInviteSessionGroup::GetInstance().Add(inviteSession);
                mediaContext.SetTime();
    
         }
         return true;
    
    }
    

      

        整个下级平台与上级平台交互的流程如下图所示:

                                 图5.  上、下级平台交互流程

             作为国标下级平台还有很多其他的功能,比如云台控制、报警、预置位设置等待,基本的流程跟发送Catalog

    相似这里不再赘述。后面找时间会整理下级平台demo放到公司网站。最后提供一个Android端国标app(本质也是国标下级平台)供测试使用 ,该app实现了上述的

    基本功能。下载地址http://www.chungen90.com/?news_32/

    如需交流可加QQ群 1038388075 

  • 相关阅读:
    <玩转Django2.0>读书笔记:模板和模型
    <玩转Django2.0>读书笔记:URL规则和视图
    学习随笔:Vue.js与Django交互以及Ajax和axios
    <算法图解>读书笔记:第4章 快速排序
    <算法图解>读书笔记:第3章 递归
    <算法图解>读书笔记:第2章 选择排序
    <算法图解>读书笔记:第1章 算法简介
    PostgreSQL自学笔记:与python交互
    AS3 setInterval
    AS3 事件流
  • 原文地址:https://www.cnblogs.com/wanggang123/p/14518162.html
Copyright © 2011-2022 走看看