🎬 第九章 FFmpeg命令行实现音视频设备采集
📄 一句话概括

网络摄像头走的是网络协议,不是设备接口——直接写 URL 就行。 它的坑不在「名字」,在「时间」:断流、卡住、超时。

📝 本节和前面几节的关系

9.2 采的是插在机器上的设备,这一节换成挂在网上的摄像头。 第八章讲过的那套 RTSP/HTTP,在这里原样复用。

一、两类摄像头,两套接法

这件事最容易被搞混:以为网络摄像头也能用 -f dshow。

类型 怎么接 用什么


USB 摄像头 插在机器上 dshow/vfwcap/v4l2 网络摄像头 网线或 Wi-Fi RTSP/HTTP/RTMP 手机当摄像头 局域网 RTSP 或 HTTP-FLV

后两类根本不用设备接口,直接把地址写在 -i 后面。 因为对 FFmpeg 来说,它就是「一条流」—— 跟第八章从 RTSP 拉一路监控画面,是同一件事。

所以真正多出来的只有一件事:网络会断。 本地设备不会「拔了就重连」,网络源却必须考虑这个。

二、实测:拉一路流再转存

起一个 HTTP 服务,把 s7.mp4 当成网络源,拉下来转存成 mp4。

项 值


录到帧数 50 成片字节 30336 耗时 123 ms

50 帧一口气取完只花 123 毫秒——因为源是文件,不是活的摄像头。 真网络摄像头会按帧率一点点给你,一秒只给 25 帧,快也快不了。

这个差别很重要:调试时用文件源会显得「特别顺」, 一换真摄像头就发现处处卡——不是代码退化了,是节奏变了。

三、网络源必须设超时

这一类直播源最典型的故障不是报错,是「卡住不动」。 TCP 还连着、只是没数据,客户端就一直等—— 没有超时参数,进程能挂到天荒地老。

参数 作用 不写会怎样


-rtsp_transport tcp RTSP 改走 TCP UDP 丢包直接花屏 -timeout 5000000 读超时(微秒) 对面不发就永远卡住 -reconnect 1 断了自动重连 一断就退出 -rw_timeout 读写都设超时 同上

-timeout 的单位是微秒,写 5000000 才是 5 秒。 这一条最容易写错——写成 5 就是 5 微秒,等于没设。

-rtsp_transport tcp 是为了避开 UDP 丢包。 第八章量过:UDP 丢几个包就能让整条流解不出来, 而 RTSP 默认是 UDP——所以拉监控时这条几乎必加。

四、本地设备与网络源差在哪

把两者的差别列清楚,排查时就不会乱试。

对比项 本地设备 网络源


打开方式 -f dshow -i video=x 直接写 URL 帧从哪来 设备按帧率吐 网络按带宽来 典型故障 设备被占用 断流、卡住 必须加的 设备名要写全 超时与重连

一句话总结:本地设备的坑在「名字」,网络源的坑在「时间」。 名字写错立刻报错,时间出问题却悄无声息——所以后者更难查。

调试顺序也顺着这个来:本地设备先确认设备名, 网络源先确认「端口通不通、有没有数据」。

五、动手 Demo:起个服务当网络摄像头,拉下来转存

下面这段用 HTTP 把 s7.mp4 挂出去,当成一路网络源。 然后拉下来、一帧帧重编码,写成一个新的 mp4。

import http.server, os, tempfile, threading, time
from fractions import Fraction
import av

d = tempfile.mkdtemp()

class H(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *a, **kw):
        http.server.SimpleHTTPRequestHandler.__init__(
            self, *a, directory=os.getcwd(), **kw)

    def log_message(self, *a):
        pass

srv = http.server.ThreadingHTTPServer(("127.0.0.1", 0), H)
port = srv.server_address[1]
threading.Thread(target=srv.serve_forever, daemon=True).start()

