📹 第七章 ONVIF协议原理解析
📄 一句话概括

ONVIF 不是一个接口,是十几组接口,每组一个 service、一个命名空间、一个地址。 ver10 和 ver20 是规范版本不是 ONVIF 版本,两套可以同时存在。

📝 本节和前面几节的关系

上一节看到了四层栈,这一节看最上面那层「描述」到底描述了多少东西。 这一节的操作表是后面 07.02 十七节的地图——每一节讲其中一组。

一、十几组接口,各有各的命名空间

本机模拟设备支持 30 个操作,分成 11 组。 真实设备的 device_service 一个 WSDL 就有几十个操作,这里只挑了常用的。

service 管什么 命名空间尾段
device 设备管理(5 个) ver10/device/wsdl
media 媒体配置(7 个) ver10/media/wsdl
ptz 云台控制(4 个) ver20/ptz/wsdl
events 事件处理(3 个) ver10/events/wsdl
imaging 图像配置(3 个) ver20/imaging/wsdl
analytics 视频分析(2 个) ver20/analytics/wsdl
recording 录像(2 个) ver10/recording/wsdl
search 录像检索(1 个) ver10/search/wsdl
display 显示(1 个) ver10/display/wsdl
receiver 接收器(1 个) ver10/receiver/wsdl
deviceio 输入输出(1 个) ver10/deviceIO/wsdl

device 和 media 两组占了 12 个,四成。 这也是「接一台陌生设备,前两条报文基本都发给这两组」的原因—— 先问「你是谁」,再问「你的流怎么要」。 剩下的九组是「按需才用」:没有云台就不用碰 ptz,不录像就不用碰 recording。

二、ver10 和 ver20 不是笔误

设备管理一直是 ver10,而云台和图像配置是 ver20。 这不是更新不及时,是「升了版本但老地址还留着」。

写法 含义 见到它的设备
ver10/device/wsdl device_service 第一版 几乎所有设备
ver10/ptz/wsdl 云台的老版本 老设备可能只认这个
ver20/ptz/wsdl 云台的新版本(有不兼容改动) 2014 年后的设备
ver20/imaging/wsdl 图像配置 绝大多数设备

第二行和第三行并存是常态。 所以代码里不能写死 ver20/ptz——要靠 GetServices 问出来的地址,不能自己拼。 这是「不要硬编码 URL」在 ONVIF 上最实际的落地理由: 同一个操作在两代规范里有两个命名空间,拼错了设备就不认。

三、GetServices:一次问清全部地址

这台设备每个 service 一个地址,靠一次 GetServices 就能全拿到。 Demo 真跑的结果(端口是每次运行随机分配的):

