ONVIF系列三:ONVIF客户端实现

        ONVIF系列:

        ONVIF系列一:ONVIF介绍

        ONVIF系列二:Ubuntu安装gSOAP、生成ONVIF代码框架

        ONVIF系列三:ONVIF客户端实现

        在系列二中完成了在Ubuntu上安装gSOAP并生成ONVIF代码框架,接下来我们利用生成的框架实现ONVIF客户端。

一、设备发现(WS-Discovery)

        一般C/S模型中,客户端事先知道Web Service的地址,通过这个地址(HTTP URL)向Web Service发情请求,但是在ONVIF中,客户端不知道IPC摄像头的ONVIF服务地址,要怎么向服务端发起请求呢,这就需要WS-Discovery设备发现协议。

        WS-Discovery的全称为Web Services Dynamic Discovery,这个技术规范定义了一套多播发现协议来定位服务,它工作在TCP和UDP的DISCOVERY_PORT为3702,其使用IP组播地址为239.255.255.250(IPV4)或FF02::C(IPV6)。客户端预先不知道目标服务地址的情况下,可以动态地探测到可用的目标服务,以便进行服务调用,这个过程就是「设备发现」的过程。关于设备发现可参考这篇文章:ONVIF协议--ONVIF WS-Discovery (设备发现协议)-CSDN博客。

        支持ONVIF协议的设备启动之后会加入多播组239.255.255.250端口3702,ONVIF客户端通过UDP向239.255.255.250:3702多播地址发送探测消息(Probe),ONVIF设备收到消息会返回响应(Probe Match),响应消息中包含设备的ONVIF服务地址,注意:Probe是多播过程,ONVIF客户端发,多个ONVIF设备收;Probe Match是UDP单播,ONVIF设备向ONVIF客户端发送。

        Probe请求SOAP消息示例:

