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 秒实验
- 把
Stream改成RTP-Multicast,看模拟设备是返回组播地址还是回 Fault。 - 把
ProfileToken换成不存在的 token,看 Fault 的Code是Sender还是Receiver。 - 用
urlparse拆一个真实的海康地址(/Streaming/Channels/101),看 path 长什么样。
常见误区
- 以为
Protocol=HTTP就是用 HTTP 传视频。 它是用 HTTP 隧道把 RTSP 包起来, 媒体还是 RTP。 - 以为自己拼 RTSP 地址更快。 路径没有标准,每家都不一样,必须问设备要。
- 以为拿到 URL 就能播。 ONVIF 只管第 0 步,后面还有 RTSP 握手、RTP 拼帧、解码三步。
自测三问(点开对答案)
1. `GetStreamUri` 的 `StreamSetup` 决定什么? —— **决定「要什么样的流」**:单播还是组播、信令走 RTSP 还是 HTTP 隧道。 2. `Timeout=PT0S` 是什么意思? —— **ISO 8601 的零时长,表示这个 URL 不过期**,可以放心缓存。 3. URL 的四段里,哪一段没有标准? —— **path**。scheme 固定是 `rtsp`,554 是默认端口,**但路径由厂家自定,必须问设备要。**下一节讲事件处理:为什么不该轮询,而该订阅;以及订阅会过期这件事有多容易被忘。
- ☐ (本节完,下一节 07.02.11 事件处理)