网络摄像机(IPC)开发(10):使用gSOAP生成ONVIF框架C代码

前言

    前面有一系类自行实现ONVIF协议网络摄像机(IPC)开发的专栏,相对比使用gSOAP生成的ONVIF框架代码会复杂些,
但是编译出的应用程序大小会小很多,而且更加适合于初学者学习和懂得原理,如果想了解可以查看博客专栏:自行实现ONVIF协议网络摄像机(IPC)开发(0):专栏开篇_ProYuan的博客-CSDN博客,这篇博客讲解的是使用gSOAP生成ONVIF框架C代码,
然后编写onvif应用程序,实现对onvif摄像头的数据采集。

一、linux下安装gsoap

1、下载源码

gSOAP官方网址:gSOAP: SOAP/XML Web Services and XML Data Bindings for C and C++

2、编译生成wsdl2h和soapcpp2两个工具

# cd  gsoap-2.8/
# ./configure --prefix=$(pwd)/install 
# make
# make install

编译完成,在install下的bin下生成soapcpp2  wsdl2h工具

注意:

a. 在安装gsoap之前须先安装gsoap所依赖的安装包;

b. 若在安装前没有安装前置软件包导致make出错,在安装了那些软件后,需要重新进行 ./configure,然后make clean,最后再make。

二、使用gsoap工具生成onvif框架代代码

1、创建onvif目录

    拷贝bin下soapcpp2 wsdl2h工具到onvif,拷贝gsoap目录下的typemap.dat到onvif;

2、wsdl2h的参数说明;

-c : 生成c风格代码(注:后缀名还是.cpp ,但实际上是.c)

-c++:生成c++风格代码(注 : 默认是生成c++代码)

-x : 表示不生成xml 文件(注:生成的xml文件,有助于了解发送是SOAP是怎样的结构,建议不使用-x)

-l : 表示指定导入路径

-C : 表示生成客户端代码

-S : 表示生成服务端代码

-s : 不使用STL代码

-o: 生成.h文件叫什么名字

-t : 后面紧跟“typemap.dat”这个批处理文件

3、选择wsdl文件

(1)到ONVF官网:Home - ONVIF Mandarin下载最新的wsdl文件

(2)通过网址链接:https://www.onvif.org/ver10/network/wsdl/remotediscovery.wsdl

(3)onvif 常用的几个wsdl文件:
    remotediscovery.wsdl :用于发现设备
    devicemgmt.wsdl :用于获取设备参数
    media.wsdl:用于获取视频流地址
注意:根据业务需求选择wsdl文件,如果对文件不熟悉,可以全部包含进去,防止出现函数找不到的情况,弊端是导致代码量过大,编译时间过长。

4、生成onvif.h文件

#./bin/wsdl2h -P -x -c -s -t ./typemap.dat -o onvif.h 
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

5、使用soapcpp2工具,根据头文件产生框架代码

(1).把gsoap目录下的custom和import目录拷贝到onvif目录下

(2).执行soapcpp2工具

#./bin/soapcpp2 -2 -C -L -c -x -I./custom  -I./import  onvif.h

(3).因「鉴权(认证)」需要,修改onvif.h头文件

有些ONVIF接口调用时需要携带认证信息,要使用soap_wsse_add_UsernameTokenDigest函数进行授权,所以要在onvif.h头文件开头加入

#import "wsse.h"

 如果onvif.h不加入#import "wsse.h",使用soap_wsse_add_UsernameTokenDigest函数会导致编译出错(错误信息如下):
wsse2api.c(183): error C2039: “wsse__Security”: 不是“SOAP_ENV__Header”的成员

(4).如果遇到下面的错误:

wsa5.h(288): **ERROR**: service operation name clash: struct/class 'SOAP_ENV__Fault' 
already declared at wsa.h:273

之所有会出现这个错误,是因为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
再次使用soapcpp2工具编译就成功了

三、编写onvif客服端程序onvif.c

1、编写onvif.c

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "soapH.h"
#include "wsaapi.h"
#include "wsdd.nsmap"

#define SOAP_ASSERT     assert
#define SOAP_DBGLOG     printf
#define SOAP_DBGERR     printf

#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"                       // onvif规定的组播地址

#define SOAP_ITEM       ""                                                      // 寻找的设备范围
#define SOAP_TYPES      "dn:NetworkVideoTransmitter"                            // 寻找的设备类型

