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\" ?>\n""<Notify>\n""<CmdType>Keepalive</CmdType>\n""<SN>12</SN>\n""<DeviceID>%s</DeviceID>\n""<Status>OK</Status>\n""</Notify>\n", 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

GB28181 下级平台(设备)实现相关推荐

  1. 几款支持GB28181的平台

    1.第一款就是海康自己的ISC,HIKVISION iSecure Center综合安防管理平台. 入口 提供视频实时预览.网络录像回放.语音对讲取流URL获取能力,通过集成视频SDK.APPSDK实 ...

  2. LiveGBS流媒体平台GB/T28181功能-作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备

    LiveGBS作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备 1.背景说明 2.部署国标平台 2.1.安装使用说明 2.2.服务器网络环境 2.3.信令服务配 ...

  3. GB28181上级平台接入下级平台

    GB28181上级平台接入下级平台 最近在搞gb28181平台接入,记录下吧,github找的demo,看了下流程,发现下级平台注册和ipc注册是一样的,不过就是catalog 不一样 外部库 使用了 ...

  4. GB28181流媒体平台LiveGBS中设置摄像头设备报警订阅、报警查询,以及抓图设备报警图片和录像的步骤

    LiveGBS国标GB/T28181配置报警订阅配置报警预案告警计划自动触发快照截取视频录像快照关联录像关联 1.报警信息 1.1.报警查询 1.2.配置开启报警订阅 1.2.1.国标设备编辑 1.2 ...

  5. LiveGBS流媒体平台国标GB/T28181功能-国标流媒体服务平台作为上级接入海康大华华为宇视等下级平台及摄像头

    LiveGBS国标流媒体服务平台作为上级接入海康大华华为宇视等下级平台及摄像头 1.背景说明 2.部署国标平台 2.1.安装使用说明 2.2.服务器网络环境 2.3.信令服务配置 3.监控摄像头设备接 ...

  6. 基于srs流媒体服务器搭建gb28181视频平台的微服务系统架构

    gb28181安防视频平台 引言 安防就是视频监控,小区或者办公室装几个摄像头,物业或者保安在监控室盯着大 屏坐一整天. 对于安防架构的理解:摄像头+网络布线+数据存储管理硬盘 (RAID)+媒体软件 ...

  7. 海康、大华、华为等GB28181国标平台向上级联给LiveGBS GB28181平台的操作示例

    @ 目录 1.部署安装国标视频平台 1.1.服务器网络环境 1.2.信令服务配置 2.接入网络摄像头设备 2.1.海康GB28181接入示例 2.2.大华GB28181接入示例 2.3.华为IPC G ...

  8. 下级平台科达录像机级联接入EasyCVR出现字段报错是什么原因?

    安防市场的不断高清化.智能化,推动了视频监控技术与监控平台的升级.智能化设备产生的海量数据,也促使平台朝着综合化.网格化.集成化方向的发展.EasyCVR具备较强的视频能力,可支持海量设备接入.汇聚与 ...

  9. LiveGBS流媒体平台国标GB/T28181作为上级平台对接海康大华华为宇视等下级平台硬件NVR监控摄像机

    LiveGBS流媒体平台国标GB/T28181作为上级平台对接海康.大华.华为.宇视等下级平台NVR硬件监控摄像机 1.背景说明 2.部署国标平台 2.1.安装使用说明 2.2.服务器网络环境 2.3 ...

最新文章

  1. 工业机器人抓取时怎么定位的?用什么传感器来检测?
  2. [置顶] 电信系统方案 电信Boss系统
  3. Linux_系统进程管理
  4. 洛谷 P3177 [HAOI2015]树上染色
  5. linux输出文字的颜色特效
  6. 一休自评应聘:我是如何进入51CTO的?
  7. 听说Mutex源码是出名的不好看,我不信,来试一下
  8. windows和linux没有启动选择,重装Windows后,LILO启动选单不见了,无法进入Linux系统怎么办...
  9. s:property=a value=/取的s:debug/s:debug中的value stack中的属性值
  10. python 创建工具包_使用Python工具建立网站
  11. Python简单的拼写检查
  12. feign.RetryableException: Read timed out executing POST http://......
  13. PAT (Basic Level) Practice1023 组个最小数
  14. linux DNS 简单配置
  15. 通过允许指定IP访问apahce虚拟主机加强服务器安全
  16. 国内最大最专业最活跃的前十大FPGA论坛社区网站精选
  17. “不靠谱“的布隆过滤器是怎么成为大数据世界中的韦小宝的?
  18. python正则匹配中文/英文/数字/其它字符
  19. Kanzi 记录:界面整体介绍(一),自己理解,绝非生搬硬套。
  20. FFmpeg视频剪辑常用命令

热门文章

  1. softAP下的踩坑笔记
  2. 计算机音频服务无法启动,win10系统audioendpointbuilder音频服务无法启动的解决办法...
  3. vue使用lodop打印控件
  4. docker: Error response from daemon: could not select device driver ““ with capabilities: [[gpu]]
  5. 安装haroopad
  6. 专业技能测试计算机知识,广安职业技术学院2020年单招技能测试大纲(计算机相关专业)...
  7. 实例解说:车主如何不花一分钱不出一份力处理交通事故
  8. github使用教程图文详解(一)[入门]
  9. 学习Python的最好网站
  10. 【报错】overleaf不能成功编译中文(在线latex)