ONVIF 的「Web 服务」在实现上就两样东西:一组 XAddr(地址)和一张 GetServices 能力表。 地址回答「发到哪」,能力表回答「有哪些、版本是什么」——两样要分开问。
上一节讲清了前缀,这一节讲这些前缀指向的东西怎么被「发现」和「寻址」。 06.01.06 讲过 Web 服务的通用概念,这里讲 ONVIF 把它具体落成了什么形状。
一、三个要素,两样落地
教科书上 Web 服务有三个要素:服务描述、服务地址、消息格式。 ONVIF 把前两个都塞进了一个响应里:
| 要素 | ONVIF 里是什么 | 从哪拿 |
|---|---|---|
| 服务地址 | XAddr(完整 URL) | GetServices |
| 服务描述 | WSDL + 命名空间 | 规范包,不是设备 |
| 消息格式 | SOAP 1.2 信封 | 固定 |
第二行那个「规范包,不是设备」是第六章反复强调过的一条:
设备上那句 ?wsdl 只用来验地址通不通,真文件从 ONVIF 官网的规范包里取。
原因很实际:设备固件里的 WSDL 经常是残的、旧的、甚至压根没有。
所以「接一台新设备」的正确顺序里,第一步永远是 GetServices——
不是因为它能给 WSDL,而是因为它能给地址。
WSDL 你手上早就有,缺的只是那个 IP 加端口。
二、地址和能力分头问
很多人以为 GetServices 一条就能问清全部,其实它只回答一半:
| 问题 | 问哪个操作 | 看哪一列 |
|---|---|---|
| 这台设备有哪些 service | GetServices |
看 Namespace 列表 |
| 某个 service 在哪 | GetServices |
看 XAddr |
| 某类操作支不支持 | GetCapabilities |
看大类里的布尔开关 |
第三行是很多人不知道的:
GetCapabilities 里有一堆 <tt:RTP_TCP>true</tt:RTP_TCP> 这样的开关,
它们回答的是「支不支持某个特性」,不是「地址在哪」。
所以「有没有云台」要两边都看:GetServices 里有 ptz 说明有这个 service,
GetCapabilities 里的 PTZ 段说明它支持哪些动作(比如有没有 AbsolutePanTilt)。
只看一边,就会得出「有云台但不能绝对定位」这种半对的结论。
三、那个 2.60 是规范版本
GetServices 的返回里,每个 service 都带一个版本号,本例全是 2.60。
这个数不是固件版本,是「该 service 实现到哪一版规范」。
| 看到的数 | 是什么 | 去哪查真实固件版本 |
|---|---|---|
GetServices 的 2.60 |
规范版本 | 不适用 |
GetDeviceInformation 的 FirmwareVersion |
固件版本 | 就在这里 |
混用会导致一个常见误判: 以为「service 版本 2.60」就说明设备新——其实同一代规范下,所有设备都报这个数。 它更像「我这份接口是照 2.60 版规范做的」,不是「我是 2023 年的产品」。 这个数真正的用途是「判兼容性」:规范版本低的设备,某些可选操作可能没有; 但「有没有」最终还是得看操作本身能不能调通,版本号只能给个预期。
四、三个 RTP 开关决定流怎么走
GetCapabilities 里有三个和传输有关的布尔开关,它们直接决定「这路流卡不卡」:
| 开关 | 本例 | 含义 |
|---|---|---|
RTPMulticast |
false | 组播:一路发给多个人 |
RTP_TCP |
true | RTP 也走 TCP,能穿 NAT,延迟略高 |
RTP_RTSP_TCP |
true | 只有 RTSP 走 TCP,媒体仍走 UDP |
第二行和第三行差一个词,效果完全不同:RTP_TCP 是媒体走 TCP(可靠但有延迟);
RTP_RTSP_TCP 只是信令走 TCP,媒体仍走 UDP(快但会丢包)。
很多设备只支持第三行——这意味着「信令可靠、媒体不可靠」是它唯一的模式,
在丢包严重的网络里就会花屏。
要稳,就得要求设备支持 RTP_TCP,这也是选型时该写进技术参数的一条。
第一行是 false,说明这台设备不支持组播——而 Profile S 并不强制组播,
所以「不支持组播」不影响它符合 S,但影响「一台上百人看同一路」的方案。
五、动手 Demo:Web 服务,就是一组地址 + 一张能力表
Demo 先问 GetServices,把四组地址和版本号抓出来;
再问 GetCapabilities,把三个传输开关抓出来。
两样东西合起来,才是「这台设备能干什么」的完整答案。
# ① 地址 + 版本(GetServices)
_a, b, _n = S.call(port, "device", "GetServices")
for svc in ET.fromstring(b).iter("{%s}Service" % S.NS_TDS):
ns = svc.find("{%s}Namespace" % S.NS_TDS).text
xa = svc.find("{%s}XAddr" % S.NS_TDS).text
ver = svc.find("{%s}Version/{%s}Major" % (S.NS_TDS, S.NS_TDS))
print(ns.split("//")[-1], xa.split("/")[-1], ver.text)
# ② 能力开关(GetCapabilities)
_a, b2, _n = S.call(port, "device", "GetCapabilities")
r2 = ET.fromstring(b2)
for k in ("RTPMulticast", "RTP_TCP", "RTP_RTSP_TCP"):
e = r2.find(".//{%s}%s" % (S.NS_TT, k))
print(k, e.text if e is not None else "-")
实跑输出:
--------------------------------------------------------------------
Web 服务,就是「一组地址 + 一张能力表」
--------------------------------------------------------------------
**07.02.03 讲的「Web 服务」,在 ONVIF 里落成两样东西:**
**一组 XAddr(地址),一张 GetServices 的能力表。**
**`GetServices` 的返回,四列信息:**
Namespace 尾段 XAddr 尾段 版本
----------------------------------------------------------------------
ver10/device device_service 2.60
ver10/media media_service 2.60
ver20/ptz ptz_service 2.60
ver10/events events_service 2.60
**第三列那个版本号是「规范版本」,不是设备固件版本**——
**`2.60` 意思是「这个 service 实现到 2.60 版规范」。**
**设备的固件版本在 `GetDeviceInformation` 里**,两码事。
**混用会导致一个常见误判**:
**以为「service 版本 2.60」就说明设备新**——**其实所有设备都报这个数**。
**为什么「地址」和「能力」要分开问:**
问题 问哪个 为什么
--------------------------------------------------------------------------------------
这台设备有哪些 service `GetServices` **看 Namespace 列表**
某个 service 在哪 `GetServices` **看 XAddr**
某类操作支不支持 **`GetCapabilities`** **看大类里的布尔开关**
**第三行是很多人不知道的**:
**`GetCapabilities` 里有一堆 `<tt:RTP_TCP>true</tt:RTP_TCP>` 这样的开关**,
**它们回答的是「支不支持某个特性」,不是「地址在哪」。**
**所以「有没有 PTZ」要两边都看**: **`GetServices` 里有 `ptz` 说明有这个 service**,
**`GetCapabilities` 里的 `PTZ` 段说明它支持哪些动作。**
**看一眼 `GetCapabilities` 里的流传输开关:**
`RTPMulticast` = **false**
`RTP_TCP` = **true**
`RTP_RTSP_TCP` = **true**
**这三个开关决定了「流能怎么传」**:
**`RTPMulticast` 是组播**:一路发给多个人。
**`RTP_TCP` 是 RTP 走 TCP**:能穿 NAT,但延迟略高。
**`RTP_RTSP_TCP` 是只把 RTSP 走 TCP**:媒体仍然走 UDP。
**很多设备只支持 `RTP_RTSP_TCP`**—— **这意味着「信令走 TCP、媒体走 UDP」是它唯一的模式**,
**在丢包严重的网络里会花屏。**
**要走的稳,就得要求设备支持 `RTP_TCP`**(媒体也走 TCP)。
两个响应都真发真收,XAddr 里的端口是运行时随机分配的,
所以写进笔记时只保留了路径尾段——照抄完整地址每次都不同,
这也正好说明「地址不能硬编码」。
版本号我只取了 Major(就是那个 2.60 里的 2 那段的首位),
没拼成完整字符串——
因为 Version 是个三段的复合元素(Major/Minor),完整值是 2.60,我取的是它的第一段。
GetCapabilities 里那三个开关是布尔值,解析出来是字符串 true/false,
不是 Python 的布尔——所以判真得写 == u"true",不能直接 if e.text,
因为字符串 false 在 Python 里也是真。
这个坑在 07.02.11 的事件处理里还会再出现一次。
30 秒实验
- 把
GetServices返回的 service 个数数出来,和 07.01.03 那张十一组的表比一比。 - 改
GetCapabilities的解析路径,把Media段整个打印出来,看里面还有多少开关。 - 把三个 RTP 开关里的
true判断改成== u"true",再改成直接判文本,看哪个会误判。
常见误区
- 以为
GetServices能给出全部能力。 它只给地址和版本, 特性开关在GetCapabilities里。 - 以为那个 2.60 是固件版本。 它是规范版本,固件版本在
GetDeviceInformation里。 - 以为
RTP_TCP和RTP_RTSP_TCP是一回事。 前者是媒体走 TCP,后者只是信令走 TCP, 媒体仍走 UDP。
自测三问(点开对答案)
1. ONVIF 的 Web 服务在实现上是哪两样东西? —— **一组 XAddr(地址)和一张 `GetServices` 的能力表。** **地址回答「发到哪」,能力表回答「有哪些」。** 2. `GetServices` 返回的 2.60 是什么版本? —— **规范版本**,不是固件版本。**它表示「这个 service 实现到 2.60 版规范」。** 3. `RTP_TCP` 和 `RTP_RTSP_TCP` 有什么区别? —— **前者让媒体也走 TCP**(可靠、延迟略高); **后者只让 RTSP 信令走 TCP,媒体仍走 UDP**(快但会丢包)。下一节讲设备发现:一条 UDP 组播报文怎么换回来一个 IP 地址。
- ☐ (本节完,下一节 07.02.04 设备发现)