分类
开发计划

能不能把v4l2-ctl 改造成rtsp server,完全抛弃复杂的ffmpeg?

在资源极其受限的嵌入式系统上解决问题的经典思路:用最简单、最底层的工具,只做最必要的事情。

理论上完全可以把 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-servermediamtx 依然是更好的选择,而且它更现代,对管道的支持可能更好。

  1. 编辑 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=-
    
  2. 启动 mediamtx
  3. 播放 rtsp://192.168.1.1:8554/cam

mediamtx 会自动处理管道的创建和管理,比 live555 的手动 mkfifo 方案更优雅。它本身也是用Go语言编写的,非常轻量。