ONVIF协议网络摄像机(IPC)客户端程序开发(15):遮挡报警

来源:互联网 发布:网络视频主持人 编辑:程序博客网 时间:2024/05/17 04:15

1 专栏导读

本专栏第一篇文章「专栏开篇」列出了专栏的完整目录,按目录顺序阅读,有助于你的理解,专栏前面文章讲过的知识点(或代码段),后面文章不会赘述。为了节省篇幅,突出重点,在文章中展示的示例代码仅仅是关键代码,你可以在「专栏开篇」中获取完整代码。

如有错误,欢迎你的留言纠正!让我们共同成长!你的「点赞」「打赏」是对我最大的支持和鼓励!

2 原理简介

IPC摄像头往往带有告警功能,如移动侦测、遮挡报警等,这些告警会被描述为事件传给客户端,客户端再对各类事件分析处理并产生相应联动(如邮件通知、上传中心等)。本文将以“遮挡报警”功能为例,讲解ONVIF客户端如何检测IPC摄像头的告警功能。

在「ONVIF Core Specification」规格说明书中(注:书稿时我参考的是「ONVIF-Core-Specification-v1612」版本,本文以下内容如果没有特别说明,ONVIF Core说的都是这个版本),其中一个章节「Event handling」规范了ONVIF事件处理。在开始之前,建议你先阅读下这部分规范。

如果你对英文「过敏」,可以参考基于2013版「ONVIF-Core-Specification-v230」的中文翻译:https://github.com/jimxl/onvif-core-specification-cn.git

ONVIF的事件处理标准并不是自己定义的,而是使用现成的OASIS的「WS-BaseNotification」和「WS-Topics」规范。WS-BaseNotification规范定义了消息订阅和通知操作、信息交互过程中的术语、概念、操作和交互语言的格式等,也定义了生产者和消费者的角色。WS-Topics规范定义了主题的概念来对事件进行组织和分类,并定义了对事件进行筛选的语法和表达式。有关这方面的规范,不在本文讨论范围内,你可以通过「WS-Notification」关键字在网上进行查阅了解更新信息。

总的来说,根据ONVIF的事件处理规范,我们可以:

  • 客户端通过ONVIF接口向IPC摄像头订阅感兴趣的事件主题。
  • 一旦告警发生,IPC摄像头就会告警描述为事件消息通知客户端。

根据ONVIF Core Specification规范,实现事件通知的方式有以下三种:

  • Basic Notification Interface 
    基本通知接口。这种方式要求IPC摄像机和客户端必须在同一网段,如果不在同一网段,事件通知消息将无法传输。这种方式的事件通知消息也无法穿越防火墙,这就要求即使是在生产阶段,用户也得关闭任何可能存在的防火墙机制。正因为存在诸多限制,这种方式在实际中很少被使用。

  • Real-time Pull-Point Notification Interface 
    实时拉点通知接口。因为更好的防火墙穿透能力,Pull-Point方式更受推荐,基本上所有的IPC摄像机供应商都支持这种方式。这种方式是本文重点讲解的内容。

  • Notification Streaming Interface 
    通知流接口。这种方式将事件消息通过RTP流数据包的方式通知客户端,但是书稿时,很少有供应商支持这种方式,本文将不做介绍。

2.1 Basic Notification

根据WS-BaseNotification规范,ONVIF的事件处理机制定义了三种角色,即客户端、事件服务器和订阅管理器。

  • 客户端(Client):实现NotificationConsumer接口。
  • 事件服务(Event Service):实现NotificationProducer接口。
  • 订阅管理器(Subscription Manager):实现BaseSubscriptionManager接口。


 
图1 Basic Notification序列图 

Basic Notification方式的工作流程如上图所示:

  1. 事件服务和订阅管理器都是在设备(IPC摄像头)上实现的。
  2. 客户端建立一个连接到事件服务,并通过SubscriptionRequest订阅感兴趣的事件主题。
  3. 如果事件服务接受订阅,它会动态实例化一个SubscriptionManager来表示订阅,事件服务会在SubscriptionResponse应答中返回SubscriptionManager地址)。
  4. 为了传送与订阅相匹配的通知,需要另外建立一个从事件服务到客户端的连接。通过此连接,事件服务发送一个单向通知消息到客户端的NotificationConsumer接口。由于告警通知随时可能产生,所以额外的这条连接必须保持在线。
  5. 在SubscriptionRequest中,客户端可以指定一个终止时间,一旦超时SubscriptionManager会自动销毁。客户端也可以通过SubscriptionResponse应答中的SubscriptionManager地址对订阅进行控制,如使用RenewRequests续订,使用UnsubscribeRequest退订(明确终止SubscriptionManager)。

