前言:
本博文主要是借鉴OceanStar大神的博文,在他的博文的基础之上做了一部分修改与简化。
博文链接:
Onvif协议:IPC客户端开发之鉴权_onvif鉴权方式-CSDN博客
Onvif协议:IPC客户端开发之PTZ控制_onvif ptz-CSDN博客
不知道什么是Onvif协议以及相关的基础知识可以查看大神的相关文章或者看我前面的文章:
ONVIF协议网络摄像机客户端使用gsoap获取RTSP流地址GStreamer拉流播放_onvif取流软件-CSDN博客
onvif代码都是靠工具生成的,我们得根据我们的要求生成头文件,onvif是根据模块来生成头文件,需要的模块多,生成的代码就庞大,相反则很少,使用onvif官方提供的wsdl来生成代码。
根据上面链接博文中介绍的使用gsoap的工具来生成客户端脚本
1:生成头文件
1.1:准备工作
首先先创建一个目录,目录里面放连个文件夹分别名字为:bin、gsoap
然后把gsoap的工具:
拷贝到刚刚创建好的bin文件中。
然后把源码中的
拷贝到gsoap目录下
这样准备工作就基本完成了。
1.2:编写生成头文件脚本
mkdir onvif_head
cd onvif_head
# 添加 -c 选项生成C头文件
../bin/wsdl2h -c -o onvif.h -s -d -x -t ../gsoap/WS/typemap.dat \
http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl \
https://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl \
http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl \
http://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl \
http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl
然后记得给脚本赋予权限然后执行
执行成功
1.3:修改头文件内容
在生成的Onvif.h文件中添加
#import "wsse.h"
2:自动生成程序
2.1:编写脚本
这里是使用c语言实现的所以生成的程序就只生成c语言的即可
#!/bin/bash
DIR=soap
mkdir $DIR
cd $DIR
../bin/soapcpp2 -2 -x -c -C ../onvif_head/onvif.h -L -I ../gsoap/import -I ../gsoap/
然后赋予权限运行。
运行会出现问题:
wsa5.h(290): **ERROR**: service operation name clash: struct/class 'SOAP_ENV__Fault' already declared at wsa.h:278
解决方法:
打开 gsoap\import 路径下的wsa5.h, 将277行的SOAP_ENV__Fault结构体注释掉
正确修改完后的结果为:
3:编写核心程序main.c
学习大神的方法,把相关的程序全部放在一起方便管理。
创建一个目录用来存放这些文件
创建一个application目录,存放我们需要的所有代码(比如soap下生成的soapC.c、soapClient.c、soapH.h、soapStub.h、wsdd.nsmap;gsoap源码目录下的stdsoap2.c、stdsoap2.h,gsoap/目录下(dom.c)/gsoap/import目录下的(dom.h)gsoap/plugin目录下的wsseapi.h、wsseapi.c、smdevp.h、smdevp.c、mecevp.c、mecevp.h、threads.c、threads.h、wsaapi.c、wsaapi.h等)/gsoap/custom的(struct_timeval.c、struct_timeval.h)。并创建一个main.c
我这个main.c是简化版本的,里面没有使用到设备发现和获取流地址,适用于设备已知的情况的ptz相机控制,其中的功能有绝对移动和持续移动、停止移动的控制。
#include <assert.h>
#include "soapH.h"
#include "wsdd.nsmap"
#include "soapStub.h"
#include "wsseapi.h"
#include "wsaapi.h"
#include "dom.h" // 添加 DOM 支持
#include "time.h"
// #include <map>
//定义移动枚举
enum PTZCMD
{
PTZ_CMD_LEFT,
PTZ_CMD_RIGHT,
PTZ_CMD_UP,
PTZ_CMD_DOWN,
PTZ_CMD_LEFTUP,
PTZ_CMD_LEFTDOWN,
PTZ_CMD_RIGHTUP,
PTZ_CMD_RIGHTDOWN,
PTZ_CMD_ZOOM_IN,
PTZ_CMD_ZOOM_OUT,
};
#define USERNAME "admin" // 替换为实际用户名
#define PASSWORD "admin" // 替换为实际密码
#define DEVICE_IP "192.168.20.10" // 替换为设备IP
#define DEVICE_PORT 8000 // 替换为设备端口
#define DEVICE_XADDR "http://%s:%d/onvif/device_service" // 设备服务地址模板
#define SOAP_ASSERT assert
#define SOAP_DBGERR printf
#define SOAP_SOCK_TIMEOUT (10) // socket超时时间(单秒秒)
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));
}
}
#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)
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
}
int ONVIF_GetCapabilities(const char *deviceXAddr, char **ptzXAddr)
{
int result = 0;
struct soap *soap = NULL;
struct _tds__GetCapabilities devinfo_req;
struct _tds__GetCapabilitiesResponse devinfo_resp;
// 初始化结构体
memset(&devinfo_req, 0, sizeof(devinfo_req));
memset(&devinfo_resp, 0, sizeof(devinfo_resp));
SOAP_ASSERT(deviceXAddr != NULL);
SOAP_ASSERT((soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)) != NULL);
result = soap_call___tds__GetCapabilities(soap, deviceXAddr, NULL, &devinfo_req, &devinfo_resp);
SOAP_CHECK_ERROR(result, soap, "GetCapabilities");
if (devinfo_resp.Capabilities != NULL &&
devinfo_resp.Capabilities->PTZ != NULL &&
devinfo_resp.Capabilities->PTZ->XAddr != NULL)
{
// 分配内存并复制字符串
*ptzXAddr = strdup(devinfo_resp.Capabilities->PTZ->XAddr);
if (*ptzXAddr == NULL) {
result = -1; // 内存分配失败
goto EXIT;
}
} else {
*ptzXAddr = NULL; // 设置为NULL表示未找到
}
EXIT:
if (soap != NULL) {
// 注意:这里可能需要清理devinfo_resp结构体
ONVIF_soap_delete(soap);
}
return result;
}
static int ONVIF_SetAuthInfo(struct soap *soap, const char *username, const char *password)
{
int result = 0;
SOAP_ASSERT(NULL != username);
SOAP_ASSERT(NULL != password);
result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);
SOAP_CHECK_ERROR(result, soap, "add_UsernameTokenDigest");
EXIT:
return result;
}
int ONVIF_GetProfiles(const char *ptzXAddr, char **profilesToken)
{
int result = 0;
struct soap *soap = NULL;
struct _trt__GetProfiles devinfo_req;
struct _trt__GetProfilesResponse devinfo_resp;
// 初始化结构体
memset(&devinfo_req, 0, sizeof(devinfo_req));
memset(&devinfo_resp, 0, sizeof(devinfo_resp));
SOAP_ASSERT(ptzXAddr != NULL);
SOAP_ASSERT((soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)) != NULL);
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
result = soap_call___trt__GetProfiles(soap, ptzXAddr, NULL, &devinfo_req, &devinfo_resp);
SOAP_CHECK_ERROR(result, soap, "ONVIF_GetProfiles");
SOAP_ASSERT(devinfo_resp.__sizeProfiles > 0 && devinfo_resp.Profiles != NULL);
// 分配内存并复制token
*profilesToken = strdup(devinfo_resp.Profiles[0].token);
if (*profilesToken == NULL) {
result = -1; // 内存分配失败
goto EXIT;
}
EXIT:
if (soap != NULL) {
// 注意:这里可能需要清理devinfo_resp结构体
ONVIF_soap_delete(soap);
}
return result;
}
/************************************************************************
**函数:ONVIF_PTZAbsoluteMove
**功能:绝对移动
**参数:
[in] ptzXAddr - ptzXAddr
[in] ProfileToken - 配置令牌
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_PTZAbsoluteMove(const char *ptzXAddr, const char *ProfileToken)
{
int result = 0;
struct soap *soap = NULL;
struct _tptz__AbsoluteMove absoluteMove;
struct _tptz__AbsoluteMoveResponse absoluteMoveResponse;
// 参数检查
if (ptzXAddr == NULL || ProfileToken == NULL) {
printf("Invalid input parameters.\n");
return -1;
}
// 创建 SOAP 上下文
soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT);
if (!soap) {
printf("Failed to create soap context.\n");
return -1;
}
// 设置认证信息
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
// 初始化 AbsoluteMove 请求结构体
memset(&absoluteMove, 0, sizeof(absoluteMove));
// 设置 ProfileToken(从参数拷贝)
absoluteMove.ProfileToken = (char *)soap_strdup(soap, ProfileToken);
absoluteMove.Position = soap_new_tt__PTZVector(soap,sizeof(struct tt__PTZVector));
absoluteMove.Position->PanTilt = soap_new_tt__Vector2D(soap, sizeof(struct tt__Vector2D));
absoluteMove.Position->Zoom = soap_new_tt__Vector1D(soap, sizeof(struct tt__Vector1D));
absoluteMove.Speed = soap_new_tt__PTZSpeed(soap, sizeof(struct tt__PTZSpeed));
absoluteMove.Speed->PanTilt = soap_new_tt__Vector2D(soap, sizeof(struct tt__Vector2D));
absoluteMove.Speed->Zoom = soap_new_tt__Vector1D(soap, sizeof(struct tt__Vector1D));
// 设置目标位置
absoluteMove.Position->PanTilt->x = 0; // Pan
absoluteMove.Position->PanTilt->y = 0; // Tilt
absoluteMove.Position->Zoom->x = 0; // Zoom
// 设置速度
absoluteMove.Speed->PanTilt->x = 0.5; // Pan speed
absoluteMove.Speed->PanTilt->y = 0.5; // Tilt speed
absoluteMove.Speed->Zoom->x = 0.5; // Zoom speed
//绝对移动接口函数调用
result = soap_call___tptz__AbsoluteMove(soap,ptzXAddr, NULL,&absoluteMove,&absoluteMoveResponse);
if (result != SOAP_OK) {
printf("ONVIF_PTZAbsoluteMove failed: %d\n", result);
} else {
printf("AbsoluteMove succeeded.\n");
}
return 0;
}
/************************************************************************
**函数:ONVIF_PTZStopMove
**功能:停止运动
**参数:
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_PTZStopMove(const char* ptzXAddr, const char* ProfileToken){
int result = 0;
struct soap *soap = NULL;
struct _tptz__Stop tptzStop;
struct _tptz__StopResponse tptzStopResponse;
// 参数检查
if (ptzXAddr == NULL || ProfileToken == NULL) {
printf("Invalid input parameters.\n");
return -1;
}
// 创建 SOAP 上下文
soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT);
if (!soap) {
printf("Failed to create soap context.\n");
return -1;
}
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
tptzStop.ProfileToken = (char *)ProfileToken;
result = soap_call___tptz__Stop(soap, ptzXAddr, NULL, &tptzStop, &tptzStopResponse);
SOAP_CHECK_ERROR(result, soap, "ONVIF_PTZStopMove");
printf("%s result\n", __func__);
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
/************************************************************************
**函数:ONVIF_PTZContinuousMove
**功能:连续移动
**参数:
[in] ptzXAddr - ptzXAddr
[in] ProfileToken - 配置令牌
[in] cmd - 移动命令
[in] speed - 速度
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_PTZContinuousMove(const char *ptzXAddr, char *ProfileToken, enum PTZCMD cmd, float speed)
{
int result = 0;
struct soap *soap = NULL;
struct _tptz__ContinuousMove continuousMove;
struct _tptz__ContinuousMoveResponse continuousMoveResponse;
// 参数检查
if (ptzXAddr == NULL || ProfileToken == NULL) {
printf("Invalid input parameters.\n");
return -1;
}
// 创建 SOAP 上下文
soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT);
if (!soap) {
printf("Failed to create soap context.\n");
return -1;
}
// 设置认证信息
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
// 初始化请求结构体
memset(&continuousMove, 0, sizeof(continuousMove));
continuousMove.ProfileToken = ProfileToken;
printf("continuousMove.ProfileToken:%s\n", continuousMove.ProfileToken);
continuousMove.Velocity = soap_new_tt__PTZSpeed(soap,-1);
continuousMove.Velocity->PanTilt = soap_new_tt__Vector2D(soap,-1);
continuousMove.Velocity->Zoom = soap_new_tt__Vector1D(soap,-1);
// 设置空间URI(必需字段)
continuousMove.Velocity->PanTilt->space = "http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace";
continuousMove.Velocity->Zoom->space = "http://www.onvif.org/ver10/tptz/ZoomSpaces/VelocityGenericSpace";
switch (cmd)
{
case PTZ_CMD_LEFT:
continuousMove.Velocity->PanTilt->x = -speed;
continuousMove.Velocity->PanTilt->y = 0;
break;
case PTZ_CMD_RIGHT:
continuousMove.Velocity->PanTilt->x = speed;
continuousMove.Velocity->PanTilt->y = 0;
break;
case PTZ_CMD_UP:
continuousMove.Velocity->PanTilt->x = 0;
continuousMove.Velocity->PanTilt->y = speed;
break;
case PTZ_CMD_DOWN:
continuousMove.Velocity->PanTilt->x = 0;
continuousMove.Velocity->PanTilt->y = -speed;
break;
case PTZ_CMD_LEFTUP:
continuousMove.Velocity->PanTilt->x = -speed;
continuousMove.Velocity->PanTilt->y = speed;
break;
case PTZ_CMD_LEFTDOWN:
continuousMove.Velocity->PanTilt->x = -speed;
continuousMove.Velocity->PanTilt->y = -speed;
break;
case PTZ_CMD_RIGHTUP:
continuousMove.Velocity->PanTilt->x = speed;
continuousMove.Velocity->PanTilt->y = speed;
break;
case PTZ_CMD_RIGHTDOWN:
continuousMove.Velocity->PanTilt->x = speed;
continuousMove.Velocity->PanTilt->y = -speed;
break;
case PTZ_CMD_ZOOM_IN:
continuousMove.Velocity->PanTilt->x = 0;
continuousMove.Velocity->PanTilt->y = 0;
continuousMove.Velocity->Zoom->x = speed;
break;
case PTZ_CMD_ZOOM_OUT:
continuousMove.Velocity->PanTilt->x = 0;
continuousMove.Velocity->PanTilt->y = 0;
continuousMove.Velocity->Zoom->x = -speed;
break;
default:
break;
}
result = soap_call___tptz__ContinuousMove(soap,ptzXAddr, NULL,&continuousMove,&continuousMoveResponse);
SOAP_CHECK_ERROR(result, soap, "soap_call___tptz__ContinuousMove");
printf("soap_call___tptz__ContinuousMove :result = %d\n", result);
// sleep(3); //如果当前soap被删除(或者发送stop指令),就会停止移动
// printf("延时3S停止运行\n");
// ONVIF_PTZStopMove(ptzXAddr, ProfileToken);
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
// 在main函数前添加内存释放函数
void ONVIF_FreeString(char **str)
{
if (str && *str) {
free(*str);
*str = NULL;
}
}
int main(int argc, char **argv)
{
char * ptzXAddr = NULL;
char *profilesToken = NULL;
char buffer[1024];
sprintf(buffer, DEVICE_XADDR, DEVICE_IP , DEVICE_PORT );//拼接服务器的地址
// 获取能力
if (ONVIF_GetCapabilities(buffer, &ptzXAddr) != 0) {
printf("GetCapabilities failed\n");
return -1;
}
if (!ptzXAddr) {
printf("PTZ service not found\n");
return -1;
}
// 获取配置
if (ONVIF_GetProfiles(ptzXAddr, &profilesToken) != 0) {
printf("GetProfiles failed\n");
ONVIF_FreeString(&ptzXAddr);
return -1;
}
if (!profilesToken) {
printf("No profiles found\n");
ONVIF_FreeString(&ptzXAddr);
return -1;
}
printf("Using PTZ service at: %s\n", ptzXAddr);
printf("Using profile token: %s\n", profilesToken);
ONVIF_PTZAbsoluteMove(ptzXAddr, profilesToken);
// ONVIF_PTZContinuousMove(ptzXAddr, profilesToken, PTZ_CMD_RIGHTDOWN, 0.5);//持续移动
// sleep(3); //如果当前soap被删除(或者发送stop指令),就会停止移动
// printf("延时3S停止运行\n");
// ONVIF_PTZStopMove(ptzXAddr, profilesToken);
// 释放内存
ONVIF_FreeString(&ptzXAddr);
ONVIF_FreeString(&profilesToken);
return 0;
}
4:编写Makefile
# ONVIF PTZ 控制程序 Makefile (C语言版本)
TARGET = onvif-ptz
CC = gcc
CFLAGS = -DWITH_NONAMESPACES -DWITH_DOM -DWITH_OPENSSL -DWITH_PTHREAD -I. -g -Wall -Werror
LDFLAGS = -lssl -lcrypto -lpthread
# 所有源文件
SRCS = dom.c \
main.c \
mecevp.c \
smdevp.c \
soapC.c \
soapClient.c \
stdsoap2.c \
struct_timeval.c \
threads.c \
wsaapi.c \
wsseapi.c
OBJS = $(SRCS:.c=.o)
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# 显式依赖规则
dom.o: dom.c dom.h
$(CC) $(CFLAGS) -c -o $@ $<
main.o: main.c soapH.h soapStub.h stdsoap2.h wsseapi.h dom.h
$(CC) $(CFLAGS) -c -o $@ $<
mecevp.o: mecevp.c mecevp.h
smdevp.o: smdevp.c smdevp.h
soapC.o: soapC.c soapH.h
soapClient.o: soapClient.c soapH.h
stdsoap2.o: stdsoap2.c stdsoap2.h
struct_timeval.o: struct_timeval.c struct_timeval.h
threads.o: threads.c threads.h
wsaapi.o: wsaapi.c wsaapi.h
wsseapi.o: wsseapi.c wsseapi.h
# 通用规则
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(TARGET) $(OBJS)
.PHONY: all clean
我编译出现的问题是:
这个问题出现的原因是在soapStub.h中定义过了
在dom.h中修改这个两句话就可以了
然后再编译:Make
编译成功,测试: