今天抽空写下以GB28181的方式获取摄像机视频流以备后用,同时也希望能帮助到正着手开发GB28181对接视频的同学,这块的资料实在不多。

今天讲的内容不涉及到平台对接,平台对接下次有时间再讲,平台对接相对更麻烦点。通过GB28181获取摄像机视频流,首先需要摄像机支持GB28181

,如何知道摄像机是否支持GB28181协议呢?请看下图:

图1.摄像机28181协议配置图

图1 展示了海康摄像机配置GB28181页面,其他厂家摄像机GB28181配置页面(我遇到的)基本跟海康配置的页面相同。

下面介绍下各配置项基本意义:

本地端口:默认为5060,SIP服务发送命令给摄像机时需要知道摄像机GB28181端口号,要不向哪发?

SIP服务器ID:说简单就是 服务器的标识,只不过这个标识有一定的要求,具体请参见28181-2001标准安全防范视频监控联网系统信息传输交换控制技术要求.pdf

当然也可以参考新点的文档,新旧文档这部分差异不大。文档在从群里下载。

SIP服务域:实际就是SIP服务器ID前10位。

SIP服务器地址:SIP服务所在机器的IP地址(如果存在多网卡建议将不用的网卡禁用掉)。

SIP服务器端口:SIP服务Port,其他SIP服务发送命令到此端口与之通信。

其他的配置默认即可。

GB28181配置好以后,需要启动摄像机GB28181服务。

启动摄像机GB28181的方法是勾选“启用”选项,启动成功后,摄像机会向SIP Server发送注册消息,通过抓包可以看到具体的注册消息内容:

图2 摄像机发送注册消息图

看下注册消息的具体内容:

图3 具体注册消息图

重要是Cantact信息,包含了摄像机GB28181 SIP ID 以及IP地址和端口号,这样与摄像机通信的SIP服务就知道往哪里回应答消息。

摄像机端基本介绍了完了(摄像机端相当于SIP Client),下面 介绍CG28181 服务端也即 SIP Server,这正是我们要实现的。

实现CG28181服务端可以借助于现有的开源库 PJSIP,自己实现开发量还是很大的,具体的实现步骤如下:

一. 将PJSIP运行起来,毕竟人家是一个服务。只有运行以后才能接收客户端发来的消息。

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(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 消息了。

二. 应答注册消息

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

开启摄像机,如果等摄像机自己再次发送注册消息可能是一个小时以后,我们当然不希望那么久。

服务端应答注册消息代码

bool OnReceive(pjsip_rx_data* rdata) override

{

if(rdata->msg_info.cseq->method.id == PJSIP_REGISTER_METHOD)

{

auto expires = static_cast(pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, nullptr));

auto authHdr = static_cast(pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, nullptr));

if(expires && expires->ivalue > 0 )

{

if(authHdr)

{

cout <

response(rdata, PJSIP_SC_OK, DateHead);

QureryDeviceInfo(rdata);

}

else

{

response(rdata, PJSIP_SC_UNAUTHORIZED, AuthenHead);

}

return true;

}

}

return false;

}

OnReceive 是服务端接收注册消息以后的响应方法,也就是说要将OnReceive作为入参传给PJSIP,完成此项功能在初始化

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方法实现了发送响应数据到客户端(摄像机):

void Response(pjsip_rx_data* rdata, int st_code,intheadType)

{

std::lock_guard 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)

{caseDateHead:

key= pj_str("Date");

hdr= reinterpret_cast(pjsip_date_hdr_create(pool, &key, pj_cstr(&c, date.c_str())));

pjsip_msg_add_hdr(tdata->msg, hdr);break;caseAuthenHead:

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);

}

实际也就是利用发PJSIP发送一些字符串给客户端。具体发送了些什么,可以抓个包看下。

图4 SIP服务应答注册消息

SIP 服务实际回了“200 OK” 给摄像机端。看下具体的消息内容:

图5  “200 OK” 具体内容

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

给出,具体的内容是:

void QueryDeviveInfo(GBDevice *device, const string& scheme = "Catalog")

{

char szQuerInfo[200] = { 0 };

pj_ansi_snprintf(szQuerInfo, 200,

"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"

"\n"

"%s\n"

"17430\n"

"%s\n"

"\n", scheme.c_str(), device->GetUser()

);

pjsip_tx_data *tdata;

const pjsip_method method = { PJSIP_OTHER_METHOD,{ "MESSAGE", 7 } };

auto text = StrToPjstr(string(szQuerInfo));

pjsip_endpt_create_request(endPoint, &method, &StrToPjstr(device->GetSipIpUrl()), &StrToPjstr(concat), &StrToPjstr(device->GetSipCodecUrl()),&StrToPjstr(concat), nullptr, -1, &text, &tdata);

tdata->msg->body->content_type.type = pj_str("Application");

tdata->msg->body->content_type.subtype = pj_str("MANSCDP+xml");

pjsip_endpt_send_request(endPoint, tdata, -1, nullptr, nullptr);

}

SIP服务端 发送了请求catalog  消息,摄像机端收到消息发送其自身的catalog消息,SIP 服务端将在OnReceive中收到具体的catalog消息。取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<:string>("CmdType");

if (cmdType != "Catalog") return false;

auto DeviceID = dynamicStruct.Get<:string>("DeviceID");

Vector deviceList = dynamicStruct.Get("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();

devinfo.Name = WstringToString(x["Name"].convert());

devinfo.Manufacturer = x["Manufacturer"].convert();

devinfo.Model = x["Model"].convert();

devinfo.Owner = x["Owner"].convert();

devinfo.Civilcode = x["CivilCode"].convert();

devinfo.Registerway = x["RegisterWay"].convert();

devinfo.Secrecy = x["Secrecy"].convert();

//devinfo.IPAddress = x["IPAddress"].convert();

devinfo.DeviceID = x["DeviceID"].convert();

devinfo.Status= x["Status"].convert();

}

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(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(subjectUrl.c_str()));

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

pjsip_msg_add_hdr(tdata->msg, reinterpret_cast(hdr));

pjsip_inv_send_msg(inv, tdata);

}

catch (...)

{

}

return true;

}

代码就不解释了,要想知道到底发了什么还是抓个包看看,无论你用什么方法只要抓包的数据是正确定说明发送成功了。

图6 服务端发送invite视频消息

摄像机端收到Invite请求后,会将视频数据以rtp的方式推送到指定的端口,端口在invite消息指定。

这样在指定的地址(ip + port)就可以拿到数据了。

最后提供一个测试demo,demo的作用是可以让大家抓包,看看双方都发了些什么。

demo运行界面如下:

图6 demo运行初始界面

1.运行demo后,首先配置好配置,如果不知道可以默认,但IP地址需要修改,端口不能被占用。

2.完成配置各配置项以后点击获取视频源按钮 等待摄像机端注册。

3.摄像机端开启28181功能:具体的方法可以是:平台选择方式下拉框先选择一个非28181方式,点击保存,再选择28181方式并点击保存。

4.摄像机端成功开启28181功能以后,视频源下拉框中会显示摄像机的名称信息。

5.选中视频源下拉框中出现的选项并点击播放按钮,正常情况下会可以播放从摄像机端过来的视频流。

成功接入视频源并播放的运行界面如下。

图7 demo成功运行以后的界面

Demo 可以在群里下载。

如需交流,可以加QQ群1038388075,766718184,或者QQ:350197870

