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

ONVIF 按「在系统里干什么」把设备分成四类:NVT 发流、NVD 显示、NVS 存流、NVA 分析。 分类不看外形,看 Types 字段和它有哪些 service——所以一台设备可以同时是几种。

📝 本节和前面几节的关系

上一节拿到的是「地址」,这一节讲「拿到地址之后怎么判断它是什么」。 这一节把「设备类型」和「Profile」彻底分开——这两件事经常被搅在一起,结论就会虚。

一、四个缩写

缩写 全称 是什么 典型产品
NVT Network Video Transmitter 发流的一端 摄像机
NVD Network Video Display 收流并显示的一端 解码器、大屏
NVS Network Video Storage 存流的一端 NVR
NVA Network Video Analytics 分析流的一端 分析服务器

第一行和第三行最常被搞混: 摄像机(NVT)发流,NVR(NVS)存流—— 但一台设备可以同时是两者(带 SD 卡的摄像机就同时是 NVT 和 NVS)。 所以「这台是 NVT 还是 NVS」这种问法本身不严谨,正确的问法是「它支持哪些 Types」。 Types 是一个集合,不是一个枚举—— 这一点在 WS-Discovery 的 Probe 里就体现出来了:你可以一次问好几种。

二、判类型看 service,判 Profile 看参数

两件事的判据完全不同,混用就会得出半对的结论:

要回答的问题 看什么 性质
它是什么类型 有没有对应的 service 绝对的,有就是有
它符不符合 Profile S 编码/分辨率/传输的具体参数 有条件的,逐条可查

「这是 NVT」是绝对的:有 media service 且能 GetStreamUri,它就是 NVT,没有争议。 「符合 Profile S」是有条件的:H.264、≥720p、RTP over RTSP、WS-Discovery,四条都得满足。 混淆这两件事的典型后果是:看到「1080p、H.264」就说「符合 Profile S」——前两条对了, 但传输和发现根本没验。要把话说实,就得四条逐条查,而不是看着参数表点头。

三、一台设备可以同时是几种类型

判据就是「有没有那组 service」,本机模拟设备四样都有:

类型 必须有的 service 本机有吗
NVT media(能 GetStreamUri) 有
NVS recording(能 GetRecordings) 有(简化版)
NVD display(能 GetDisplayOptions) 有(简化版)
NVA analytics(能 GetSupportedRules) 有(简化版)

所以这台模拟设备「同时是四种设备」。 这在真设备上不现实——真摄像机一般只有 media(可能加 recording), display 在解码器上,analytics 在分析服务器上。 但这正好说明一件事:类型是能力集合,不是身份。 设备不会「声明自己是 NVR」,它只是「恰好有 recording 这组接口」。 理解这一点,就不会再去找「设备类型字段」这种东西了——它根本不存在, 你得自己从 service 列表推。

四、Types 在发现报文里,也在推断里

Types 有两个出现场合,用途不同:

场合 谁写 用途
Probe 请求 客户端写 「我要找哪一类」
ProbeMatch 的 Scopes 设备写 「我是哪一类」

第一行是「筛选条件」,第二行是「自我介绍」。 很多客户端只做第一行——发一个 Probe 找 NVT,收到回包就直接连, 从来不核 Scopes 里写的是什么。 这会带来一个实际风险:有的设备(尤其是国产的低价设备)不管你要什么类型,一律回包, 于是你会「发现」一堆其实不是摄像机的东西——比如某个也开了 3702 端口的网关。 所以稳妥的做法是「收到回包后再用 GetServices 核一遍」,看它到底有没有 media。

五、动手 Demo:四种设备类型,靠 Types 和 Profile 认

Demo 从这台模拟设备的响应里推断它的类型,再逐条验「符不符合 Profile S」。 两件事分开做,结论才不虚。

# ① 判类型:有没有对应的 service
_a, b, _n = S.call(port, "device", "GetServices")
ns = []
for svc in ET.fromstring(b).iter("{%s}Service" % S.NS_TDS):
    ns.append(svc.find("{%s}Namespace" % S.NS_TDS).text)
KIND = {"media": "NVT", "recording": "NVS",
        "display": "NVD", "analytics": "NVA"}
for key, name in KIND.items():
    print(name, any(key in x for x in ns))

# ② 验 Profile S 的四条
_a, b2, _n = S.call(port, "media", "GetProfiles")
enc = ET.fromstring(b2).find(".//{%s}Encoding" % S.NS_TT)
w = ET.fromstring(b2).find(".//{%s}Width" % S.NS_TT)
h = ET.fromstring(b2).find(".//{%s}Height" % S.NS_TT)
print(enc.text, w.text, h.text, int(w.text) >= 1280)

实跑输出:

--------------------------------------------------------------------
四种设备类型,靠 Types 和 Profile 认
--------------------------------------------------------------------
**ONVIF 把设备按「在系统里干什么」分成四类。**
  分类不看外形,看 **Types 字段**和**它支持哪些 Profile**。

