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

GetStreamUri 是 ONVIF 的终点站:前面查了半天配置,就是为了拿这一个字符串。 拿到之后就不用再问 ONVIF 了——后面全是 RTSP 和 FFmpeg 的事。

📝 本节和前面几节的关系

上一节把 Profile 的配置看清了,这一节把它换成地址。 这一节之后,本课的重心就从 ONVIF 转回第一章的 RTSP——两条线索在这里交汇。

一、请求里那个 StreamSetup

GetStreamUri 不只收 ProfileToken,还收一个 StreamSetup,它决定「要什么样的流」:

字段 取值 含义
Stream RTP-Unicast 单播,一对一
Stream RTP-Multicast 组播,一对多(省带宽)
Transport/Protocol RTSP 标准做法
Transport/Protocol HTTP 穿防火墙用的

第一行是默认值,绝大多数场景用它。 第二行只有在「一台上百人看同一路」时才划算——组播不复制流,但要求整条链路都支持组播, 跨网段、跨运营商基本走不通。 第四行那个 HTTP 最容易误解:它不是「用 HTTP 传视频」,是「用 HTTP 隧道把 RTSP 包起来」, 媒体还是 RTP。 所以看到 rtsp:// 开头的地址不代表走 UDP—— 协议头写的是信令,媒体走什么要看 Transport 参数和设备能力(07.02.03 那三个开关)。

二、回来的 MediaUri 有四个字段

字段 本例的值 含义
Uri rtsp://127.0.0.1:554/onvif/profile1 RTSP 地址
InvalidAfterConnect false 用一次就失效吗
InvalidAfterReboot false 重启后失效吗
Timeout PT0S PT0S 表示不过期

第二行是最容易忽略的一个: 为 true 时,这个 URL 只能用一次——你连一次、断了,再连必须重新调 GetStreamUri。 有些设备为了「防串流」会这么设,结果客户端重连时拿到 401,查半天以为是密码问题。 第四行那个 PT0S 是 ISO 8601 的「零时长」,意思是「URL 不过期」。 看到 PT0S 就该放心缓存它, 看到 PT3600S 就该每小时重新要一次—— 这个字段在长连接项目里很关键,漏了它会在运行一小时后突然断流。

三、把 URL 拆开看

段 本例的值 说明
scheme rtsp 固定 rtsp
host 127.0.0.1 设备地址
port 554 554 是 RTSP 默认端口
path /onvif/profile1 设备自己定义的路径

第四行是唯一「没有标准」的一段:/onvif/profile1 是我这台模拟设备自己定的, 换个厂家可能是 /live/ch01、/Streaming/Channels/101。 所以「拼 RTSP 地址」这件事永远不要自己做——必须调 GetStreamUri 让设备告诉你。 这一条和 07.01.03 讲的「不要硬编码地址」是同一个道理,但更硬: service 地址还能靠 GetServices 推出来,RTSP 路径连规律都没有,只能问。 海康的 /Streaming/Channels/101 这种写法是厂家的约定,不是标准, 所以「按海康的规则拼大华的地址」一定失败。

四、从地址到画面,还剩三步

ONVIF 的活到这里就干完了,后面全不归它管:

步 做什么 在哪一册
0 要到一个 URL 本课第七章
1 RTSP 握手(DESCRIBE/SETUP/PLAY) 第 01 章
2 收 RTP 包、重排序、拼帧 第 01 章
3 解 H.264、渲染 26 课/32 课

第 0 步是 ONVIF 的全部职责, 所以「ONVIF 通了但没画面」是常见的——前三步任何一步出问题,都和 ONVIF 无关。 排查时先 ffplay <那个 URL>——能出画面就说明 ONVIF 的活干完了,问题在你的代码; 不能出画面就说明是流的问题,回头查第一章。 这一条能把「ONVIF 问题」和「流问题」一次切开,是本课最省时间的一句诊断口诀。

五、动手 Demo:要一路流,其实只要一个 URL

Demo 带两种 StreamSetup 各要一次流,对比拿回来的地址。 然后把地址拆成四段,看哪一段是「没有标准」的。

def uri_req(proto, stream="RTP-Unicast"):
    return ("<trt:GetStreamUri><trt:StreamSetup>"
            "<tt:Stream>%s</tt:Stream>"
            "<tt:Transport><tt:Protocol>%s</tt:Protocol>"
            "</tt:Transport></trt:StreamSetup>"
            "<trt:ProfileToken>Profile_1</trt:ProfileToken>"
            "</trt:GetStreamUri>") % (stream, proto)

for proto in ("RTSP", "HTTP"):
    _a, b, _n = S.call(port, "media", "GetStreamUri", uri_req(proto))
    mi = ET.fromstring(b).find(".//{%s}MediaUri" % S.NS_TT)
    print(proto, mi.find("{%s}Uri" % S.NS_TT).text,
          mi.find("{%s}Timeout" % S.NS_TT).text)

# 拆 URL 四段
from urllib.parse import urlparse
o = urlparse("rtsp://127.0.0.1:554/onvif/profile1")
print(o.scheme, o.hostname, o.port, o.path)

实跑输出:

--------------------------------------------------------------------
要一路流,其实只要一个 URL
--------------------------------------------------------------------
**`GetStreamUri` 是 ONVIF 的终点站**:前面查了半天配置,就是为了拿这一个字符串。
  **拿到之后就不用再问 ONVIF 了**——后面全是 RTSP 和 FFmpeg 的事。

**请求里那个 `StreamSetup` 决定「要什么样的流」:**

  字段                  取值                      含义
  ----------------------------------------------------------------------------------
  `Stream`              `RTP-Unicast`             **单播,一对一**
  `Stream`              `RTP-Multicast`           组播,一对多(省带宽)
  `Transport/Protocol`  `RTSP`                    **标准做法**
  `Transport/Protocol`  `HTTP`                    穿防火墙用的

  **第一行是默认值,绝大多数场景用它。**
  **第二行只有在「一台上百人看同一路」时才划算**——  **组播不复制流,但要求整条链路都支持组播**,
  **跨网段、跨运营商基本走不通。**
  **第三行那个 `HTTP` 容易误解**:
  **它不是「用 HTTP 传视频」,是「用 HTTP 隧道把 RTSP 包起来」**,  **媒体还是 RTP。**

**真跑,拿回来的 `MediaUri` 有四个字段:**

  字段                    值                                      含义
  ------------------------------------------------------------------------------------------------
  `Uri`                   `rtsp://127.0.0.1:554/onvif/profile1`   **RTSP 地址**
  `InvalidAfterConnect`   false                                   **用一次就失效吗**
  `InvalidAfterReboot`    false                                   重启后失效吗
  `Timeout`               PT0S                                    **`PT0S` 表示不过期**

  **第二行是最容易忽略的一个**:
  **为 `true` 时,这个 URL 只能用一次**——
  **你连一次、断了,再连必须重新调 `GetStreamUri`。**
  **有些设备为了「防串流」会这么设**,  **结果客户端重连时拿到 401,查半天以为是密码问题。**
  **第四行那个 `PT0S` 是 ISO 8601 的「零时长」**,
  **意思是「URL 不过期」。**  **看到 `PT0S` 就该放心缓存它。**

**把 URL 拆开看,每一段都有含义:**

  段        本例的值                      说明
  --------------------------------------------------------------------------
  scheme    `rtsp`                        **固定 `rtsp`**
  host      `127.0.0.1`                   设备地址
  port      `554`                         **554 是 RTSP 默认端口**
  path      `/onvif/profile1`             **设备自己定义的路径**

  **第四行是唯一「没有标准」的一段**:
  **`/onvif/profile1` 是我这台模拟设备自己定的**,
  **换个厂家可能是 `/live/ch01`、`/Streaming/Channels/101`。**
  **所以「拼 RTSP 地址」这件事永远不要自己做**——
  **必须调 `GetStreamUri` 让设备告诉你。**
  **这一条和第 07.01.03 讲的「不要硬编码地址」是同一个道理。**

**从拿到 URL 到看见画面,还剩三步(都在第一章和 26 课):**

  步    做什么                                用什么
  --------------------------------------------------------------------------
  1     RTSP 握手(DESCRIBE/SETUP/PLAY)    **第 01 章**
  2     收 RTP 包、重排序、拼帧               **第 01 章**
  3     解 H.264、渲染                        **26 课/32 课**

  **ONVIF 只管第 0 步——「告诉你地址」。**
  **所以「ONVIF 通了但没画面」是常见的**:
  **前三步任何一步出问题,都和 ONVIF 无关。**
  **排查时先 `ffplay <那个 URL>`**——  **能出画面就说明 ONVIF 的活干完了,问题在你的代码;**
  **不能出画面就说明是流的问题,回头查第一章。**

两种 Protocol 拿回来的地址一模一样,这也是本节最值得注意的一点: Protocol=HTTP 并不改变返回的 rtsp:// 前缀, 它只影响「怎么把 RTSP 包起来」,而那个决定在媒体传输阶段才生效。 所以「换协议看地址变化」这个验证方法是无效的——地址不变不代表参数被忽略了。 Timeout 两次都是 PT0S,说明这台设备的 URL 不过期、可缓存。 真设备上这一列常出现 PT3600S 这种值,那就要在到点前重新要一次。 最后用 urlparse 拆地址,只是为了让「四段」这件事看得见, 不是必须这么写——实际代码里通常直接把整个 URL 交给 FFmpeg/live555,根本不拆。 拆开是为了理解,不是为了使用。

30 秒实验

  1. 把 Stream 改成 RTP-Multicast,看模拟设备是返回组播地址还是回 Fault。
  2. 把 ProfileToken 换成不存在的 token,看 Fault 的 Code 是 Sender 还是 Receiver。
  3. 用 urlparse 拆一个真实的海康地址(/Streaming/Channels/101),看 path 长什么样。

常见误区

  1. 以为 Protocol=HTTP 就是用 HTTP 传视频。 它是用 HTTP 隧道把 RTSP 包起来, 媒体还是 RTP。
  2. 以为自己拼 RTSP 地址更快。 路径没有标准,每家都不一样,必须问设备要。
  3. 以为拿到 URL 就能播。 ONVIF 只管第 0 步,后面还有 RTSP 握手、RTP 拼帧、解码三步。
自测三问(点开对答案) 1. `GetStreamUri` 的 `StreamSetup` 决定什么? —— **决定「要什么样的流」**:单播还是组播、信令走 RTSP 还是 HTTP 隧道。 2. `Timeout=PT0S` 是什么意思? —— **ISO 8601 的零时长,表示这个 URL 不过期**,可以放心缓存。 3. URL 的四段里,哪一段没有标准? —— **path**。scheme 固定是 `rtsp`,554 是默认端口,**但路径由厂家自定,必须问设备要。**

下一节讲事件处理:为什么不该轮询,而该订阅;以及订阅会过期这件事有多容易被忘。

相关笔记