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 秒实验
- 把
mrt换成PT86400S,看算出来是几天。 - 把
mrt换成P1D,看那个ValueError的具体信息是什么。 - 按「16 路 4096 kbps 存 30 天」算总容量,和第五章的 20.7 TB 比。
常见误区
- 以为
PT259200S就是「能存三天」。 它是上限,硬盘满了实际只存一天。 - 以为单位是天。 单位是秒,「三天」要写 259200,写 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 客户端,把这一章里「能跑」的那些操作串起来。
- ☐ (本章完,下一节 08.01.01 下载WSDL文件)