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

Profile 声明和操作可用性是一一对应的:声明 S 的设备,调 G 的接口必然失败。 所以正确的顺序是「先 GetServices 拿能力清单,再决定发哪些报文」,不是一上来就调。

📝 本节和前面几节的关系

上一节讲 Profile 是什么,这一节讲它在代码里怎么用。 这一节的核心不是协议,是「调用顺序」——顺序错了,能用的设备也会被调坏。

一、操作和 Profile 的对应关系

操作 需要 干什么
GetStreamUri S 要 RTSP 地址
GetSnapshotUri S 抓拍地址
GetProfiles S 列码流配置
ContinuousMove S(可选) 云台转动
CreatePullPointSubscription S(可选) 事件订阅
GetRecordings G 录像列表
FindRecordings G 录像检索
GetSupportedRules M 分析规则

第四、五行的「可选」是关键:Profile S 不要求一定有云台,也不要求一定有事件。 所以「支持 S」的设备可能既没有 PTZ 也没有事件。 判断有没有云台,不能看 Profile,要看 GetServices 里有没有 ptz 那一项。 这是本节最实用的一句话—— 很多人拿 Profile 当能力清单,结果买了一台「支持 S」却没有云台的枪机。

二、三档设备,真调一遍

Demo 用一张开关表把同一台迷你设备限制成三档,逐档真调:

档位 声明 支持的操作
基础款 S stream/snapshot/profiles
带云台 S(含 PTZ) 上面 + ptz
加录像 S + G 上面 + rec/search

实测结果:每一档里「已声明」的操作全部返回成功,没有一例 Fault。 这说明「声明了就真支持」——这正是 Profile 的意义。 但「未声明」的那几格,我没有真发请求。 因为一台只声明 S 的设备收到 G 的报文会怎样,规范没有统一规定—— 可能回 404、可能回 Fault、也可能直接断连,三种都出现过。 所以那一格我写「(无)」,不编一个结果出来。

三、被拒的三种方式,和它们各自的坑

拒绝方式 客户端看到什么 难查在哪
回 SOAP Fault Code=s:Sender 最好查,有原因文本
回 HTTP 404 Not Found 容易被当成「地址写错」
直接断连 Connection reset by peer 最难查,什么信息都没有

第三行是最坑的:固件里「不认识的操作」直接关 socket, 客户端只看到「连接被重置」,完全不知道是操作不支持。 这时候只能靠「二分法」定位:把请求逐个删字段,看哪一步开始不断连。 第二行也有坑:404 会被误判成「地址拼错了」, 于是你去改 URL,怎么改都不对——其实地址没错,是这台设备没实现这个操作。

四、正确的调用顺序

七步,每步都为下一步备料:

步 做什么 为什么必须在前面
1 GetServices 拿到全部 service 地址和存在性
2 GetDeviceInformation 确认厂商型号,好在出问题时报给厂家
3 GetProfiles 拿 ProfileToken,后面所有媒体操作都要它
4 GetVideoEncoderConfigurations 看当前编码参数
5 GetStreamUri 拿 RTSP 地址
6 GetSnapshotUri 拿抓拍地址(可选)
7 CreatePullPointSubscription 要事件(可选)

第一步放 GetServices 而不是 GetDeviceInformation, 是因为它决定了「后面哪些步能走」——没有 media 这一项,第 3 到 6 步全部无意义。 第三步的 ProfileToken 是关键依赖: GetStreamUri 必须带 ProfileToken 参数,没有它连请求都拼不出来。 这就是为什么 GetProfiles 必须排在要流之前。

五、动手 Demo:设备声明了哪个 Profile,决定了你能调哪些操作

这一节把「Profile」和「操作」连起来。 Demo 用三档开关表模拟三种设备,逐档真调五个操作。 「未声明」的格子留空不调——不是为了省事,是因为结果不确定。

LEVELS = [
    ("基础款", {"GetProfiles", "GetStreamUri", "GetSnapshotUri"}),
    ("带云台", {... , "ContinuousMove", "GetStatus"}),
    ("加录像", {... , "GetRecordings", "FindRecordings"}),
]

ops = ["GetProfiles", "GetStreamUri", "ContinuousMove", "GetRecordings"]
for name, allow in LEVELS:
    cells = [name]
    for o in ops:
        if o not in allow:
            cells.append("(无)")      # 不发请求,留空
            continue
        _a, b, _n = S.call(port, _svc_of(o), o)
        f = ET.fromstring(b).find(".//{%s}Fault" % NS_S)
        cells.append("**Fault**" if f is not None else "✓")
    print(cells)

实跑输出:

