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

         我的开源:

         1、Nvidia视频硬解码、渲染、软/硬编码并写入MP4文件。项目地址:https://github.com/BreakingY/Nvidia-Video-Codec
        2、Jetson Jetpack5.x视频编解码。项目地址:https://github.com/BreakingY/jetpack-dec-enc
        3、ffmpeg音视频(H264/H265/AAC)封装、解封装、编解码pipeline,支持NVIDIA硬编解码。项目地址:https://github.com/BreakingY/FFmpeg-Media-Codec-Pipeline
        4、simple rtsp server,小而高效的rtsp服务器,支持H264、H265、AAC、PCMA;支持TCP、UDP;支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-server

        5、simple rtp client,rtsp客户端,支持TCP、UDP、H264、H265、AAC、PCMA,支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-client

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实现一个 C# 的 Onvif 客户端,你需要遵循以下步骤: 1. 引用 Onvif 标准的 WSDL 文件 Onvif 标准定义了一组 Web Services Description Language (WSDL) 文件,你需要将这些文件添加到你的 C# 项目中。这些文件包含了 Onvif 标准所定义的服务接口和数据类型。 2. 生成客户端代理类 使用 Visual Studio 或者命令行工具,基于 WSDL 文件生成客户端代理类,这样你就可以使用 C# 代码调用 Onvif 标准定义的服务接口。 3. 创建客户端对象 使用生成的客户端代理类创建客户端对象,你需要提供 Onvif 设备的 IP 地址、端口号、用户名和密码等信息。 4. 调用服务接口 使用客户端对象调用 Onvif 标准定义的服务接口,例如获取设备信息、控制云台、获取视频流等操作。 下面是一个简单的示例代码,演示了如何使用 C# 实现 Onvif 客户端: ```csharp using System; using System.ServiceModel; using onvif.services; namespace onvif_client { class Program { static void Main(string[] args) { // Onvif 设备的 IP 地址、端口号、用户名和密码 string address = "http://192.168.1.100/onvif/device_service"; string username = "admin"; string password = "admin"; // 创建服务绑定和客户端对象 var binding = new BasicHttpBinding(); var endpoint = new EndpointAddress(address); var client = new DeviceClient(binding, endpoint); // 设置验证凭据 client.ClientCredentials.UserName.UserName = username; client.ClientCredentials.UserName.Password = password; try { // 调用服务接口 var response = client.GetDeviceInformation(new GetDeviceInformationRequest()); // 打印设备信息 Console.WriteLine("Manufacturer: {0}", response.Manufacturer); Console.WriteLine("Model: {0}", response.Model); Console.WriteLine("Serial Number: {0}", response.SerialNumber); } catch (Exception ex) { Console.WriteLine("Error: {0}", ex.Message); } finally { // 关闭客户端 client.Close(); } } } } ``` 以上示例代码演示了如何使用 Onvif 标准定义的 GetDeviceInformation 接口获取设备信息。你可以根据需要调用其他的服务接口,实现更加复杂的操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值