如何利用UDP组播实现海康网络摄像机(IPC)的自动探测【源码】【监控】【录播】【NVR】【ONVIF】

前言:

    监控项目中,经常会遇到管理大量网络摄像机IPC的情况,如果每个IPC都要手动输入IP和端口,是非常繁琐的事情,于是,出现了与设备无关的ONVIF协议。海康提供的SADPTool就是基于ONVIF协议实现的,另外,还有 ONVIF Device Test Tool 官方的ONVIF协议测试工具。我们今天要实现的就是自己使用 VS2010 实现自己的ONVIF设备发现程序。

    ONVIF致力于通过全球性的开放接口标准来推进网络视频在安防市场的应用,这一接口标准将确保不同厂商生产的网络视频产品具有互通性。2008年11月,论坛正式发布了ONVIF第一版规范——ONVIF核心规范1.0。

ONVIF协议:

    在进入正式的编码之前,我们先对ONVIF协议的概念、组成、内容做一个大致的了解。

1、服务器和客户端:

    服务器:通常是你要对接的其他厂家的数字摄像头(IPC)

    客户端:通常是对接的IPC的设备程序,安防业内多称(NVR),当然其他软件工具也可称为客户端,如ONVIF Device Test Tool, vlc软件,浩一云监控的【采集端】。

    服务器,通常是以Web Server的形式出现的,等待 客户端 的接入,进行通讯交互;服务器 和 客户端 具体如何进行交互的呢?ONVIF协议为了方便 服务器 和 客户端 交互,并保持设备无关性,引入了一些基本语法和概念,具体如下:

2、基本语法和概念:

    XML:可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。

    HTTP:超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。

    SOAP:简单对象访问协议是交换数据的一种协议规范,是一种轻量的、简单的、基于XML(标准通用标记语言下的一个子集)的协议,它被设计成在WEB上交换结构化的和固化的信息。

    WSDL:网络服务描述语言是Web Service的描述语言,它包含一系列描述某个web service的定义。这里可以通俗的理解为协议定义。

    WS-discovery:你在预先不知道目标服务的情况下,可以动态的探测可用的服务并调用(这就是我们今天要实现的重点

3、ONVIF对接流程:

    搜索设备:客户端对接IPC第一件事,是在组网内发现可用的IPC的IP、端口,这里用到的是WS-discovery协议,当然WS-discovery协议本身又涉及到了xml,http,soap。这个协议是基于upd协议的广播(组播)包实现了,那么读者需要熟悉一下udp的数据广播(组播)原理了。后面的具体代码当中会详细说明。
    链接设备实现参数获取与设置:获取到组网内可用的IPC的IP、端口之后,需要建立tcp链接和服务器进行通信,来获取和设置参数了。这里面涉及的协议有xml,http,soap,wsdl。

源码下载:

    CSDN: https://download.csdn.net/download/haoyitech/10285918

源码说明:

    开发工具:下载后,请用 VS2010 打开。

    基本原理:这是一个基于对话框的 MFC 例子程序,点击“开始探测”按钮,会创建一个探测线程,每隔30秒向本地网络中发送组播(广播)数据探测包,标准的XML格式,如果本地网络中有在线的支持ONVIF协议的IPC会在收到探测包之后,向网络中发送回应数据包,例子程序会收到IPC的回应数据包,也是标准的XML格式,解析XML,通过 TRACE 命令打印在调试输出栏。

    为了实现这个自动探测过程,使用到了由浩一科技开发的公共辅助类,具体如下:

    OSThread => 基础线程类,对线程API进行了封装。
    CHyperLink => 静态框超链接功能实现类。
    Socket   => 基础套接字类,对套接字API进行了封装。
    UDPSocket => UDP套接字管理类,是对UDP相关API的封装。
    SocketUtils  => 套接字工具类,对套接字外围相关API进行了封装。
    StrPtrLen => 字符串管理类,可以对字符串指针和长度管理。
    CUtilTool => 针对系统常用工具的API封装。
    TinyXML  => 对XML的解析和保存做了封装。
    CHKUdpThread => 海康(IPC)网络摄像机自动探测类。

探测协议:

1、关键信息定义:

    组播地址 => 239.255.255.250
    组播端口 => 37020
    探测间隔 => 30 (秒)
    事件等待 => 5000(毫秒),探测线程使用事件等待,如果5秒都没有等来IPC的回应,就超时退出。

2、客户端发送的探测包内容:



15213211-C74B-4AEC-934E-DAF526FFC12C
inquiry

具体实现代码如下:(发送组播探测查询指令)

GM_Error CHKUdpThread::SendCmdQuiry()
{
	GM_Error theErr = GM_NoErr;
	GUID     theGuid = {0};
	string   strGuid;
	// 准备摄像机查询命令需要的数据...
	HRESULT  hr = ::CoCreateGuid(&theGuid);
	ASSERT( theGuid != GUID_NULL );
	strGuid = CUtilTool::GUIDToStr(theGuid);
	// 组合字符串,直接发送xml格式的命令...
	TCHAR szCmd[MAX_PATH] = {0};
	sprintf(szCmd, "%s\r\n%sinquiry", XML_DECLARE_UTF8, strGuid.c_str());
	theErr = m_UDPSocket.SendTo(szCmd, strlen(szCmd));
	// 发送数据包失败的处理...
	if( theErr != GM_NoErr ) {
		MsgLogGM(theErr);
		return theErr;
	}
	return GM_NoErr;
}

3、服务器反馈的数据包内容:


15213211-C74B-4AEC-934E-DAF526FFC12C
inquiry
10145
DS-2DE2106IW-D3
DS-2DE2106IW-D320160328CCCH586723321
8000
80
bc-31-28-11-19-52
192.168.2.65
255.255.255.0
192.168.2.1
::
::
64
false
0
1
V5.3.11build 151216
V7.2 build 151216
2018-03-14 15:11:52
false
0
true
true

具体实现代码如下:(解析获取到的IPC反馈数据包)

GM_Error CHKUdpThread::ForRead()
{
	UInt32 uRomAddr = 0;
	UInt16 uRomPort = 0;
	UInt32 uLenReturn = 0;	
	char   szBuffer[2048] = {0};
	// 获取网络组播数据内容...
	ASSERT(SocketUtils::GetNumIPAddrs() >= 1 );
	UInt32 iRemoteAddr = SocketUtils::GetIPAddr(0);
	GM_Error theErr = m_UDPSocket.RecvFrom(&uRomAddr, &uRomPort, szBuffer, 2048, &uLenReturn);
	if( theErr != GM_NoErr ) {
		MsgLogGM(theErr);
		return theErr;
	}
	// 打印获取的数据信息...
	TRACE("\r%s\r\n", szBuffer);
	// 扔掉来自本机的数据包...
	if( iRemoteAddr == uRomAddr )
		return GM_NoErr;
	ASSERT( iRemoteAddr != uRomAddr );
	// 解析获取到的数据包 => xml...
	TiXmlNode    * lpNode     = NULL;
	TiXmlElement * lpDataElem = NULL;
	TiXmlElement * lpRootElem = NULL;
	TiXmlDocument  theXDoc;
	theXDoc.Parse(szBuffer);
	lpRootElem = theXDoc.RootElement();
	// 解析xml节点错误...
	if( lpRootElem == NULL ) {
		theErr = GM_No_Xml_Node;
		MsgLogGM(theErr);
		return theErr;
	}
	// 遍历xml节点数据内容...
	LPCTSTR		lpszText = NULL;
	LPCTSTR		lpszValue = NULL;
	lpNode = lpRootElem->FirstChild();
	while( lpNode != NULL ) {
		lpDataElem = lpNode->ToElement();
		lpszValue = lpDataElem->Value();
		lpszText = lpDataElem->GetText();
		ASSERT( lpszValue != NULL && lpszText != NULL );
		// 去掉 Uuid 这个节点数据...
		if( stricmp(lpszValue, "Uuid") != 0 ) {
			//theMapData[lpszValue] = lpszText;
		}
		// 打印获取到的网络摄像机相关节点信息...
		TRACE("%s: %s\n", lpszValue, lpszText);
		// 继续下一个节点...
		lpNode = lpNode->NextSibling();
	}
	// 通知管理层,发现一个摄像头,需要进行事件处理...
	return GM_NoErr;
}

关键代码:(详见 Csample_udp_ipcDlg)

1、初始化公共函数库:

BOOL Csample_udp_ipcDlg::OnInitDialog()
{
        ...................
	// 初始化网络、线程、套接字...
	WORD	wsVersion = MAKEWORD(2, 2);
	WSADATA	wsData	  = {0};
	(void)::WSAStartup(wsVersion, &wsData);
	OSThread::Initialize();
	SocketUtils::Initialize();
        ...................
}

2、启动IPC探测线程:

void Csample_udp_ipcDlg::OnBnClickedButtonStart()
{
	// 删除已经创建的探测线程...
	if( m_lpHKUdpThread != NULL ) {
		delete m_lpHKUdpThread;
		m_lpHKUdpThread = NULL;
	}
	// 创建新的探测线程,并启动...
	GM_Error theErr = GM_NoErr;
	m_lpHKUdpThread = new CHKUdpThread();
	theErr = m_lpHKUdpThread->InitMulticast();
}
GM_Error CHKUdpThread::InitMulticast()
{
	// 建立UDP,接收组播...
	GM_Error theErr = GM_NoErr;
	theErr = m_UDPSocket.Open();
	if( theErr != GM_NoErr ) {
		MsgLogGM(theErr);
		return theErr;
	}
	// 设置重复使用端口...
	m_UDPSocket.ReuseAddr();
	// 必须直接绑定组播端口...
	theErr = m_UDPSocket.Bind(INADDR_ANY, DEF_MCAST_PORT);
	if( theErr != GM_NoErr ) {
		MsgLogGM(theErr);
	}
	// 创建事件对象...
	theErr = m_UDPSocket.CreateEvent();
	if( theErr != GM_NoErr ) {
		MsgLogGM(theErr);
		return theErr;
	}
	// 加入组播组,接收数据...
	theErr = m_UDPSocket.JoinMulticastForRecv(inet_addr(DEF_MCAST_ADDRV4), INADDR_ANY);
	if( theErr != GM_NoErr ) {
		MsgLogGM(theErr);
		return theErr;
	}
	// 加入组播组,使用第一IP地址发送数据包...
	ASSERT(SocketUtils::GetNumIPAddrs() >= 1 );
	theErr = m_UDPSocket.JoinMulticastForSend(inet_addr(DEF_MCAST_ADDRV4), htonl(SocketUtils::GetIPAddr(0)));
	if( theErr != GM_NoErr ) {
		MsgLogGM(theErr);
		return theErr;
	}
	// 设置TTL和组播地址,简化SendTo参数......
	m_UDPSocket.SetTtl(32);
	m_UDPSocket.SetRemoteAddr(DEF_MCAST_ADDRV4, DEF_MCAST_PORT);
	// 启动组播接收线程...
	this->Start();
	return theErr;
}

3、释放已分配的资源:

Csample_udp_ipcDlg::~Csample_udp_ipcDlg()
{
	// 删除已经创建的探测线程...
	if( m_lpHKUdpThread != NULL ) {
		delete m_lpHKUdpThread;
		m_lpHKUdpThread = NULL;
	}
	// 释放分配的系统资源...
	SocketUtils::UnInitialize();
	OSThread::UnInitialize();
	//::WSACleanup();
}

更多信息:

************************************************************
 * 浩一科技,提供云监控、云录播的全平台无插件解决方案。
 * 支持按需直播,多点布控,分布式海量存储,动态扩容;
 * 支持微信扫码登录,全平台帐号统一,关联微信小程序;
 * 支持多种数据输入:摄像头IPC、rtmp、rtsp、MP4文件;
 * 支持全实时、全动态、全网页管理,网页前后台兼容IE8;
 * 支持多终端无插件自适应播放,flvjs/hls/rtmp自动适配;
************************************************************
 * 官方网站 => https://myhaoyi.com
 * 技术博客 => http://blog.csdn.net/haoyitech
 * 开源代码 => https://github.com/HaoYiTech/

************************************************************

你可能感兴趣的:(ONVIF,采集端)