<Envelope xmlns:dn="http://www.onvif.org/ver10/network/wsdl" xmlns="http://www.w3.org/2003/05/soap-envelope"><Header><wsa:MessageID xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">uuid:25862dc4-4866-42a9-87a7-edcf2918ab10</wsa:MessageID><wsa:To xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To><wsa:Action xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action></Header><Body><Probe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/ws/2005/04/discovery"><Types>dn:NetworkVideoTransmitter</Types><Scopes /></Probe></Body>
</Envelope>

        Probe Match响应SOAP消息示例:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:xs="http://www.w3.org/2000/10/XMLSchema"xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"xmlns:wsa5="http://www.w3.org/2005/08/addressing"xmlns:xop="http://www.w3.org/2004/08/xop/include"xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"xmlns:tt="http://www.onvif.org/ver10/schema"xmlns:ns1="http://www.w3.org/2005/05/xmlmime"xmlns:wstop="http://docs.oasis-open.org/wsn/t-1"xmlns:ns7="http://docs.oasis-open.org/wsrf/r-2"xmlns:ns2="http://docs.oasis-open.org/wsrf/bf-2"xmlns:dndl="http://www.onvif.org/ver10/network/wsdl/DiscoveryLookupBinding"xmlns:dnrd="http://www.onvif.org/ver10/network/wsdl/RemoteDiscoveryBinding"xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"xmlns:dn="http://www.onvif.org/ver10/network/wsdl"xmlns:ns10="http://www.onvif.org/ver10/replay/wsdl"xmlns:ns11="http://www.onvif.org/ver10/search/wsdl"xmlns:ns13="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding"xmlns:ns14="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding"xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl"xmlns:ns15="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding"xmlns:ns16="http://www.onvif.org/ver10/events/wsdl/EventBinding"xmlns:tev="http://www.onvif.org/ver10/events/wsdl"xmlns:ns17="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding"xmlns:ns18="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding"xmlns:ns19="http://www.onvif.org/ver10/events/wsdl/NotificationConsumerBinding"xmlns:ns20="http://www.onvif.org/ver10/events/wsdl/PullPointBinding"xmlns:ns21="http://www.onvif.org/ver10/events/wsdl/CreatePullPointBinding"xmlns:ns22="http://www.onvif.org/ver10/events/wsdl/PausableSubscriptionManagerBinding"xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"xmlns:ns3="http://www.onvif.org/ver10/analyticsdevice/wsdl"xmlns:ns4="http://www.onvif.org/ver10/deviceIO/wsdl"xmlns:ns5="http://www.onvif.org/ver10/display/wsdl"xmlns:ns8="http://www.onvif.org/ver10/receiver/wsdl"xmlns:ns9="http://www.onvif.org/ver10/recording/wsdl"xmlns:tds="http://www.onvif.org/ver10/device/wsdl"xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl"xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl"xmlns:trt="http://www.onvif.org/ver10/media/wsdl"xmlns:trt2="http://www.onvif.org/ver20/media/wsdl"xmlns:ter="http://www.onvif.org/ver10/error"xmlns:tns1="http://www.onvif.org/ver10/topics"xmlns:tnsn="http://www.eventextension.com/2011/event/topics"><SOAP-ENV:Header><wsa:MessageID>urn:uuid:327b23c6-5566-7788-99ac-001217d7bf0f</wsa:MessageID><wsa:RelatesTo>uuid:25862dc4-4866-42a9-87a7-edcf2918ab10</wsa:RelatesTo><wsa:To SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To><wsa:Action SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><d:ProbeMatches><d:ProbeMatch><wsa:EndpointReference><wsa:Address>urn:uuid:b12dca78-5566-7788-99aa-001217d7bf0f</wsa:Address></wsa:EndpointReference><d:Types>dn:NetworkVideoTransmitter</d:Types><d:Scopes>onvif://www.onvif.org/type/video_encoderonvif://www.onvif.org/type/audio_encoder onvif://www.onvif.org/hardware/IPC-model onvif://www.onvif.org/location/country/china onvif://www.onvif.org/name/NVT onvif://www.onvif.org/Profile/Streaming</d:Scopes><d:XAddrs>http://192.168.100.123:8099/onvif/device_service</d:XAddrs><d:MetadataVersion>10</d:MetadataVersion></d:ProbeMatch></d:ProbeMatches></SOAP-ENV:Body>
</SOAP-ENV:Envelope>

        ONVIF设备服务地址就是http://192.168.100.123:8099/onvif/device_service,每个消息都会有一个MessageID,且每个消息都不一样;RelatesTo表示当前消息是对哪一个MessageID的消息的响应。拿到ONVIF设备服务地址我们就可以向设备发起ONVIF请求了,上面的XML并不需要我们自己封装,gSOAP生成的代码框架会帮我们完成,我们只需要调用API即可。

二:ONVIF客户端开发

        基于系列二,我们在/work/onvif下创建一个client目录用来保存onvif客户端代码

cd /work/onvif
mkdir client
cd client

        把gSOAP生成的客户端框架拷贝到client下,之前在安装的时候加了鉴权,在wsse.h中指出了鉴权依赖的文件,这里也需要拷贝过来,除此之外,还要拷贝其他一些文件过来。

