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

GetRecordings 返回的 MaximumRetentionTime 是 ISO 8601 的时长, 不是「能存几天」这个数字。 而它是「上限」不是「实际」——硬盘满了最老的会被覆盖,实际可能只存一天。

📝 本节和前面几节的关系

上一节讲「拉流」, 这一节讲「存下来」——这是 07.02 的最后一节,也是整个 ONVIF 功能清单的最后一组。 容量算法在第五章讲过,这里把它和 ONVIF 的字段对上。

一、PT259200S 是什么

拆开看:P 是 Period,T 是 Time 部分的开始,259200 是数值,S 是秒。 259200 秒 = 72 小时 = 3 天。

写法 含义 注意
PT0S 不限(或没设) 别当成「不能存」
PT86400S 一天 —
P1D 一天(另一种写法) ISO 8601 允许 D 和 S

第二行那个单位要看清:单位是秒,不是分钟也不是小时。 所以「三天」要写成 259200,不能写 3——写 3 就是「3 秒」,设备可能照收,然后真的只存 3 秒。 第一行最容易误判:PT0S 在很多设备上表示「没配置上限」,不是「零秒」。 第三行说明解析不能只认 S:P1DT12H 这种混合写法也是合法的。 自己写解析器很容易漏,最好用现成的库。

二、上限不是实际

这是本节最该记住的一句:MaximumRetentionTime 是能力的上限声明,不是运行结果。

字段说的是 实际可能 为什么
最多保留三天 只存了一天 硬盘满了会覆盖最老的
最多保留三天 存了五天 不会,超了必须删

第一行是常态: 48 路 1080p 的 NVR,标称「保留 30 天」,实际往往只跑得到 7 天—— 因为标称是按「半载」算的,实际码率比标称高。 第二行不会发生:它是上限,设备一定会按上限删, 所以「存了五天」说明你看到的不是这个字段,而是别的配置。 这个「上限一定会被执行、下限不会」的不对称,是理解这个字段的关键。

三、把保留时长和容量对一遍

码率 一路一天 三天一路 10 路三天
2048 kbps 22.1 GB 66.4 GB 663.6 GB
4096 kbps 44.2 GB 132.7 GB 1327.1 GB
8192 kbps 88.5 GB 265.4 GB 2654.2 GB

第三列那个「三天一路」就是 MaximumRetentionTime 承诺的量级。 第四列是 10 路的量——1080p 10 路存三天要 1.3 TB,这已经是一块硬盘的量了。 所以「保留三天」在小项目里轻松,在几十路的项目里就是几块盘的事, 而 MaximumRetentionTime 照样写 PT259200S——它只是能力的上限声明。 这也是报价时最容易出错的地方:按「一路三天」算出 66 GB,觉得很便宜; 乘上 48 路就变成 3.2 TB,要加盘了。 口径一定要先定清楚:几路、几天、什么码率。

四、存储相关的三组接口

service 管什么 典型操作
recording 存:录像任务怎么建 GetRecordings 列任务
search 查:哪些时段有录像 FindRecordings 按时间查
replay 放:按时间回放 GetReplayUri 拿地址

三行是三个独立的 service,很多设备只实现前两个,replay 没有。 没有 replay 时的替代方案是: 用 search 查到录像的 RTSP 地址,直接当普通流播—— 代价是不能精确定位到秒,只能从片段开头播。 这个妥协在实际项目里很常见,因为 replay 对设备的存储结构要求更高(要能按时间索引到帧)。 所以「能不能回放」要问三件事:有 replay 吗、search 给的是什么地址、精度到秒还是到片段。

五、动手 Demo:存储,保留时间是个「时长」不是「天数」

Demo 读一次录像任务,把 MaximumRetentionTime 换算成天数。 再把码率换算成容量,和保留时长对一遍。 最后验一件事:解析器只认 PT…S,遇到 P1D 会怎样。

_a, b, _n = S.call(port, "recording", "GetRecordings")
r = ET.fromstring(b)
it = r.find(".//{%s}RecordingItem" % S.NS_TRC)
mrt = it.find(".//{%s}MaximumRetentionTime" % S.NS_TT).text
print(it.get("RecordingToken"), mrt)          # Recording_1 PT259200S

# 只处理 PT…S 这种写法
sec = int(mrt[2:-1])
print(sec, "秒 =", sec / 86400.0, "天")

# 码率 -> 容量(和第五章一个公式)
for kbps in (2048, 4096, 8192):
    gb = kbps / 8.0 * 86400 / 1e6
    print(kbps, "%.1f GB" % gb, "%.1f GB" % (gb * 3))

# 非 PT…S 的写法会怎样
print(int("P1D"[2:-1]))

实跑输出:

--------------------------------------------------------------------
存储,保留时间是个「时长」不是「天数」
--------------------------------------------------------------------
**`GetRecordings` 返回的 `MaximumRetentionTime` 是 ISO 8601 的时长**,
  **不是「能存几天」这个数字。**  这一节把它换算出来,再和容量对一遍。

**`GetRecordings`:这台设备上有哪些录像任务**

  Token             存储位置              保留时长
  ----------------------------------------------------------------
  Recording_1       `/mnt/sd`             `PT259200S`

