在之前的文章中(《python-onvif实现客户端控制相机云台》),介绍过用python实现基于onvif协议的相机云台控制,考虑到嵌入式端的执行效率问题,还是需要实现C/C++版本的接口,因此尝试这方面的工作。经过将近一周的折腾,终于调通了onvif协议云台控制的代码,里边遇坑无数,从一个对onvif一无所知的小白,到最后顺利调通功能,还是有所收获的,将过程记录下来,给其他同学减少入坑的次数,目的也就达到了。话不多说,直接进入正题
1 预备知识
关于onvif的背景介绍,网上有很多资料,我这里就不具体展开,我之前也转载过一篇,《基于ONVIF协议的摄像头开发总结》,对onvif的原理有一个大概的介绍,而且许振坪博主的专栏也介绍的很详细,对onvif不了解的可以参考他的前几篇介绍文章。
2 开发流程
首先保证网络相机支持onvif协议,并且启用该协议,设置方法参考《海康相机之onvif测试工具使用》;
然后使用gsoap工具生成onvif代码框架;编写客户端程序,实现云台控制,客户端程序的调用流程会在后边详细介绍。
我要实现的是在嵌入式linux上开发客户端,来实现网络摄像头的云台控制。其它平台如x86、windows等平台流程类似,也同样可以采用类似的流程。
3 gsoap安装
用gsoap生成onvif代码框架的时候,主要用到wsdl2h和soapcpp2两个可执行文件,在gsoap/bin目录下,官方已经编译好win32和macosx平台的可执行文件,但linux下需要自己编译,因此我们使用源码编译的方法来生成这两个工具。
3.1 环境依赖
在编译之前,需要先安装OpenSSL,以便得到开启SSL/TLS的wsdl2h文件。建议先把系统中的SSL卸载掉重新安装,避免后边报错,卸载方法
1sudo apt-get purge openssl
下载链接:https://www.openssl.org/source/,下载1.0.2版本并解压到home目录下
进入目录安装
1
2
3
4cd /openssl-1.0.2r ./config sudo make sudo make install
3.2 安装gsoap
下载地址:https://sourceforge.net/projects/gsoap2/
下载后解压到home目录下,然后按照以下指令安装
1
2
3
4
5$ cd gsoap-2.8 $ sudo make distclean $ sudo ./configure --with-openssl=/usr/local/ssl/lib --host=arm-linux $ sudo make $ sudo make install
说明:
--with-openssl的路径要与自己环境中的路径保持一致,如果是按照我上边源码安装SSL的方法,路径就是/usr/local/ssl/lib。
--host用来指定生成Gsoap工具的编译器,如果是在x86上面执行的,则默认的就可以,即不用配置。
--prefix用来指定生成工具的路径,采用默认即可。
查看安装路径
1
2
3$ which wsdl2h /usr/local/bin/wsdl2h
找到文件夹/usr/local/bin,找到wsdl2h和soapcpp2这两个可执行文件,拷贝到/home/gsoap-2.8/gsoap/bin路径下,方便我们使用。
4 gsoap生成代码框架
经过上边的步骤,环境已经准备完毕,接下来就可以生成代码框架了
4.1 wsdl文件准备
下载地址:https://www.onvif.org/profiles/specifications/
在Onvif官网Specification页面中下载提供的功能相应的wsdl文件,如analytics.wsdl;devicemgmt.wsdl等。直接将WSDL的链接另存为,保存下来就是wsdl文件了。因为不太确定哪些文件不需要,所以我这里全部都下载了,全部放在bin下新建的wsdl文件夹内,包括这些wsdl中需要调用的xsd文件,
在gsoap2.8/gsoap/bin目录下,新建一个文件夹wsdl,用来存放刚才下载的所有wsdl和xsd文件。
4.2 onvif.h文件生成
这一步我们用到的工具是wsdl2h,我们要实现PTZ控制,有几个主要的wsdl文件就够了,我用到的是device.wsdl、event.wsdl、media.wsdl、ptz.wsdl,cd进入bin目录,使用如下指令
1./wsdl2h -c -t ../typemap.dat -o onvif.h ./wsdl/devicemgmt.wsdl ./wsdl/event.wsdl ./wsdl/media.wsdl ./wsdl/ptz.wsdl
各个选项的含义,可通过wsdl2h -help查看帮助。其中-c为产生纯c代码,默认为c++代码,-t为typemap.dat的标识。
有一个小技巧,如果网络条件不好的话,可以把wsdl文件中的schemaLocation的路径修改为本地文件路径,前提是下载好对应的文件,这样会速度快一些。
运行成功后,生成onvif.h文件,由于摄像头需要鉴权认证,需要在该文件头部添加如下代码
1#import "wsse.h"
4.3 c文件生成
接下来使用onvif.h文件来生成c文件,用到的工具是soapcpp2,运行如下代码
1./soapcpp2 -c -x onvif.h -I ../ -I ../import -I ../custom
其中,-c表示只生成c代码,-x表示不要产生XML示例文件,-I是包含的路径,运行成功后,会生成如下文件
soapC.c、soapH.h、soapClient.c、soapClientLib.c、soapStub.h、soapServer.c、soapServerLib.c、*.nsmap
新建一个文件夹onvif,把生成的这些文件全部拷贝到onvif文件夹中,其中*.nsmap文件内容都一样,我们只保留一个PTZBingding.nsmap即可。
5 PTZ控制
5.1 环境准备
在onvif文件夹中,继续添加文件,cd到上一级目录,直接使用cp命令来复制
1
2$ cd gsoap-2.8/gsoap $ cp stdsoap2.c stdsoap2.h dom.c plugin/wsaapi.c plugin/wsaapi.h plugin/wsseapi.c plugin/wsseapi.h plugin/mecevp.c plugin/mecevp.h plugin/smdevp.c plugin/smdevp.h plugin/threads.c plugin/threads.h custom/duration.c custom/duration.h bin/onvif/
最终onvif文件夹中的文件内容如下
1
2
3
4
5dom.c onvif.h soapC.c soapServerLib.c threads.h duration.c PTZBinding.nsmap soapClient.c soapStub.h wsaapi.c duration.h README.txt soapClientLib.c stdsoap2.c wsaapi.h mecevp.c smdevp.c soapH.h stdsoap2.h wsseapi.c mecevp.h smdevp.h soapServer.c threads.c wsseapi.h
5.2 控制流程
1、通过设备服务地址(形如http://xx/onvif/device_service),调用GetCapabilities函数接口,获取到Media的URL;
2、通过Media的URL,调用GetProfiles函数接口,获取到ProfileToken;
3、对_tptz__AbsoluteMove结构体进行填充;
4、调用soap_call___tptz__AbsoluteMove函数接口实现摄像头转动功能;
5.3 代码实现
在bin文件夹下新建一个PTZ文件夹,新建myptz.c文件,内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124#include <stdio.h> #include <stdlib.h> #include <assert.h> #include "soapH.h" #include "wsseapi.h" #include "wsaapi.h" #include "PTZBinding.nsmap" //宏定义设备鉴权的用户名和密码 //注意对于海康相机而言,鉴权的用户名和密码需要单独设置,不一定等同于登录账户密码 //设置方法参考https://zongxp.blog.csdn.net/article/details/89632354 #define USERNAME "admin" #define PASSWORD "123456" int main(int argc, char** argv) { struct soap soap; soap_init(&soap); char * ip; char Mediaddr[256]=""; char profile[256]=""; float pan = 1; float panSpeed = 1; float tilt = 1; float tiltSpeed = 0.5; float zoom = 0; float zoomSpeed = 0.5; struct _tds__GetCapabilities req; struct _tds__GetCapabilitiesResponse rep; struct _trt__GetProfiles getProfiles; struct _trt__GetProfilesResponse response; struct _tptz__AbsoluteMove absoluteMove; struct _tptz__AbsoluteMoveResponse absoluteMoveResponse; req.Category = (enum tt__CapabilityCategory *)soap_malloc(&soap, sizeof(int)); req.__sizeCategory = 1; *(req.Category) = (enum tt__CapabilityCategory)0; //第一步:获取capability char endpoint[255]; memset(endpoint, '', 255); if (argc > 1) { ip = argv[1]; } else { ip = "192.168.170.248"; } sprintf(endpoint, "http://%s/onvif/device_service", ip); soap_call___tds__GetCapabilities(&soap, endpoint, NULL, &req, &rep); if (soap.error) { printf("[%s][%d]--->>> soap result: %d, %s, %sn", __func__, __LINE__, soap.error, *soap_faultcode(&soap), *soap_faultstring(&soap)); } else { printf("get capability successn"); //printf("Dev_XAddr====%sn",rep.Capabilities->Device->XAddr); printf("Med_XAddr====%sn",rep.Capabilities->Media->XAddr); //printf("PTZ_XAddr====%sn",rep.Capabilities->PTZ->XAddr); strcpy(Mediaddr,rep.Capabilities->Media->XAddr); } printf("n"); //第二步:获取profile,需要鉴权 //自动鉴权 soap_wsse_add_UsernameTokenDigest(&soap, NULL, USERNAME, PASSWORD); //获取profile if(soap_call___trt__GetProfiles(&soap,Mediaddr,NULL,&getProfiles,&response)==SOAP_OK) { strcpy(profile, response.Profiles[0].token); printf("get profile succeed n"); printf("profile====%sn",profile); } else { printf("get profile failed n"); printf("[%s][%d]--->>> soap result: %d, %s, %sn", __func__, __LINE__, soap.error, *soap_faultcode(&soap), *soap_faultstring(&soap)); } printf("n"); //第三步:PTZ结构体填充 char PTZendpoint[255]; memset(PTZendpoint, '', 255); sprintf(PTZendpoint, "http://%s/onvif/PTZ", ip); printf("PTZendpoint is %s n", PTZendpoint); absoluteMove.ProfileToken = profile; //setting pan and tilt absoluteMove.Position = soap_new_tt__PTZVector(&soap, -1); absoluteMove.Position->PanTilt = soap_new_tt__Vector2D(&soap, -1); absoluteMove.Speed = soap_new_tt__PTZSpeed(&soap, -1); absoluteMove.Speed->PanTilt = soap_new_tt__Vector2D(&soap, -1); //pan absoluteMove.Position->PanTilt->x = pan; absoluteMove.Speed->PanTilt->x = panSpeed; //tilt absoluteMove.Position->PanTilt->y = tilt; absoluteMove.Speed->PanTilt->y = tiltSpeed; //setting zoom absoluteMove.Position->Zoom = soap_new_tt__Vector1D(&soap, -1); absoluteMove.Speed->Zoom = soap_new_tt__Vector1D(&soap, -1); absoluteMove.Position->Zoom->x = zoom; absoluteMove.Speed->Zoom->x = zoomSpeed; //第四步:执行绝对位置控制指令,需要再次鉴权 soap_wsse_add_UsernameTokenDigest(&soap, NULL, USERNAME, PASSWORD); soap_call___tptz__AbsoluteMove(&soap, PTZendpoint, NULL, &absoluteMove, &absoluteMoveResponse); //第五步:清除结构体 soap_destroy(&soap); // clean up class instances soap_end(&soap); // clean up everything and close socket, // userid and passwd were deallocated soap_done(&soap); // close master socket and detach context printf("n"); return 0; }
新建一个makefile文件,内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16include Makefile.inc PROGRAM = PTZ SOURCES += myptz.c OBJECTS := $(patsubst %.c,$(TEMPDIR)%.o,$(filter %.c, $(SOURCES))) all: $(OBJECTS_ONVIF) $(OBJECTS_COMM) $(OBJECTS) $(CC) -o $(PROGRAM) $(OBJECTS_ONVIF) $(OBJECTS_COMM) $(OBJECTS) $(LDLIBS) clean: rm -f $(OBJECTS_ONVIF) rm -f $(OBJECTS_COMM) rm -f $(OBJECTS) rm -f $(PROGRAM)
新建Makefile.inc文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50SHELL = /bin/bash CC := gcc CPP := g++ LD := ld AR := ar STRIP := strip CFLAGS += -c -g -Wall -DWITH_DOM -DWITH_OPENSSL -DDEBUG CFLAGS += $(INCLUDE) # openssl目录名 OPENSSL_DIR = /usr/local/ssl # 源文件 SOURCES_ONVIF += ../onvif/soapC.c ../onvif/soapClient.c ../onvif/stdsoap2.c ../onvif/wsaapi.c ../onvif/dom.c ../onvif/mecevp.c ../onvif/smdevp.c ../onvif/threads.c ../onvif/wsseapi.c # 目标文件 OBJECTS_ONVIF := $(patsubst %.c,$(TEMPDIR)%.o,$(filter %.c, $(SOURCES_ONVIF))) # 头文件路径 INCLUDE += -I../onvif/ -I$(OPENSSL_DIR)/include # 静态库链接OpenSSL LDLIBS += $(OPENSSL_DIR)/lib/libssl.a $(OPENSSL_DIR)/lib/libcrypto.a -ldl # 链接库(其他) LDLIBS += -lpthread %.o: %.cpp @echo " CPP " $@; @$(CPP) $(CFLAGS) -c -o $@ $< %.o: %.c @echo " CC " $@; @$(CC) $(CFLAGS) -c -o $@ $< .PHONY: all clean
然后运行make,即可在PTZ文件夹中生成PTZ可执行文件,运行该文件,可在目录下生成log日志文件,如果报错的话,可查看日志文件。注意如果是在嵌入式端部署的话,一定不要加-DDEBUG选项,否则日志文件会越来越大,影响板子性能
6 其它
6.1 关于鉴权
有很多onvif接口在调用之前需要鉴权(即调用soap_wsse_add_UsernameTokenDigest(&soap, NULL, USERNAME, PASSWORD)函数),并且鉴权完一次之后还需要重新鉴权,具体可参考《ONVIF协议网络摄像机(IPC)客户端程序开发(9):鉴权(认证)》,总结一下,只有以下接口是可以在不认证的情况下调用,一定要注意。
- GetWsdlUrl
- GetServices
- GetServiceCapabilities
- GetCapabilities
- GetHostname
- GetSystemDateAndTime
- GetEndpointReference
- GetRelayOutputOptions
6.2 segmentation fault错误
这个错误很常见,主要是由于访问了没有分配地址的内存导致的,在填充功能函数时,很容易漏掉为必须的结构体分配内存,导致gSoap产生的代码会在不知情的状况下访问该结构体,然后报segmentation fault错误,需要注意这点
6.3 其它控制
本文实现的是PTZ绝对控制,当然onvif还支持其它模式,具体实现代码可参考这个链接进行实现
6.4 结构体描述
关于结构体的描述,可参考这篇文章。
最后
以上就是清秀吐司最近收集整理的关于ONVIF协议开发之网络摄像头云台控制(C版)1 预备知识2 开发流程3 gsoap安装4 gsoap生成代码框架5 PTZ控制6 其它的全部内容,更多相关ONVIF协议开发之网络摄像头云台控制(C版)1内容请搜索靠谱客的其他文章。
发表评论 取消回复