ONVIF系列三:ONVIF客户端实现

news2024/10/6 14:28:57

        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_encoder
                    onvif://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 0

void 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的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 = 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.coloradmin.cn/o/1672240.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探究NVMe SSD HMB应用场景与影响-<续>

如果需要采用HMB功能&#xff0c;需要SSD支持NVME协议且NVMe 1.2及以上版本。NVME协议中对HMB对应有2个关键参数&#xff1a; HMB建议值&#xff08;HMPRE&#xff09;&#xff1a;设定实际分配给HMB使用的主机内存容量&#xff0c;为设备提供最优性能的内存分配量。 HMB最小值…

冥想的时候怎么专注自己

冥想的时候怎么专注自己&#xff1f;我国传统的打坐养生功法&#xff0c;实际最早可追溯到五千年前的黄帝时代。   每天投资两个半小时的打坐&#xff0c;有上千年之久的功效。因为当你们打坐进入永恒时&#xff0c;时间停止了。这不只是两个半小时&#xff0c;而是百千万亿年…

Github上5个实用的ChatGPT仓库

ChatGPT是一款基于聊天场景的大模型AI&#xff0c;最近火出圈。 Chat表示聊天&#xff0c;GPT表示大模型算法&#xff0c;它通过生成式的人机对话功能&#xff0c;让使用者第一次有了AI机器人‘懂我‘的感觉&#xff0c;而不是Siri、小爱那种傻瓜式的语音服务。 ChatGPT不仅仅…

现货黄金白银行情走高带来的投资机会分析

当现货黄金和白银行情呈现出走高的态势时&#xff0c;这常常被投资者解读为一个潜在的投资机会。本文旨在分析在黄金白银价格上涨时的投资机会&#xff0c;并指出应对策略。 一、走高行情背后的机会 行情的上升&#xff0c;往往代表了市场在某种程度上的认可&#xff0c;无论这…

知乎知+广告推广该如何做?怎么收费?

知乎作为一个汇聚高质量用户群体的知识分享平台&#xff0c;成为了众多品牌和产品推广的优选之地。特别是知乎的“知”广告推广服务&#xff0c;以其精准定向、内容原生的特点&#xff0c;深受广告主青睐。 一、知乎知广告推广基础 1. 什么是知乎知&#xff1f; 知是知乎官方…

企业网站慎用免费SSL证书!OV证书才是首选

市面上有很多免费证书提供&#xff0c;免费的SSL证书更适用于个人博客、学生学习、测试等应用场景。如果您的网站是企业网站一定慎用免费的SSL证书&#xff0c;而是选择企业级的OV证书。 一&#xff0e;免费SSL证书的风险 1安全性&#xff1a;免费SSL证书通常只提供基本的加密…

全新Transformer模型:全球与局部双重突破!

DeepVisionary 每日深度学习前沿科技推送&顶会论文分享&#xff0c;与你一起了解前沿深度学习信息&#xff01; 引言&#xff1a;探索视觉变换器在对象重识别中的全局与局部特征 在对象重识别&#xff08;Re-ID&#xff09;的研究领域中&#xff0c;如何有效地从不同时间…

pytest教程-45-钩子函数-pytest_report_testitemFinished

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_report_collectionfinish钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_report_testitemFinished钩子函数的使用方法。 pytest_report_testitemFinished 钩子函数在每个测试…

7天精通Web APIs——正则阶段案例(理论+实战)(第六天)

正则表达式的定义和使用 定义&#xff1a;是一种匹配模式&#xff0c;用于匹配字符串中字符组合 作用&#xff1a;表单验证&#xff08;匹配&#xff09;、过滤敏感词&#xff08;替换&#xff09;、字符串中提取我们想要的部分&#xff08;提取&#xff09; 使用分为两步&…

MySQL-InnoDB数据存储结构

1、存储结构-页 索引结构提供了高效的索引方式&#xff0c;索引信息以及数据记录都保存在数据文件或索引文件中&#xff08;本质存储在页结构中&#xff09; 1.1、磁盘与内存交互的基本单位&#xff1a;页 在InnoDB中将数据划分为若干页&#xff0c;页的默认大小为&#xff…

