原理简介

ONVIF协议接口由多个模块组成,每个模块分别对应着不同的WSDL文档,在ONVIF官网中能查看到这些模块,以及每个模块中的接口函数,这里列举几个模块:

  • DeviceMgmt(设备管理)
  • 使用GetDeviceInformation接口获取设备基本信息
  • 使用GetSystemDateAndTime接口和SetSystemDateAndTime接口对设备进行校时
  • DeviceIO(设备IO服务)
  • Event(事件处理)
  • Analytics(视频分析)
  • AnalyticsDevice(分析设备)
  • Display(显示服务)
  • Imaging(图像配置)
  • Media(媒体配置)
  • PTZ(PTZ控制)
  • Receiver(接收端配置)
  • RemoteDiscovery(设备发现)
  • Recording(录像控制)
  • Replay(重放控制)
  • Search(记录搜索)

除了「RemoteDiscovery」模块之外,每个模块都有各自的「服务地址」,客户端要使用这些模块接口之前,必须先知道对应模块的「服务地址」。

怎样才能获取这些模块的「服务地址」呢?分两步:

  • 利用WS-Discovery搜索到IPC,就能得知该设备「DeviceMgmt」模块的「服务地址」,我们称之为「设备服务地址」(就是我们搜索设备的时候获取到的http://10.0.0.47/onvif/device_service)。
  • 使用「设备服务地址」调用「DeviceMgmt」模块的GetCapabilities接口(这个接口不需要鉴权就可以得到),就能获取到所有模块的「服务地址」。

实践

具体请参考

但是main.cpp修改为:

#include <assert.h>
#include "soapH.h"
#include "wsdd.nsmap"
#include "soapStub.h"
#include "wsseapi.h"
#include "wsaapi.h"


#define USERNAME    "admin"
#define PASSWORD    "hik12345"


#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超时时间(单秒秒)

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) {
                            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;
}
/************************************************************************
**函数:ONVIF_GetDeviceInformation
**功能:获取设备基本信息
**参数:
        [in] DeviceXAddr - 设备服务地址
**返回:
        0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_GetDeviceInformation(const char *DeviceXAddr)
{
    int result = 0;
    struct soap *soap = nullptr;
    _tds__GetDeviceInformation           devinfo_req;
    _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_GetCapabilities
**功能:获取设备能力信息
**参数:
        [in] DeviceXAddr - 设备服务地址
**返回:
        0表明成功,非0表明失败
**备注:
    1). 其中最主要的参数之一是媒体服务地址
************************************************************************/
int ONVIF_GetCapabilities(const char *DeviceXAddr)
{
    int result = 0;
    struct soap *soap = NULL;
    _tds__GetCapabilities            devinfo_req;
    _tds__GetCapabilitiesResponse    devinfo_resp;

    SOAP_ASSERT(NULL != DeviceXAddr);
    SOAP_ASSERT(NULL != (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");
    std::cout << "      Device.XAddr:       " << devinfo_resp.Capabilities->Device->XAddr    << "\n";
    std::cout << "      Analytics.XAddr:    " << devinfo_resp.Capabilities->Analytics->XAddr << "\n";
    std::cout << "      Events.XAddr:       " << devinfo_resp.Capabilities->Events->XAddr    << "\n";
    std::cout << "      Imaging.XAddr:      " << devinfo_resp.Capabilities->Imaging->XAddr   << "\n";
    std::cout << "      Media.XAddr:        " << devinfo_resp.Capabilities->Media->XAddr  << "\n";
    if (devinfo_resp.Capabilities->PTZ != nullptr){
        std::cout << "      PTZ.XAddr:          " << devinfo_resp.Capabilities->PTZ->XAddr  << "\n";
    }
    std::cout << "      Extension.DeviceIO.XAddr:            " << devinfo_resp.Capabilities->Extension->DeviceIO->XAddr  << "\n";

    EXIT:

    if (NULL != soap) {
        ONVIF_soap_delete(soap);
    }
    return result;
}


/************************************************************************
**函数:ONVIF_GetCapabilities
**功能:获取wsdl的地址
**参数:
        [in] DeviceXAddr - 设备服务地址
**返回:
        0表明成功,非0表明失败
**备注:
    1). 其中最主要的参数之一是媒体服务地址
************************************************************************/
int ONVIF_GetWsdlUrl(const char *DeviceXAddr)
{
    int result = 0;
    struct soap *soap = nullptr;
    _tds__GetWsdlUrl           devinfo_req;
    _tds__GetWsdlUrlResponse   devinfo_resp;

            SOAP_ASSERT(nullptr != DeviceXAddr);
            SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));





    result = soap_call___tds__GetWsdlUrl(soap, DeviceXAddr, nullptr, &devinfo_req, devinfo_resp);
    SOAP_CHECK_ERROR(result, soap, "ONVIF_GetWsdlUrl");
    std::cout << "      WsdlUrl:\t" << devinfo_resp.WsdlUrl << "\n";
    
    EXIT:

    if (nullptr != soap) {
        ONVIF_soap_delete(soap);
    }
    return result;
}


void cb_discovery(char *DeviceXAddr)
{
    ONVIF_GetDeviceInformation(DeviceXAddr);
    ONVIF_GetCapabilities(DeviceXAddr);
}

int main(int argc, char **argv)
{
    ONVIF_DetectDevice(cb_discovery);
    return 0;
}