Namespace XAddr(去掉 http://)
ver10/device/wsdl 127.0.0.1:<端口>/onvif/device_service
ver10/media/wsdl 127.0.0.1:<端口>/onvif/media_service
ver20/ptz/wsdl 127.0.0.1:<端口>/onvif/ptz_service
ver10/events/wsdl 127.0.0.1:<端口>/onvif/events_service

GetServices 和 GetCapabilities 的区别: 后者只给「大类地址」(Device/Media/PTZ),前者给「每个 service 的完整条目」。 GetServices 更细,但老的 ONVIF 1.0 设备可能没有这个操作—— 那时候只能退回 GetCapabilities。 所以健壮的代码要两条都试:先 GetServices,失败再 GetCapabilities。 只写一条,遇到老设备就断。

四、规范包里都有什么

ONVIF 官网给的规范包,解压之后大概是这个结构:

目录 里面是什么 什么时候看
wsdl/ 所有 service 的 WSDL 生成代码时必用
schema/ XSD 类型定义 查某个字段的类型
core.pdf 核心规范(设备管理、发现) 排查规范性问题
profiles/ 每个 Profile 的规范 + 测试用例说明 判断「算不算」时

第一行是唯一的「必需品」:没有它就没法用 gSOAP 生成代码(第六章的 wsdl2h 要吃它)。 第四行是「吵架时用的」: 厂家说支持、你说不支持,就翻这个——它写明了某个 Profile 要求哪些操作必须实现。 注意 WSDL 不是从设备上抓的(06.01.04 讲过原因)—— 设备上那句 ?wsdl 只用来验地址通不通,真文件从规范包里拿。

五、动手 Demo:规范按 service 切开,一共多少个操作

Demo 用本机模拟设备的操作表,按 service 分组数一遍,再看每组的命名空间长什么样。 最后真发一次 GetServices,把四个地址抓出来。

# 按响应函数的命名空间给 OPS 表分组
cnt = {}
for op, (ns, _f) in S.OPS.items():
    cnt[ns] = cnt.get(ns, 0) + 1

for key, use, ns in SVC:
    print(key, use, cnt.get(ns, 0), ns.split("//")[-1])

# 真问一次 GetServices,把 Namespace 和 XAddr 抓出来
_a, body, _n = S.call(port, "device", "GetServices")
root = ET.fromstring(body)
for svc in root.iter("{%s}Service" % S.NS_TDS):
    ns = svc.find("{%s}Namespace" % S.NS_TDS)
    xa = svc.find("{%s}XAddr" % S.NS_TDS)
    print(ns.text, xa.text)

实跑输出:

--------------------------------------------------------------------
规范按 service 切开,一共多少个操作
--------------------------------------------------------------------
**ONVIF 不是一个接口,是十几组接口。**  每一组叫一个 service,**各有各的命名空间和地址。**
  这一节按 service 把本机模拟设备支持的操作分组数一遍。

**本机模拟设备支持 30 个操作,分布如下:**

  service     管什么          操作数    命名空间尾段
  ------------------------------------------------------------------------------
  device      设备管理        5         www.onvif.org/ver10/device/wsdl
  media       媒体配置        7         www.onvif.org/ver10/media/wsdl
  ptz         云台控制        4         www.onvif.org/ver20/ptz/wsdl
  events      事件处理        3         www.onvif.org/ver10/events/wsdl
  imaging     图像配置        3         www.onvif.org/ver20/imaging/wsdl
  analytics   视频分析        2         www.onvif.org/ver20/analytics/wsdl
  recording   录像            2         www.onvif.org/ver10/recording/wsdl
  search      录像检索        1         www.onvif.org/ver10/search/wsdl
  display     显示            1         www.onvif.org/ver10/display/wsdl
  receiver    接收器          1         www.onvif.org/ver10/receiver/wsdl
  deviceio    输入输出        1         www.onvif.org/ver10/deviceIO/wsdl

  **合计 30 个操作**,和 `OPS` 表的条数对得上(30)。
  **`device` 和 `media` 两组占了 12 个,四成。**
  **这也是「接一台陌生设备时,前两条报文基本都发给这两组」的原因。**

**命名空间的长相有规律,别背:**

  - `http://www.onvif.org/ver10/device/wsdl` — **ver10**
  - `http://www.onvif.org/ver20/ptz/wsdl` — **ver20**

  **`ver10` 和 `ver20` 是规范版本,不是 ONVIF 版本。**
  **PTZ 从 ver10 升到了 ver20**(接口有不兼容改动),**而 device 一直是 ver10。**
  **所以看到 `ver20/ptz` 不要以为是笔误**,**它是真的两套并存。**
  **老设备可能还是 `ver10/ptz`,两套同时存在。**

**每个 service 一个地址,靠 `GetServices` 一次问全:**

  Namespace                                             XAddr
  ----------------------------------------------------------------------------------------------
  www.onvif.org/ver10/device/wsdl                       127.0.0.1:50669/onvif/device_service
  www.onvif.org/ver10/media/wsdl                        127.0.0.1:50669/onvif/media_service
  www.onvif.org/ver20/ptz/wsdl                          127.0.0.1:50669/onvif/ptz_service
  www.onvif.org/ver10/events/wsdl                       127.0.0.1:50669/onvif/events_service

  **`GetServices` 是「一次问清这台设备有哪些本事」的唯一入口**,
  **比 `GetCapabilities` 更细**(后者只给大类地址)。**对接新设备,第一个该发的就是它。**

「30 个操作」是这台迷你设备的数,不是 ONVIF 的全部。 真实规范里 device_service 一个就有几十个操作,全加起来是几百个。 这个数字只用来演示「怎么分组」,不代表规范的规模。 分组用的键是「响应函数的命名空间」而不是「函数名」, 因为同一个操作名在不同 service 里可能重名 (比如好几个 service 都有 GetConfigurationOptions)。按命名空间分才不会串。 XAddr 里那个端口是运行时随机分配的, 所以写进笔记时不能照抄——它每次跑都不一样,这也正好说明「地址不能硬编码」。

30 秒实验

  1. 把 GetServices 的响应里 <tds:Service> 的个数数出来,和 11 组比一比(这台只暴露了 4 组)。
  2. 给模拟设备加一个 ver10/ptz 别名地址,看能不能同时返回两套。
  3. 把某个操作的命名空间改错一位,看设备返回的是 Fault 还是直接断连。

常见误区

  1. 以为 ONVIF 只有一个地址。 每个 service 一个地址, 必须靠 GetServices 或 GetCapabilities 问出来。
  2. 以为 ver20 比 ver10 新所以更好。 两套并存,老设备可能只认 ver10/ptz, 拼错了就不通。
  3. 以为 WSDL 是从设备上抓的。 真文件从 ONVIF 规范包里拿,设备上那句 ?wsdl 只能验地址。
自测三问(点开对答案) 1. ONVIF 的 service 有几个?每组一个还是共用一个地址? —— **十几组,每组一个独立地址。** 所以必须靠 `GetServices` 问出来,**不能自己拼 URL。** 2. `ver10/ptz` 和 `ver20/ptz` 有什么区别? —— **两代规范,接口有不兼容改动。** 新设备给 `ver20`, **老设备可能只给 `ver10`,两套能同时存在。** 3. `GetServices` 和 `GetCapabilities` 该用哪个? —— **先 `GetServices`(更细),失败再退回 `GetCapabilities`。** **老设备可能没有前者。**

下一节讲 Profile:为什么「支持 ONVIF」不算一句有用的话,而「支持 Profile S」算。

相关笔记