# /work/onvif/onvif-code为onvif框架生成路径
cp /work/onvif/onvif-code/soap/soapC.c .
cp /work/onvif/onvif-code/soap/soapH.h .
cp /work/onvif/onvif-code/soap/soapStub.h .
cp /work/onvif/onvif-code/soap/soapClient.c .
cp /work/onvif/onvif-code/soap/wsdd.nsmap .
cp /work/onvif/onvif-code/gsoap/plugin/wsseapi.h .
cp /work/onvif/onvif-code/gsoap/plugin/wsseapi.c .
cp /work/onvif/onvif-code/gsoap/plugin/mecevp.h .
cp /work/onvif/onvif-code/gsoap/plugin/mecevp.c .
cp /work/onvif/onvif-code/gsoap/plugin/smdevp.h .
cp /work/onvif/onvif-code/gsoap/plugin/smdevp.c .
cp /work/onvif/onvif-code/gsoap/custom/struct_timeval.h .
cp /work/onvif/onvif-code/gsoap/custom/struct_timeval.c .
cp /work/onvif/onvif-code/gsoap/plugin/wsaapi.h .
cp /work/onvif/onvif-code/gsoap/plugin/wsaapi.c .
cp /work/onvif/onvif-code/gsoap/plugin/threads.h .
cp /work/onvif/onvif-code/gsoap/plugin/threads.c .
cp /work/onvif/onvif-code/gsoap/import/dom.h .
cp /work/onvif/onvif-code/gsoap/dom.c .# /work/onvif/gsoap-2.8为gsoap源码路径
cp /work/onvif/gsoap-2.8/gsoap/stdsoap2.c .
cp /work/onvif/gsoap-2.8/gsoap/stdsoap2.h .

        在client中添加main.c和CMakeLists.txt

        CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.5)
project(ONVIF-DEMO)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DWITH_NONAMESPACES  -DWITH_DOM -DWITH_OPENSSL")
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(/usr/include)
add_definitions("-Wall -g ")
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME}  ${SRC_LIST})
target_link_libraries(${PROJECT_NAME}  -lcrypto -lssl)

        在main.c中实现ONVIF客户端。

        下面介绍gSOAP生成的代码框架中的API,这里主要讲解客户端的API。

        使用gsoap框架必须要初始化一个上下文struct soap,gSOAP API都需要传递一个struct soap参数并且是第一个参数,每个线程都需要创建一个struct soap,不支持多线程。

        创建销毁一个struct soap

struct soap *soap = soap_new();
soap_free(soap);

        创建完struct soap,第一步要调用soap_set_namespaces设置xml中的命名空间:

soap_set_namespaces(soap, namespaces);  //设置命名空间,就是xml文件的头

        namespaces定义在wsdd.nsmap文件中,要在main.c中包含该文件:#include "wsdd.nsmap"

        struct soap也可以在栈上创建:

struct soap soap1;
soap_init(soap1);

        在开发中我们可以使用malloc分配内存,还可以调用soap_malloc分配内存。

void* soap_malloc(struct soap *soap, size_t n)

        此时分配的内存被struct soap管理,n为分配的内存大小,返回值是分配的内存首地址,这个地址我们直接用即可,不用使用free释放,在任务结束的时候调用soap_end即可释放这块内存。函数原型:

void soap_end(struct soap *soap)

        上下文资源释放,一般涉及到4个API

soap_destroy(struct soap *soap); // 删除反序列化的类实例(仅限C++)soap_end(struct soap *soap); // 清理反序列化的数据(类实例除外)和临时数据soap_done(struct soap *soap); // 重置、关闭通信并删除回调soap_free(struct soap *soap); // 重置和取消分配使用soap_new或soap_copy创建的上下文

        客户端API:

        如上图所示,是客户端侧的API,soap_send_SOAP_ENV__Fault是用来发送错误消息,soap_recv_SOAP_ENV__Fault是用来接收错误消息的,回顾一下SOAP组成部分: Envelope、Header、Body、Fault。这里对Fault不做过多介绍,可自行了解。

        gSOAP API命名规则soap_call/send/recv__命名空间__服务名(RPC函数名),call是同步操作,send和recv是异步操作。例如:soap_send___wsdd__Probe表示发送一个探测消息发现设备,soap_recv___wsdd__Probe表示接收探测消息,soap_recv___wsdd__ProbeMatches表示接收Probe Match消息。

        关于ONVIF各个服务的名称、参数、返回结果在WSDL中查看,地址:https://www.onvif.org/profiles/specifications/。

        ONVIF客户端开发按流程:

        1、设备发现获取ONVIF设备服务地址

        2、获取设备能力GetCapabilities

        服务返回设备对各个功能的支持情况,及各个功能的服务地址。我们比较关心的是Media,GetCapabilitiesResponse返回的Media信息如下图所示:

        这里会返回Media服务的地址,这个地址后面会用来获取摄像头的RTSP地址。media.wsdl描述了Media服务。

        3、获取媒体配置信息,记录token。通过Media服务地址,调用Media服务里面的GetProfiles服务,WSDL对GetProfiles的描述如下图所示:

        Profiles.token需要保存下来,后面访问Media都要用到。

        4、通过Media服务地址,调用Media服务中的GetStreamUri服务,获取RTSP地址,需要步骤三中的token,WSDL对GetStreamUri的描述如下图所示:

        Output中的MediaUrl.Url就是RTSP地址,至此ONVIF客户端基本功能就开发完了,拿到RTSP地址就可以拉取视频流了,就可以为所欲为了。

