摄像头的本质是「一个永远有画面的源」,换成循环播放的文件也一样。 拉模式下服务器不攒画面:客户端不来,一个包都不发。
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 秒实验
- 换端口:把 8554 改成 554,看 Windows 上能不能绑(能,但换 Linux 就不行)。
- 改帧率:在
#transcode里加fps=15,看码率与流畅度的变化。 - 试 stream_loop:
ffmpeg -stream_loop -1 -i s7.mp4 …,看接缝是不是真的跳。
常见误区
- 把 Errno 5 当权限问题。它是「没有这个设备」。
- 以为晚连会少看一段。拉模式从当下开始给,不看历史。
- 循环文件当无缝源。接缝处第一帧接不上最后一帧,会跳。
自测三问(点开对答案)
1. 摄像头与文件在协议层面的区别是什么? —— 摄像头不结束、不能随机访问;文件会结束、能随便拖。 2. 为什么 RTSP 端口习惯写 8554? —— 554 是公认端口,在 Linux 上绑定要 root 权限;8554 谁都能绑。 3. 晚两秒连上会少看两秒吗? —— 不会。拉模式从当下这一帧开始给,之前的不补。- ☐ 8.1 完,下一节 8.2.1 RTP 简介