--------------------------------------------------------------------
设备声明了哪个 Profile,决定了你能调哪些操作
--------------------------------------------------------------------
**这一节把「Profile」和「操作」连起来。**
  **声明 Profile S 的设备,调录像接口必然失败**——这不叫 bug,叫规范。

**先看操作和 Profile 的对应关系:**

  操作                              需要      干什么
  ------------------------------------------------------------------
  `GetStreamUri`                    S         实时流
  `GetSnapshotUri`                  S         抓拍
  `GetProfiles`                     S         媒体配置
  `ContinuousMove`                  S         云台(可选)
  `CreatePullPointSubscription`     S         事件(可选)
  `GetRecordings`                   G         录像列表
  `FindRecordings`                  G         录像检索
  `GetSupportedRules`               M         分析规则
  `GetAnalyticsModules`             M         分析模块

  **注意第四、五行的「可选」**:**Profile S 不要求一定有云台,也不要求一定有事件**,
  **所以「支持 S」的设备可能没有 PTZ。**
  **判断一台设备有没有云台,不能看 Profile,要看 `GetServices` 里有没有 ptz 那一项。**

**真跑:同一台设备,用三组不同的「声明」各调一遍,看结果。**

  这里的「声明」是**我用代码模拟的**:  本机迷你设备默认全支持,**我用一张开关表把它限制成三档**。

  设备档位          声明的 Profile      实际支持的操作
  ------------------------------------------------------------------------
  基础款            S                   stream/snapshot/profiles
  带云台            S(含 PTZ)         上面 + ptz
  加录像            S + G               上面 + rec/search

**逐档真调,结果如下(`✓` 表示成功,`Fault` 表示被拒):**

  档位        GetProfiles    GetStreamUri   ContinuousMove   GetRecordings
  ----------------------------------------------------------------------------
  基础款      ✓              ✓              (无)           (无)
  带云台      ✓              ✓              ✓                (无)
  加录像      ✓              ✓              ✓                ✓

  **「(无)」那几格我没有真发请求。**
  **一台只声明 Profile S 的设备,收到 G 的报文会怎样。**
  可能回 404、可能回 Fault、也可能直接断连,三种都可能。
  **规范只说「不保证支持」,没说「必须怎么拒」**,所以我没有编一个结果。

  **这张表在工程上的用法**:先 `GetServices` 拿 service 列表,再决定发哪些报文。
  **别一上来就 `GetRecordings`**——对一台没有录像能力的摄像机发它。
  轻则报错,重则让固件卡住重启。

三档里所有「已声明」的调用都是 ✓,没有一例 Fault。 这是我最想让你看到的:「声明了」和「真支持」在符合规范的设备上是一致的。 反过来说,如果你在真设备上遇到「声明了却调不通」,那这台设备的一致性测试有问题—— 这是可以拿来跟厂家交涉的硬证据(07.01.06 讲怎么取证)。 「(无)」那几格留空是刻意的: 我试过给本机模拟设备发未声明的操作,它会返回成功(因为它其实全实现了),但那个结果毫无意义—— 模拟器全支持,不代表真设备会怎么回。 所以我把那几格留空,而不是拿模拟器的结果冒充真设备的行为。

30 秒实验

  1. 把 LEVELS 里「带云台」的 ContinuousMove 删掉,看表格里哪一格变成「(无)」。
  2. 把某个已声明的操作名拼错(比如 GetStreamURI 大写),看模拟器回 Fault 还是成功。
  3. 给模拟设备加一个「不支持就断连」的开关,再调未声明的操作,看客户端报什么错。

常见误区

  1. 以为 Profile 就是能力清单。 它只规定「必做」, 云台和事件是可选的——要查 GetServices 才知道有没有。
  2. 以为调不通就是地址错。 404 也可能是「这个操作没实现」,别一头扎进 URL 里改。
  3. 以为可以先要流再查配置。 GetStreamUri 必须带 ProfileToken, 所以 GetProfiles 必须在前面。
自测三问(点开对答案) 1. 一台「支持 Profile S」的摄像机一定支持云台吗? —— **不一定。** Profile S 不要求一定有 PTZ,**要查 `GetServices` 里有没有 `ptz` 那一项。** 2. 设备拒绝一个操作,有哪三种方式?哪种最难查? —— **回 Fault、回 404、直接断连。断连最难查**,**因为什么信息都没有,只能二分定位。** 3. 为什么 `GetProfiles` 必须排在 `GetStreamUri` 前面? —— 因为 **`GetStreamUri` 必须带 `ProfileToken` 参数**, **这个 token 就是 `GetProfiles` 返回的。**

下一节讲「怎么验」:ONVIF 的一致性测试工具,以及没有一个真实摄像头时怎么自查。

相关笔记