📹 第八章 ONVIF框架代码案例应用
📄 一句话概括

SetVideoEncoderConfiguration 几乎总是返回成功,哪怕设备不支持你设的值。 它会把值悄悄夹到最接近的一档,所以唯一的成功判据是「回读」。

📝 本节和前面几节的关系

上一节是「读」,这一节是「写」——而 ONVIF 的写操作有一个共同的坑:返回值不代表结果。 这一节讲的四步流程(读→改→写→回读)适用于所有 Set* 操作,不只是分辨率。

一、先看设备支持哪些档

GetVideoEncoderConfigurationOptions 会告诉你「哪些值合法」: 分辨率列表、码率范围、帧率范围、GOP 范围。

字段 本机设备的值 说明
分辨率 640×480/1280×720/1920×1080 只有三档
码率范围 64 ~ 16384 kbps 超出会被夹
帧率范围 1 ~ 30 同上

第一行那个「只有三档」是关键: 设备并不支持「任意分辨率」——你设 3000×2000,它不会报错,只会换成最接近的那一档。 所以「先查选项」这一步不是可选的:不查就写,等于在赌设备会不会夹、夹到哪儿。 第二、三行是范围型的限制,同样的道理: 码率 99999 会被夹到 16384,帧率 99 会被夹到 30——而且都不报错。

二、静默夹边界有多坑

这一节的核心现象:设 3000×2000,返回「成功」,回读是 1920×1080。

你以为的 实际发生的 后果
设成 3000×2000 被换成 1920×1080 画质没提升也没报错
设成码率 99999 被换成 16384 带宽按预期算会错
设成帧率 99 被换成 30 流畅度没变

三行都是「成功」,三行都被改——而客户看到的只是「我改了啊,怎么没变化」。 更麻烦的是「按预期算会错」这一条:你按 99999 kbps 去算存储容量,结果实际只有 16384, 差了六倍—— 这种错误不会当场暴露,而是在「录像存了三个月就满了」的时候暴露。 所以「静默夹边界」这件事,在方案设计阶段就要防: 所有写操作之后都回读一遍,把实际值记下来,别用请求值去算。

三、四步流程缺一不可

步 做什么 缺了会怎样
1 GetVideoEncoderConfigurationOptions 不知道有哪些档,写进去靠赌
2 读现有配置再改 漏填的字段被重置成默认值
3 SetVideoEncoderConfiguration 没得写
4 回读确认 唯一能发现「被夹」的手段

第二步是「最容易被省、代价最隐蔽」的一步: 很多人直接新建一个空结构体,只填分辨率和码率就发过去—— 结果 GOP、H.264 profile、质量等级全被重置成默认值。 表现是「分辨率对了,但画质变了、带宽也变了」,而返回值一切正常。 正确做法是「读回来、改一个字段、再写回去」:这样没改的字段保持原值。 这个模式在所有 Set* 操作里都适用, 比如 SetImagingSettings(改曝光)、SetNetworkInterfaces(改 IP)。

四、改完还要注意什么

变化 会影响什么 要不要重启
分辨率 流地址通常不变,但客户端要重新协商 一般不用
码率 存储与带宽预算 不用
GOP 首帧延迟、seek 精度 不用

第一行那个「流地址通常不变」是有例外的:有的设备改分辨率之后 Profile 会重建, 于是 ProfileToken 变了——这时候你手里的旧 token 就失效了。 所以改完配置之后,如果还要取流,最好重新 GetProfiles 一次,别用改之前缓存的 token。 第三行那个「GOP 影响首帧延迟」和上一节呼应:GOP 越长,等关键帧越久; 所以「首帧慢」的调优手段之一就是把 GOP 调短。 但 GOP 短了码率会涨——这是个取舍,不是单方面优化。

五、动手 Demo:改分辨率必须回读

Demo 先查支持的档,再设一个不支持的、一个支持的,每次都回读。 重点看第二行:返回「成功」,回读却是 1920×1080。

# ① 先查有哪些档
_a, b, _n = S.call(port, "media", "GetVideoEncoderConfigurationOptions")
avail = re.findall(r"<tt:Width>(\d+)</tt:Width><tt:Height>(\d+)</tt:Height>", b)

# ② 设一个设备不支持的尺寸
write(3000, 2000, 2048, 30)

# ③ 回读——这才是唯一的判据
print(read())        # (1920, 1080, 2048, 30)

实跑输出:

--------------------------------------------------------------------
改分辨率必须回读
--------------------------------------------------------------------
**`SetVideoEncoderConfiguration` 是整个 ONVIF 里最容易误判成功的操作。**
**因为它几乎总是返回成功**——哪怕设备根本不支持你设的值。

**先看设备支持哪些分辨率**(`GetVideoEncoderConfigurationOptions`):

640×480、1280×720、1920×1080

  动作                            回读分辨率     回读码率       返回
  -----------------------------------------------------------------------
  读现状                          1920×1080      4096 kbps      —
  **设 3000×2000**                1920×1080      2048 kbps      **成功**
  **设 1280×720、码率 99999**     1280×720       16384 kbps     **成功**

**第二行是这一节的核心**:我把 3000×2000 发过去,
**设备返回「成功」,但回读是 1920×1080。**
**它没报错,而是悄悄换成了最接近的一档。**
**这叫「静默夹边界」**,是安防设备很常见的实现方式。
**后果是:任何只看返回值不看回读的代码,都会以为设置成功了。**

**第三行同理**:码率 99999 被夹到 16384 kbps,帧率 99 被夹到 30。
**都是「成功」但实际被改。**

**所以正确流程一定是四步**:

  步     做什么                                      为什么
  --------------------------------------------------------------------------------
  1      `GetVideoEncoderConfigurationOptions`       **先知道有哪些档**
  2      从现有配置改(**不要新建空结构体**)        漏字段会被填默认值
  3      `SetVideoEncoderConfiguration`              **返回值只能说明「收到了」**
  4      **回读** `GetVideoEncoderConfiguration`     **唯一的成功判据**

**第二行那个「改现有配置」最容易被省掉**,代价很隐蔽:
**只填了分辨率,其它字段(码率、GOP、H.264 profile)会变成默认值。**
**结果是「分辨率对了,但画质变了、码流也变了」**——
**用户只会说「你没改好」,而你看返回值一切正常。**

第三段那个回读结果是这一节的全部证据: 我设了 3000×2000,回读是 1920×1080——而 write() 的返回值是「成功」。 注意码率 2048 和帧率 30 是被接受的, 只有分辨率被夹——说明「夹边界」是逐字段判断的,不是整条配置一起拒。 第二次设 1280×720、码率 99999 时,分辨率被接受、码率被夹到 16384——同样是逐字段的。 所以「回读之后要逐字段比对」,不能只比一个字段就以为全对了。

30 秒实验

  1. 把 write() 改成只填分辨率、其它字段留空,看回读时 GOP 变成多少。
  2. 设一个「介于两档之间」的尺寸(比如 1600×1200),看被夹到哪一档。
  3. 连续设两次不同的分辨率,看第二次的返回值有什么不同。

常见误区

  1. 看 Set* 的返回值判断成功。 它几乎总是返回成功,不支持的值会被静默夹到最接近的一档。
  2. 新建空结构体只填要改的字段。 没填的字段会被重置成默认值, 症状是「分辨率对了但画质变了」。
  3. 用请求值去算存储容量。 实际值可能被夹到六分之一, 错误要到「录像存了三个月就满」时才暴露。
自测三问(点开对答案) 1. 设 3000×2000 为什么没报错? —— **设备会静默夹到最接近的支持档位**(这里是 1920×1080),**并且返回成功**。只有回读才能发现。 2. 为什么「读现有配置再改」这一步不能省? —— **不填的字段会被重置成默认值**,结果是「分辨率对了、画质和带宽都变了」。 3. 改完分辨率之后,缓存的 `ProfileToken` 还能用吗? —— **不一定。有的设备改配置会重建 Profile,token 就变了。**要取流就重新 `GetProfiles` 一次。

下一节进 08.08:同样的流程换到 Linux 上做,差别只有三处。

相关笔记