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

改图像参数失败,多半不是权限问题,是「值超范围」——而设备对超范围的处理并不一致。 正确顺序永远是三步:读当前值、读允许范围、用代码判断合法性再写。

📝 本节和前面几节的关系

上一节是「设备身上有什么接口」,这一节开始进「参数怎么改」。 图像配置是 imaging service 的地盘,注意它是 ver20(07.01.03 那张表里的例外之一)。

一、先读范围,再改值

GetImagingSettings 给当前值,GetOptions 给允许范围,两个响应要对着看:

参数 当前值 最小值 最大值 合法吗
Brightness 50 -100 100 是
Contrast 50 — — —
Sharpness 50 0 100 是
ColorSaturation 50 — — —

最后那一列是用代码算的:float(lo) <= float(cur) <= float(hi),一行判断。 这个检查必须做在前面,因为 ONVIF 对超范围的处理不一致: 有的设备回 Fault,有的静默夹到边界值,还有的直接重启。 「静默夹到边界」最坑——你设了 200,设备接受 200 但实际用 100, 回读还是 100,你以为成功了。 所以改完必须回读一次,不能拿「没报错」当成功。 第二、四行是 —,因为这台设备的 GetOptions 里没给这两个参数的范围—— 没给范围不等于没有范围,只说明「设备没告诉你」,这时候只能试、然后再回读。

二、曝光三个字段,单位各不相同

字段 本例的值 单位
Mode AUTO AUTO 时下面两个由设备定
ExposureTime 16 微秒(16 就是万分之 1.6 秒)
Gain 6 dB(增益,大了噪点多)

第二行那个单位是「微秒」,不是「毫秒」也不是「秒」—— 16 微秒在白天是正常的,但要是在夜里报 16 微秒,画面肯定黑。 所以「曝光时间」这个数要结合光线看:夜里的正常值是几万微秒(几十毫秒)。 第三行的增益有个取舍:曝光长了会拖影(运动模糊),增益大了会噪点。 自动曝光就是在两者之间找平衡,而平衡点取决于「你要清晰还是要流畅」。 这三个字段里,只有 Mode 是安全可改的: 手动改 ExposureTime 和 Gain 要在 Mode=MANUAL 下才有意义, AUTO 下改了也会被设备覆盖。很多人「改了没反应」就是这个原因,不是设备不听话。

三、Set 是整块回写

这条规则和下一节的媒体配置完全一样,所以在这里就要建立起来:

做法 后果 对不对
只传要改的字段 没传的被当成清空或重置 错
整块回写 没动的字段原样带回去 对

第一行的后果比想象中严重: 有的设备会把没传的字段重置成出厂值——于是你只想改个亮度,结果分辨率也变了。 这种故障最难查,因为「改了 A 结果 B 变了」不符合直觉。 正确做法是三步:① 读完整块;② 只改目标字段;③ 整块写回去。 这个「读-改-写」模式在 ONVIF 里到处都是(媒体、网络、OSD 都是这个套路), 它不优雅,但有一个好处:不会误删你没注意到的字段。

四、为什么 imaging 是 ver20

imaging 是少数几个 ver20 的 service,和 ptz 一样:

service 版本 说明
device ver10 从第一版就没大改过
media ver10 接口稳定
imaging ver20 2.0 版有不兼容改动
ptz ver20 两套并存,老设备只认 ver10

第三行的改动主要在「聚焦」和「曝光」的参数结构上,老客户端的解析代码在新设备上会拿不到值。 所以接设备时要看 GetServices 里报的是哪个版本,不能按「都是 ver10」的假设写死。 第四行更麻烦:ptz 的两套是并存而不是替换, 所以代码里得准备两个命名空间常量,按 GetServices 的结果选一个用(07.02.12 会用到)。

五、动手 Demo:图像配置,先查范围再改值

Demo 先读当前值,再读允许范围,然后逐项判断「当前值合法吗」。 最后改一个参数再回读,验证「设备是不是真按你说的改了」。

_a, b, _n = S.call(port, "imaging", "GetImagingSettings",
                   "<timg:GetImagingSettings>"
                   "<timg:VideoSourceToken>VideoSource_1"
                   "</timg:VideoSourceToken></timg:GetImagingSettings>")
r = ET.fromstring(b)
cur = {}
for k in ("Brightness", "Contrast", "Sharpness", "ColorSaturation"):
    e = r.find(".//{%s}%s" % (S.NS_TT, k))
    cur[k] = e.text if e is not None else "-"

_a, b2, _n = S.call(port, "imaging", "GetOptions",
                   "<timg:GetOptions><timg:VideoSourceToken>VideoSource_1"
                   "</timg:VideoSourceToken></timg:GetOptions>")
r2 = ET.fromstring(b2)
for k, v in cur.items():
    lo = r2.find(".//{%s}%s/{%s}Min" % (S.NS_TT, k, S.NS_TT))
    hi = r2.find(".//{%s}%s/{%s}Max" % (S.NS_TT, k, S.NS_TT))
    ok = "-"
    if lo is not None and hi is not None and v != "-":
        ok = "是" if float(lo.text) <= float(v) <= float(hi.text) else "否"
    print(k, v, lo.text if lo is not None else "-",
          hi.text if hi is not None else "-", ok)

实跑输出:

--------------------------------------------------------------------
图像配置,先查范围再改值
--------------------------------------------------------------------
**很多人改图像参数失败,不是权限问题,是「值超范围」。**
  这一节先读当前值,再读允许范围,**用代码判断合法性**。

**当前值和允许范围对照:**

  参数              当前值      最小值      最大值      合法吗
  ------------------------------------------------------------------
  Brightness        50          -100        100         **是**
  Contrast          50          —           —           —
  Sharpness         50          0           100         **是**
  ColorSaturation   50          —           —           —

  **最后那一列是用代码算的**:  **`float(lo) <= float(cur) <= float(hi)`,一行判断。**
  **这个检查必须做在前面**,  **因为 ONVIF 对超范围的处理不一致**:
  **有的设备回 Fault,有的设备静默夹到边界值,还有的直接重启。**
  **「静默夹到边界」最坑**——
  **你设了 200,设备接受 200 但实际用 100,回读还是 100,你以为成功了。**

**曝光那两个字段最容易搞混:**

  字段                值                  单位
  --------------------------------------------------------------------------
  `Mode`              AUTO                **`AUTO` 时下面两个由设备自己定**
  `ExposureTime`      16                  **微秒**(16 就是 1/62500 秒)
  `Gain`              6                   **dB**(增益,大了噪点多)

  **第二行那个单位是「微秒」,不是「毫秒」也不是「秒」**——
  **16 微秒在白天是正常的,但要是在夜里报 16 微秒,画面肯定黑。**
  **所以「曝光时间」这个数要结合光线看**:  **夜里的正常值是几万微秒(几十毫秒)。**
  **第三行的增益有个取舍**:
  **曝光长了会拖影(运动模糊),增益大了会噪点。**
  **自动曝光就是在两者之间找平衡,而平衡点取决于「你要清晰还是要流畅」。**

这台设备的 GetOptions 只给了 Brightness 和 Sharpness 的范围,另外两个返回 —, 所以「合法吗」那一列也是 —,不是「否」。 「没数据」和「不合法」必须分开写——糊在一起就会得出「当前值非法」的错误结论。 判断用的是链式比较 lo <= v <= hi,这是 Python 的特性,等价于 lo <= v and v <= hi, 但它只求值一次 v,所以在有副作用时更安全。 换成 C 就要写两个 &&。 最后一个「改完回读」我没做——因为模拟设备的 SetImagingSettings 是「记账式」的, 写完回读一定能读回来,这个验证没有信息量。 真设备上这一步必须做,因为「静默夹到边界」这个行为只有回读才看得见。

30 秒实验

  1. 把 Brightness 的当前值改成 200,看「合法吗」那一列变成 否。
  2. 把 float(lo.text) <= float(v) <= float(hi.text) 拆成两句比较,看结果是否一致。
  3. 打印整个 GetOptions 的响应,数一数它到底给了几个参数的范围。

常见误区

  1. 以为改参数失败是权限问题。 多半是值超范围,而且设备对超范围的处理不一致, 有的静默夹边界。
  2. 以为 ExposureTime 的单位是毫秒。 是微秒,白天 16 微秒正常,夜里要几万微秒。
  3. 只传要改的字段去 Set。 没传的字段可能被重置,必须整块回写。
自测三问(点开对答案) 1. 改图像参数的正确顺序是什么? —— **读当前值 → 读允许范围 → 判断合法性 → 整块写回 → 回读验证。** **少了回读这一步,就发现不了「静默夹边界」。** 2. `AUTO` 模式下改 `ExposureTime` 有意义吗? —— **没有。** `AUTO` 时曝光由设备自己定,**改了会被覆盖**,要手动必须先切 `MANUAL`。 3. 为什么 `imaging` 是 `ver20` 而 `media` 是 `ver10`? —— 因为 **2.0 版对聚焦和曝光的参数结构做了不兼容改动**,**只能开新命名空间**; `media` 一直没大改,所以还是 `ver10`。

下一节讲媒体配置:一个 Profile 里到底套了几层,以及「改分辨率」为什么不是改 URL。

相关笔记