main.c代码如下:

#include "soapH.h"
#include "soapStub.h"
#include "wsaapi.h"
#include "wsdd.nsmap"
#include "wsseapi.h"
#include <assert.h>#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 nullptr NULL
#define bool int
#define true 1
#define false 0void soap_perror(struct soap *soap, const char *str)
{if (nullptr == 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));}
}void *ONVIF_soap_malloc(struct soap *soap, unsigned int n)
{void *p = nullptr;if (n > 0) {p = soap_malloc(soap, n);SOAP_ASSERT(nullptr != p);memset(p, 0x00, n);}return p;
}struct soap *ONVIF_soap_new(int timeout)
{struct soap *soap = nullptr; // soap环境变量SOAP_ASSERT(nullptr != (soap = soap_new()));soap_set_namespaces(soap, namespaces); // 设置soap的namespacessoap->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
#endifsoap_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 datasoap_done(soap);    // Reset, close communications, and remove callbackssoap_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 = nullptr;SOAP_ASSERT(nullptr != 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;
}/************************************************************************
**函数: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 = nullptr; // 用于描述查找哪类的Web服务SOAP_ASSERT(nullptr != soap);SOAP_ASSERT(nullptr != 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);
}void ONVIF_DetectDevice(void (*cb)(char *DeviceXAddr))
{int i;int result = 0;unsigned int count = 0;          // 搜索到的设备个数struct soap *soap = nullptr;     // soap环境变量struct wsdd__ProbeType req;      // 用于发送Probe消息struct __wsdd__ProbeMatches rep; // 用于接收Probe应答struct wsdd__ProbeMatchType *probeMatch;SOAP_ASSERT(nullptr != (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, nullptr, &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 { // 成功接收到设备的应答消息//  printf("__sizeProbeMatch:%d\n",rep.wsdd__ProbeMatches->__sizeProbeMatch);if (nullptr != rep.wsdd__ProbeMatches) {count += rep.wsdd__ProbeMatches->__sizeProbeMatch;for (i = 0; i < rep.wsdd__ProbeMatches->__sizeProbeMatch; i++) {probeMatch = rep.wsdd__ProbeMatches->ProbeMatch + i;if (nullptr != cb) {char *url = "http://192.168.0.116/onvif/device_service";if (memcpy(probeMatch->XAddrs, url, strlen(url)) == 0) {cb(probeMatch->XAddrs); // 使用设备服务地址执行函数回调}}}}}} else if (soap->error) {break;}}SOAP_DBGLOG("\ndetect end! It has detected %d devices!\n", count);if (nullptr != soap) {ONVIF_soap_delete(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)/************************************************************************
**函数:ONVIF_SetAuthInfo
**功能:设置认证信息
**参数:[in] soap     - soap环境变量[in] username - 用户名[in] password - 密码
**返回:0表明成功,非0表明失败
**备注:
************************************************************************/
static int ONVIF_SetAuthInfo(struct soap *soap, const char *username, const char *password)
{int result = 0;SOAP_ASSERT(nullptr != username);SOAP_ASSERT(nullptr != password);result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);SOAP_CHECK_ERROR(result, soap, "add_UsernameTokenDigest");EXIT:return result;
}#define USERNAME "admin"
#define PASSWORD "hik12345"
/************************************************************************
**函数:ONVIF_GetDeviceInformation
**功能:获取设备基本信息
**参数:[in] DeviceXAddr - 设备服务地址
**返回:0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_GetDeviceInformation(const char *DeviceXAddr)
{int result = 0;struct soap *soap = nullptr;struct _tds__GetDeviceInformation devinfo_req;struct _tds__GetDeviceInformationResponse devinfo_resp;SOAP_ASSERT(nullptr != DeviceXAddr);SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);result = soap_call___tds__GetDeviceInformation(soap, DeviceXAddr, nullptr, &devinfo_req, &devinfo_resp);SOAP_CHECK_ERROR(result, soap, "GetDeviceInformation");// std::cout << "      Manufacturer:\t" << devinfo_resp.Manufacturer << "\n";// std::cout << "      Model:\t" << devinfo_resp.Model << "\n";// std::cout << "      FirmwareVersion:\t" << devinfo_resp.FirmwareVersion << "\n";// std::cout << "      SerialNumber:\t" << devinfo_resp.SerialNumber << "\n";// std::cout << "      HardwareId:\t" << devinfo_resp.HardwareId << "\n";EXIT:if (nullptr != soap) {ONVIF_soap_delete(soap);}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, const char *ProfileToken, char **snapUri)
{int result = 0;struct soap *soap = nullptr;struct _trt__GetSnapshotUri req;struct _trt__GetSnapshotUriResponse rep;SOAP_ASSERT(MediaXAddr != NULL && ProfileToken != NULL);SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);req.ProfileToken = (char *)ProfileToken;result = soap_call___trt__GetSnapshotUri(soap, MediaXAddr, NULL, &req, &rep);SOAP_CHECK_ERROR(result, soap, "GetSnapshotUri");if (nullptr != rep.MediaUri && nullptr != rep.MediaUri->Uri) {*snapUri = rep.MediaUri->Uri;}EXIT:if (NULL != soap) {ONVIF_soap_delete(soap);}return result;
}bool ONVIF_GetProfiles(const char *mediaXAddr, char **profilesToken)
{int result = 0;struct soap *soap = nullptr;struct _trt__GetProfiles devinfo_req;struct _trt__GetProfilesResponse devinfo_resp;SOAP_ASSERT(mediaXAddr != NULL);SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);result = soap_call___trt__GetProfiles(soap, mediaXAddr, nullptr, &devinfo_req, &devinfo_resp);SOAP_CHECK_ERROR(result, soap, "ONVIF_GetProfiles");SOAP_ASSERT(devinfo_resp.__sizeProfiles > 0);*profilesToken = devinfo_resp.Profiles->token;EXIT:if (nullptr != soap) {ONVIF_soap_delete(soap);}return result;
}/************************************************************************
**函数:ONVIF_GetCapabilities
**功能:获取设备能力信息
**参数:[in] DeviceXAddr - 设备服务地址[in]
**返回:0表明成功,非0表明失败
**备注:1). 其中最主要的参数之一是媒体服务地址
************************************************************************/
int ONVIF_GetCapabilities(const char *deviceXAddr, char **mediaXAddr)
{int result = 0;struct soap *soap = nullptr;struct _tds__GetCapabilities devinfo_req;struct _tds__GetCapabilitiesResponse devinfo_resp;SOAP_ASSERT(deviceXAddr != NULL);SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));result = soap_call___tds__GetCapabilities(soap, deviceXAddr, NULL, &devinfo_req, &devinfo_resp);SOAP_CHECK_ERROR(result, soap, "GetCapabilities");if (devinfo_resp.Capabilities->Media != nullptr) {*mediaXAddr = devinfo_resp.Capabilities->Media->XAddr;}EXIT:if (nullptr != soap) {ONVIF_soap_delete(soap);}return result;
}/************************************************************************
**函数: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
************************************************************************/
static int make_uri_withauth(const char *src_uri, const char *username, char *password, char **dest_uri)
{int result = 0;SOAP_ASSERT(src_uri != NULL);if (username == NULL && password == NULL) { // 生成新的uri地址*dest_uri = src_uri;} else {/*std::string::size_type position = src_uri.find("//");if (std::string::npos == position) {SOAP_DBGERR("can't found '//', src uri is: %s.\n", src_uri.c_str());result = -1;return result;}position += 2;dest_uri->append(src_uri,0,   position) ;dest_uri->append(username + ":" + password + "@");dest_uri->append(src_uri,position, std::string::npos) ;*/}return result;
}/************************************************************************
**函数:ONVIF_GetStreamUri
**功能:获取设备码流地址(RTSP)
**参数:[in]  MediaXAddr    - 媒体服务地址[in]  ProfileToken  - the media profile token
**返回:0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_GetStreamUri(const char *MediaXAddr, const char *ProfileToken)
{int result = 0;struct soap *soap = nullptr;struct tt__StreamSetup ttStreamSetup;struct tt__Transport ttTransport;struct _trt__GetStreamUri req;struct _trt__GetStreamUriResponse rep;SOAP_ASSERT(MediaXAddr != NULL);SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));ttStreamSetup.Stream = tt__StreamType__RTP_Unicast;ttStreamSetup.Transport = &ttTransport;ttStreamSetup.Transport->Protocol = tt__TransportProtocol__RTSP;ttStreamSetup.Transport->Tunnel = nullptr;req.StreamSetup = &ttStreamSetup;req.ProfileToken = (char *)(ProfileToken);ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);result = soap_call___trt__GetStreamUri(soap, MediaXAddr, nullptr, &req, &rep);SOAP_CHECK_ERROR(result, soap, "GetServices");if (nullptr != rep.MediaUri) {if (nullptr != rep.MediaUri->Uri) {// std::cout << rep.MediaUri->Uri << "\n";printf("Url:%s\n", rep.MediaUri->Uri);}}EXIT:if (nullptr != soap) {ONVIF_soap_delete(soap);}return result;
}
void cb_discovery(char *deviceXAddr)
{char *mediaXAddr, profilesToken, snapUri, snapAuthUri;ONVIF_GetCapabilities(deviceXAddr, &mediaXAddr);ONVIF_GetProfiles(mediaXAddr, &profilesToken);ONVIF_GetStreamUri(mediaXAddr, profilesToken);/*    ONVIF_GetSnapshotUri(mediaXAddr, profilesToken, &snapUri);make_uri_withauth(snapUri, USERNAME, PASSWORD, &snapAuthUri);char cmd[256];sprintf(cmd, "wget -O %s '%s'",   "out.jpeg", snapAuthUri.c_str());                        // 使用wget下载图片system(cmd);*/
}int main(int argc, char **argv)
{ONVIF_DetectDevice(cb_discovery);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/696973.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于STM32H750的DCMI接口OV5640摄像头条码识别

好久没写文章了&#xff0c;闭上眼睛&#xff0c;算了一下&#xff0c;大概有十年了&#xff0c;近来接到一个项目&#xff0c;需要做条码识别&#xff0c;客户要求用MCU做&#xff0c;理由成本低、价格可控。 于是乎&#xff0c;打开某宝软件&#xff0c;搜索后发现STM32H7/ST…

线性表(2)

第二章、线性表&#xff08;linear list&#xff09; 线性表是第一个数据结构&#xff0c;再提一遍&#xff0c;学习一个具体的数据结构需要关注它的逻辑结构&#xff0c;物理结构和数据的运算&#xff0c;即三要素。 2.1、线性表的定义和基本操作 线性表的定义 需要注意的是…

Windows2016系统禁止关闭系统自动更新教程

目录 1.输入cmd--适合系统2016版本2.输入sconfig&#xff0c;然后按回车键3.输入5&#xff0c;然后按回车键4.示例需要设置为手动更新&#xff0c;即输入M&#xff0c;然后按回车键 1.输入cmd–适合系统2016版本 2.输入sconfig&#xff0c;然后按回车键 3.输入5&#xff0c;然后…

基于SpringBoot + Vue的扶贫助农管理系统设计与实现+毕业论文

系统介绍 系统分为用户和管理员两个角色 用户&#xff1a;登录、注册、论坛信息、查看扶贫公告信息、查看扶贫任务信息、报名任务、查看新闻信息&#xff08;新闻收藏、新闻留言&#xff09;、个人中心、在线客服等功能 管理员&#xff1a;登录、管理员管理、基础信息管理、客…

2000-2022年上市公司供应链效率数据(含原始数据+结果)

2000-2022年上市公司供应链效率数据&#xff08;含原始数据结果&#xff09; 1、时间&#xff1a;2000-2022年 2、指标&#xff1a;年份、股票代码、省份、城市、区县、省份代码、城市代码、区县代码、首次上市年份、上市状态、股票简称、行业名称、行业代码、库存周转率、供…

docker安装时报错:Error: Nothing to do

安装docker时报以下错误 解决方法&#xff1a; 1.下载关于docker的相关依赖环境 yum -y install yum-utils device-mapper-persistent-data lvm22.设置下载Docker的镜像源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo3…

【RabbitMQ】消息队列 - RabbitMQ的使用记录

目录 一、什么是消息队列 二、什么是RabbitMQ 三、安装RabbitMQ 3.1 安装Erlang环境 3.2 安装RabbitMQ 3.3 打开服务管理界面 3.4 常用命令 四、Python示例代码 4.1 发送数据 4.2 接收数据 一、什么是消息队列 消息队列(Message Queue)是一种用于在应用程序之间传递消…

鸿蒙ArkUI开发:常用布局【弹性布局方向图】

弹性布局方向图 Flex({ direction: FlexDirection.Row }) FlexDirection.Row&#xff08;默认值&#xff09;&#xff1a;主轴为水平方向&#xff0c;子组件从起始端沿着水平方向开始排布FlexDirection.RowReverse&#xff1a;主轴为水平方向&#xff0c;子组件从终点端沿着F…

物联网设计竞赛_3_Jetson Nano连接摄像头

ls /dev/video* 查看是否有摄像头 camorama 开启摄像头 关闭摄像头用&#xff1a; ctr c结束进程 若有camorama被启动用ps aux 或者 ps aux l grep camorama 找到对应进程用 kill -9 <PID>杀死进程再启动 必要的时候也能重启系统再试试&#xff1a; shutdown -r …

Java入门基础学习笔记21——Scanner

在程序中接收用户通过键盘输入的数据&#xff1a; 需求&#xff1a; 请在程序中&#xff0c;提示用户通过键盘输入自己的姓名、年龄、并能在程序中收到这些信息&#xff0c;怎么解决&#xff1f; Java已经写好了实现程序&#xff0c;我们调用即可。 API&#xff1a;Applicat…

基于Springboot的大学生平时成绩量化管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的大学生平时成绩量化管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三…

LVS + Keepalived 高可用群集

一、准备环境 主keepalived&#xff1a;172.168.1.11 lvs 备keepalived&#xff1a;172.168.1.12 lvs web1&#xff1a;172.168.1.13 web2&#xff1a;172.168.1.14 vip&#xff1a;172.168.1.100 客户机访问 关闭防火墙 二、配置 主keepalived 服务器 1. 安装…