ONVIF使用总结
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
设备发现
onvif的设备发现功能,做了好久,写了改,改了写,c语言版本,C++版本,它的核心接口:
1)发送组播消息:SOAP_FMAC5 int SOAP_FMAC6 soap_send___wsdd__Probe(struct soap *soap, const char *soap_endpoint, const char *soap_action, struct wsdd__ProbeType *wsdd__Probe)。
2) 接收组播消息:SOAP_FMAC5 int SOAP_FMAC6 soap_recv___wsdd__ProbeMatches(struct soap *soap, struct __wsdd__ProbeMatches *_param_1)。
通过其参数不难看出,它的调用实际上也很简单;但是真正的难点是该接口涉及到的***UDP组播通信原理***;
通过gsoap生成的ONVIF源码,从某种程度上简化了,这个过程;因此在应用的过程中,经常会出现设备无法找到;尤其是本地网卡比较多的时候;
真正的想做好设备发现接口,一定好掌握UDP组播的工作原理;在实现本接口时,建议用传统的socket通信的实现,在接收的时候绑定本机IP;
建议:通过本方式获取IPC的返回信息,需要解析接收到的内容(xml),如果不依赖于开发工具,其解析有点麻烦;特备不同的厂家,xml在子节点上的定义也是一样的;因此,建议先用抓包工具,仔细分析下接收到的内容。
其核心代码如下:
int ret;
SOAP_SOCKET s;
SOAP_SOCKLEN_T len;
char recv_buff[4096] = { 0 };
sockaddr_in multi_addr;
sockaddr_in client_addr;
sockaddr_in bind_addr;
#ifdef WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { // 初始化Windows Sockets DLL
printf("Could not open Windows connection.\n");
return obj_ArrayList;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
printf("the version of WinSock DLL is not 2.2.\n");
return obj_ArrayList;
}
#endif
s = socket(AF_INET, SOCK_DGRAM, 0);
// 建立数据报套接字
if (s < 0) {
printf("socket init failed\n");
}
memset(&bind_addr,0,sizeof(bind_addr));
char local_ip4[INET_ADDRSTRLEN] = {0};
get_local_ip_using_create_socket(local_ip4);
printf("local_ip4 ===================%s\n",local_ip4);
bind_addr.sin_addr.s_addr = inet_addr(local_ip4);
bind_addr.sin_family = AF_INET;
bind_addr.sin_port = htons(0);
if(bind(s,(struct sockaddr*)&bind_addr,sizeof(bind_addr))<0)
printf("bind fail\n");
multi_addr.sin_addr.s_addr = inet_addr(CAST_ADDR);
multi_addr.sin_family = AF_INET;
multi_addr.sin_port = htons(CAST_PORT);
if(UDPsendMessage(multi_addr, s, probe) < 0)
{
SLEEP(1);
UDPsendMessage(multi_addr, s, probe);
}//发送UDP广播
printf("Send Probe message to [%s:%d]\n\n", CAST_ADDR, CAST_PORT);
unsigned long on = 1;
#if defined(__linux__) || defined(__linux)
int flag = fcntl(s, F_GETFL, 0);
if (flag < 0)
{
printf("fcntl failed.\n");
exit(1);
}
flag |= O_NONBLOCK;
if (fcntl(s, F_SETFL, flag) < 0)
{
printf("fcntl failed.\n");
exit(1);
}
#else
int ReValue =ioctlsocket(s, FIONBIO, &on);
printf("ioctlsocket === %d\n",ReValue);
#endif
fd_set read_fdset;
FD_ZERO(&read_fdset);
FD_SET(s,&read_fdset);
struct timeval timeout;
timeout.tv_sec=5;
timeout.tv_usec=0;
for(;;)
{
ret=select(s+1,&read_fdset,NULL,NULL,&timeout);
printf("select result is %d\n",ret);
if(ret<=0)
break;
// 接收IPC的应答消息(ProbeMatch)
len = sizeof(client_addr);
memset(recv_buff, 0, sizeof(recv_buff));
memset(&client_addr, 0, sizeof(sockaddr));
ret = recvfrom(s, recv_buff, sizeof(recv_buff) - 1, 0, (struct sockaddr*)&client_addr, &len);
printf("recvfrom ====%d\n",ret);
if(ret < 0)
{
break;
}else
{
printf("===Recv ProbeMatch from [%s:%d]===\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
printf("===recv_buff===%s\n\n", recv_buff);
}
设备属性相关
设备发现接口相当重要,通过它可以获得设备服务地址(deviceAddr),这个地址是实现ONVIF其他功能的基础。
设备能力接口:SOAP_FMAC5 int SOAP_FMAC6 soap_call___tds__GetCapabilities(struct soap *soap, const char *soap_endpoint, const char *soap_action, struct _tds__GetCapabilities *tds__GetCapabilities, struct _tds__GetCapabilitiesResponse *tds__GetCapabilitiesResponse)
配置信息:SOAP_FMAC5 int SOAP_FMAC6 soap_call___trt__GetProfiles(struct soap *soap, const char *soap_endpoint, const char *soap_action, struct _trt__GetProfiles *trt__GetProfiles, struct _trt__GetProfilesResponse *trt__GetProfilesResponse)
通过以上2个接口,基本上可以获取IPC的基本信息;
鉴权
ONVIF有点恶心,除了设备搜索不需要鉴权,其他的任何功能在调用前都的需要鉴权;说实在的没有鉴权,IPC对于有些人来说是裸着的,可以随便访问,甚至想干嘛就干嘛。
soap_wsse_add_UsernameTokenDigest(struct soap *soap, const char *id, const char *username, const char *password)。
音视频配置
相同的接口在不同的IPC上返回的结果也是不同的,不知道ONVIF协议是完整支持,还是部分支持。
其获取配置信息的流程如下:
1)获取设备服务地址;
2)调用获取设备能力接口,获取media地址;
3) 调用获取设备配置信息接口(mediaAddr作为参数)获取设备信息,记录token。此处设备信息,相当于总体上的概述;
4)视频配置接口:SOAP_FMAC5 int SOAP_FMAC6 soap_call___trt__GetVideoEncoderConfiguration(struct soap *soap, const char *soap_endpoint, const char *soap_action, _trt__GetVideoEncoderConfiguration *trt__GetVideoEncoderConfiguration, _trt__GetVideoEncoderConfigurationResponse &trt__GetVideoEncoderConfigurationResponse);
5)调用音频接口:SOAP_FMAC5 int SOAP_FMAC6 soap_call___trt__GetAudioEncoderConfiguration(struct soap *soap, const char *soap_endpoint, const char *soap_action, _trt__GetAudioEncoderConfiguration *trt__GetAudioEncoderConfiguration, _trt__GetAudioEncoderConfigurationResponse &trt__GetAudioEncoderConfigurationResponse);
修改音视频配置也是这些接口;
唯一要注意的是在实现某些功能时,需要多测试,不同厂家的IPC,接口返回是有差异的;
总结
《ONVIF2.0协议珍藏版》是值得仔细琢磨的,其中包含了接口的定义及功能说明;在做实际的应用时,做好接口的组合调用即可。