我试图使用这种方法来检测遮挡报警,但未能成功。我使用gSOAP自动生成的函数soap_recv___tev__Notify来接收IPC摄像头的消息,但soap_recv___tev__Notify函数一直处于阻塞状态,不懂是IPC摄像头不支持这种方式,还是我的代码有问题。

2.2 Pull-Point Notification


 
图2 Real-time Pull-Point Notification序列图 

Pull-Point方式的工作流程如上图所示:

  1. 事件服务和订阅管理器(PullPoint)都是在设备(IPC摄像头)上实现的。
  2. 客户端使用 CreatePullPointSubscriptionRequest 向事件服务订阅感兴趣的事件主题。
  3. 如果事件服务接受订阅,它会动态实例化一个PullPoint来表示订阅,事件服务会在CreatePullPointSubscriptionResponse应答中返回PullPoint地址,这个地址在后续的PullMessages操作会用到。设备可以支持多个pull points,可以通过ONVIF接口GetServiceCapabilities查询到MaxPullPoints值。
  4. 客户端通过PullMessages向PullPoint拉取消息。当跟订阅相匹配的事件发生时,立即通过PullMessagesRequest应答向客户端返回事件描述;如果在指定时间内未发生事件,则超时返回(不同IPC摄像头厂家的超时时间单位不同)。客户端在接收到应答后可立即发起新的PullMessages请求。
  5. 最后客户端通过UnSubscribeRequest退订事件主题。

跟Pull-Point方式相比,Basic Notification方式需要多创建一个连接,这是有原因的。Event上报事件可以分为两种pull和push。

  • Pull采用client定时发送获取告警的消息,即轮询方式,device如果有告警则上报,将notification中填入告警的信息,client根据notification中的信息显示告警。Pull-Point方式属于Pull。

  • Push应该是采用device一旦有告警发生,则通过额外的连接主动上报给client,client实时监听device的上报信息。所以,push实时性应该更好,pull模式的实时性差一点。Basic Notification方式属于Push。

3 启用遮挡报警

要测试遮挡报警,得先启用遮挡报警功能,可以通过web登录IPC后台进行配置,如下图所示。那能不能用ONVIF接口去开启/关闭这个功能呢,还有待研究。



图3 大华IPC 

除了得启用之外,有的IPC还要求绘制区域,否则还用不了遮挡报警功能,如海康:



图4 海康IPC 

4 重新生成ONVIF代码

为了检测遮挡报警,ONVIF代码得加入Event模块。本专栏前面的ONVIF代码没有加入Event模块,所以得重新生成ONVIF代码。如何使用gSOAP工具生成ONVIF框架代码,可以参考之前的一篇文章:

「ONVIF协议网络摄像机(IPC)客户端程序开发(6):使用gSOAP生成ONVIF框架代码」

以下简要说明步骤:

(1). 参考gSOAP官网说明修改gsoap\typemap.dat

参考「How do I use gSOAP with the ONVIF specifications」说明,看是否需要修改typemap.dat。我用的gSOAP工具版本是gsoap_2.8.45,typemap.dat文件刚好符合要求,不用改。

(2). 为了支持Events模块,在gsoap\typemap.dat文件末尾加上

# 解决:PullMessages收不到事件通知_wsnt__NotificationMessageHolderType_Message = $ struct _tt__Message* tt__Message;# 解决:CreatePullPointSubscription无法订阅感兴趣的主题wsnt__FilterType = $ struct wsnt__TopicExpressionType* TopicExpression;# 解决:GetEventProperties无法解析TopicSet字段wstop__TopicSetType = $ _XML __mixed;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

为什么要加这几行,详情见「为什么typemap.dat要加几行」章节的说明,这几行可是折腾了我好长时间。

(3). 使用wsdl2h工具,根据WSDL产生头文件

# cd gsoap-2.8/gsoap/# mkdir -p samples/onvif# wsdl2h -P -x -c -s -t ./typemap.dat -o samples/onvif/onvif.h https://www.onvif.org/ver10/network/wsdl/remotediscovery.wsdl https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl https://www.onvif.org/ver10/media/wsdl/media.wsdl https://www.onvif.org/ver10/events/wsdl/event.wsdl
  • 1
  • 2
  • 3

(4). 因「授权」需要,修改onvif.h头文件