SQLZOO:The JOIN operation

数据表&#xff1a;game-gaol-eteam game idmdatestadiumteam1team210018 June 2012National Stadium, WarsawPOLGRE10028 June 2012Stadion Miejski (Wroclaw)RUSCZE100312 June 2012Stadion Miejski (Wroclaw)GRECZE100412 June 2012National Stadium, WarsawPOLRUS... goal …

@游戏行业er!MongoDB广州线下沙龙邀您报名!

随着游戏和应用程序的发展&#xff0c;数据变得越来越重要。在为您的下一个游戏选择数据库时&#xff0c;数据库管理者常常会面对灵活性、可扩展性、可靠性、运营效率等问题或挑战。 MongoDB在游戏开发领域有着广泛的应用&#xff0c;灵活数据模型可以存储和处理各种类型的数据…

C++--String类

系列文章目录 文章目录 目录 系列文章目录 文章目录 前言 一、为什么要学习string 1.c语言的字符串 2.OJ上的使用 二、string类的接口介绍 1.string简介 2.string构造成员函数 3.operator函数 4.string容器size和length 5.重载operator[]和引用返回的意义 5.1 oper…

嫦娥六号揭秘真相:阿波罗登月是真是假?一文终结所有疑问!

近期&#xff0c;嫦娥六号的成功发射如同璀璨的星辰&#xff0c;再次将人们的视线聚焦于浩瀚的宇宙&#xff0c;与此同时&#xff0c;网络上关于美国阿波罗登月是否造假的争议也如潮水般涌现。一些声音宣称&#xff0c;嫦娥六号的发射为揭示美国阿波罗登月任务的真实性提供了关…

Java面试八股之String类的常用方法有哪些

Java中String类的常用方法有哪些 获取字符串信息&#xff1a; length()&#xff1a;返回字符串的字符数。 isEmpty()&#xff1a;判断字符串是否为空&#xff08;即长度为0&#xff09;。 访问单个字符&#xff1a; charAt(int index)&#xff1a;返回指定索引处的字符。 …

InstantStyle —— 文本到图像生成中的风格保持新突破

在人工智能领域&#xff0c;文本到图像生成&#xff08;Text-to-Image Generation&#xff09;技术正迅速发展&#xff0c;其应用范围从娱乐到专业设计不断扩展。然而&#xff0c;风格一致性生成一直是该领域的一个技术难题。最近&#xff0c;InstantX团队提出了一种名为Instan…

GEVernova推出GEV新能源平台,引领新能源未来

近日&#xff0c;全球领先的能源设备制造和服务公司 GE Vernova 宣布推出 GEV 新能源平台&#xff0c;这是一个将金融、科技和产业深度融合的全新投资平台。GEV 新能源平台旨在为用户提供一站式可持续新能源投资解决方案&#xff0c;助力全球新能源转型和可持续发展。 新能源已…

vs-qt中无法加载qsqlite驱动,但是单独新建demo测试却又是正常的。。。

开发环境: Vs2015 + qt5.12 背景: 接手了一个项目,可以编译过去,也可以运行,, 但是登录一直失败,,但是数据库文件也是正常的。。。 最主要的是环境和同事的是一样的,,,但是他那边可以加载成功,我这边不可以。。 后来单独在vs中创建了一个demo,用来测试QSqlData…

如何高效管理微信?快速掌握捷径!

对于那些需要管理多个微信号的人来说&#xff0c;如何高效地管理这些账号成为了一个难题。今天&#xff0c;就给大家分享一个管理多个微信号的捷径——微信管理系统。 通过微信管理系统&#xff0c;你可以轻松实现高效管理多个微信号&#xff0c;一起来看看吧&#xff01; 首…

一文汇总对比英伟达、AMD、英特尔显卡GPU

‍‍&#x1f3e1;博客主页&#xff1a; virobotics(仪酷智能)&#xff1a;LabVIEW深度学习、人工智能博主 &#x1f4d1;上期文章&#xff1a;『【仪酷LabVIEW AI工具包案例】使用LabVIEW AI工具包YOLOv5结合Dobot机械臂实现智能垃圾分类』 &#x1f37b;本文由virobotics(仪酷…