**四种类型(NVT/NVD/NVS/NVA):**

  缩写    全称                                    是什么                典型产品
  --------------------------------------------------------------------------------------------
  NVT     **N**etwork **V**ideo **T**ransmitter   **发流的一端**        **摄像机**
  NVD     **N**etwork **V**ideo **D**isplay       收流并显示的一端      解码器、大屏
  NVS     **N**etwork **V**ideo **S**torage       **存流的一端**        **NVR**
  NVA     **N**etwork **V**ideo **A**nalytics     分析流的一端          分析服务器

  **第一行和第三行最常被搞混**:
  **摄像机(NVT)发流,NVR(NVS)存流**——
  **一台设备可以同时是两者**(带 SD 卡的摄像机就同时是 NVT 和 NVS)。
  **所以「这台是 NVT 还是 NVS」这种问法本身不严谨**,  **正确的问法是「它支持哪些 Types」。**

**Types 在发现报文里,也在这台设备的 Profile 里:**

**按这台设备的响应推断它的类型:**

  证据                    值                    指向
  ----------------------------------------------------------------------------
  有 `media` service      **4 个 service**      **能发流 → NVT**
  编码格式                H264                  **H.264 → 符合 Profile S**
  分辨率                  1920×1080             1080p ≥ 720p → **符合 S**
  帧率上限                25 fps                ≥ 25 算正常
  码率上限                4096 kbps             **4 Mbps,1080p 的常态**

  **第一行是决定性的**:  **有 `media` service 且能 `GetStreamUri`,就是 NVT。**
  **第二到五行是在验「符不符合 Profile S」**,
  **不是判类型**——  **判类型只看有没有那组能力,判 Profile 才看具体参数。**
  **这两件事经常被搅在一起,结论就会虚**:
  **「这是 NVT」是绝对的,「符合 Profile S」是有具体条件可查的。**

**一台设备能是什么类型,看它有没有对应的 service:**

  类型      必须有的 service                        本机有吗
  ----------------------------------------------------------------------
  NVT       `media`(能 `GetStreamUri`)            **有**
  NVS       `recording`(能 `GetRecordings`)       **有(简化版)**
  NVD       `display`(能 `GetDisplayOptions`)     **有(简化版)**
  NVA       `analytics`(能 `GetSupportedRules`)   **有(简化版)**

  **这台模拟设备四样都有,所以它「同时是四种设备」**——
  **这在真设备上不现实,但正好说明「类型是能力集合,不是身份」。**
  **真摄像机一般只有 `media`(+ 可能 `recording`)**,
  **`display` 在解码器上,`analytics` 在分析服务器上。**

第一段那个 any(key in x for x in ns) 是「子串匹配」, 所以 ver10/media/wsdl 能被 media 匹配到。 这种写法在演示里够用,但工程里应该精确匹配尾段——否则 recording 和 recording2 会撞。 第二段只验了四条里的两条(编码和分辨率), 因为另外两条(RTP over RTSP、WS-Discovery)本机验不了:前者要真发 RTP,后者要组播。 所以这里只给「一半的结论」,不假装验全了。 「≥720p」我用的是宽度 ≥1280,这对应 720p 的 1280×720。 没写成 h >= 720 是因为「720p」这个叫法按行数命名,而实际上 1280×720 的宽才是那个 1280。 两个判据在这里等价,但写清「我按宽度判」比含糊过去好。

30 秒实验

  1. 把 KIND 里的 display 换成 ptz,看判出来的类型变成什么。
  2. 把 int(w.text) >= 1280 改成 int(w.text) >= 1920,看 1080p 还算不算符合。
  3. 给模拟设备去掉 analytics 那一组,看 NVA 那一行变成什么。

常见误区

  1. 以为设备会「声明自己是 NVR」。 没有这个字段,类型是从 service 列表推出来的, 一台设备可以同时是几种。
  2. 把「判类型」和「判 Profile」混在一起。 前者看有没有 service,后者看具体参数, 混用会得出半对的结论。
  3. 以为 Probe 回包一定是你要的类型。 有的设备不管类型一律回包, 要再用 GetServices 核一遍。
自测三问(点开对答案) 1. NVT、NVD、NVS、NVA 分别是什么? —— **发流的(摄像机)、显示的(解码器)、存流的(NVR)、分析的(分析服务器)。** 2. 怎么判断一台设备是不是 NVT? —— **看它有没有 `media` service 且能 `GetStreamUri`。** **这是绝对的判据**,和 Profile 无关。 3. 为什么说「这台是 NVT 还是 NVS」这种问法不严谨? —— 因为**类型是能力集合,不是身份**。 **一台设备可以同时有 `media` 和 `recording`,同时是 NVT 和 NVS。**

下一节讲设备管理:GetSystemDateAndTime 返回三个时间, 以及为什么「录像时间对不上」多半是时区问题。

相关笔记