**`PT259200S` 是什么:**

  **拆开看**:`P` = Period,`T` = Time 部分,`259200` = 数值,`S` = 秒。
  **259200 秒 = 72 小时 = 3 天**

  **注意它不是「能存三天」这个结论**——
  **它是「最多保留三天」,实际存多久取决于容量和码率。**
  **如果硬盘满了,最老的会被覆盖,实际可能只存一天。**
  **所以这个字段是「上限」,不是「实际」**,  **混用会导致「明明说三天怎么只剩一天了」的误会。**

**把保留时长和容量对一遍(用第四、五章的算法):**

  码率            一路一天        三天一路          10 路三天
  ----------------------------------------------------------------------
  2048 kbps       22.1 GB         66.4 GB           663.6 GB
  4096 kbps       44.2 GB         132.7 GB          1327.1 GB
  8192 kbps       88.5 GB         265.4 GB          2654.2 GB

  **第三列那个「三天一路」就是 `MaximumRetentionTime` 承诺的量级。**
  **第四列是 10 路的量**——  **1080p 10 路存三天要 1.3 TB,这已经是一块硬盘的量了。**
  **所以「保留三天」在小项目里轻松,在几十路的项目里就是几块盘的事**,
  **而 `MaximumRetentionTime` 照样写 `PT259200S`**——  **它只是能力的上限声明。**

**存储相关的三组接口,别搞混:**

  service           管什么                    典型操作
  ------------------------------------------------------------------------------
  **`recording`**   **存**:录像任务怎么建    `GetRecordings` 列任务
  **`search`**      **查**:哪些时段有录像    `FindRecordings` 按时间查
  **`replay`**      **放**:按时间回放        `GetReplayUri` 拿地址

  **三行是三个独立的 service**,  **很多设备只实现前两个,`replay` 没有。**
  **没有 `replay` 时的替代方案是**:
  **用 `search` 查到录像的 RTSP 地址,直接当普通流播**——
  **代价是不能精确定位到秒,只能从片段开头播。**
  **这个妥协在实际项目里很常见**,  **因为 `replay` 对设备的存储结构要求更高。**

**`MaximumRetentionTime` 的三种写法,都可能在真设备上见到:**

  写法                  含义                        注意
  --------------------------------------------------------------------------------
  `PT0S`                不限(或没设)              **别当成「不能存」**
  `PT86400S`            一天                        —
  `P1D`                 一天(另一种写法)          **ISO 8601 允许 `D` 和 `S`**

  **第一行最容易误判**:  **`PT0S` 在很多设备上表示「没配置上限」,不是「零秒」。**
  **第三行说明解析不能只认 `S`**:
  **`P1DT12H` 这种混合写法也是合法的。**  **自己写解析器很容易漏,最好用现成的库**
  (Python 可以用 `isodate`,但本机没装,所以我这里只处理了 `PT…S` 这一种)。

mrt[2:-1] 这个切法是「只认 PT…S」的偷懒写法: 去掉 PT 两个字符和末尾的 S,剩下的转整数。 它对 PT259200S 有效,对 P1D 会切出 1,对 P1DT12H 会直接报错。 最后一行就是让它报错给你看——这个 ValueError 是本节的证据:解析器不能只认一种写法。 正确做法是用现成的库(Python 里是 isodate),但本机没装, 所以这一节只处理 PT…S,并且把限制写在正文里。 「我知道它不全,所以我只承诺它处理的那一种」—— 这比写一个「看起来什么都能解」的半吊子解析器诚实得多。 码率那三行和第五章的公式是同一个:kbps ÷ 8 × 86400 ÷ 10⁶ = GB/天。 ×3 是三天。 这里没有「实测」,全是算术——我没有硬盘也没有录像文件,编不出实测数据。

30 秒实验

  1. 把 mrt 换成 PT86400S,看算出来是几天。
  2. 把 mrt 换成 P1D,看那个 ValueError 的具体信息是什么。
  3. 按「16 路 4096 kbps 存 30 天」算总容量,和第五章的 20.7 TB 比。

常见误区

  1. 以为 PT259200S 就是「能存三天」。 它是上限,硬盘满了实际只存一天。
  2. 以为单位是天。 单位是秒,「三天」要写 259200,写 3 就是三秒。
  3. 只写能解析 PT…S 的解析器就上线。 P1D、P1DT12H 都是合法写法, 必须用现成的 ISO 8601 时长库。
自测三问(点开对答案) 1. `PT259200S` 是什么意思? —— **ISO 8601 时长:259200 秒 = 72 小时 = 3 天。** **`P` 是 Period,`T` 是 Time 部分,`S` 是秒。** 2. `MaximumRetentionTime` 是「上限」还是「实际」? —— **上限。** 实际存多久取决于容量和码率,**硬盘满了会覆盖最老的。** 3. 存储相关有哪三组 service?哪一组常缺? —— **`recording`(存)、`search`(查)、`replay`(放)。** **`replay` 常缺**,替代方案是用 `search` 拿地址当普通流播。

07.02 这十七节把 ONVIF 的功能清单走完了。下一章开始动手写代码: 用 gSOAP 生成的框架搭一个真正的 ONVIF 客户端,把这一章里「能跑」的那些操作串起来。

相关笔记