ffmpeg编译gb28181_国标GB28181对接视频流
今天抽空写下以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对接视频流相关推荐
- ffmpeg编译gb28181_国标GB28181协议视频推流平台EasyGBD在Linux下编译报“UINT64_C在此作用领域中尚未声明”错误...
上一篇我们讲了国标GB28181协议视频推流平台EasyGBD正在重新编译,在编译中难免遇到不同的报错,比如"UINT64_C在此作用领域中尚未声明"错误就是其中一个. 出现该问题 ...
- 【解决方案】公安网内网如何通过国标GB28181协议将视频流对接至公众号进行直播?
我们发现越来越多的人开始用微信做直播,经过调研,我们了解到不仅是由于微信的用户越来越多,更是由于微信直播适应了现在用户终端移动化趋势的情况.采用微信直播,企业可以借助微信生态的优势,进行线上培训和营销 ...
- 【解决方案】公安网内网如何通过国标GB28181协议将视频流对接至微信公众号实现在线直播?
我们发现越来越多的人开始用微信做直播,经过调研,我们了解到不仅是由于微信的用户越来越多,更是由于微信直播适应了现在用户终端移动化趋势的情况.采用微信直播,企业可以借助微信生态的优势,进行线上培训和营销 ...
- 国标GB28181视频流媒体平台4G摄像头无插件直播平台EasyGBS出现实时视频点播错误问题解析
GB28181协议介绍 近年来,国内视频监控应用发展迅猛,系统接入规模不断扩大,涌现了大量平台提供商,平台提供商的接入协议各不相同,终端制造商需要给每款终端维护提供各种不同平台的软件版本,造成了极大的 ...
- 如何解决国标GB28181协议流媒体视频平台新增ws_flv视频流无法播放问题?
流媒体服务器最主要的作用就是进行视频流的转码分发,比如EasyGBS视频平台,可通过国标GB28181协议将视频流转化成可播放的视频.目前EasyGBS可以输出RTMP.HLS.FLV三种格式的视频流 ...
- 网络摄像头IPC国标GB28181协议国标安防视频流媒体平台EasyGBS视频流不上线排查步骤
近期使用国标GB28181协议平台的人越来越多,而我们的EasyGBS为了提升用户体验,近期做的升级也不少. 很多用户选择EasyGBS时,初期都由TSINGSEE青犀视频团队来进行产品的运维,日常运 ...
- 国标GB28181安防摄像头如何通过视频流媒体服务器建设阳光考场?
现实中,刷新三观的事件总是在不断发生,这段时间又是被山东考生高考被顶替19年的事寒透了心.考试是一件严肃的事,所以我们每个人都必须对此负责,不能容忍考试中作弊.顶替等不公平的事情发生. 近些年,全国都 ...
- 如何解决海康大华摄像头接入国标GB28181协议视频平台EasyGBS播放视频流,出现多个播放器的问题?
我们在前段时间更新了支持国标GB28181协议的视频平台EasyGBS,有很多用户都已经更新使用了,并且反响都比较好,新增的视频调阅功能满足了大部分人对直播界面直观显示的需求,所以我们仍在不断进行新的 ...
- 视频国标GB28181及一个相关平台的应用
一.国标GB28181 1.概述 所谓国标GB28181,是我国制订的一项视频流接入协议.好处是,只要摄像头支持该项协议,那么无论是海康还是大华,或者别的什么摄像头,都能接入一个支持该协议的媒体平台, ...
最新文章
- libsvm的安装和使用(1)
- SQL Server 中关于EXCEPT和INTERSECT的使用方法
- Vue3 Composition API(一)——setup、reactive、ref、readonly
- python怎么做回归分析_如何在Python中进行二维回归分析?
- Java中proc是什么意思,Java PatientProcedureVo.setSignifProc方法代码示例
- (十七)用JAVA编写MP3解码器——解码Layer1
- Java面试个人简历
- 如何监测土壤pH值和水分含量
- weka下载安装以及源码运行
- antd组件:Table表格去掉表格边框线,#字类型表格
- 《520婚恋报告》 20%的人婚后都无比后悔
- windows server 12 r2用Hyper-v安装centos7
- 开学送礼最佳选择,有名的蓝牙耳机推荐
- js 主动触发 a 标签 href 链接写法,
- 天宝S6测量机器人/天宝S6全站仪参数/教程/Trimble 天宝全站仪
- 多线程----守护线程---Deamon
- 【论文翻译】Automatic Conversion of Road Networks from OpenDRIVE to Lanelets
- android 背景描边,Android告别使用shape标签,自定义实现圆角、背景色、描边Button...
- 【知识】快乐物质:多巴胺和内啡肽(内酚酞)的区别
- php对mysql的增删改