1 前言
现在网上一些关于gsoap的文章有部分过时了,博主参考起来做的时候遇到了挺多问题,所以想着自己梳理一遍,于是有了这片文章。
因为写这篇文章的时候比较忙,写的时候断断续续,所以内容可能会有所疏漏,麻烦见谅和指出。
2 前置文件下载以及安装
1.依赖的库文件以及程序
首先,要用gsoap框架实现抓图需要一系列的库文件和程序,这里直接给出安装的命令
sudo apt install flex bison libssl-dev unzip zlib1g-dev ffmpeg
当然也可以自己下载源码包自己编译安装,好处是使用的版本可以自己控制,具体的下载方式可以自己百度。
2.下载gsoap
gSOAP官方网址:http://www.cs.fsu.edu/~engelen/soap.html
gSOAP开源版下载网址(最新版本):http://sourceforge.net/projects/gsoap2
gSOAP开源版下载网址(历史版本):https://sourceforge.net/projects/gsoap2/files/gSOAP/
3.下载wsdl文件(可选)
说明一下,这一步如果你需要在没网的环境下生产gsoap框架或者说生成的gsoap框架过大需要裁剪的话就需要下载所需要的wsdl文件。
wsdl相关文件的下载:Network Interface Specifications - ONVIF
进去网页后右键所需的文件的超链接另存为下载到了,如下图
以本篇博文演示的抓图功能所需的wsdl文件和xsd文件需要以下几种:devicemgmt.wsdl 、media.wsdl、remotediscovery.wsdl 、onvif.xsd
3 编译gsoap
这一步如果是自己下载了wsdl文件的话
1.解压gsoap压缩包
将下载到的gsoap压缩包放到你的工程文件夹内,然后解压,解压方式可以自己搜索,博主这里用的zip解压,命令如下:
unzip gsoap_2.8.131.zip
得到文件夹,cd进去
2.编译gsoap
在gsoap的configure可执行文件所在的文件夹下进行编译配置,所需的命令:
./configure --with-openssl=/usr/lib/x86_64-linux-gnu/ --prefix=/root/Project/Onvif_snapshot/gsoap-2.8/new
这里说明一下:1.–with-openssl= 后面跟的是你自己系统上libssl库所在的路径,如果不知道库文件的位置的话,可以用如下命令:
sudo find / -name libssl.so 或者 sudo find / -name libssl.a
2、–prefix=后面跟的是你想要编译出来的文件所在的文件夹的路径,这个按自己的喜好填就行,博主这里是直接在gsoap的文件夹下面创建了一个new文件夹
3、在gsoap的configure可执行文件所在的文件夹下进行编译,命令如下:
sudo make && sudo make install
没问题的话界面如图:
4、然后新建一个文件夹,将new中的对应文件复制出来
其中typemap.dat 在 share/WS下,stdsoap2.h在include下,custom和import在share下
5、在typemap.dat文件的最后新增一行
xsd__duration = #import "custom/duration.h" | xsd__duration
6、使用wsdl2h工具生成onvif.h文件
./bin/wsdl2h -o onvif.h -c -s -t ./typemap.dat \
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
如果使用本地文件,就把里面./typemap.dat \后面的路径改为本地文件的路径,例如:
./bin/wsdl2h -o onvif.h -c -s -t ./typemap.dat \
devicemgmt.wsdl \
media.wsdl \
remotediscovery.wsdl \
onvif.xsd
生成过程如下:
root@iZbp1bslec5v3fdrobnq0qZ:~/Project/Onvif_snapshot/onvif_gsoap# ./bin/wsdl2h -o onvif.h -c -s -t ./typemap.dat \
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
Saving onvif.h
** The gSOAP WSDL/WADL/XSD processor for C and C++, wsdl2h release 2.8.131
** Copyright (C) 2000-2023 Genivia Inc. All Rights Reserved.
** 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"
Connecting to 'https://www.onvif.org/ver10/network/wsdl/remotediscovery.wsdl' to retrieve WSDL/WADL or XSD... connected, receiving...
Done reading 'https://www.onvif.org/ver10/network/wsdl/remotediscovery.wsdl'
Connecting to 'https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl' to retrieve WSDL/WADL or XSD... connected, receiving...
Connecting to 'https://www.onvif.org/ver10/schema/onvif.xsd' to retrieve schema... connected, receiving...
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'
Connecting to 'https://www.onvif.org/ver10/schema/common.xsd' to retrieve schema... connected, receiving...
Done reading 'https://www.onvif.org/ver10/schema/common.xsd'
Done reading 'https://www.onvif.org/ver10/schema/onvif.xsd'
Done reading 'https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl'
Connecting to 'https://www.onvif.org/ver10/media/wsdl/media.wsdl' to retrieve WSDL/WADL or XSD... connected, receiving...
Done reading 'https://www.onvif.org/ver10/media/wsdl/media.wsdl'
Warning: ignoring type inheritance by default for C, use option -F to generate struct declarations with simulated inheritance using transient pointer members pointing to derived types to serialize derived types as elements annotated by xsi:type attributes in XML.
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 onvif.h
7、如果要鉴权功能的话,需要使用soap_wsse_add_UsernameTokenDigest函数,所以要在onvif.h头文件开头加入
#import "wsse.h"
8、因为onvif.h中同时包含了
#import "wsdd10.h" // wsdd10.h中又#import "wsa.h"
#import "wsa5.h" // wsa.h和wsa5.h两个文件重复定义了int SOAP_ENV__Fault
所以修改import\wsa5.h文件
将int SOAP_ENV__Fault修改为int SOAP_ENV__Fault_alex
9、使用soapcpp2工具生成onvif框架
./bin/soapcpp2 -2 -x -c onvif.h -Iimport .
生成过程如下:
** The gSOAP code generator for C and C++, soapcpp2 release 2.8.131
** Copyright (C) 2000-2023 Genivia Inc. All Rights Reserved.
** The soapcpp2 tool and its generated software are released under the GPL.
** ----------------------------------------------------------------------------
** A commercial use license is available from Genivia Inc., contact@genivia.com
** ----------------------------------------------------------------------------
Saving soapStub.h annotated copy of the source interface header file
Saving soapH.h serialization functions to #include in projects
Saving soap.nsmap namespace mapping table
Saving soapC.c serialization functions
Compilation successful
10、修改stdsoap2.c文件,在开头加入:
#include "wsdd.nsmap"
4 功能实现
1、文件整合
从编译目录下的custom、import、plugin、WS文件夹内复制文件到本地文件夹,删去多余文件,最后结果如下:
将这些文件都归为一个文件夹,我命名为onvif_frame
2、样例代码
新建文件夹src作为功能实现文件夹,抓图功能的样例代码如下:
onvif_comm.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <netinet/in.h>
#include "wsseapi.h"
#include "onvif_comm.h"
void soap_perror(struct soap *soap, const char *str)
{
ERROR_DBLOG();
if (NULL == str) {
SOAP_DBGERR("[soap] error: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
} else {
SOAP_DBGERR("[soap] %s error: %d, %s, %s\n", str, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
}
return;
}
void* ONVIF_soap_malloc(struct soap *soap, unsigned int n)
{
ERROR_DBLOG();
void *p = NULL;
if (n > 0) {
p = soap_malloc(soap, n);
assert(NULL != p);
memset(p, 0x00 ,n);
}
return p;
}
struct soap *ONVIF_soap_new(int timeout)
{
ERROR_DBLOG();
struct soap *soap = NULL; // soap环境变量
soap = soap_new();
assert(NULL != soap);
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 ONVIF_soap_delete(struct soap *soap)
{
ERROR_DBLOG();
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
}
/************************************************************************
**函数:ONVIF_SetAuthInfo
**功能:设置认证信息
**参数:
[in] soap - soap环境变量
[in] username - 用户名
[in] password - 密码
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_SetAuthInfo(struct soap *soap, const char *username, const char *password)
{
ERROR_DBLOG();
int result = 0;
assert(NULL != username);
assert(NULL != password);
result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);
SOAP_CHECK_ERROR(result, soap, "add_UsernameTokenDigest");
EXIT:
return result;
}
/************************************************************************
**函数:ONVIF_init_header
**功能:初始化soap描述消息头
**参数:
[in] soap - soap环境变量
**返回:无
**备注:
1). 在本函数内部通过ONVIF_soap_malloc分配的内存,将在ONVIF_soap_delete中被释放
************************************************************************/
void ONVIF_init_header(struct soap *soap)
{
ERROR_DBLOG();
struct SOAP_ENV__Header *header = NULL;
assert(NULL != soap);
header = (struct SOAP_ENV__Header *)ONVIF_soap_malloc(soap, sizeof(struct SOAP_ENV__Header));
soap_default_SOAP_ENV__Header(soap, header);
header->wsa__MessageID = (char*)soap_wsa_rand_uuid(soap);
header->wsa__To = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_TO) + 1);
header->wsa__Action = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_ACTION) + 1);
strcpy(header->wsa__To, SOAP_TO);
strcpy(header->wsa__Action, SOAP_ACTION);
soap->header = header;
return;
}
/************************************************************************
**函数:ONVIF_init_ProbeType
**功能:初始化探测设备的范围和类型
**参数:
[in] soap - soap环境变量
[out] probe - 填充要探测的设备范围和类型
**返回:
0表明探测到,非0表明未探测到
**备注:
1). 在本函数内部通过ONVIF_soap_malloc分配的内存,将在ONVIF_soap_delete中被释放
************************************************************************/
void ONVIF_init_ProbeType(struct soap *soap, struct wsdd__ProbeType *probe)
{
ERROR_DBLOG();
struct wsdd__ScopesType *scope = NULL; // 用于描述查找哪类的Web服务
assert(NULL != soap);
assert(NULL != probe);
scope = (struct wsdd__ScopesType *)ONVIF_soap_malloc(soap, sizeof(struct wsdd__ScopesType));
soap_default_wsdd__ScopesType(soap, scope); // 设置寻找设备的范围
scope->__item = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_ITEM) + 1);
strcpy(scope->__item, SOAP_ITEM);
memset(probe, 0x00, sizeof(struct wsdd__ProbeType));
soap_default_wsdd__ProbeType(soap, probe);
probe->Scopes = scope;
probe->Types = (char*)ONVIF_soap_malloc(soap, strlen(SOAP_TYPES) + 1); // 设置寻找设备的类型
strcpy(probe->Types, SOAP_TYPES);
return;
}
void ONVIF_DetectDevice(void (*cb)(char *DeviceXAddr))
{
ERROR_DBLOG();
int i;
int result = 0;
unsigned int count = 0; // 搜索到的设备个数
struct soap *soap = NULL; // soap环境变量
struct wsdd__ProbeType req; // 用于发送Probe消息
struct __wsdd__ProbeMatches rep; // 用于接收Probe应答
struct wsdd__ProbeMatchType *probeMatch;
soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT);
assert(NULL != soap);
ONVIF_init_header(soap); // 设置消息头描述
ONVIF_init_ProbeType(soap, &req); // 设置寻找的设备的范围和类型
struct in_addr if_req;
if_req.s_addr = inet_addr(NET_CARD); // 想绑定的IP地址
soap->ipv4_multicast_if = (char*)soap_malloc(soap, sizeof(struct in_addr));
memset(soap->ipv4_multicast_if, 0, sizeof(struct in_addr));
memcpy(soap->ipv4_multicast_if, (char*)&if_req, sizeof(if_req));
result = soap_send___wsdd__Probe(soap, SOAP_MCAST_ADDR, NULL, &req); // 向组播地址广播Probe消息
while (SOAP_OK == result) // 开始循环接收设备发送过来的消息
{
memset(&rep, 0x00, sizeof(rep));
result = soap_recv___wsdd__ProbeMatches(soap, &rep);
if (SOAP_OK == result)
{
if (soap->error)
{
soap_perror(soap, "ProbeMatches");
}
else
{ // 成功接收到设备的应答消息
if (NULL != rep.wsdd__ProbeMatches)
{
count += rep.wsdd__ProbeMatches->__sizeProbeMatch;
for(i = 0; i < rep.wsdd__ProbeMatches->__sizeProbeMatch; i++)
{
probeMatch = rep.wsdd__ProbeMatches->ProbeMatch + i;
if (NULL != cb)
{
cb(probeMatch->XAddrs); // 使用设备服务地址执行函数回调
}
}
}
}
}
else if (soap->error)
{
break;
}
}
SOAP_DBGLOG("\ndetect end! It has detected %d devices!\n", count);
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return ;
}
/************************************************************************
**函数:ONVIF_GetCapabilities
**功能:获取设备能力信息
**参数:
[in] DeviceXAddr - 设备服务地址
[out] capa - 返回设备能力信息信息
**返回:
0表明成功,非0表明失败
**备注:
1). 其中最主要的参数之一是媒体服务地址
************************************************************************/
int ONVIF_GetCapabilities(const char *DeviceXAddr, struct tagCapabilities *capa)
{
ERROR_DBLOG();
int result = 0;
struct soap *soap = NULL;
struct _tds__GetCapabilities req;
struct _tds__GetCapabilitiesResponse rep;
assert(NULL != DeviceXAddr);
assert(NULL != capa);
soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT);
assert(NULL != soap);
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
memset(&req, 0x00, sizeof(req));
memset(&rep, 0x00, sizeof(rep));
result = soap_call___tds__GetCapabilities(soap, DeviceXAddr, NULL, &req, &rep);
SOAP_CHECK_ERROR(result, soap, "GetCapabilities");
//dump_tds__GetCapabilitiesResponse(&rep);
memset(capa, 0x00, sizeof(struct tagCapabilities));
if (NULL != rep.Capabilities) {
if (NULL != rep.Capabilities->Media) {
if (NULL != rep.Capabilities->Media->XAddr) {
strncpy(capa->MediaXAddr, rep.Capabilities->Media->XAddr, sizeof(capa->MediaXAddr) - 1);
}
}
if (NULL != rep.Capabilities->Events) {
if (NULL != rep.Capabilities->Events->XAddr) {
strncpy(capa->EventXAddr, rep.Capabilities->Events->XAddr, sizeof(capa->EventXAddr) - 1);
}
}
}
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
/************************************************************************
**函数:ONVIF_GetProfiles
**功能:获取设备的音视频码流配置信息
**参数:
[in] MediaXAddr - 媒体服务地址
[out] profiles - 返回的设备音视频码流配置信息列表,调用者有责任使用free释放该缓存
**返回:
返回设备可支持的码流数量(通常是主/辅码流),即使profiles列表个数
**备注:
1). 注意:一个码流(如主码流)可以包含视频和音频数据,也可以仅仅包含视频数据。
************************************************************************/
int ONVIF_GetProfiles(const char *MediaXAddr, struct tagProfile **profiles)
{
ERROR_DBLOG();
int i = 0;
int result = 0;
struct soap *soap = NULL;
struct _trt__GetProfiles req;
struct _trt__GetProfilesResponse rep;
assert(NULL != MediaXAddr);
soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT);
assert(NULL != soap);
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
memset(&req, 0x00, sizeof(req));
memset(&rep, 0x00, sizeof(rep));
result = soap_call___trt__GetProfiles(soap, MediaXAddr, NULL, &req, &rep);
SOAP_CHECK_ERROR(result, soap, "GetProfiles");
//dump_trt__GetProfilesResponse(&rep);
if (rep.__sizeProfiles > 0) { // 分配缓存
(*profiles) = (struct tagProfile *)malloc(rep.__sizeProfiles * sizeof(struct tagProfile));
assert(NULL != (*profiles));
memset((*profiles), 0x00, rep.__sizeProfiles * sizeof(struct tagProfile));
}
for(i = 0; i < rep.__sizeProfiles; i++) { // 提取所有配置文件信息(我们所关心的)
struct tt__Profile *ttProfile = &rep.Profiles[i];
struct tagProfile *plst = &(*profiles)[i];
if (NULL != ttProfile->token) { // 配置文件Token
strncpy(plst->token, ttProfile->token, sizeof(plst->token) - 1);
}
if (NULL != ttProfile->VideoEncoderConfiguration) { // 视频编码器配置信息
if (NULL != ttProfile->VideoEncoderConfiguration->token) { // 视频编码器Token
strncpy(plst->venc.token, ttProfile->VideoEncoderConfiguration->token, sizeof(plst->venc.token) - 1);
}
if (NULL != ttProfile->VideoEncoderConfiguration->Resolution) { // 视频编码器分辨率
plst->venc.Width = ttProfile->VideoEncoderConfiguration->Resolution->Width;
plst->venc.Height = ttProfile->VideoEncoderConfiguration->Resolution->Height;
}
}
}
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return rep.__sizeProfiles;
}
/************************************************************************
**函数:make_uri_withauth
**功能:构造带有认证信息的URI地址
**参数:
[in] src_uri - 未带认证信息的URI地址
[in] username - 用户名
[in] password - 密码
[out] dest_uri - 返回的带认证信息的URI地址
[in] size_dest_uri - dest_uri缓存大小
**返回:
0成功,非0失败
**备注:
1). 例子:
无认证信息的uri:rtsp://100.100.100.140:554/av0_0
带认证信息的uri:rtsp://username:password@100.100.100.140:554/av0_0
************************************************************************/
int make_uri_withauth(char *src_uri, char *username, char *password, char *dest_uri, unsigned int size_dest_uri)
{
ERROR_DBLOG();
int result = 0;
unsigned int needBufSize = 0;
assert(NULL != src_uri);
assert(NULL != username);
assert(NULL != password);
assert(NULL != dest_uri);
memset(dest_uri, 0x00, size_dest_uri);
needBufSize = strlen(src_uri) + strlen(username) + strlen(password) + 3; // 检查缓存是否足够,包括‘:’和‘@’和字符串结束符
if (size_dest_uri < needBufSize) {
SOAP_DBGERR("dest uri buf size is not enough.\n");
result = -1;
goto EXIT;
}
if (0 == strlen(username) && 0 == strlen(password)) { // 生成新的uri地址
strcpy(dest_uri, src_uri);
} else {
char *p = strstr(src_uri, "//");
if (NULL == p) {
SOAP_DBGERR("can't found '//', src uri is: %s.\n", src_uri);
result = -1;
goto EXIT;
}
p += 2;
memcpy(dest_uri, src_uri, p - src_uri);
sprintf(dest_uri + strlen(dest_uri), "%s:%s@", username, password);
strcat(dest_uri, p);
}
EXIT:
return result;
}
/************************************************************************
**函数:ONVIF_GetSnapshotUri
**功能:获取设备图像抓拍地址(HTTP)
**参数:
[in] MediaXAddr - 媒体服务地址
[in] ProfileToken - the media profile token
[out] uri - 返回的地址
[in] sizeuri - 地址缓存大小
**返回:
0表明成功,非0表明失败
**备注:
1). 并非所有的ProfileToken都支持图像抓拍地址。举例:XXX品牌的IPC有如下三个配置
profile0/profile1/TestMediaProfile,其中TestMediaProfile返回的图像抓拍地址就是
空指针。
************************************************************************/
int ONVIF_GetSnapshotUri(const char *MediaXAddr, char *ProfileToken, char *uri, unsigned int sizeuri)
{
ERROR_DBLOG();
int result = 0;
struct soap *soap = NULL;
struct _trt__GetSnapshotUri req;
struct _trt__GetSnapshotUriResponse rep;
assert(NULL != MediaXAddr);
assert(NULL != uri);
memset(uri, 0x00, sizeuri);
soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT);
assert(NULL != soap);
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
memset(&req, 0x00, sizeof(req));
memset(&rep, 0x00, sizeof(rep));
req.ProfileToken = ProfileToken;
result = soap_call___trt__GetSnapshotUri(soap, MediaXAddr, NULL, &req, &rep);
SOAP_CHECK_ERROR(result, soap, "GetSnapshotUri");
//dump_trt__GetSnapshotUriResponse(&rep);
result = -1;
if (NULL != rep.MediaUri) {
if (NULL != rep.MediaUri->Uri) {
if (sizeuri > strlen(rep.MediaUri->Uri)) {
strcpy(uri, rep.MediaUri->Uri);
result = 0;
} else {
SOAP_DBGERR("Not enough cache!\n");
}
}
}
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
void cb_discovery(char *DeviceXAddr)
{
ERROR_DBLOG();
int stmno = 0; // 码流序号,0为主码流,1为辅码流
int profile_cnt = 0; // 设备配置文件个数
struct tagProfile *profiles = NULL; // 设备配置文件列表
struct tagCapabilities capa; // 设备能力信息
char cmd[256];
char uri[ONVIF_ADDRESS_SIZE] = {0}; // 不带认证信息的URI地址
char uri_auth[ONVIF_ADDRESS_SIZE + 50] = {0}; // 带有认证信息的URI地址
ONVIF_GetCapabilities(DeviceXAddr, &capa); // 获取设备能力信息(获取媒体服务地址)
profile_cnt = ONVIF_GetProfiles(DeviceXAddr, &profiles); // 获取媒体配置信息(主/辅码流配置信息)
if (profile_cnt > stmno) {
ONVIF_GetSnapshotUri(capa.MediaXAddr, profiles[stmno].token, uri, sizeof(uri)); // 获取图像抓拍URI
make_uri_withauth(uri, USERNAME, PASSWORD, uri_auth, sizeof(uri_auth)); // 生成带认证信息的URI(有的IPC要求认证)
sprintf(cmd, "wget -O out.jpeg '%s'", uri_auth); // 使用wget下载图片
system(cmd);
printf("抓图uri: %s\n", uri_auth);
}
if (NULL != profiles) {
free(profiles);
profiles = NULL;
}
}
onvif_comm.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "soapH.h"
#include "wsaapi.h"
#ifndef __ONVIF_COMM_H__
#define __ONVIF_COMM_H__
#ifdef __cplusplus
extern "C" {
#endif
#ifndef DIM
#define DIM(array) (sizeof(array) / sizeof(array[0]))
#endif
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
#define ERROR_DBLOG() //printf(">> File:%s Line:%d Function:%s\n", __FILE__, __LINE__, __FUNCTION__)
#define SOAP_DBGLOG printf
#define SOAP_DBGERR printf
#define USERNAME "admin"
#define PASSWORD "Xinzailing_Web"
#define SOAP_TO "urn:schemas-xmlsoap-org:ws:2005:04:discovery"
#define SOAP_ACTION "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"
#define SOAP_MCAST_ADDR "soap.udp://239.255.255.250:3702"
#define SOAP_ITEM ""
#define SOAP_TYPES "dn:NetworkVideoTransmitter"
#define SOAP_SOCK_TIMEOUT (10)
#define ONVIF_ADDRESS_SIZE (128)
#define ONVIF_TOKEN_SIZE (65)
#define NET_CARD "172.18.0.1"
struct tagVideoEncoderConfiguration
{
char token[ONVIF_TOKEN_SIZE];
int Width;
int Height;
};
struct tagProfile {
char token[ONVIF_TOKEN_SIZE];
struct tagVideoEncoderConfiguration venc;
};
struct tagCapabilities {
char MediaXAddr[ONVIF_ADDRESS_SIZE];
char EventXAddr[ONVIF_ADDRESS_SIZE];
};
#define SOAP_CHECK_ERROR(result, soap, str) \
do { \
if (SOAP_OK != (result) || SOAP_OK != (soap)->error) { \
soap_perror((soap), (str)); \
if (SOAP_OK == (result)) { \
(result) = (soap)->error; \
} \
goto EXIT; \
} \
} while (0)
void soap_perror(struct soap *soap, const char *str);
void * ONVIF_soap_malloc(struct soap *soap, unsigned int n);
struct soap * ONVIF_soap_new(int timeout);
void ONVIF_soap_delete(struct soap *soap);
int ONVIF_SetAuthInfo(struct soap *soap, const char *username, const char *password);
void ONVIF_init_header(struct soap *soap);
void ONVIF_init_ProbeType(struct soap *soap, struct wsdd__ProbeType *probe);
void ONVIF_DetectDevice(void (*cb)(char *DeviceXAddr));
int ONVIF_GetCapabilities(const char *DeviceXAddr, struct tagCapabilities *capa);
int ONVIF_GetProfiles(const char *MediaXAddr, struct tagProfile **profiles);
int ONVIF_GetSnapshotUri(const char *MediaXAddr, char *ProfileToken, char *uri, unsigned int sizeuri);
int make_uri_withauth(char *src_uri, char *username, char *password, char *dest_uri, unsigned int size_dest_uri);
void cb_discovery(char *DeviceXAddr);
#ifdef __cplusplus
}
#endif
#endif
main.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "onvif_comm.h"
int main(int argc, char **argv)
{
ONVIF_DetectDevice(cb_discovery);
return 0;
}
博主是用cmake编译的,顺便贴一下CMakeLists.txt的代码,这样写是为了保证编译出的文件比较小
# CMake 最低版本号要求
cmake_minimum_required(VERSION 2.6)
# 项目信息
project (onvif)
# 取消默认链接参数
string(REGEX REPLACE "-rdynamic" "" CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "${CMAKE_SHARED_LIBRARY_LINK_C_FLAGS}")
message("CMAKE_SHARED_LIBRARY_LINK_C_FLAGS = " ${CMAKE_SHARED_LIBRARY_LINK_C_FLAGS})
# 编译器参数
add_compile_options(-DWITH_OPENSSL -DWITH_DOM -DWITH_ZLIB -DWITH_NOIDREF -Os -ffunction-sections -fdata-sections)
# 链接器参数
set(LINK_FLAGS "-Wl,-exclude-libs=ALL,-gc-sections -s")
set(CMAKE_SHARED_LINKER_FLAGS "${LINK_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${LINK_FLAGS}")
# 指定生成目标
add_executable(${PROJECT_NAME})
# 添加头文件路径
INCLUDE_DIRECTORIES(./onvif_frame)
INCLUDE_DIRECTORIES(./src)
# 添加所有源文件源文件路径
aux_source_directory(./src sources)
aux_source_directory(./onvif_frame sources)
target_sources(${PROJECT_NAME} PUBLIC ${sources})
# 指定要链接的动态库
TARGET_LINK_LIBRARIES(${PROJECT_NAME} z ssl crypto ${LIBS})
# 设置安装路径
INSTALL(TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION sbin
)
5 最后的话
博主最近比较忙,可能写的没那么详细,有什么问题可以问,后续可能会更新。
除了本篇博客外和onvif相关的后面可能会出裁剪wsdl文件的方法,以及不使用wget用curl库进行图片下载、不使用gsoap框架实现抓图等内容(有空的话)。