在资源极其受限的嵌入式系统上解决问题的经典思路:用最简单、最底层的工具,只做最必要的事情。
理论上完全可以把 v4l2-ctl 的数据流对接到一个极其轻量的RTSP服务逻辑上,从而抛弃 ffmpeg 这个“庞然大物”。
我们不是要去“改造” v4l2-ctl 本身(那需要修改C源码),而是利用它已经被验证过的、稳定的抓流能力,通过管道将纯净的H.264数据流喂给一个只负责网络封包的“小弟”。
这个“小弟”必须满足以下条件:
1. 极其轻量: 内存和CPU占用极低。
2. 功能专一: 只做一件事——从标准输入(stdin)读取H.264数据,将其封装成RTP包,然后通过RTSP协议发送出去。
3. 无需配置文件: 最好所有参数都能通过命令行指定。
幸运的是,确实有这样的工具存在。
方案:v4l2-ctl + live555-proxy-server
live555 是一个非常著名和基础的C++流媒体库,很多RTSP服务器(包括v4l2rtspserver)都是基于它开发的。它提供了一个叫 live555-proxy-server 的示例程序,虽然名字叫“代理服务器”,但它有一个隐藏的、非常强大的功能:可以从一个本地文件流(FIFO,命名管道)中读取数据并作为RTSP源。我们可以利用这个特性。
还有一个更现代、更简单的选择是之前提到的 mediamtx (RTSP-Simple-Server),但我们用一种更精简的方式来使用它,让它从管道读取,而不是通过ffmpeg回环。
让我们先专注于最经典、最轻量的 live555 方案。
使用 v4l2-ctl + live555 的终极精简方案
这个方案分为两步,需要两个终端窗口来执行。
第一步:安装 live555 工具集
live555 的工具通常打包在一起。在OpenWrt上,你可能需要安装 live555-proxyserver 或类似的包。
opkg update
# 搜索一下可用的包名
opkg list | grep live555
# 假设找到了 live555-proxyserver,就安装它
opkg install live555-proxyserver
如果软件源里没有预编译好的,这个方案就比较麻烦,可能需要自己交叉编译。但我们先假设有。
第二步:创建命名管道 (FIFO)
我们需要一个“连接点”来让 v4l2-ctl 和代理服务器通信,这个连接点就是命名管道。
# 在 /tmp 目录下创建一个名为 h264.pipe 的命名管道
mkfifo /tmp/h264.pipe
这个文件很特殊,它没有大小,只是一个内核中的数据通道。
第三步:启动RTSP服务,监听管道
在一个终端窗口中,启动 live555-proxy-server。它的用法可能略有不同,但通常是这样的:
# -v 表示 verbose,输出详细日志
# 最后的 h264.pipe 就是我们让它代理的“源”
# 它会自动创建一个RTSP流,URL通常就是 h264.pipe
live555-proxy-server -v "file:///tmp/h264.pipe"
或者有时它的用法是直接指定文件名作为流名称:
# 这个命令会创建一个 rtsp://<ip>:8554/h264.pipe 的流
live555-proxy-server -v h264.pipe:/tmp/h264.pipe
你需要查看一下它的帮助文档 (live555-proxy-server -h) 来确定具体用法。
这个命令执行后会阻塞住,等待数据写入 /tmp/h264.pipe。
第四步:启动 v4l2-ctl,向管道写数据
在另一个终端窗口中,执行我们已经验证过可以稳定工作的 v4l2-ctl 命令,只是这次的输出目标不再是普通文件,而是我们创建的命名管道。
# 先设置好格式和帧率
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat='H264' --set-parm=15
# 让 v4l2-ctl 持续不断地把H264数据流写入管道
# --stream-count=0 表示无限流,直到手动停止
v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=0 --stream-to=/tmp/h264.pipe
注意: 我将帧率改为了 15fps。虽然
v4l2-ctl在60fps下可以短时间成功,但长时间运行依然可能因为USB I/O满载而变得不稳定。我们从一个绝对稳定的15fps开始测试。如果成功了,你可以再尝试提高到30fps。
这个命令执行后,它就会开始抓取视频流,并通过管道源源不断地喂给 live555-proxy-server。
第五步:客户端播放
现在,live555-proxy-server 就有数据了,你可以用 ffplay 播放了。URL通常是 rtsp://192.168.1.1:8554/h264.pipe。
ffplay -rtsp_transport tcp rtsp://192.168.1.1:8554/h264.pipe
为什么这个方案可能成功?
- 分工明确:
v4l2-ctl这个“憨厚”的搬运工只负责最高效地从硬件搬运数据。live555-proxy-server这个“轻量级”的网络工只负责把收到的数据打包发出去。两者都极其简单,内存占用极低。 - 避免了
ffmpeg的复杂性: 我们完全绕开了ffmpeg庞大的框架、内部多线程、复杂的缓冲队列和内存管理机制。 - 压力分解: CPU压力被清晰地分解:
kworker负责USB I/O,live555-proxy-server负责非常少量的网络封包计算。总的CPU和内存占用理论上会远低于使用ffmpeg的方案。
备选方案:v4l2-ctl + mediamtx (更现代)
如果找不到 live555-proxy-server,mediamtx 依然是更好的选择,而且它更现代,对管道的支持可能更好。
- 编辑
mediamtx.yml:paths: cam: source: pipe sourceOnDemand: yes sourceOnDemandStartTimeout: 10s sourceOnDemandCloseAfter: 10s # 这里的命令会在有客户端请求时执行 runOnDemand: v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat='H264' --set-parm=15 --stream-mmap --stream-count=0 --stream-to=- - 启动
mediamtx。 - 播放
rtsp://192.168.1.1:8554/cam。
mediamtx 会自动处理管道的创建和管理,比 live555 的手动 mkfifo 方案更优雅。它本身也是用Go语言编写的,非常轻量。