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_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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring框架核心:揭秘Java厨房的智能烹饪艺术

前情回顾&#xff1a;Spring框架深度解析&#xff1a;打造你的Java应用梦工厂 六. 实现控制反转 6.1 描述如何在Spring中实现IoC 在Spring Town的厨房里&#xff0c;实现控制反转就像是将食材的采购和准备过程外包给了一个智能系统。这个系统知道每种食材的特性&#xff0c;也…

质量保障之精准测试!

一、背景与概念 随着软件测试行业的长足发展&#xff0c;测试理念、技术都在发生着日新月异的变化。因此一套完整的自动化测试用例对于每个软件公司都是不可或缺的&#xff0c;然而虽然有如此规模宏大的自动化案例集资源投入&#xff0c;同时也有大量人力的投入&#xff0c;但…

深入理解Python的类,实例和type函数

问题起源&#xff1a; class t():pass s1 t() s2 type("Student2",(),{}) isinstance(s1, type), isinstance(s2, type)为什么第一个是false&#xff0c;第二个是true呢 根因定位&#xff1a; 在Python中&#xff0c;一切皆对象&#xff0c;类是对象&#xff0c…

AI+新能源充电桩数据集

需要的同学私信联系&#xff0c;推荐关注上面图片右下角的订阅号平台 自取下载。 随着我国新能源汽车市场的蓬勃发展&#xff0c;充电桩的需求量日益增加&#xff0c;充电桩的智能化程度不仅影响充电站运营商的经营效益&#xff0c;也大大影响着用户的充电体验。AI技术可以涵盖…

STK12 RPO模块学习 (1)

一、背景介绍 在STK12中&#xff0c;在Astrogator的模块上开发了新的模块&#xff08;Rendezvous and proximity operations)。轨道交会接近通常来说是一个很复杂的过程。RPO实现需要对轨道动力学有一个清晰的理解&#xff0c;并且对于Astrogator模块具备很强的背景和经验&…

AI翻唱+视频剪辑全流程实战

目录 一、AI翻唱之模型训练 &#xff08;1&#xff09;模型部署 &#xff08;2&#xff09;数据集制作——搜集素材 &#xff08;3&#xff09;数据集制作——提升音频质量 方法一&#xff1a;使用RVC提供的音频处理功能。 方法二&#xff1a;可以使用音频剪辑工具Ad…

【软设】常见易错题汇总

目录 计算机系统基础 程序语言基础 数据结构 算法设计与分析 计算机网络与信息安全 软件工程基础 开发方法&#xff08;结构化与面向对象&#xff09; 数据库 操作系统 知识产权相关的法律法规 &#x1f92f;&#x1f92f;&#x1f92f;&#x1f92f;&#x1f92f;&#x1f9…

基于Springboot的实习生管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的实习生管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&a…

APP反抓包 - 客户端证书验证进阶(代码混淆)

1.关于混淆 在安卓开发中,对于第三方的包是可以进行混淆的,例如:OKHttp3.Http.Cert.check 被混淆后可以是a.f.c.b 形式。在安卓开发中,系统包是无法混淆的,例如:java.security.KeyStore不会被混淆。由于这种的情况的存在,再次审示我们之前的通用脚本,就会发现他是不通用…

基于GD32的简易数字示波器(5)- 软件_控制LED

这期记录的是项目实战&#xff0c;做一个简易的数字示波器。 教程来源于嘉立创&#xff0c;帖子主要做学习记录&#xff0c;方便以后查看。 本期主要介绍GPIO口的输入输出模式&#xff0c;使用其中的输出模式驱动LED。详细教程可观看下方链接。 2.2 LED控制实验 语雀 1、LE…

synchronized 使用及实现原理

synchronized 关键字 如何使用 synchronized 关键字的使用方式主要有下面 3 种&#xff1a; 修饰实例方法 修饰静态方法 修饰代码块 1、修饰实例方法 &#xff08;锁当前对象实例&#xff09; 给当前对象实例加锁&#xff0c;进入同步代码前要获得 当前对象实例的锁 。 …

ViewModel 完全指南:实践与背后原理全解

一、引言 在现代Android应用开发中&#xff0c;处理UI数据的有效管理和状态保持是开发者面临的重要挑战之一。Google推出的Jetpack组件库中的ViewModel已成为解决这些问题的关键工具。ViewModel旨在以生命周期意识的方式存储和管理界面相关的数据&#xff0c;从而使数据在配置…

暴力法解决最近对问题和凸包问题-实现可视化

目录 最近对问题 凸包问题 最近对问题 顾名思义就是采用蛮力法求出所有点之间的距离&#xff0c;然后进行比较找出第一个最近对&#xff0c;一个一个进行比较。 大概思路就是如图&#xff08;每个圈代表一个数对&#xff09; 第一个和其他四个比较 第二个和其他三个比较 …

C++类和对象下——实现日期类

前言 在学习了类和对象的六大成员函数后&#xff0c;为了巩固我们学习的知识可以手写一个日期类来帮助我们理解类和对象&#xff0c;加深对于其的了解。 默认函数 构造函数 既然是写类和对象&#xff0c;我们首先就要定义一个类&#xff0c;然后根据实际需要来加入类的数据与函…

计算机Java项目|Springboot房产销售系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、Python项目、前端项目、人工智能与大数据、简…

Windows下安装Node.js、npm和electronic,并运行一个Hello, World!脚本程序

20240510 By wdhuag 目录 简介&#xff1a; 参考&#xff1a; 安装Node.js 安装npm 配置npm&#xff1a; 修改包存放目录和缓存目录 切换镜像源 使用 nrm 切换镜像源 安装Electron 运行一个Hello, World!脚本程序 安装Yarn JavaScript 指南 简介&#xff1a; Nod…

flash attention的CUDA实现探讨-V3

之前关于flash attention的实现参考添加链接描述,添加链接描述,添加链接描述 lash attention的数学变换:给定三个矩阵Q,K,V,形状都是[N,d],计算S=QK.T,然后针对dim=1做softmax,然后和V继续做矩阵乘法得到形状为[N,d]的输出矩阵O,即O=softmax(QK.T,dim=1)V。 下面本人的…

物联网设计竞赛_2_Jetson Nano中文输入法配置安装vscode

1、装ibus和ibus依赖框架 sudo apt-get install ibus ibus-clutter ibus-gtk ibus-gtk3 ibus-qt4 2、启动ibus im-config -s ibus 3、安装拼音引擎 sudo apt-get install ibus-pinyin 4、重启linux系统 shutdown -r now 5、进入ibus设置添加中文 ibus-setup 插入中文p…

四川汇昌联信:拼多多网点怎么开?大概需要多少钱?

想要开一家拼多多网点&#xff0c;你肯定很关心需要准备多少资金。下面&#xff0c;我们就来详细解答这个问题&#xff0c;并从多个角度分析开设网点的要点。 一、 开设拼多多网点&#xff0c;首要任务是确定启动资金。根据不同的经营模式和地区差异&#xff0c;成本会有所不同…

基于SpringBoot + Vue的兼职网站管理系统设计与实现+毕业论文+答辩PPT

系统介绍 本系统包含管理员、用户、企业三个角色。 管理员角色&#xff1a;前台首页、个人中心、用户管理、企业管理、兼职信息管理、职位申请管理、留言板管理、系统管理。 用户角色&#xff1a;前台首页、个人中心、职位申请管理。 企业角色&#xff1a;前台首页、个人中心、…