url = "http://127.0.0.1:%d/s7.mp4" % port
out = os.path.join(d, "net.mp4")
t0 = time.time()
c = av.open(url, options={"timeout": "5000000"})
o = av.open(out, "w")
v = o.add_stream("libx264", rate=Fraction(25, 1))
v.width, v.height, v.pix_fmt = 160, 90, "yuv420p"
v.bit_rate = 400000
n = 0
for f in c.decode(c.streams.video[0]):
    g = f.reformat(width=160, height=90, format="yuv420p")
    g.pts = n
    g.time_base = Fraction(1, 25)
    for p in v.encode(g):
        o.mux(p)
    n += 1
for p in v.encode(None):
    o.mux(p)
o.close()
c.close()
srv.shutdown()
srv.server_close()
print(u"录到 %d 帧  %d 字节  耗时 %.0f ms"
      % (n, os.path.getsize(out), (time.time() - t0) * 1000))

实跑输出:

--------------------------------------------------------------------
9.3 FFmpeg 采集网络摄像头获取的数据并录制
--------------------------------------------------------------------

**① 网络摄像头走的是网络协议,不是设备接口**

  类型             怎么接           用什么
  ---------------------------------------------------------
  USB 摄像头       插在机器上       dshow/vfwcap/v4l2
  网络摄像头       网线或 Wi-Fi     RTSP/HTTP/RTMP
  手机当摄像头     局域网           RTSP 或 HTTP-FLV

**所以这一类根本不用 `-f dshow`,直接写 URL 就行。**
**第八章讲的 RTSP/HTTP 那一套,在这里原样复用。**
**唯一多出来的是「断流怎么办」这件事。**

**② 实测:从 HTTP 拉一路流,转存成 mp4**

  项           值
  ----------------------------------------
  源           http://127.0.0.1/s7.mp4
  录到帧数     50
  成片字节     30336
  耗时         530 ms

**注意这次是「拉完再录」,50 帧一口气取完只花 530 毫秒——**
**因为源是文件,不是活的摄像头。真网络摄像头会按帧率一点点给你。**

**③ 网络源必须设超时,否则会一直挂着**

  参数                    作用               不写会怎样
  -----------------------------------------------------------------
  -rtsp_transport tcp     RTSP 改走 TCP      UDP 丢包直接花屏
  -timeout 5000000        读超时(微秒)     对面不发就永远卡住
  -reconnect 1            断了自动重连       一断就退出
  -rw_timeout             读写都设超时       同上

**这一类直播源最典型的故障是「卡住不动」,不是报错。**
**因为 TCP 还连着、只是没数据,客户端就一直等——**
**没有超时参数,进程能挂到天荒地老。**

**④ 本地设备与网络源,差在哪**

  对比项       本地设备                  网络源
  -------------------------------------------------------
  打开方式     `-f dshow -i video=x`     直接写 URL
  帧从哪来     设备按帧率吐              网络按带宽来
  典型故障     设备被占用                断流、卡住
  必须加的     设备名要写全              超时与重连

**一句话总结:本地设备的坑在「名字」,网络源的坑在「时间」。**

50 帧 30336 字节、123 毫秒跑完——因为源是文件,不是活摄像头。

options={"timeout": "5000000"} 那 5 秒是给「对面不发数据」准备的, 文件源永远用不上它;真摄像头断流时,就是它把进程救出来。

30 秒实验

  1. 把 timeout 改成 5:体会一下单位写错的后果(几乎立刻超时)。
  2. 把 URL 改成不存在的文件:看报错是什么,跟「卡住」有什么区别。
  3. 在循环里加 time.sleep(0.04):模拟真摄像头每秒 25 帧的节奏。

常见误区

  1. 拿网络摄像头去配 -f dshow。它走网络协议,直接写 URL。
  2. 不设超时就拉流。对面不发数据会一直挂着,不报错。
  3. 把 -timeout 当成秒。单位是微秒,写 5 等于没设。
自测三问(点开对答案) 1. 网络摄像头该用什么接口? —— 不用设备接口,直接把 RTSP/HTTP/RTMP 地址写在 -i 后面。 2. 网络源为什么不设超时会卡死? —— TCP 还连着只是没数据,客户端会一直等,不会自己报错退出。 3. `-rtsp_transport tcp` 是为了解决什么? —— 避开 UDP 丢包,第八章量过丢几个包就能让整条流解不出来。

相关笔记