有些ONVIF接口调用时需要携带认证信息,要使用soap_wsse_add_UsernameTokenDigest函数进行授权,所以要在onvif.h头文件开头加入

#import "wsse.h"
  • 1

(5). 使用soapcpp2工具,根据头文件产生框架代码

# soapcpp2 -2 -C -L -c -x -I import:custom -d samples/onvif/ samples/onvif/onvif.h
  • 1

如果出现以下错误:

wsa5.h(288): **ERROR**: service operation name clash: struct/class 'SOAP_ENV__Fault' already declared at wsa.h:273
  • 1

解决方法:

修改import\wsa5.h文件,将int SOAP_ENV__Fault修改为int SOAP_ENV__Fault_alex,再次使用soapcpp2工具编译就成功了。

(6). 拷贝其他还有会用的源码

# cp stdsoap2.c stdsoap2.h dom.c plugin/wsaapi.c plugin/wsaapi.h custom/duration.c custom/duration.h plugin/mecevp.c plugin/mecevp.h plugin/smdevp.c plugin/smdevp.h plugin/threads.c plugin/threads.h  plugin/wsseapi.c plugin/wsseapi.h samples/onvif/
  • 1

(7). 关联自己的命名空间,修改stdsoap2.c文件

在samples\onvif\stdsoap2.h中有命名空间「namespaces变量」的定义声明,如下所示:

extern SOAP_NMAC struct Namespace namespaces[];
  • 1

但「namespaces变量」的定义实现,是在samples\onvif\wsdd.nsmap文件中,为了后续应用程序要顺利编译,修改samples\onvif\stdsoap2.c文件,在开头加入:

#include "wsdd.nsmap"
  • 1

当然,你可以在其他源码中(更上层的应用程序源码)include,我这里是选择在stdsoap2.c中include的。

5 编码流程

本文只介绍Pull-Point Notification方式检测IPC遮挡报警,其他方式不做介绍。Pull-Point Notification的编码流程在前面的系列图中已经介绍过了。

6 示例代码

#include <stdio.h>#include <stdlib.h>#include <assert.h>#include "onvif_comm.h"#include "onvif_dump.h"#ifdef WIN32#include <windows.h>#endif/* 遮挡报警 */#define TAMPER_TOPIC            "tns1:RuleEngine/TamperDetector/Tamper"#define TAMPER_NAME             "IsTamper"#define TAMPER_VALUE            "true"/**************************************************************************函数:find_event**功能:查找指定主题、指定内容的事件**参数:略**返回:        0表明未找到,非0表明找到************************************************************************/int find_event(struct _tev__PullMessagesResponse *rep, char *topic, char *name, char *value){    int i, j;    if(NULL == rep) {        return 0;    }    for (i = 0; i < rep->__sizeNotificationMessage; i++) {        struct wsnt__NotificationMessageHolderType *p = rep->wsnt__NotificationMessage + i;        if (NULL == p->Topic) {            continue;        }        if (NULL == p->Topic->__mixed ) {            continue;        }        if (0 != strcmp(topic, p->Topic->__mixed)) {            continue;        }        if (NULL == p->Message.tt__Message) {            continue;        }        if (NULL == p->Message.tt__Message->Data) {            continue;        }        if (NULL == p->Message.tt__Message->Data->SimpleItem) {            continue;        }        for (j = 0; j < p->Message.tt__Message->Data->__sizeSimpleItem; j++) {            struct _tt__ItemList_SimpleItem *a = p->Message.tt__Message->Data->SimpleItem + j;            if (NULL == a->Name || NULL == a->Value) {                continue;            }            if (0 != strcmp(name, a->Name)) {                continue;            }            if (0 != strcmp(value, a->Value)) {                continue;            }            return 1;        }    }    return 0;}/**************************************************************************函数:IsTamper**功能:判断是否有遮挡报警**参数:略**返回:        0表明没有,非0表明有************************************************************************/int IsTamper(struct _tev__PullMessagesResponse *rep){    return find_event(rep, TAMPER_TOPIC, TAMPER_NAME, TAMPER_VALUE);}/**************************************************************************函数:ONVIF_CreatePullPointSubscription**功能:使用Pull-Point方式订阅事件**参数:        [in] EventXAddr - 事件服务地址**返回:        0表明成功,非0表明失败************************************************************************/int ONVIF_CreatePullPointSubscription(const char *EventXAddr){    int i;    int result = 0;    struct soap *soap = NULL;    char *pullpoint = NULL;    struct _tev__CreatePullPointSubscription         req;    struct _tev__CreatePullPointSubscriptionResponse rep;    struct _tev__PullMessages                        req_pm;    struct _tev__PullMessagesResponse                rep_pm;    struct _wsnt__Unsubscribe                        req_u;    struct _wsnt__UnsubscribeResponse                rep_u;#if 0    #define PULLMSG_TIMEOUT_UNIT    (1000)                                      // 海康IPC单位#else    #define PULLMSG_TIMEOUT_UNIT    (5000000)                                   // 大华IPC单位#endif    LONG64 pullmsg_timeout = 5 * PULLMSG_TIMEOUT_UNIT;                          // PullMessages查询事件的超时时间,不同IPC厂家的单位不同    int socket_timeout = 10;                                                    // 创建soap的socket超时时间,单位秒    SOAP_ASSERT(pullmsg_timeout < socket_timeout * PULLMSG_TIMEOUT_UNIT);       // 要确保查询事件的超时时间比socket超时时间小,否则,事件没查询到就socket超时,导致PullMessages返回失败    SOAP_ASSERT(NULL != EventXAddr);    SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(socket_timeout)));    /*    *    * 可以通过主题过滤我们所订阅的事件,过滤规则在官方「ONVIF Core Specification」规格说明书「Topic Filter」章节里有详细的介绍。    * 比如:    * tns1:RuleEngine/TamperDetector/Tamper   只关心遮挡报警    * tns1:RuleEngine/TamperDetector//.       只关心主题TamperDetector树下的事件    * NULL                                    关心所有事件,即不过滤    *                                         也可以通过 '|' 表示或的关系,即同时关心某几类事件    *    */    memset(&req, 0x00, sizeof(req));                                            // 订阅事件    memset(&rep, 0x00, sizeof(rep));    req.Filter = (struct wsnt__FilterType *)ONVIF_soap_malloc(soap, sizeof(struct wsnt__FilterType));    req.Filter->TopicExpression = (struct wsnt__TopicExpressionType *)ONVIF_soap_malloc(soap, sizeof(struct wsnt__TopicExpressionType));    req.Filter->TopicExpression->Dialect = "http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet";    req.Filter->TopicExpression->__mixed = TAMPER_TOPIC;    ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);    result = soap_call___tev__CreatePullPointSubscription(soap, EventXAddr, NULL, &req, &rep);    SOAP_CHECK_ERROR(result, soap, "CreatePullPointSubscription");    dump_tev__CreatePullPointSubscriptionResponse(&rep);    pullpoint = rep.SubscriptionReference.Address;                              // 提取pull point地址    for (i = 0; i < 30; i++) {                                                  // 轮询事件        memset(&req_pm, 0x00, sizeof(req_pm));        memset(&rep_pm, 0x00, sizeof(rep_pm));        req_pm.Timeout      = pullmsg_timeout;        req_pm.MessageLimit = 0;        ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);        result = soap_call___tev__PullMessages(soap, pullpoint, NULL, &req_pm, &rep_pm);        SOAP_CHECK_ERROR(result, soap, "PullMessages");        dump_tev__PullMessagesResponse(&rep_pm);        if(IsTamper(&rep_pm)) {                                                 // 是遮挡报警?            SOAP_DBGLOG("Tamper...\n");        }    }EXIT:    memset(&req_u, 0x00, sizeof(req_u));                                        // 退订事件    memset(&rep_u, 0x00, sizeof(rep_u));    ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);    result = soap_call___tev__Unsubscribe(soap, pullpoint, NULL, &req_u, &rep_u);    if (SOAP_OK != result || SOAP_OK != soap->error) {        soap_perror(soap, "Unsubscribe");        if (SOAP_OK == result) {            result = soap->error;        }    }    if (NULL != soap) {        ONVIF_soap_delete(soap);    }    return result;}/**************************************************************************函数:ONVIF_GetEventProperties**功能:获取事件属性**参数:        [in] EventXAddr - 事件服务地址**返回:        0表明成功,非0表明失败************************************************************************/int ONVIF_GetEventProperties(const char *EventXAddr){    int result = 0;    struct soap *soap = NULL;    struct _tev__GetEventProperties         req;    struct _tev__GetEventPropertiesResponse rep;    SOAP_ASSERT(NULL != EventXAddr);    SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));    memset(&req, 0x00, sizeof(req));    memset(&rep, 0x00, sizeof(rep));    ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);    result = soap_call___tev__GetEventProperties(soap, EventXAddr, NULL, &req, &rep);    SOAP_CHECK_ERROR(result, soap, "GetEventProperties");    dump_tev__GetEventPropertiesResponse(&rep);EXIT:    if (NULL != soap) {        ONVIF_soap_delete(soap);    }    return result;}/**************************************************************************函数:ONVIF_GetServiceCapabilities**功能:获取服务功能**参数:        [in] EventXAddr - 事件服务地址**返回:        0表明成功,非0表明失败************************************************************************/int ONVIF_GetServiceCapabilities(const char *EventXAddr){    int result = 0;    struct soap *soap = NULL;    struct _tev__GetServiceCapabilities         req;    struct _tev__GetServiceCapabilitiesResponse rep;    SOAP_ASSERT(NULL != EventXAddr);    SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));    memset(&req, 0x00, sizeof(req));    memset(&rep, 0x00, sizeof(rep));    ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);    result = soap_call___tev__GetServiceCapabilities(soap, EventXAddr, NULL, &req, &rep);    SOAP_CHECK_ERROR(result, soap, "GetServiceCapabilities");    dump_tev__GetServiceCapabilitiesResponse(&rep);EXIT:    if (NULL != soap) {        ONVIF_soap_delete(soap);    }    return result;}void cb_discovery(char *DeviceXAddr){    struct tagCapabilities capa;    ONVIF_GetCapabilities(DeviceXAddr, &capa);                                  // 获取设备能力信息(获取媒体服务地址)    ONVIF_GetServiceCapabilities(capa.EventXAddr);                              // 获取服务功能    ONVIF_GetEventProperties(capa.EventXAddr);                                  // 获取事件属性    ONVIF_CreatePullPointSubscription(capa.EventXAddr);                         // 使用Pull-Point方式订阅事件}int main(int argc, char **argv){    parse_options(argc, argv);    if (NULL == g_deviceXAddr) {        ONVIF_DetectDevice(cb_discovery);    } else {        cb_discovery(g_deviceXAddr);    }    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269

7 PullMessages超时时间

在上面的示例代码中,soap_call___tev__PullMessages函数输入参数有一个超时时间Timeout,如果在指定时间内未发生事件,函数则超时返回。Timeout所在的结构体如下所示,那Timeout单位是什么,官方没有明确说明(或许是我没找到)。

struct _tev__PullMessages {        LONG64 Timeout;        int MessageLimit;};
  • 1
  • 2
  • 3
  • 4

我们先写一个小测试程序测试下Timeout的单位,让Timeout从0开始递增,通过分析SOAP协议中的XML数据,观察每次递增会带来什么变化,测试程序主要代码片段如下:

for (i = 0; i < 30; i++) {    memset(&req_pm, 0x00, sizeof(req_pm));    memset(&rep_pm, 0x00, sizeof(rep_pm));    req_pm.Timeout = i;    req_pm.MessageLimit = 0;    ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);    soap_call___tev__PullMessages(soap, pullpoint, NULL, &req_pm, &rep_pm);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我们可以通过Wireshark工具抓包SOAP协议数据,但还有更好的方法,就是编译代码时,加上-DDEBUG宏开启SOAP协议收发日志,程序运行后,SOAP协议数据会被写入RECV.log、SENT.log、TEST.log文件。

我们分析的是PullMessages产生的SOAP协议数据,它的日志会被保存在SENT.log中,用关键字「Timeout」搜索即可快速定位。从以下测试数据,很容易看出来,Timeout每次递增,SOAP协议数据中的超时时间就会增加0.001秒,所以ONVIF标准里规定的Timeout单位是毫秒。


 
图5 测试PullMessages中Timeout单位 

似乎我们找到答案了,但,但,但,通过实际测试,不同IPC摄像头厂家的超时时间单位是不同的。海康IPC摄像头确实按标准来,Timeout单位为1毫秒,大华IPC摄像头的Timeout单位是5微妙(经验值,可能会有一些偏差,没有得到大华官方资料的确认,也不知道是否精确)。如果设定的Timeout值超出IPC摄像头能接受的范围,会报如下错误:

[soap] PullMessages error: 12, SOAP-ENV:Sender, the parameter value is illegal
  • 1

另外还需要注意的是,除了PullMessages接口自带的Timeout超时时间之外,创建sock时还有一个tcp超时时间,如果tcp超时时间先起作用,会导致PullMessages函数返回失败,并且连接被断开,导致后续的PullMessages调用马上失败。所以,最好保证Timeout小于tcp超时时间,以避免逻辑上的错误。

8 为什么typemap.dat要加几行

在前面的「重新生成ONVIF代码」章节中,为了支持Events模块,在执行wsdl2h命令之前,得在gsoap\typemap.dat文件末尾加上:

# 解决:PullMessages收不到事件通知_wsnt__NotificationMessageHolderType_Message = $ struct _tt__Message* tt__Message;# 解决:CreatePullPointSubscription无法订阅感兴趣的主题wsnt__FilterType = $ struct wsnt__TopicExpressionType* TopicExpression;# 解决:GetEventProperties无法解析TopicSet字段wstop__TopicSetType = $ _XML __mixed;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这几行可是折腾了我好长时间,很多初学者搞不懂为什么要加这几行,这里做个详细解释。

8.1 PullMessages为何收不到事件通知

刚开始,我的demo程序通过PullMessages接口一直收不到IPC摄像头的事件通知。

为了解释清楚,我们先看看PullMessagesResponse应答消息是长什么样的,里面都有哪一些信息。「ONVIF Core Specification」规格说明书中,在Event handling > Notification example > PullMessagesResponse章节,给出了PullMessagesResponse应答XML消息样例,如下所示。如果你想看自己手上IPC摄像头的PullMessagesResponse应答XML消息,你可以通过ONVIF Device Test Tool 工具或者Wireshark等网络抓包工具抓包查看。

<?xml version="1.0" encoding="UTF-8"?>  <SOAP-ENV:Envelope    xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"    xmlns:wsa="http://www.w3.org/2005/08/addressing"    xmlns:wstop="http://docs.oasis-open.org/wsn/t-1"    xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"    xmlns:tet="http://www.onvif.org/ver10/events/wsdl"    xmlns:tns1="http://www.onvif.org/ver10/topics"    xmlns:tt="http://www.onvif.org/ver10/schema">    <SOAP-ENV:Header>      <wsa:Action>        http://www.onvif.org/ver10/events/wsdl/PullPointSubscription/PullMessagesResponse      </wsa:Action>    </SOAP-ENV:Header>    <SOAP-ENV:Body>      <tet:PullMessagesResponse>        <tet:CurrentTime>          2008-10-10T12:24:58        </tet:CurrentTime>        <tet:TerminationTime>          2008-10-10T12:25:58        </tet:TerminationTime>        <wsnt:NotificationMessage>          <wsnt:Topic            Dialect="http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet">            tns1:RuleEngine/LineDetector/Crossed          </wsnt:Topic>          <wsnt:Message>            <tt:Message  UtcTime="2008-10-10T12:24:57.321Z">              <tt:Source>                <tt:SimpleItem Name="VideoSourceConfigurationToken" Value="1"/>                <tt:SimpleItem Name="VideoAnalyticsConfigurationToken" Value="2"/>                <tt:SimpleItem Value="MyImportantFence1" Name="Rule"/>              </tt:Source>              <tt:Data>                <tt:SimpleItem Name="ObjectId" Value="15" />              </tt:Data>            </tt:Message>          </wsnt:Message>        </wsnt:NotificationMessage>        <wsnt:NotificationMessage>          <wsnt:Topic            Dialect="http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet">            tns1:RuleEngine/LineDetector/Crossed          </wsnt:Topic>          <wsnt:Message>            <tt:Message UtcTime="2008-10-10T12:24:57.789Z">              <tt:Source>                <tt:SimpleItem Name="VideoSourceConfigurationToken" Value="1"/>                <tt:SimpleItem Name="VideoAnalyticsConfigurationToken" Value="2"/>                <tt:SimpleItem Value="MyImportantFence2" Name="Rule"/>              </tt:Source>              <tt:Data>                <tt:SimpleItem Name="ObjectId" Value="19"/>              </tt:Data>            </tt:Message>          </wsnt:Message>        </wsnt:NotificationMessage>      </tet:PullMessagesResponse>    </SOAP-ENV:Body>  </SOAP-ENV:Envelope>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

如果没在gsoap\typemap.dat文件末尾加上那两行,生成的ONVIF代码跟PullMessagesResponse相关的结构体定义如下(在soapStub.h头文件中)。

struct _tev__PullMessagesResponse {    time_t CurrentTime;    time_t TerminationTime;    int __sizeNotificationMessage;    struct wsnt__NotificationMessageHolderType *wsnt__NotificationMessage;};struct wsnt__NotificationMessageHolderType {    struct wsa5__EndpointReferenceType *SubscriptionReference;    struct wsnt__TopicExpressionType *Topic;    struct wsa5__EndpointReferenceType *ProducerReference;    struct _wsnt__NotificationMessageHolderType_Message Message;};struct _wsnt__NotificationMessageHolderType_Message {    char dummy;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

gSOAP生成的代码,结构体变量命名,都是有规律的,跟前面的XML是一一对应,比如:

  • <tet:PullMessagesResponse> 对应着结构体struct _tev__PullMessagesResponse
  • <tet:CurrentTime>对应着结构体成员变量time_t CurrentTime
  • <tet:TerminationTime>对应着结构体成员变量time_t TerminationTime
  • <wsnt:NotificationMessage>对应着结构体成员变量struct wsnt__NotificationMessageHolderType *wsnt__NotificationMessage;
  • <wsnt:Topic对应着struct wsnt__NotificationMessageHolderType结构体中的成员变量struct wsnt__TopicExpressionType *Topic;
  • 以此类推,<wsnt:Message>对应着结构体struct _wsnt__NotificationMessageHolderType_Message Message;
  • 可是到了<tt:Message,结构体定义就没有变量跟它对应了。最终导致的结果就是,gSOAP生成的代码接口soap_call___tev__PullMessages无法解析这部分XML数据,即客户端得不到IPC摄像头送过来的事件通知消息,从而导致客户端检测不到订阅的事件消息。

在gsoap\typemap.dat文件末尾增加:

_wsnt__NotificationMessageHolderType_Message = $ struct _tt__Message* tt__Message;
  • 1

对PullMessages相关结构体带来的变化(对比soapStub.h差异),如下图所示:


 
图6 改typemap.dat给soapStub.h带来的差别 

其实结构体定义的变化,在使用wsdl2h工具生成的头文件onvif.h也能看出端倪,如下图所示:



图7 改typemap.dat给onvif.h带来的差别 

不仅结构体定义发生变化,内部函数实现也有不同,soapC.c源文件中的soap_in__wsnt__NotificationMessageHolderType_Message函数就有差异:如下图所示:



图8 改typemap.dat给soapC.c带来的差别 

如此改完,PullMessages才能正确解析出PullMessagesResponse的应答XML消息,客户端才能探测到事件通知消息。

8.2 CreatePullPointSubscription无法订阅感兴趣的主题

在gsoap\typemap.dat文件末尾增加:

wsnt__FilterType = $ struct wsnt__TopicExpressionType* TopicExpression;
  • 1

对CreatePullPointSubscription相关结构体带来的变化(对比soapStub.h差异),如下图所示:



图9 改typemap.dat给soapStub.h带来的差别 

修改之前,因为缺少TopicExpression结构体变量,导致CreatePullPointSubscription接口无法订阅感兴趣的主题。这会带来一个问题,比如说IPC摄像头支持十种报警事件,但我只关心其中的遮挡报警,因为不能过滤主题(即关心所有事件),其他我不关心的事件发生变化也会源源不断的汇报过来,浪费带宽资源。

修改之后,有了TopicExpression结构体变量,CreatePullPointSubscription接口就能够订阅感兴趣的主题了。我们订阅的事件变化才会通知,不感兴趣的事件发生变化不会通知。

8.3 GetEventProperties无法解析TopicSet字段

在我的demo中,将GetEventProperties应答信息的各个字段打印出来(日志如下),发现无法解析IPC摄像头送过来的TopicSet字段,但从ONVIF Device Test Tool工具观察GetEventProperties应答信息,TopicSet字段信息是很多的。有关于该信息,官网ONVIF Core Specification规格手册中的GetEventPropertiesResponse章节也有例子说明,但这些信息还不足以解决问题。

================= + dump_tev__GetEventPropertiesResponse + >>>__sizeTopicNamespaceLocation: 1TopicNamespaceLocation: (0x917f120)   |- http://www.onvif.org/onvif/ver10/topics/topicns.xmlwsnt__FixedTopicSet: truewstop__TopicSet: (0x917ef50)   |- documentation: (null)__sizeTopicExpressionDialect: 2wsnt__TopicExpressionDialect: (0x917eef0)   |- http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete   |- http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet__sizeMessageContentFilterDialect: 1MessageContentFilterDialect: (0x917ef68)   |- http://www.onvif.org/ver10/tev/messageContentFilter/ItemFilter__sizeProducerPropertiesFilterDialect: 0ProducerPropertiesFilterDialect: (null)__sizeMessageContentSchemaLocation: 1MessageContentSchemaLocation: (0x917f040)   |- http://www.onvif.org/onvif/ver10/schema/onvif.xsd================= - dump_tev__GetEventPropertiesResponse - <<<
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20



图10 ONVIF Device Test Tool工具中执行GetEventProperties 

观察代码struct _tev__GetEventPropertiesResponse结构体中的struct wstop__TopicSetType定义,发现只有一个struct wstop__Documentation *documentation;成员变量,如下所示,自然无法解析出上图的TopicSet字段信息。

struct _tev__GetEventPropertiesResponse {        int __sizeTopicNamespaceLocation;        char **TopicNamespaceLocation;        enum xsd__boolean wsnt__FixedTopicSet;        struct wstop__TopicSetType *wstop__TopicSet;        int __sizeTopicExpressionDialect;        char **wsnt__TopicExpressionDialect;        int __sizeMessageContentFilterDialect;        char **MessageContentFilterDialect;        int __sizeProducerPropertiesFilterDialect;        char **ProducerPropertiesFilterDialect;        int __sizeMessageContentSchemaLocation;        char **MessageContentSchemaLocation;};struct wstop__TopicSetType {        struct wstop__Documentation *documentation;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

通过以下方法可以获取到TopicSet字段信息,在gsoap\typemap.dat文件末尾增加:

wstop__TopicSetType = $ _XML __mixed;
  • 1

对GetEventProperties相关结构体带来的变化(对比soapStub.h差异),如下图所示:



图11 改typemap.dat给soapStub.h带来的差别 

即struct wstop__TopicSetType结构体多一个char *__mixed变量,通过这个变量,就能获取TopicSet字段信息,如下所示。当然这种方式获取的是XML字符串,不利于应用层解析,是否有更好的方法,还有待研究。

================= + dump_tev__GetEventPropertiesResponse + >>>__sizeTopicNamespaceLocation: 1TopicNamespaceLocation: (0x8cee020)   |- http://www.onvif.org/onvif/ver10/topics/topicns.xmlwsnt__FixedTopicSet: truewstop__TopicSet: (0x8cedf50)   |- documentation: (null)   |- __mixed: <tns1:RuleEngine><CellMotionDetector><Motion wstop:topic="true"><tt:MessageDescription IsProperty="true"><tt:Source><tt:SimpleItemDescription Name="VideoSourceConfigurationToken" Type="tt:ReferenceToken"/><tt:SimpleItemDescription Name="VideoAnalyticsConfigurationToken" Type="tt:ReferenceToken"/><tt:SimpleItemDescription Name="Rule" Type="xs:string"/></tt:Source><tt:Data><tt:SimpleItemDescription Name="IsMotion" Type="xs:boolean"/></tt:Data></tt:MessageDescription></Motion></CellMotionDetector><TamperDetector><Tamper wstop:topic="true"><tt:MessageDescription IsProperty="true"><tt:Source><tt:SimpleItemDescription Name="VideoSourceConfigurationToken" Type="tt:ReferenceToken"/><tt:SimpleItemDescription Name="TamperWindowIndex" Type="xs:string"/></tt:Source><tt:Data><tt:SimpleItemDescription Name="IsTamper" Type="xs:string"/></tt:Data></tt:MessageDescription></Tamper></TamperDetector></tns1:RuleEngine>__sizeTopicExpressionDialect: 2wsnt__TopicExpressionDialect: (0x8cedef0)   |- http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete   |- http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet__sizeMessageContentFilterDialect: 1MessageContentFilterDialect: (0x8ced140)   |- http://www.onvif.org/ver10/tev/messageContentFilter/ItemFilter__sizeProducerPropertiesFilterDialect: 0ProducerPropertiesFilterDialect: (null)__sizeMessageContentSchemaLocation: 1MessageContentSchemaLocation: (0x8ced218)   |- http://www.onvif.org/onvif/ver10/schema/onvif.xsd================= - dump_tev__GetEventPropertiesResponse - <<<
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

9 其他问题

9.1 各类事件的主题Topic是什么

使用CreatePullPointSubscription订阅主题时,需要指定topic,并设定过滤规则,以下是常用事件的topic:

  • 遮挡报警:tns1:RuleEngine/TamperDetector/Tamper
  • 移动侦测:tns1:RuleEngine/CellMotionDetector/Motion

ONVIF还支持其他事件,它们的topic又是什么呢,在「ONVIF-Core-Specification-v250」版本中的「ONVIF Topic Namespace」章节有提到,如下图所示,但也仅仅是提到root topics,信息也不足,这方面的知识还有待研究。奇怪的是,ONVIF官方打从「ONVIF-Core-Specification-v260」版本就被删掉了这部分信息,也不知道是几个意思。



图12 ONVIF Topic Namespace 
阅读全文
0 0
原创粉丝点击