ffmpeg编译gb28181_国标GB28181对接视频流相关推荐

  1. ffmpeg编译gb28181_国标GB28181协议视频推流平台EasyGBD在Linux下编译报“UINT64_C在此作用领域中尚未声明”错误...

    上一篇我们讲了国标GB28181协议视频推流平台EasyGBD正在重新编译,在编译中难免遇到不同的报错,比如"UINT64_C在此作用领域中尚未声明"错误就是其中一个. 出现该问题 ...

  2. 【解决方案】公安网内网如何通过国标GB28181协议将视频流对接至公众号进行直播?

    我们发现越来越多的人开始用微信做直播,经过调研,我们了解到不仅是由于微信的用户越来越多,更是由于微信直播适应了现在用户终端移动化趋势的情况.采用微信直播,企业可以借助微信生态的优势,进行线上培训和营销 ...

  3. 【解决方案】公安网内网如何通过国标GB28181协议将视频流对接至微信公众号实现在线直播?

    我们发现越来越多的人开始用微信做直播,经过调研,我们了解到不仅是由于微信的用户越来越多,更是由于微信直播适应了现在用户终端移动化趋势的情况.采用微信直播,企业可以借助微信生态的优势,进行线上培训和营销 ...

  4. 国标GB28181视频流媒体平台4G摄像头无插件直播平台EasyGBS出现实时视频点播错误问题解析

    GB28181协议介绍 近年来,国内视频监控应用发展迅猛,系统接入规模不断扩大,涌现了大量平台提供商,平台提供商的接入协议各不相同,终端制造商需要给每款终端维护提供各种不同平台的软件版本,造成了极大的 ...

  5. 如何解决国标GB28181协议流媒体视频平台新增ws_flv视频流无法播放问题?

    流媒体服务器最主要的作用就是进行视频流的转码分发,比如EasyGBS视频平台,可通过国标GB28181协议将视频流转化成可播放的视频.目前EasyGBS可以输出RTMP.HLS.FLV三种格式的视频流 ...

  6. 网络摄像头IPC国标GB28181协议国标安防视频流媒体平台EasyGBS视频流不上线排查步骤

    近期使用国标GB28181协议平台的人越来越多,而我们的EasyGBS为了提升用户体验,近期做的升级也不少. 很多用户选择EasyGBS时,初期都由TSINGSEE青犀视频团队来进行产品的运维,日常运 ...

  7. 国标GB28181安防摄像头如何通过视频流媒体服务器建设阳光考场?

    现实中,刷新三观的事件总是在不断发生,这段时间又是被山东考生高考被顶替19年的事寒透了心.考试是一件严肃的事,所以我们每个人都必须对此负责,不能容忍考试中作弊.顶替等不公平的事情发生. 近些年,全国都 ...

  8. 如何解决海康大华摄像头接入国标GB28181协议视频平台EasyGBS播放视频流,出现多个播放器的问题?

    我们在前段时间更新了支持国标GB28181协议的视频平台EasyGBS,有很多用户都已经更新使用了,并且反响都比较好,新增的视频调阅功能满足了大部分人对直播界面直观显示的需求,所以我们仍在不断进行新的 ...

  9. 视频国标GB28181及一个相关平台的应用

    一.国标GB28181 1.概述 所谓国标GB28181,是我国制订的一项视频流接入协议.好处是,只要摄像头支持该项协议,那么无论是海康还是大华,或者别的什么摄像头,都能接入一个支持该协议的媒体平台, ...

最新文章

  1. libsvm的安装和使用(1)
  2. SQL Server 中关于EXCEPT和INTERSECT的使用方法
  3. Vue3 Composition API(一)——setup、reactive、ref、readonly
  4. python怎么做回归分析_如何在Python中进行二维回归分析?
  5. Java中proc是什么意思,Java PatientProcedureVo.setSignifProc方法代码示例
  6. (十七)用JAVA编写MP3解码器——解码Layer1
  7. Java面试个人简历
  8. 如何监测土壤pH值和水分含量
  9. weka下载安装以及源码运行
  10. antd组件:Table表格去掉表格边框线,#字类型表格
  11. 《520婚恋报告》 20%的人婚后都无比后悔
  12. windows server 12 r2用Hyper-v安装centos7
  13. 开学送礼最佳选择,有名的蓝牙耳机推荐
  14. js 主动触发 a 标签 href 链接写法,
  15. 天宝S6测量机器人/天宝S6全站仪参数/教程/Trimble 天宝全站仪
  16. 多线程----守护线程---Deamon
  17. 【论文翻译】Automatic Conversion of Road Networks from OpenDRIVE to Lanelets
  18. android 背景描边,Android告别使用shape标签,自定义实现圆角、背景色、描边Button...
  19. 【知识】快乐物质:多巴胺和内啡肽(内酚酞)的区别
  20. php对mysql的增删改

热门文章

  1. PHY(Physical Layer,PHY)通俗理解
  2. PHP中防止SQL注入的方法
  3. 【计算机组成原理】奇偶校验码
  4. 单机游戏副业项目,赚钱的虚拟项目,适合新手
  5. Vue项目 HBuilder X打包H5 APP
  6. 句句经典,挺伤感的几句话
  7. The free SSH client for Android 之JuiceSSH神器
  8. PHP面试问题总结整理
  9. 微软new bing chatgpt 逆向爬虫实战
  10. LeetCode224 基本计算器