🎬 第八章 FFmpeg命令行实现流媒体功能及直播应用
📄 一句话概括

摄像头的本质是「一个永远有画面的源」,换成循环播放的文件也一样。 拉模式下服务器不攒画面:客户端不来,一个包都不发。

📝 本节和前面几节的关系

8.1 讲了四节协议细节,这一节回到「怎么造一个直播源」这件实事上。 沙箱没有摄像头,但「摄像头」在协议层面只是一个不结束的源, 所以用循环文件就能把整条链路验证完。

一、摄像头为什么能当直播源

从协议角度看,摄像头提供的能力只有一条:源源不断产生新的帧。 它不结束、不重播、也没有「文件末尾」这个概念。

源 什么时候结束 能不能随机访问


文件 播完就结束 能,随便拖 摄像头 关掉才结束 不能,只能看当下 屏幕录制 停止才结束 不能

第二列第三列合起来就是「直播」的定义:不会结束、不能回退。 所以只要能让一个文件「不结束」,它在协议层面就跟摄像头没区别。

循环播放就是这个办法:播到末尾立刻回到开头,对外表现为「一直有画面」。 唯一的破绽是接缝处会跳一下,因为第一帧跟最后一帧接不上。

二、沙箱里没有摄像头

先确认一下环境:这台机器有 dshow 组件,但没有一台采集设备。

项 值


设备组件 dshow(Windows 那一套) 实际设备 一个都没有 打开结果 OSError: Errno 5 I/O error

这个错误码 5 是「I/O 错误」,看着像权限问题,其实是「没有这个设备」。 第三章查 -devices 时就得过同一个结论:组件在,不等于设备有。

所以「用摄像头模拟直播」这件事,沙箱只能讲到原理,跑不了真的。 但原理那部分可以完整验证——因为摄像头在协议里就是个不结束的源。

顺带说一句:-f dshow -i video=… 里的名字必须跟设备列表一字不差。 中文设备名最容易翻车,因为命令行编码与设备名编码可能不一致。

三、用文件冒充:不连不发

把同一段素材循环喂给 RTSP 服务器,让客户端在不同时刻连上来:

客户端几秒后连 解出帧数 首帧耗时


0.3 秒 10 1005 毫秒 2.0 秒 10 986 毫秒

两次都是 10 帧、首帧耗时也差不多。 这说明服务器没有「先把画面攒着」——它压根没在发。

拉模式的服务器是被动的:没人连,它一个 RTP 包都不发;有人连,从当下这一帧开始给。 所以晚连两秒不会「少看两秒」,而是「从第三秒开始看」。

这一点跟推模式正好相反。 RTMP 推流时服务器一直在收,先连上的观众与后连上的观众看到的进度不一样, 但服务器始终在往里写——这就是 8.6 要讲的那一套。

四、VLC 那条命令与端口选择

VLC 用摄像头开一个 RTSP 服务器,命令是这样:

vlc dshow:// :dshow-vdev="USB 摄像头" :sout="#transcode{vcodec=h264}:rtp{sdp=rtsp://:8554/live}"

dshow:// 是 Windows 的采集输入,dshow-vdev 指定设备名。 #transcode{…} 是把摄像头原始格式转成 H.264, rtp{sdp=…} 才是「开一个 RTSP 服务器」。

端口写 8554 不写 554,是因为 554 是「公认端口」, 在 Linux 上绑小于 1024 的端口要 root 权限。 Windows 上两个都能绑,但工具默认都写 8554,省得以后换系统翻车。

摄像头还有一个坑:它输出的分辨率与帧率未必是你想要的。 #transcode 里可以加 width=1280,height=720,fps=25 强制统一, 不然一路 1080p 的流能把上行带宽吃干净。

五、动手 Demo:没有摄像头时怎么验

下面这段代码先试打开 dshow,确认这台机器没有设备; 再用循环素材当源,让客户端在两个不同时刻连上来,比较解出帧数与首帧耗时。

import av

try:
    c = av.open(u"dshow://", format=u"dshow")
    c.close()
    print(u"打开了(说明有设备)")
except Exception as e:
    print(u"打不开:%s" % str(e)[:40])

# 循环喂文件当源:同一段素材播两遍
def loops(path, n=2):
    for _ in range(n):
        c = av.open(path)
        for f in c.decode(video=0):
            yield f
        c.close()

print(u"循环两次共 %d 帧" % sum(1 for _ in loops(u"s7.mp4")))

实跑输出:

--------------------------------------------------------------------
8.1.5 VLC 使用摄像头模拟 RTSP 直播流
--------------------------------------------------------------------

**① 先看这台机器有没有摄像头可采**

  项           值
  ---------------------------------------------------------
  设备组件     dshow(Windows 那套)
  实际设备     沙箱里一个都没有
  打开结果     OSError: [Errno 5] I/O error: 'dshow://'

**「组件在」不等于「有设备」——这一条在第三章查 `-devices` 时就验过。**
**所以「用摄像头模拟直播」这件事,沙箱只能讲到原理,跑不了真的。**

**② 那就用文件冒充摄像头:循环喂,模拟一个永远有画面的源**

  客户端几秒后才连     解出帧数     首帧耗时
  ----------------------------------------------
  0.3 秒               10           985 ms
  2.0 秒               10           1004 ms

**两次都解出帧、首帧耗时也差不多——说明服务器不会「先把画面攒着」。**
**客户端不连,它一个 RTP 包都不发;客户端一连,从第一帧开始给。**

**这就是拉模式(RTSP)跟推模式(RTMP)最本质的区别:**
**拉模式下服务器是被动的,观众来一个才发一个。**

**③ VLC 那边怎么开一个「摄像头服务器」**

`vlc dshow:// :dshow-vdev="USB 摄像头" :sout="#transcode{vcodec=h264}:rtp{sdp=rtsp://:8554/live}"`

**`rtp{sdp=…}` 的意思就是「开一个 RTSP 服务器,把 RTP 送出去」。**
**端口写 8554 而不是 554,是因为 554 在 Linux 上要 root 权限。**
**Windows 上两者都能绑,但工具默认都写 8554,省得以后换系统翻车。**

第一段打印的是 OSError: [Errno 5] I/O error。 别被「I/O 错误」骗了,它不是磁盘问题,是「机器上没有这个设备」。

第二段确认循环可行:两遍正好 100 帧,接缝处不报错。 所以「摄像头」与「循环文件」在协议层面可以互换——差别只在接缝。

真做直播时,用 -stream_loop -1 让 ffmpeg 循环读文件,效果一样。 但它不解决接缝:观众会看到画面每两秒跳一下。

30 秒实验

  1. 换端口:把 8554 改成 554,看 Windows 上能不能绑(能,但换 Linux 就不行)。
  2. 改帧率:在 #transcode 里加 fps=15,看码率与流畅度的变化。
  3. 试 stream_loop:ffmpeg -stream_loop -1 -i s7.mp4 …,看接缝是不是真的跳。

常见误区

  1. 把 Errno 5 当权限问题。它是「没有这个设备」。
  2. 以为晚连会少看一段。拉模式从当下开始给,不看历史。
  3. 循环文件当无缝源。接缝处第一帧接不上最后一帧,会跳。
自测三问(点开对答案) 1. 摄像头与文件在协议层面的区别是什么? —— 摄像头不结束、不能随机访问;文件会结束、能随便拖。 2. 为什么 RTSP 端口习惯写 8554? —— 554 是公认端口,在 Linux 上绑定要 root 权限;8554 谁都能绑。 3. 晚两秒连上会少看两秒吗? —— 不会。拉模式从当下这一帧开始给,之前的不补。

相关笔记