根据ONVIF官网 的介绍:
ONVIF是一个开放的安防行业组织,致力于为安防行业提供和促进标准化开放接口,以实现IP网络安防产品和服务的有效互操作性。
在具体实现上,ONVIF使用了Web Service的方式,设备通过WSDL定义的接口提供服务。ONVIF支持的接口众多,本文仅涉及使用C语言与Onvif摄像机互操作的过程。
C语言使用Web Service
在C语言中使用Web Service,可以用gsoap来自动生相应的实现。
方法分为三步:
- 安装gsoap软件包。
- 取得Web Service的xsd、wsdl。
- 生成接口函数。
- 调用接口函数,完成Web Service的使用。
安装gsoap软件包
安装gsoap软件包的过程,与其它软件并无二致。唯一需要注意的是,安装的版本如果比较低,可能个别API的实现稍有不同。
另外,有的系统上把gsoap分成了gsoap与gsoap-devel多个包。因为我们是开发Web Service,所以需要安装gsoap-devel包,安装完成之后,/usr/share/gsoap
目录下会有:
~/$ ls /usr/share/gsoap/
custom import plugin WS
这些目录。
对于不同的Web Service服务,会使用这里面提供的实现。
比如,如果我们的Onvif摄像机需要认证,就需要使用/usr/share/gsoap/plugin
里面的httpda.h
、httpda.c
等实现。
取得Web Service的xsd、wsdl
与Onvif摄像机取流相关的文件,大概有:
b-2.xsd common.xsd event.wsdl media.wsdl t-1.xsd ws-discovery.wsdl
bf-2.xsd devicemgmt.wsdl media2.wsdl onvif.xsd typemap.dat
其中,xsd主要是一些结构定义,wsdl则是接口定义。这些文件可以从Onvif 官网的网络接口规范页面下载。
生成接口函数
我们使用gsoap的工具软件,根据这些wsdl文件,生成接口函数。
主要使用两个工具,wsdl2h与soapcpp2。
wsdl2h
wsdl2h的功能,是把我们下载的xsd、wsdl等文件,生成相应的数据结构以及接口定义。参数比较多,我们只需要调整其中的很少的一部分参数就足够了。
比如:
wsdl2h -c -d -o onvif.h -t typemap.dat *.xsd *.wsdl
其中,
-c
表示生成C语言的定义-d
表示使用Dom来表示xs:any等-o onvif.h
表示生成到onvif.h这个头文件中-t typemap.dat
表示使用自定义的typemap
执行完成之后,将会生成一个onvif.h
头文件。我们的程序中,使用这个头文件即可完成Web Service相关函数的引入。
soapcpp2
soapcpp2的功能是根据onvif.h来生成接口实现的代码片段。
如:
soapcpp2 -c -d ./impl -L -x -T -I/usr/include
其中,
-c
表示生成C语言源文件-d
表示生成文件的路径-L
表示不用生成Server相关的实现-x
表示不用生成示例消息文件-T
表示生成Server测试代码-I
表示头文件包含路径
我们执行上面的命令,将在./impl 目录中得到soapC.c、soapClient.c、soapServer.c 等源文件。
当然,为了做Onvif的认证授权,我们还需要在项目中编译连接
wsseapi.c
mecevp.c
smdevp.c
wsaapi.c
wsddapi.c
httpda.c
等源文件。
使用接口函数
有了onvif.h头文件,又有了soapC.c等实现文件,我们就可以使用Onvif的相关函数,进行Onvif的开发了。
使用gsoap生成的接口函数,核心需要一个struct soap
结构的指针。
使用struct soap
的大概步骤为:
- 生成
struct soap
指针 - 执行
struct soap
相应的函数 - 释放
struct soap
指针
其中在第二步,也就是使用struct soap
相应的函数过程中,会使用我们前面根据wsdl生成的接口函数,来处理Web Service相应的结构。
生成struct soap
指针
生成struct soap
的方法为使用soap_new()
函数。
如果打开/usr/include/stdsoap2.h
查看,会发现struct soap
结构的定义很长,包括了各种各样的参数。
其中大部分参数,都可以见文知义,只有一少部分,需要额外解释。
比如,
user
上层应用的指针mode
、imode
、omode
指定协议,比如使用soap_new1(SOAO_IO_UDP)
来生成一个UDP的soap结构。bind_flags
绑定的标识,比如SO_REUSEADDR。
以下代码,生成了一个struct soap
,又设置了一些超时相关的参数,最后注册了HTTP认证插件。
struct soap *s;s = soap_new (); s->connect_timeout = 2;
s->recv_timeout = 2;
s->send_timeout = 2;
s->transfer_timeout = 2;soap_register_plugin (service->s, http_da);
释放struct soap
指针
soap_destroy (s);
soap_end (s);
soap_free (s);
执行struct soap
相应的函数
void
soap_getdeviceinformation (struct soap *soap)
{ struct _tds__GetDeviceInformation req[1]; struct _tds__GetDeviceInformationResponse response[1];int res; res = soap_call___tds__GetDeviceInformation(soap, req, response);if (res == 0){ if (response->SerialNumber != NULL) { printf ("Manufacturer: %s\n", response->Manufacturer); printf ("Model: %s\n", response->Model); printf ("FirmwareVersion: %s\n", response->FirmwareVersion); printf ("SerialNumber: %s\n", response->SerialNumber); printf ("HardwareId: %s\n", response->HardwareId);}} soap_destroy(soap);soap_end(soap);
}
以上代码中,struct _tds__GetDeviceInformation
和struct _tds__GetDeviceInformationResponse
结构,以及soap_call___tds__GetDeviceInformation
函数的定义,都是gsoap的工具自动生成的。
在soapClient.c
中, soap_call___tds__GetDeviceInformation
实现如下:
SOAP_FMAC5 int SOAP_FMAC6 soap_call___tds__GetDeviceInformation(struct soap *soap, const char *soap_endpoint, const char *soap_action, struct _tds__GetDeviceInformation *tds__GetDeviceInformation, struct _tds__GetDeviceInformationResponse *tds__GetDeviceInformationResponse)
{ if (soap_send___tds__GetDeviceInformation(soap, soap_endpoint, soap_action, tds__GetDeviceInformation) || soap_recv___tds__GetDeviceInformation(soap, tds__GetDeviceInformationResponse)) return soap->error; return SOAP_OK;
}
这个函数的下面,就是soap_send___tds__GetDeviceInformation
、soap_recv___tds__GetDeviceInformation
的实现,也都是这样类似的代码:
SOAP_FMAC5 int SOAP_FMAC6 soap_send___tds__GetDeviceInformation(struct soap *soap, const char *soap_endpoint, const char *soap_action, struct _tds__GetDeviceInformation *tds__GetDeviceInformation)
{ struct __tds__GetDeviceInformation soap_tmp___tds__GetDeviceInformation;if (soap_action == NULL)soap_action = "http://www.onvif.org/ver10/device/wsdl/GetDeviceInformation";soap_tmp___tds__GetDeviceInformation.tds__GetDeviceInformation = tds__GetDeviceInformation;soap_begin(soap);soap->encodingStyle = NULL; /* use SOAP literal style */soap_serializeheader(soap);soap_serialize___tds__GetDeviceInformation(soap, &soap_tmp___tds__GetDeviceInformation);if (soap_begin_count(soap))return soap->error;if ((soap->mode & SOAP_IO_LENGTH)){ if (soap_envelope_begin_out(soap)|| soap_putheader(soap)|| soap_body_begin_out(soap)|| soap_put___tds__GetDeviceInformation(soap, &soap_tmp___tds__GetDeviceInformation, "-tds:GetDeviceInformation", "")|| soap_body_end_out(soap)|| soap_envelope_end_out(soap))return soap->error;}if (soap_end_count(soap))return soap->error;if (soap_connect(soap, soap_endpoint, NULL)|| soap_envelope_begin_out(soap)|| soap_putheader(soap)|| soap_body_begin_out(soap)|| soap_put___tds__GetDeviceInformation(soap, &soap_tmp___tds__GetDeviceInformation, "-tds:GetDeviceInformation", "")|| soap_body_end_out(soap)|| soap_envelope_end_out(soap)|| soap_end_send(soap))return soap_closesock(soap);return SOAP_OK;
}SOAP_FMAC5 int SOAP_FMAC6 soap_recv___tds__GetDeviceInformation(struct soap *soap, struct _tds__GetDeviceInformationResponse *tds__GetDeviceInformationResponse)
{if (!tds__GetDeviceInformationResponse)return soap_closesock(soap);soap_default__tds__GetDeviceInformationResponse(soap, tds__GetDeviceInformationResponse);if (soap_begin_recv(soap)|| soap_envelope_begin_in(soap)|| soap_recv_header(soap)|| soap_body_begin_in(soap))return soap_closesock(soap);soap_get__tds__GetDeviceInformationResponse(soap, tds__GetDeviceInformationResponse, "tds:GetDeviceInformationResponse", NULL);if (soap->error)return soap_recv_fault(soap, 0);if (soap_body_end_in(soap)|| soap_envelope_end_in(soap)|| soap_end_recv(soap))return soap_closesock(soap);return soap_closesock(soap);
}