GB28181设备对接视频流的流程

搭建CG28181 服务端,也即 SIP Server,这正是我们要实现的。实现CG28181服务端可以借助于现有的开源库 PJSIP,具体的实现步骤如下:

1、启动GB28181服务端,接收客户端消息请求

bool Init(std::string concat, int logLevel)
{
    this->concat = concat;
    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, &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;
      auto realm = StrToPjstr(GetLocalDomain());
      return pjsip_auth_srv_init(pool, &authentication, &realm, lookup, 0) == PJ_SUCCESS ? true : false;
}

以上是PJSip初始化的代码,需要将服务将要监听的端口传给PJSIP,这样服务就在监听的端口接收SIP 消息了。

2、应答注册消息

摄像机端发送来Register消息后,如果服务端不应答,摄像机端会一直发送直到收到服务端应答为止。如果服务器端重新运行,需要手动再次开启设备。

服务端应答注册消息代码如下:

bool OnReceive(pjsip_rx_data* rdata) override
{
    if(rdata->msg_info.cseq->method.id == PJSIP_REGISTER_METHOD)
    {
      auto expires = static_cast<pjsip_expires_hdr*>(pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, nullptr));
      auto authHdr = static_cast<pjsip_authorization_hdr*>(pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, nullptr));
      if(expires && expires->ivalue > 0 )
      {
               if(authHdr)
               { 
          cout <<"receive register info"<<endl;
          response(rdata, PJSIP_SC_OK, DateHead);
          QureryDeviceInfo(rdata);
               }
            else
             {
          response(rdata, PJSIP_SC_UNAUTHORIZED, AuthenHead);
              }
              return true;  
      }
    }
     return false;
}

OnReceive 是服务端接收注册消息以后的响应方法,也就是说要将OnReceive作为入参传给PJSIP,完成此项功能在初始化<br>PJSIP Moudle时。至于PJSIP moudle,细节的话,可以查看PJSIP文档,代码如下:

bool  Init(std::string concat, int loglevel)
{
  bool ret = false;
 
  if(!mainModule)
 {
    ret = context.Init(concat,loglevel);
    if(!ret) return ret;
    static struct pjsip_module moudle =
    {
      nullptr, 
    nullptr,
      { "MainModule", 10 },
      -1,
     PJSIP_MOD_PRIORITY_APPLICATION,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
    &CGSipMedia::OnReceive,
    nullptr,
    nullptr,
    nullptr,
 
    };
    mainModule = &moudle;
    pjsip_inv_callback callback;
    pj_bzero(&callback, sizeof(callback));
    callback.on_state_changed = &onStateChanged;
    callback.on_new_session = &onNewSession;
    callback.on_tsx_state_changed = &onTsxStateChanged;
    callback.on_rx_offer = &onRxOffer;
    callback.on_rx_reinvite = &onRxReinvite;
    callback.on_create_offer = &onCreateOffer;
    callback.on_send_ack = &onSendAck;
    ret = context.RegisterCallback(&callback);
    if(!ret ) return ret;
    context.InitModule();
    ret  = context.RegisterModule(mainModule);
    if(!ret ) return ret;
    CGSipModule::GetInstance().Init();
    ret = context.CreateWorkThread(&proc,workthread,nullptr,"proxy");
    }
    return ret;
    }

OnReceive方法内Resonse方法实现了发送响应数据到客户端(设备):

接到PJSIP发送一些字符串给客户端:

void  Response(pjsip_rc_data* rdata, int st_code, int headType)
{
std::lock_guard<mutex> lk(lock);
pjsip_tx_data* tdata;
pjsip_endpt_create_response(endPoint, rdata, st_code, nullptr, &tdata);
auto date = DateTimeFormatter::format(LocalDateTime(), "Y-%m-%dT%H:%M:%s");
pj_str_t c;
pj_str_t key;
pjsip_hdr * hdr;
switch(headType)
{
case  DateHead:
key = pj_str("Date");
hdr = reinterpret_cast<pjsip_hdr*>(pjsip_date_hdr_create(poll, &key, pj_cstr(&c, date.c_str())));
pjsip_msg_add_hdr(tdata->msg, hdr);
break;
case AuthenHead:
       pjsip_auth_srv_challenge(&authentication, nullptr, nullptr, nullptr, PJ_FALSE, tdata);
break;
default:
break;
}
          pjsip_response_addr addr;
          pjsip_get_response_addr(pool, rdata, &addr);
   pjsip_endpt_send_response(endPoint, &addr, tdata, nullptr, nullptr);
 
}

SIP服务端响应注册命令后,发送Invite请求,请求catalog信息,也就是设备基本信息,具体的方法上面已

给出,具体的内容是:

bool OnReceive(pjsip_rx_data* rdata) override
{
  if (rdata->msg_info.cseq->method.id == PJSIP_OTHER_METHOD)
  {
  	      CGXmlParser xmlParser(context.GetMessageBody(rdata));
CGDynamicStruct dynamicStruct;
               dynamicStruct.Set(xmlParser.GetXml());
               auto cmd = xmlParser.GetXml()->firstChild()->nodeName();
               auto cmdType = dynamicStruct.Get<std::string>("CmdType");
               if (cmdType != "Catalog") return false;
              auto DeviceID = dynamicStruct.Get<std::string>("DeviceID");
             Vector deviceList = dynamicStruct.Get<Vector>("DeviceList");
    for (auto& x : deviceList)
    {
      CGCatalogInfo devinfo;
    try
    {
      devinfo.PlatformAddr = rdata->pkt_info.src_name;
      devinfo.PlatformPort = rdata->pkt_info.src_port;
      devinfo.Address = x["Address"].convert<string>();
      devinfo.Name = WstringToString(x["Name"].convert<wstring>());
      devinfo.Manufacturer = x["Manufacturer"].convert<string>();
      devinfo.Model = x["Model"].convert<string>();
      devinfo.Owner = x["Owner"].convert<string>();
      devinfo.Civilcode = x["CivilCode"].convert<string>();
      devinfo.Registerway = x["RegisterWay"].convert<int>();
      devinfo.Secrecy = x["Secrecy"].convert<int>();
      //devinfo.IPAddress = x["IPAddress"].convert<string>();
      devinfo.DeviceID = x["DeviceID"].convert<string>();
      devinfo.Status= x["Status"].convert<string>();
    }
    catch (...)
    {
        //continue;
    }
    if(callback)
    {
        callback(user, &devinfo);
    }
  	  //SipControlModule::GetInstance().CatalogCallBack(devinfo);
  }
    response(rdata, PJSIP_SC_OK,NoHead);
    return true;
}

SIP服务端获取设备端的信息后就可以发送请求视频信息了,请求视频最为关键的是SDP,下面看下SDP信息如何填写:

static string createSDP(MediaContext& mediaContext)
{
    char str[500] = { 0 };
    pj_ansi_snprintf(str, 500,
    "v=0\n"
    "o=%s 0 0 IN IP4 %s\n"
    "s=Play\n"
    "c=IN IP4 %s\n"
    "t=0 0\n"
    "m=video %d RTP/AVP 96 98 97\n"
    "a=recvonly\n"
    "a=rtpmap:96 PS/90000\n"
    "a=rtpmap:98 H264/90000\n"
    "a=rtpmap:97 MPEG4/90000\n"
    "y=0100000001\n",
    mediaContext.GetDeviceId().c_str(),
    mediaContext.GetRecvAddress().c_str(),
    mediaContext.GetRecvAddress().c_str(),
    mediaContext.GetRecvPort()
    );
    return str;
}

发送请求视频命令到设备端也是通过PJSIP API实现,实现代码如下:

bool Invite(pjsip_dialog *dlg, MediaContext mediaContext, string sdp)

{

pjsip_inv_session *inv;

if (PJ_SUCCESS != pjsip_inv_create_uac(dlg, nullptr, 0, &inv)) return false;

pjsip_tx_data *tdata;

if (PJ_SUCCESS != pjsip_inv_invite(inv, &tdata)) return false;

pjsip_media_type type;

type.type = pj_str("application");

type.subtype = pj_str("sdp");

auto text = pj_str(const_cast<char *>(sdp.c_str()));

try

{

tdata->msg->body = pjsip_msg_body_create(pool, &type.type, &type.subtype, &text);

auto hName = pj_str("Subject");

auto subjectUrl = mediaContext.GetDeviceId() + ":" + SiralNum + "," + GetInstance().GetCode() + ":" + SiralNum;

auto hValue = pj_str(const_cast<char*>(subjectUrl.c_str()));

auto hdr = pjsip_generic_string_hdr_create(pool, &hName, &hValue);

pjsip_msg_add_hdr(tdata->msg, reinterpret_cast<pjsip_hdr*>(hdr));

pjsip_inv_send_msg(inv, tdata);

}

catch (...)

{

}

return true;

}

设备端收到Invite请求后,会将视频数据以rtp的方式推送到指定的端口,端口在invite消息指定。这样在指定的地址(ip + port)就可以拿到数据了。

交流联系:

微信:

LiveMedia视频汇聚平台www.houhangkeji.com

QQ技术交流群:698793654

相关推荐
LNTON羚通1 小时前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
小屁孩大帅-杨一凡6 小时前
Python-flet实现个人视频播放器
开发语言·python·音视频
EasyCVR9 小时前
私有化部署视频平台EasyCVR宇视设备视频平台如何构建视频联网平台及升级视频转码业务?
大数据·网络·音视频·h.265
天空中的野鸟9 小时前
Android音频采集
android·音视频
计算机毕设孵化场10 小时前
计算机毕设-基于springboot的高校网上缴费综合务系统视频的设计与实现(附源码+lw+ppt+开题报告)
java·spring boot·计算机外设·音视频·课程设计·高校网上缴费综合务系统视频·计算机毕设ppt
隔着天花板看星星11 小时前
Kafka-Consumer理论知识
大数据·分布式·中间件·kafka
隔着天花板看星星11 小时前
Kafka-副本分配策略
大数据·分布式·中间件·kafka
wenyue112113 小时前
Ease Monitor 会把基础层,中间件层的监控数据和服务的监控数据打通,从总体的视角提供监控分析
运维·中间件·监控
简鹿办公17 小时前
如何提取某站 MV 视频中的音乐为 MP3 音频
音视频·简鹿视频格式转换器·视频提取mp3音频
yufengxinpian17 小时前
集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1
单片机·嵌入式硬件·音视频·智能硬件