📹 第五章 FFmpeg二次开发IPC视频监控
📄 一句话概括

连 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 秒实验

  1. 把 timeout 从 2000000 改成 3000(3 毫秒),看报错变成什么、耗时多少。
  2. 把 rtsp_transport 设成 udp,连同一个地址,看报错有没有变。
  3. 用 gdigrab 采 10 帧,按 04.05.01 的流程编成 h264,验证整条链能跑。

常见误区

  1. 以为连不上就是摄像头坏了。 三类错要先分清,多半是网络或端口。
  2. 以为 timeout 的单位是毫秒。 是微秒,差一千倍。
  3. 以为 av.open 失败会立刻返回。 RTSP 会重试,不给 timeout 就挂住。
自测三问(点开对答案) 1. 「重试到超时」和「TCP 超时」怎么区分? —— 前者说明网络能通到主机、端口没服务;**后者说明包到不了**。排查方向完全不同。 2. 为什么 `timeout` 要给微秒? —— FFmpeg 的 `timeout` 选项按微秒算。**写 3000 是 3 毫秒**,不是 3 秒。 3. 没有摄像头时用什么替代? —— `gdigrab` 屏幕采集。**它和摄像头走同一条输入设备路径**,代码结构一样,只换 URL。

下一节把编码器包成一个类:书上讲 C++,Python 里用 with 表达同样的思想。

相关笔记