#define SOAP_SOCK_TIMEOUT    (10)               // socket????(???)

#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)
{
    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)
{
    void *p = NULL;

    if (n > 0) {
        p = soap_malloc(soap, n);
        SOAP_ASSERT(NULL != p);
        memset(p, 0x00 ,n);
    }
    return p;
}

struct soap *ONVIF_soap_new(int timeout)
{
    struct soap *soap = NULL;                                                   // soap环境变量

    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 ONVIF_soap_delete(struct soap *soap)
{
    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_init_header
**功能:初始化soap描述消息头
**参数:
        [in] soap - soap环境变量
**返回:无
**备注:
    1). 在本函数内部通过ONVIF_soap_malloc分配的内存,将在ONVIF_soap_delete中被释放
************************************************************************/
void ONVIF_init_header(struct soap *soap)
{
    struct SOAP_ENV__Header *header = NULL;

    SOAP_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)
{
    struct wsdd__ScopesType *scope = NULL;                                      // 用于描述查找哪类的Web服务

    SOAP_ASSERT(NULL != soap);
    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))
{
    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_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));

    ONVIF_init_header(soap);                                                    // 设置消息头描述
    ONVIF_init_ProbeType(soap, &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 {                                                            // 成功接收到设备的应答消息
               // dump__wsdd__ProbeMatches(&rep);
				printf("__sizeProbeMatch:%d\n",rep.wsdd__ProbeMatches->__sizeProbeMatch);
                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;
						printf("XAddrs = %s,cb = %p\n",probeMatch->XAddrs,cb);
                        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_GetDeviceInformation
**功能:获取设备基本信息
**参数:
        [in] DeviceXAddr - 设备服务地址
**返回:
        0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_GetDeviceInformation(const char *DeviceXAddr)
{
    int result = 0;
    struct soap *soap = NULL;
    struct _tds__GetDeviceInformation           devinfo_req;
    struct _tds__GetDeviceInformationResponse   devinfo_resp;

    SOAP_ASSERT(NULL != DeviceXAddr);
	SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));

    memset(&devinfo_req, 0x00, sizeof(devinfo_req));
    memset(&devinfo_resp, 0x00, sizeof(devinfo_resp));
    result = soap_call___tds__GetDeviceInformation(soap, DeviceXAddr, NULL, &devinfo_req, &devinfo_resp);
    SOAP_CHECK_ERROR(result, soap, "GetDeviceInformation");

    //dump_tds__GetDeviceInformationResponse(&devinfo_resp);
    printf("Manufacturer:%s\n",devinfo_resp.Manufacturer);
    printf("Model:%s\n",devinfo_resp.Model);
    printf("FirmwareVersion:%s\n",devinfo_resp.FirmwareVersion);
    printf("SerialNumber:%s\n",devinfo_resp.SerialNumber);
    printf("HardwareId:%s\n",devinfo_resp.HardwareId);

EXIT:

    if (NULL != soap) {
        ONVIF_soap_delete(soap);
    }
    return result;
}

void cb_discovery(char *DeviceXAddr)
{
    ONVIF_GetDeviceInformation(DeviceXAddr);
}



int main(int argc, char **argv)
{
    ONVIF_DetectDevice(cb_discovery);
    return 0;
}

2、编写makefile程序

CC = gcc -g -DWITH_NONAMESPACES  
INCLUDE := -I./ 

ONVIf_CBJS += $(wildcard ./*.c) 
ONVIf_OBJS :=$(patsubst %.c,%.o,$(ONVIf_CBJS))

all: onvif  

onvif: $(ONVIf_OBJS)   
	$(CC)  -o onvif $(ONVIf_OBJS)  

%.o : %.c
	$(CC) -c $< -o $@  $(INCLUDE) 
clean: 
	rm -f *.o onvif 

3、编译生成onvif应用程序

# make
# ./onvif

生成结果如下:

XAddrs = http://192.168.100.123:8099/onvif/device_service,cb = 0x402f32
Manufacturer:H264
Model:50X10_32M
FirmwareVersion:V5.00.R02.00023650.10010.248201..ONVIF 2.41
SerialNumber:7abe02981ea7ec68
HardwareId:00001

注意:IPC和linux系统在同一局域网内,并且相同网段;

4、整个工程代码下载linux使用gSOAP生成ONVIF框架C代码_gsoap生成onvif框架-C代码类资源-CSDN下载

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ProYuan28

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值