连 IPC 就是用 av.open("rtsp://…"),但报错有三类,处理完全不同。
第一条军规:永远给 timeout——不给它可能挂几十秒。
前面章节处理的是本地文件和屏幕采集,这一节第一次面对网络设备。 URL 与 RTSP 交互过程在第一、二章,这里只讲「连不上怎么办」。
一、URL 就是设备地址
av.open() 靠 URL 前缀选 demuxer,前缀不认识就直接报错。
| URL | 协议 | 说明 |
|---|---|---|
rtsp://ip:554/… |
rtsp | IPC 最常用 |
rtmp://… |
rtmp | 推流/部分 NVR |
http://…/a.mp4 |
http | 下载或伪流 |
海康的路径通常是 /Streaming/Channels/101(1 号通道主码流),
大华是 /cam/realmonitor?channel=1&subtype=0。
这些路径不是标准,是各家约定——拿不到就查厂商文档,别猜。
二、三类报错,处置完全不同
| 现象 | 原因 | 怎么查 |
|---|---|---|
| 立刻报找不到格式 | URL 前缀写错 | 查协议拼写 |
| 重试到超时才报 | 地址通、端口没服务 | 查设备有没有开 RTSP |
| TCP 直接超时 | 地址不通、被防火墙丢 | ping、查路由 |
第二类和第三类的区别最关键: 「重试到超时」说明网络能通到主机,只是端口没应答;「TCP 超时」说明包都到不了。 现场排查先分清这两类,别一上来就怀疑摄像头坏了。 实测数据:连一个本机没开 RTSP 的端口,花了 2.2 秒才报错(因为我给了 2 秒的 timeout)。
三、为什么必须给 timeout
av.open() 对 RTSP 失败会反复重试,不给 timeout 就可能挂几十秒。
我第一次写这个 Demo 就挂住了,只能靠外面的守时机制掐掉。
| 选项 | 含义 | 建议值 |
|---|---|---|
timeout |
打开超时(微秒) | 3000000(3 秒) |
rtsp_transport |
tcp/udp |
tcp 更稳 |
stimeout |
套接字超时(部分版本) | 同上 |
timeout 的单位是微秒,不是毫秒——写 3000 你以为 3 秒,其实是 3 毫秒,立刻超时。
rtsp_transport=tcp 在监控里几乎必设:UDP 丢包会花屏,TCP 至少保证画面完整,
代价是延迟略高。
四、没有摄像头怎么开发
本机没有 IPC,所以用屏幕采集当替身。
c = av.open("desktop", format="gdigrab",
options={"framerate": "5", "video_size": "320x180"})
这条路径和摄像头是同一条(都是「输入设备」),所以采集、编码、显示的逻辑可以先用它调通, 等设备到位只换 URL。 这个方法在 03.01 用过一次,这里再用一次,因为它是本机唯一能真采到画面的入口。
五、动手 Demo:连一个不存在的摄像头,看它报什么错
Demo 连两个不存在的地址:一个协议名乱写,一个端口没服务。
再把「网络不通」这一类用 socket 单独试一遍,因为让 av.open 去试会挂很久。
try:
av.open("no_such_scheme://x/y") # 协议不认识 -> 立刻报错
except Exception as e:
print(type(e).__name__) # ArgumentError
try:
av.open("rtsp://127.0.0.1:554/x",
options={"timeout": "2000000"}) # 2 秒超时
except Exception as e:
print(type(e).__name__) # UndefinedError,花了 2.2 秒
实跑输出:
--------------------------------------------------------------------
连一个不存在的摄像头,看它报什么错
--------------------------------------------------------------------
**本节要读网络摄像头,但本机没有 IPC、也没有 RTSP 服务器。**
**所以我做的是「连一个不存在的地址」,把报错逐一试出来**——**这几种错误在现场排查时最有用。**
**先看 URL 怎么被认出来:**
URL 协议 谁处理
------------------------------------------------------------------------------------
rtsp://192.168.1.64:554/Streaming/Channels/101 rtsp 对应 demuxer
rtmp://live/live/stream rtmp 对应 demuxer
http://127.0.0.1/a.mp4 http 对应 demuxer
**`av.open()` 就是靠 URL 前缀选 demuxer 的**,**前缀不认识就直接报「找不到格式」。**
**然后逐个试,看报什么、花了多久:**
地址 报错类型 耗时 说明
----------------------------------------------------------------------
no_such_scheme://x/y ArgumentError 0.0 秒 协议不认识
rtsp://127.0.0.1:554/x UndefinedError 2.2 秒 端口没服务
**「端口没服务」那一行花了两秒**,因为我把 `timeout` 设成了 2000000 微秒。
**这不是「立刻拒绝」,是「重试到超时」。**
**这一点很要命**:`av.open()` 对 RTSP 失败会反复重试,
**不设 timeout 就可能挂几十秒**——我自己第一版就挂住了,**只能靠外面的守时机制掐掉。**
**所以连摄像头的第一条军规:永远给 `timeout`。**
**再看「网络根本不通」这一类**,它连 TCP 都建不起来:
`socket.create_connection` 报 **TimeoutError**,耗时 1.0 秒。
**三种错要分清,现场处置完全不同:**
现象 原因 怎么查
----------------------------------------------------------------------------
立刻报「找不到格式」 URL 前缀写错 查协议拼写
重试到超时才报 地址通、端口没服务 查设备有没有开 RTSP
TCP 直接超时 地址不通、被防火墙丢 ping 一下、查路由
**第二类和第三类的区别最关键**:
**「重试到超时」说明网络能通到主机,只是端口没应答;「TCP 超时」说明包都到不了。**
**现场排查先分清这两类**,别一上来就怀疑摄像头坏了。
**没有摄像头时怎么继续开发**:用屏幕采集当替身。
`gdigrab` 打开成功:**320 × 180,格式 bgra**
**这条路径和摄像头是同一条**(都是「输入设备」),**所以采集、编码、显示的逻辑可以先用它调通**,
等设备到位只换 URL。
**这个方法在第三章用过一次(03.01)**,这里再用一次,**因为它是本机唯一能真采到画面的入口。**
两个耗时放在一起看才有意义:0.0 秒 vs 2.2 秒,
说明第二类错误是「重试到超时」而不是「立刻拒绝」。
gdigrab 那一段是本机的替身,它和摄像头走同一条「输入设备」路径,
所以代码结构可以完全复用。
30 秒实验
- 把
timeout从 2000000 改成 3000(3 毫秒),看报错变成什么、耗时多少。 - 把
rtsp_transport设成udp,连同一个地址,看报错有没有变。 - 用
gdigrab采 10 帧,按 04.05.01 的流程编成 h264,验证整条链能跑。
常见误区
- 以为连不上就是摄像头坏了。 三类错要先分清,多半是网络或端口。
- 以为
timeout的单位是毫秒。 是微秒,差一千倍。 - 以为
av.open失败会立刻返回。 RTSP 会重试,不给 timeout 就挂住。
自测三问(点开对答案)
1. 「重试到超时」和「TCP 超时」怎么区分? —— 前者说明网络能通到主机、端口没服务;**后者说明包到不了**。排查方向完全不同。 2. 为什么 `timeout` 要给微秒? —— FFmpeg 的 `timeout` 选项按微秒算。**写 3000 是 3 毫秒**,不是 3 秒。 3. 没有摄像头时用什么替代? —— `gdigrab` 屏幕采集。**它和摄像头走同一条输入设备路径**,代码结构一样,只换 URL。下一节把编码器包成一个类:书上讲 C++,Python 里用 with 表达同样的思想。
- ☐ (本节完,下一节 05.03 FFmpeg实现H.264/H.265编码的C++类封装)