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 秒实验
- 把
LEVELS里「带云台」的ContinuousMove删掉,看表格里哪一格变成「(无)」。 - 把某个已声明的操作名拼错(比如
GetStreamURI大写),看模拟器回 Fault 还是成功。 - 给模拟设备加一个「不支持就断连」的开关,再调未声明的操作,看客户端报什么错。
常见误区
- 以为 Profile 就是能力清单。 它只规定「必做」,
云台和事件是可选的——要查
GetServices才知道有没有。 - 以为调不通就是地址错。 404 也可能是「这个操作没实现」,别一头扎进 URL 里改。
- 以为可以先要流再查配置。
GetStreamUri必须带ProfileToken, 所以GetProfiles必须在前面。
自测三问(点开对答案)
1. 一台「支持 Profile S」的摄像机一定支持云台吗? —— **不一定。** Profile S 不要求一定有 PTZ,**要查 `GetServices` 里有没有 `ptz` 那一项。** 2. 设备拒绝一个操作,有哪三种方式?哪种最难查? —— **回 Fault、回 404、直接断连。断连最难查**,**因为什么信息都没有,只能二分定位。** 3. 为什么 `GetProfiles` 必须排在 `GetStreamUri` 前面? —— 因为 **`GetStreamUri` 必须带 `ProfileToken` 参数**, **这个 token 就是 `GetProfiles` 返回的。**下一节讲「怎么验」:ONVIF 的一致性测试工具,以及没有一个真实摄像头时怎么自查。
- ☐ (本节完,下一节 07.01.06 ONVIF测试工具)