1. gSoap依赖库的编译
先下载和编译下面三个包,为避免与系统库冲突,这里将生成文件安装到指定的目录,如 /home/test/output,版本用最新的都可以
bison-3.6.4.tar.xz
官网:http://www.gnu.org/software/bison/
tar -xf bison-3.6.4.tar.xz
cd bison-3.6.4
./configure --prefix=/home/test/output/
make && make install
flex-2.6.4.tar.gz
官网:http://flex.sourceforge.net/
tar -xf flex-2.6.4.tar.gz
cd flex-2.6.4
./configure --prefix=/home/test/output/
make && make install
openssl-1.1.1g.tar.gz
tar -xf openssl-1.1.1g.tar.gz
cd openssl-1.1.1g
./config --prefix=/home/test/output/
make && make install
这里openssl安装到了 /home/test/output/lib 下面
2. gSoap工具编译
gSOAP开源版下载网址:http://sourceforge.net/projects/gsoap2
1. unzip gsoap_2.8.104.zip
2. ./configure --with-openssl=/home/test/output/lib --prefix=/home/test/output
3. make
4. make install
可见 /home/test/output/bin 下面生成了 wsdl2h 和 soapcpp2,
这里为与windows下保持一致,将这两个文件拷贝到 gsoap-2.8/gsoap/bin/linux 目录下
3. ONVIF框架代码生成
3.1 修改typemap.dat
由于需要使用time_duration,需要先修改 typemap.dat 文件218行,将注释取消
# Uncomment the line below to use LONG64 int for xsd:duration instead of
# mapping xsd:duration to string (in milliseconds precision).
# Then rerun wsdl2h and also compile and link custom/duration.c.
#
xsd__duration = #import "custom/duration.h" | xsd__duration
3.2 使用wsdl2h工具,根据WSDL产生头文件
创建一个目录samples/onvif,用于存放生成的ONVIF框架头文件。创建目录samples/onvif-out,用于存放生成的ONVIF框架源码。这里可先把需要的wsdl下载到本目录下
https://www.onvif.org/ver10/network/wsdl/remotediscovery.wsdl
https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl
https://www.onvif.org/ver10/media/wsdl/media.wsdl
https://www.onvif.org/ver20/ptz/wsdl/ptz.wsdl
cd gsoap-2.8/gsoap/bin/linux
wsdl2h -P -c++ -x -s -t ../../typemap.dat -o ../../samples/onvif/onvif.h remotediscovery.wsdl devicemgmt.wsdl media.wsdl ptz.wsdl
可通过wsdl2h.exe -help查看各个选项的含义。这里-cxx为产生c++代码;-s为不使用STL库,-t为typemap.dat的标识。
出现错误提示:Cannot open '../../../ver10/schema/onvif.xsd' to retrieve schema
手动生成相关目录,这里正好是在 gSoap-2.8/ver10/schema,
下载下面几个文件放到该目录中
https://www.onvif.org/ver10/schema/onvif.xsd
https://www.onvif.org/ver10/schema/common.xsd
然后重新运行上面的命令
Saving ../../samples/onvif/onvif.h
** The gSOAP WSDL/WADL/XSD processor for C and C++, wsdl2h release 2.8.104
** Copyright (C) 2000-2020 Robert van Engelen, Genivia Inc.
** All Rights Reserved. This product is provided "as is", without any warranty.
** The wsdl2h tool and its generated software are released under the GPL.
** ----------------------------------------------------------------------------
** A commercial use license is available from Genivia Inc., contact@genivia.com
** ----------------------------------------------------------------------------
Reading type definitions from type map "../../typemap.dat"
Reading 'remotediscovery.wsdl'...
Done reading 'remotediscovery.wsdl'
Reading 'devicemgmt.wsdl'...
Reading schema '../../../ver10/schema/onvif.xsd'...
Connecting to 'http://docs.oasis-open.org/wsn/b-2.xsd' to retrieve schema... connected, receiving...
Connecting to 'http://docs.oasis-open.org/wsrf/bf-2.xsd' to retrieve schema... connected, receiving...
Done reading 'http://docs.oasis-open.org/wsrf/bf-2.xsd'
Connecting to 'http://docs.oasis-open.org/wsn/t-1.xsd' to retrieve schema... connected, receiving...
Done reading 'http://docs.oasis-open.org/wsn/t-1.xsd'
Done reading 'http://docs.oasis-open.org/wsn/b-2.xsd'
Reading schema '../../../ver10/schema/common.xsd'...
Done reading '../../../ver10/schema/common.xsd'
Done reading '../../../ver10/schema/onvif.xsd'
Done reading 'devicemgmt.wsdl'
Reading 'media.wsdl'...
Done reading 'media.wsdl'
Reading 'ptz.wsdl'...
Done reading 'ptz.wsdl'
Warning: 2 service bindings found, but collected as one service (use option -Nname to produce a separate service for each binding)
To finalize code generation, execute:
> soapcpp2 ../../samples/onvif/onvif.h
Or to generate C++ proxy and service classes:
> soapcpp2 -j ../../samples/onvif/onvif.h
可见生成了 samples/onvif/onvif.h文件
这里不用Proxy模式
3.3 修改头文件 onvif.h,使得支持鉴权
#import "wsa5.h" // wsa5 = <http://www.w3.org/2005/08/addressing>
增加下面一句
#import "wsse.h"
3.4 避免定义冲突
修改import\wsa5.h文件,将int SOAP_ENV__Fault修改为int SOAP_ENV__Fault_alex
//gsoap SOAP_ENV service method-action: Fault http://www.w3.org/2005/08/addressing/soap/fault
int SOAP_ENV__Fault_alex
( _QName faultcode, // SOAP 1.1
char *faultstring, // SOAP 1.1
char *faultactor, // SOAP 1.1
struct SOAP_ENV__Detail *detail, // SOAP 1.1
struct SOAP_ENV__Code *SOAP_ENV__Code, // SOAP 1.2
struct SOAP_ENV__Reason *SOAP_ENV__Reason, // SOAP 1.2
char *SOAP_ENV__Node, // SOAP 1.2
char *SOAP_ENV__Role, // SOAP 1.2
struct SOAP_ENV__Detail *SOAP_ENV__Detail, // SOAP 1.2
void
);
3.5 使用soapcpp2工具,生成框架
./soapcpp2 -2 -C -c++ -L -x -I ../../import -I ../../custom -I../../ -d ../../samples/onvif-out/ ../../samples/onvif/onvif.h
文件产生框架代码
** The gSOAP code generator for C and C++, soapcpp2 release 2.8.104
** Copyright (C) 2000-2020, Robert van Engelen, Genivia Inc.
** All Rights Reserved. This product is provided "as is", without any warranty.
** The soapcpp2 tool and its generated software are released under the GPL.
** ----------------------------------------------------------------------------
** A commercial use license is available from Genivia Inc., contact@genivia.com
** ----------------------------------------------------------------------------
soap12.h(54): *WARNING*: option -1 or -2 overrides SOAP-ENV namespace
soap12.h(55): *WARNING*: option -1 or -2 overrides SOAP-ENC namespace
Using project directory path: ../../samples/onvif-out/
Saving ../../samples/onvif-out/soapStub.h annotated copy of the source interface header file
Saving ../../samples/onvif-out/soapH.h serialization functions to #include in projects
Using wsdd service name: wsdd
Using wsdd service style: document
Using wsdd service encoding: literal
Using wsdd schema import namespace: http://schemas.xmlsoap.org/ws/2005/04/discovery
Saving ../../samples/onvif-out/wsdd.nsmap namespace mapping table
Using tdn service name: RemoteDiscoveryBinding
Using tdn service style: document
Using tdn service encoding: literal
Using tdn schema namespace: http://www.onvif.org/ver10/network/wsdl
Saving ../../samples/onvif-out/RemoteDiscoveryBinding.nsmap namespace mapping table
Using tds service name: DeviceBinding
Using tds service style: document
Using tds service encoding: literal
Using tds schema namespace: http://www.onvif.org/ver10/device/wsdl
Saving ../../samples/onvif-out/DeviceBinding.nsmap namespace mapping table
Using tptz service name: PTZBinding
Using tptz service style: document
Using tptz service encoding: literal
Using tptz schema namespace: http://www.onvif.org/ver20/ptz/wsdl
Saving ../../samples/onvif-out/PTZBinding.nsmap namespace mapping table
Using trt service name: MediaBinding
Using trt service style: document
Using trt service encoding: literal
Using trt schema namespace: http://www.onvif.org/ver10/media/wsdl
Saving ../../samples/onvif-out/MediaBinding.nsmap namespace mapping table
Saving ../../samples/onvif-out/soapClient.cpp client call stub functions
Saving ../../samples/onvif-out/soapC.cpp serialization functions
Compilation successful (2 warnings)
警告可以不处理
在samples/onvif-out下生成如下文件:
-rw-r--r--. 1 root root 2528 7月 22 14:41 DeviceBinding.nsmap
-rw-r--r--. 1 root root 2528 7月 22 14:41 MediaBinding.nsmap
-rw-r--r--. 1 root root 2528 7月 22 14:41 PTZBinding.nsmap
-rw-r--r--. 1 root root 2528 7月 22 14:41 RemoteDiscoveryBinding.nsmap
-rw-r--r--. 1 root root 11609899 7月 22 14:41 soapC.cpp
-rw-r--r--. 1 root root 560372 7月 22 14:41 soapClient.cpp
-rw-r--r--. 1 root root 7097772 7月 22 14:41 soapH.h
-rw-r--r--. 1 root root 3623591 7月 22 14:41 soapStub.h
-rw-r--r--. 1 root root 2528 7月 22 14:41 wsdd.nsmap
这些nsmap文件都相同,可以只保留wsdd.nsmap
同时把需要的一些文件都复制到指定目录中
[root@localhost onvif-out]# cp ../../plugin/mecevp.h .
[root@localhost onvif-out]# cp ../../plugin/mecevp.c mecevp.cpp
[root@localhost onvif-out]# cp ../../plugin/wsaapi.h .
[root@localhost onvif-out]# cp ../../plugin/wsaapi.c wsaapi.cpp
[root@localhost onvif-out]# cp ../../plugin/wsseapi.h .
[root@localhost onvif-out]# cp ../../plugin/wsseapi.c wsseapi.cpp
[root@localhost onvif-out]# cp ../../custom/duration.h .
[root@localhost onvif-out]# cp ../../custom/duration.c duration.cpp
[root@localhost onvif-out]# cp ../../custom/struct_timeval.h .
[root@localhost onvif-out]# cp ../../custom/struct_timeval.c struct_timeval.cpp
[root@localhost onvif-out]# cp ../../dom.cpp .
[root@localhost onvif-out]# cp ../../plugin/threads.h .
[root@localhost onvif-out]# cp ../../plugin/threads.c threads.cpp
[root@localhost onvif-out]# cp ../../stdsoap2.h .
[root@localhost onvif-out]# cp ../../stdsoap2.cpp .
[root@localhost onvif-out]# cp ../../plugin/smdevp.h .
[root@localhost onvif-out]# cp ../../plugin/smdevp.c smdevp.cpp
为使用命名空间,在stdsoap2.cpp中包含wsdd.nsmap文件,避免后面每个工程中都要包含
#define GSOAP_LIB_VERSION 208104
#include "wsdd.nsmap"
4. C++的测试代码
这里不进行设备发现,假定摄像机的服务地址为固定地址,查找到第一个媒体流,然后调用指定的预置位
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "soapH.h"
#include "wsaapi.h"
#include "wsseapi.h"
#define SOAP_SOCK_TIMEOUT (10) // socket超时时间(单秒秒)
#define USERNAME "admin"
#define PASSWORD "admin"
/************************************************************************
**函数:setAuthInfo
**功能:设置认证信息
**参数:
[in] soap - soap环境变量
[in] username - 用户名
[in] password - 密码
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
static int setAuthInfo(struct soap *soap, const char *username, const char *password)
{
int result = soap_wsse_add_UsernameTokenDigest( soap, NULL, username, password );
if ( result != 0 )
printf("[soap] add_UsernameTokenDigest error: %d, %s, %s\n", result, *soap_faultcode(soap), *soap_faultstring(soap) );
return result;
}
void* soapMalloc( struct soap *soap, unsigned int n )
{
if ( n > 0 )
{
void* p = soap_malloc( soap, n );
assert(NULL != p);
memset(p, 0x00 ,n);
return p;
}
return NULL;
}
struct soap *soapNew( int timeout )
{
struct soap *soap = NULL; // soap环境变量
assert( NULL != (soap = soap_new()) );
soap_set_namespaces(soap, namespaces); // 设置soap的namespaces
soap->recv_timeout = timeout; // 设置超时(超过指定时间没有数据就退出)
soap->send_timeout = timeout;
soap->connect_timeout = timeout;
#if defined(__linux__) || defined(__linux) // 参考https://www.genivia.com/dev.html#client-c的修改:
soap->socket_flags = MSG_NOSIGNAL; // To prevent connection reset errors
#endif
soap_set_mode(soap, SOAP_C_UTFSTRING); // 设置为UTF-8编码,否则叠加中文OSD会乱码
return soap;
}
void soapDelete(struct soap *soap)
{
if ( soap == NULL )
return;
soap_destroy(soap); // remove deserialized class instances (C++ only)
soap_end(soap); // Clean up deserialized data (except class instances) and temporary data
soap_done(soap); // Reset, close communications, and remove callbacks
soap_free(soap); // Reset and deallocate the context created with soap_new or soap_copy
}
/************************************************************************
**函数:isH264Profile
**功能:媒体文件是否264
**参数:
[in] addr - media地址
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
bool isH264Profile( const char* addr, const char* profile )
{
int result = 0;
struct soap *soap = NULL;
assert( NULL != (soap = soapNew(SOAP_SOCK_TIMEOUT)));
setAuthInfo( soap, USERNAME, PASSWORD );
_trt__GetProfile req;
_trt__GetProfileResponse resp;
req.ProfileToken = (char*)profile;
bool bRet = false;
soap_call___trt__GetProfile( soap, addr, NULL, &req, resp );
if ( soap->error )
{
soap_print_fault( soap, stderr );
}
else
{
if ( resp.Profile && resp.Profile->VideoEncoderConfiguration
&& resp.Profile->VideoEncoderConfiguration->Encoding == tt__VideoEncoding__H264 )
bRet = true;
}
soapDelete(soap);
return bRet;
}
/************************************************************************
**函数:getProfiles
**功能:获得Profiles
**参数:
[in] addr - 设备媒体地址
[out] chProfile -- h264Profile
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int getProfiles( const char* addr, char* chProfile )
{
int result = 0;
struct soap *soap = NULL;
assert(NULL != (soap = soapNew(SOAP_SOCK_TIMEOUT)));
setAuthInfo(soap, USERNAME, PASSWORD);
_trt__GetProfiles req;
_trt__GetProfilesResponse resp;
soap_call___trt__GetProfiles( soap, addr, NULL, &req, resp );
if ( soap->error )
{
soap_print_fault( soap, stderr );
}
else
{
int size = resp.__sizeProfiles;
for ( int i = 0; i < size; ++i )
{
if ( resp.Profiles[i] && resp.Profiles[i]->token )
{
char* chToken = resp.Profiles[i]->token;
printf( "%s\n", resp.Profiles[i]->token );
if ( !isH264Profile( addr, chToken ) )
continue;
memcpy( chProfile, chToken, strlen( chToken ) );
break;
}
}
}
soapDelete(soap);
return 0;
}
/************************************************************************
**函数:gotoPreset
**功能:调用指定的预置位
**参数:
[in] addr - 设备媒体地址
[in] profile -- Profile
[in] iPreset -- 要调用的预置位id
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int gotoPreset( const char *addr, const char* profile, int iPreset )
{
int result = 0;
struct soap *soap = NULL;
assert(NULL != (soap = soapNew(SOAP_SOCK_TIMEOUT)));
setAuthInfo(soap, USERNAME, PASSWORD);
char chTmp[10];
sprintf(chTmp, "%d", iPreset );
_tptz__GotoPreset req;
req.ProfileToken = (char*)profile;
req.PresetToken = chTmp;
_tptz__GotoPresetResponse resp;
soap_call___tptz__GotoPreset( soap, addr, NULL, &req, resp );
if ( soap->error )
{
soap_print_fault( soap, stderr );
}
else
{
printf("call preset %d is ok\n", iPreset );
}
soapDelete(soap);
return 0;
}
/************************************************************************
**函数:getDeviceInfo
**功能:获取设备基本信息
**参数:
[in] chIp - 设备IP地址
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int getDeviceInfo( char* chIp )
{
char DeviceXAddr[100] = { 0 };
//http://192.168.3.101/onvif/device_service
sprintf( DeviceXAddr, "http://%s/onvif/device_service", chIp );
struct soap *soap = NULL;
assert(NULL != ( soap = soapNew( SOAP_SOCK_TIMEOUT ) ) );
//用户鉴权
setAuthInfo(soap, USERNAME, PASSWORD);
_tds__GetServices req;
req.IncludeCapability = true;
_tds__GetServicesResponse resp;
soap_call___tds__GetServices( soap, DeviceXAddr, NULL, &req, resp );
if ( soap->error )
{
soap_print_fault( soap, stderr );
}
else
{
int size = resp.__sizeService;
std::string m_szPtzAddr;
std::string m_szProfile;
for ( int i = 0; i < size; ++i )
{
if ( resp.Service[i] && resp.Service[i]->XAddr )
{
char* pTmp = resp.Service[i]->XAddr;
printf( "%s\n", resp.Service[i]->XAddr );
std::string addr = pTmp;
if ( addr.find( "media" ) != std::string::npos
|| addr.find( "Media" ) != std::string::npos )
{
char chProfile[100] = {0};
getProfiles( pTmp, chProfile );
m_szProfile = std::string( chProfile );
}
else if ( addr.find( "ptz" ) != std::string::npos
|| addr.find( "PTZ" ) != std::string::npos )
{
m_szPtzAddr = addr;
}
}
}
gotoPreset( m_szPtzAddr.c_str(), m_szProfile.c_str(), 1 );
}
soapDelete(soap);
return 0;
}
int main(int argc, char **argv)
{
char* chIp = NULL;
if ( argc > 1 )
chIp = argv[1];
getDeviceInfo( chIp );
return 0;
}
MakeFile中关键信息:
g++ -Wall -DWITH_OPENSSL -DWITH_DOM -O0 -g3 -D_DEBUG -o debug/duration.o -c duration.cpp
g++ -o ./testOnvifD ./debug/main.o ./debug/dom.o ./debug/mecevp.o ./debug/smdevp.o ./debug/soapC.o ./debug/soapClient.o ./debug/stdsoap2.o ./debug/struct_timeval.o ./debug/threads.o ./debug/wsaapi.o ./debug/wsseapi.o ./debug/duration.o -L. -ldl -lssl -lcrypto
Finished building: ./testOnvifD
运行结果如下:
# ./testOnvifD 192.168.77.112
http://192.168.77.112/onvif/device_service
http://192.168.77.112/onvif/Media
Profile_1
http://192.168.77.112/onvif/Events
http://192.168.77.112/onvif/PTZ
http://192.168.77.112/onvif/Imaging
http://192.168.77.112/onvif/DeviceIO
http://192.168.77.112/onvif/Analytics
http://192.168.77.112/onvif/Recording
http://192.168.77.112/onvif/SearchRecording
http://192.168.77.112/onvif/Replay
call preset 1 is ok