onvif使用介绍及ipc设备构建
onvif入门思路及快速理解开发过程和协议抓取分析。
onvif介绍
ONVIF是一个开放的安防行业组织,致力于为安防行业提供和促进标准化开放接口,
以实现IP网络安防产品的有效互操作性。
ONVIF通过一系列规范profiile A、C、D、G、M、S、T ,涵盖局域网和广域网场景的网络视频用例,并已扩展到涵盖通用 IP 设备用例;也就是通过这些规范定制了一套符合IP网络设备应用于不同场景的产品通用协议接口定义。
- ONVIF的形成:ONVIF原意为开放型网络视频接口论坛,即Open Network Video Interface Forum;是安讯士、博世、索尼等多家公司在2008年共同成立的一个国际性开放型网络视频产品标准网络接口的开发论坛,后来由这个技术开发论坛共同制定的开放性行业标准,习惯性简称为ONVIF协议。
- ONVIF协议:ONVIF规范包括像网络配置,查找设备,设备管理,PTZ摄像机控制,和视频分析等。这些规格都被写入到ONVIF配置文件(ONVIF Profiles)。 其中Profile C专门为网络门禁控制系统的协议标准, Profile G用于视频存储、搜索和重放管理;而Profile S应用于网络视频监控系统。
- ONVIF的作用:ONVIF标准将为网络视频设备之间的信息交换定义通用协议,包括装置搜寻、实时视频、音频、元数据和控制信息等。解决了不同厂商之间开发的各类设备不能接入使用的难题,即最终能够通过ONVIF这个标准化的平台实现不同产品之间的集成。
- ONVIF的实现机制:ONVIF协议中规定,服务端和客户端之间采用soap协议进行交互,而视频流的传输与控制采用rtsp协议。
框架介绍
ONVIF规范通过定义网络设备的服务器端接口,定义了一组核心接口函数,用于网络设备的配置和操作,所有服务共享一个通用 XML 模式,所有数据类型都在 [ONVIF 模式] 中提供。不同的服务在相应的部分和服务 WSDL 文档中定义。
XML 用作数据描述语法,SOAP 用于消息传输,WSDL 用于描述服务。
该框架建立在 Web Services标准之上。标准中定义的所有配置服务都表示为 Web Services操作,并以 HTTP 作为底层传输机制在 WSDL 中定义。下图为soap的设计理念,所谓的web services也是基于soap+wsdl来实现。
RPC的实现是基于WSDL描述的一套接口函数调用实现的。
服务端一般指IPC设备端,客户端一般指NVR设备。
术语介绍
-
Web Services:一种跨编程语言和跨操作系统平台的远程调用技术。Web Service也叫XML Web Service WebService是一种可以接收从Internet或者Intranet上的其它系统中传递过来的请求,轻量级的独立的通讯技术。是:通过SOAP在Web上提供的软件服务,使用WSDL文件进行说明,并通过UDDI进行注册。
-
Gsoap:gsoap是一种编译工具,提供了一个SOAP/XML 关于C/C++ 语言的实现,从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多。用于根据WSDL生成SOAP框架代码,可以像本地调用一样调用gsoap生成的代码接口,这些代码接口最终转化为soap协议的XML语法数据进行传递。
-
SOAP:(Simple Object Access Protoco)简单对象访问协议是在分散或分布式的环境中交换信息的简单的协议,是一个基于XML 的协议。协议格式如下:
-
RPC: Remote Procedure Call 是一种进程间通信方式。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的,本质上编写的调用代码基本相同
-
XML:指可扩展标记语言(EXtensible Markup Language)。
-
WSDL:WSDL描述Web服务的公共接口。 这是一个基于XML的关于如何与Web服务通讯和使用的服务描述;也就是描述与目录中列出的Web服务进行交互时需要绑定的协议和信息格式。
-
IPC:网络摄像机。
-
NVR:网络视频录像机。
开发流程
可以这么说,onvif就是一套技术规范,他提供了协议接口定义wsdl,我通过填充wsdl协议接口参数来进行设备发现,设备音视频源获取(rtsp码流)。所以onvif和rtsp是两个不同协议,只是onvif负责信息交互,而rtsp负责码流播放。
ipc设备端基础架构可以采用gsoap+wsdl生成web services框架,然后rtsp可以采用ffmpeg或者live555来进行码流传输。NVR(IPC客户端)可以采用gsoap+wsdl生成client基础代码,用ffmpeg获取rtsp码流,并解码h264、265码流。另外如果嫌gsoap麻烦可以自己去组装wsdl协议(相对麻烦)只需要填充参数部分然后将xml数据协议发出。
代码示例:
设备发现:
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <netdb.h>
//#include <errno.h>
//#include <uuid/uuid.h>
#define BUFLEN 4095
int main (int argc, char **argv)
{
char uu_buf[1024]={0};
char *cust_uuid = 0;
struct sockaddr_in groupcast_addr,the_member;
int sockfd;
unsigned char loop;
char recmsg[BUFLEN + 1];
unsigned int socklen, n;
struct ip_mreq mreq;
char *aa= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:SOAP-ENC=\"http://www.w3.org/2003/05/soap-encoding\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:wsdd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:chan=\"http://schemas.microsoft.com/ws/2005/02/duplex\" xmlns:wsa5=\"http://www.w3.org/2005/08/addressing\" xmlns:xmime=\"http://tempuri.org/xmime.xsd\" xmlns:xop=\"http://www.w3.org/2004/08/xop/include\" xmlns:tt=\"http://www.onvif.org/ver10/schema\" xmlns:wsrfbf=\"http://docs.oasis-open.org/wsrf/bf-2\" xmlns:wstop=\"http://docs.oasis-open.org/wsn/t-1\" xmlns:wsrfr=\"http://docs.oasis-open.org/wsrf/r-2\" xmlns:tdn=\"http://www.onvif.org/ver10/network/wsdl\" xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tev=\"http://www.onvif.org/ver10/events/wsdl\" xmlns:wsnt=\"http://docs.oasis-open.org/wsn/b-2\" xmlns:tptz=\"http://www.onvif.org/ver20/ptz/wsdl\" xmlns:trt=\"http://www.onvif.org/ver10/media/wsdl\">\n\
<SOAP-ENV:Header>\n\
<wsa:MessageID>uuid:2419d68a-2dd2-21b2-a205-4A69A95DB56D</wsa:MessageID>\n\
<wsa:RelatesTo>uuid:";
char *bb = "</wsa:RelatesTo>\n\
<wsa:To SOAP-ENV:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>\n\
<wsa:Action SOAP-ENV:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action>\n\
</SOAP-ENV:Header>\n\
<SOAP-ENV:Body>\n\
<wsdd:ProbeMatches>\n\
<wsdd:ProbeMatch>\n\
<wsa:EndpointReference>\n\
<wsa:Address>urn:uuid:2419d68a-2dd2-21b2-a205-4A69A95DB56D</wsa:Address>\n\
<wsa:ReferenceProperties />\n\
<wsa:ReferenceParameters />\n\
<wsa:PortType>ttl</wsa:PortType>\n\
</wsa:EndpointReference>\n\
<wsdd:Types>tds:Device</wsdd:Types>\n\
<wsdd:Scopes>onvif://www.onvif.org/type/NetworkVideoTransmitter\r\nonvif://www.onvif.org/name/IPC_2802222\r\nonvif://www.onvif.org/location/Country/China</wsdd:Scopes>\n\
<wsdd:XAddrs>http://192.168.42.100:5000/onvif/device_service</wsdd:XAddrs>\n\
<wsdd:MetadataVersion>1</wsdd:MetadataVersion>\n\</wsdd:ProbeMatch>\n\
</wsdd:ProbeMatches>\n\
</SOAP-ENV:Body>\n\
</SOAP-ENV:Envelope>\n\n";
/* 创建 socket 用于UDP通讯 */
sockfd = socket (AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
printf ("socket creating err in udptalk\n");
exit (1);
}
/* 设置要加入组播的地址 */
bzero(&mreq, sizeof (struct ip_mreq));
inet_pton(AF_INET,"239.255.255.250",&the_member.sin_addr);
/* 设置组地址 */
bcopy (&the_member.sin_addr.s_addr, &mreq.imr_multiaddr.s_addr, sizeof (struct in_addr));
/* 设置发送组播消息的源主机的地址信息 */
mreq.imr_interface.s_addr = htonl (INADDR_ANY);
/* 把本机加入组播地址,即本机网卡作为组播成员,只有加入组才能收到组播消息 */
//if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP | IP_MULTICAST_LOOP, &mreq,sizeof (struct ip_mreq)) == -1)
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP , &mreq,sizeof (struct ip_mreq)) == -1)
{
perror ("setsockopt");
exit (-1);
}
loop = 0;
if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop,sizeof (loop)) == -1)
{
printf("IP_MULTICAST_LOOP set fail!\n");
}
socklen = sizeof (struct sockaddr_in);
memset (&groupcast_addr, 0, socklen);
groupcast_addr.sin_family = AF_INET;
groupcast_addr.sin_port = htons (3702);
inet_pton(AF_INET, "239.255.255.250", &groupcast_addr.sin_addr);
/* 绑定自己的端口和IP信息到socket上 */
if (bind(sockfd, (struct sockaddr *) &groupcast_addr,sizeof (struct sockaddr_in)) == -1)
{
printf ("Bind error\n");
exit (0);
}
while (1)
{
bzero (recmsg, BUFLEN + 1);
n = recvfrom (sockfd, recmsg, BUFLEN, 0, (struct sockaddr *) &the_member, &socklen);
if (n < 0)
{
printf ("recvfrom err in udptalk!\n");
exit (4);
}
else{
recmsg[n] = 0;
printf ("recv:[%s]\n\n", recmsg);
printf("ip:%s\n",inet_ntoa(the_member.sin_addr));
printf("port:%d\n", ntohs(the_member.sin_port));
}
cust_uuid = strstr(recmsg, "uuid:"); //获取recmsg字符串中 子字符串"uuid:"的位置
if (cust_uuid == 0)
{
printf("uuid: err!\n");
return 0;
}
cust_uuid += 5; //获取接收的uuid的值
strncpy(uu_buf, cust_uuid, 36);
printf("%s\n",uu_buf);
memset(recmsg,0,sizeof(recmsg));
strcpy(recmsg, aa);
strcat(recmsg, uu_buf);
strcat(recmsg, bb);
if (sendto(sockfd, recmsg, strlen (recmsg), 0, (struct sockaddr *) &the_member, sizeof (the_member)) < 0)
{
printf ("sendto error!\n");
exit (3);
}
printf ("send ok\n");
break;
}
}
运行结果如下:在这里插入代码片
recv:[<?xml version="1.0" encoding="utf-8"?><Envelope xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns="http://www.w3.org/2003/05/soap-envelope"><Header><wsa:MessageID xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">uuid:e68f33fc-13b5-48d9-9e42-37a985eee5e6</wsa:MessageID><wsa:To xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To><wsa:Action xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action></Header><Body><Probe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/ws/2005/04/discovery"><Types>tds:Device</Types><Scopes /></Probe></Body></Envelope>]
ip:192.168.1.5
port:3702
e68f33fc-13b5-48d9-9e42-37a985eee5e6
send ok
wireshark抓包:
onvif device test tool 客户端:
onvif设备端:
另外device info 、capability等采用gsoap抓到的网络协议如下:
服务端用的demo是https://github.com/zhu457823/onvif-ipc-server也可自己组装协议发送应答信息用http协议应答。
客户端请求GetDeviceInformation协议抓包:
服务端应答GetDeviceInformationResponse抓包:
更多推荐
所有评论(0)