rpi-cam:给 Raspberry Pi 造的轻量级 ONVIF 相机服务

原文:《rpi-cam:给 Raspberry Pi 造的轻量级 ONVIF 相机服务

做 MiBee NVR 的 ONVIF 协议支持时,我需要深入理解 ONVIF 的完整流程------从 WS-Discovery 设备发现到流地址获取,光看文档和抓包不够,得有一个能跑的 ONVIF 相机服务端来对接调试。手边刚好有台树莓派 3B + OV5647 摄像头,本来跑着 MediaMTX 推 RTSP 流,但 MediaMTX 没有 ONVIF 服务端模式(GitHub issue #1402),NVR 发现不了它。

于是决定自己写一个轻量级的 Go ONVIF 相机服务,把树莓派上原来的 MediaMTX 替换掉。这样做的好处是 MiBee NVR 的 ONVIF 客户端和这个相机服务端用的是同一个 0x524a/onvif-go 库,客户端和服务端完全兼容,调试起来特别方便。

为什么要造 rpi-cam

起因很简单:开发 MiBee NVR 的 ONVIF 协议支持,需要一个本地的 ONVIF 相机来调试。过程中遇到的痛点大概有这么几个:

MediaMTX 缺 ONVIF 服务端 。MediaMTX 确实是个好工具,RTSP 流媒体做得不错,但问题在于它没有 ONVIF 服务端模式(GitHub issue #1402)。你用它推流可以,但 NVR 无法通过 ONVIF 协议发现和控制这个相机。

现有 ONVIF 方案太重。找了一圈,要么是 Python 写的性能不行,内存占用高;要么是功能不完整,只支持 Device 服务,缺 Media/PTZ/Imaging 服务;要么是需要 CGO 编译,交叉编译太麻烦。

相机控制不完整。很多开源方案只推个流,相机的亮度、对比度、白平衡这些参数根本控制不了。但实际项目中,NVR 往往需要根据场景调整相机参数。

资源占用太高。树莓派 3B 只有 905MB 内存,之前 MediaMTX + mtxrpicam 就占了 45MB,跑起来偶尔还会因为内存压力重启。换成 rpi-cam 之后实测只占 15-25MB,而且跑了几个月没掉过线,比 MediaMTX 稳定不少。

相机支持单一。现在手里可能有 OV5647、IMX219、IMX708,将来还要换 IMX477,甚至 USB 摄像头。方案得支持多种相机,不能换了相机就得重写代码。

我的需求其实很简单:

  • 一个二进制文件,下载就能跑

  • 支持 ONVIF Profile S,和 MiBee NVR 兼容

  • 内存占用 <30MB,适合 905MB 内存的小设备

  • 支持 CSI 和 USB 两种相机接口

  • RTSP 推流 + RTMP 推送到云服务

  • 相机参数实时控制

没有现成的,那就自己造。

rpi-cam 是什么

rpi-cam 是一个用 Go 写的轻量级树莓派 ONVIF 相机服务,专门为资源受限的 ARM 设备设计。它提供完整的 ONVIF Device/Media/PTZ/Imaging 服务,支持 RTSP 流媒体、RTMP 推流和 WS-Discovery 自动发现。

整体架构

rpi-cam 围绕几个核心需求设计,架构简洁高效:

外部系统rpi-cam相机层CSI/USB相机相机捕获RTSP服务器ONVIF服务RTMP推流相机控制NVR/VMS系统云服务

相机捕获通过 libcamera 接口,支持 OV5647、IMX219、IMX708、IMX477 等模块。RTSP 服务器使用和 MediaMTX 同样的 gortsplib 库,确保流媒体兼容性。ONVIF 服务提供完整的设备发现、媒体控制、PTZ 操作和图像参数调节。RTMP 推流支持阿里云、腾讯云等云服务。

内部实现采用标准的 Go 分层架构:

HTTP/SOAP请求中间件认证/限流HandlerServiceRepository配置文件

Handler → Service → Repository → 配置文件,配置通过 YAML 管理,易于部署和维护。

核心功能

ONVIF 服务端。完整的 ONVIF Profile S 支持,包括:

  • Device Service:设备信息、能力查询、WS-Discovery

  • Media Service:媒体配置、流地址获取、快照

  • PTZ Service:数字 PTZ 控制(裁剪实现)

  • Imaging Service:亮度、对比度、饱和度、锐度调节

和 MiBee NVR 使用同一个 onvif-go 库,客户端和服务端完全兼容,测试起来特别方便。

RTSP 流媒体。使用 MediaMTX 同样的 gortsplib 库,确保和现有 NVR 系统的兼容性。支持 720p@15fps、1080p@30fps 等多种分辨率配置。

RTMP 推流。集成 lal 库,支持推送到阿里云、腾讯云、Twitch、YouTube 等云服务。可以用简单的配置实现云端存储和直播。

相机控制。支持实时的图像参数调节:

  • 基础参数:亮度、对比度、饱和度、锐度

  • 高级参数:曝光模式、曝光时间、增益、白平衡模式

  • 图像处理:水平翻转、垂直翻转

  • 数字 PTZ:通过软件裁剪实现平移、缩放

多相机支持。支持常见的树莓派相机模块:

模块 传感器 分辨率 对焦 特点
Pi Camera V1 OV5647 2592×1944 固定 基础 5MP,当前使用
Pi Camera V2 IMX219 3280×2464 固定 低光性能更好
Pi Camera V3 IMX708 4608×2592 自动对焦 PDAF,HDR 支持
Pi HQ Camera IMX477 4056×3040 手动对焦 可更换镜头
USB UVC 各种 各种 各种 即插即用

实际应用场景

NVR 集成。这是 rpi-cam 的本职工作------作为 MiBee NVR 的测试相机:

云服务MiBee NVRrpi-cam云服务MiBee NVRrpi-cam① WS-Discovery 发现② 获取设备信息③ 获取媒体配置④ 获取流地址⑤ RTSP 播放⑥ RTMP 推流Probe 请求ProbeMatch (XAddr)GetDeviceInformation制造商/型号/固件GetCapabilities + GetProfiles编码器/分辨率GetStreamUrirtsp://host:port/streamSETUP / PLAYH.264 视频流RTMP 推流推流确认

这个流程覆盖了大多数 NVR 系统的集成场景,从设备发现到视频播放,再到云端存储,一气呵成。

宠物孵化箱监控。这是 rpi-cam 的一个意外收获。家里养宠物需要观察孵化箱里的情况,市面上的微距摄像头体积都很大,塞不进箱子里。但树莓派的 CSI 相机模块只有指甲盖大小,用排线引出来,摄像头贴在孵化箱内部,树莓派放在外面,轻松解决空间问题。配合 ONVIF 的 Imaging 服务可以远程调亮度、对比度,不用打开箱子就能看清细节。

同一摄像头的 RTSP vs ONVIF 对比。在 MiBee NVR 监控大屏上,同一个 rpi-cam 摄像头分别通过 RTSP 直连和 ONVIF 发现接入,效果对比:

左是RTSP直连,右是ONVIF发现

技术选型

选技术的时候有几个硬约束:

  1. 纯 Go:不依赖 CGO,交叉编译方便,树莓派 3B 上能跑

  2. 内存占用小:目标 <30MB 内存,不能像 MediaMTX 那样占 45MB

  3. 稳定可靠:相机不能掉线,NVR 接连不上就麻烦了

  4. 兼容性好:和现有 NVR 系统无缝对接

最终的技术栈:

组件 选择 理由
ONVIF 服务端 0x524a/onvif-go 纯 Go,完整的 Device/Media/PTZ/Imaging 服务
RTSP 服务器 bluenviron/gortsplib/v5 MediaMTX 同款库,兼容性有保证
RTMP 推流 q191201771/lal Go 原生,资源占用适中,维护活跃
相机捕获 MediaMTX rpicam 经过验证的 libcamera 接口,不需要 CGO
配置管理 YAML 人类可读,易于部署

核心亮点是零 CGO。本来考虑过 go4vl 直接操作 V4L2,但需要 CGO 交叉编译,太麻烦。最后选择 MediaMTX 的 rpicam 方式,用 subprocess 调用,Go 层面处理 pipe 流,编译部署简单多了。

整个项目只有不到 20 个 Go 文件,依赖也很少。go mod tidy 之后也就几个核心库,维护起来很轻松。

实际部署

树莓派 3B,905MB 内存,WiFi 连网。之前跑 MediaMTX 大概占 45MB 内存,偶尔会因内存压力自动重启。换成 rpi-cam 之后稳定在 15-25MB,跑了几个个月没出过问题。

编译部署

复制代码
# 克隆项目 复制代码
git clone https://github.com/Mi-Bee-Studio/raspberrypi-camera 复制代码
cd raspberrypi-camera 复制代码
复制代码
# 交叉编译到 arm64 复制代码
GOOS=linux GOARCH=arm64 go build -o build/rpi-cam ./cmd/... 复制代码
复制代码
# 复制到目标设备 复制代码
复制代码
# 复制到目标设备 复制代码
scp build/rpi-cam user@your-rpi-host:~/rpi-cam 复制代码
复制代码
### 配置文件 复制代码
复制代码

```` config.yaml 配置示例:

复制代码
复制代码
```` ```yaml
# 相机配置 复制代码
camera: 复制代码
width: 1280 复制代码
height: 720 复制代码
fps: 15 复制代码
bitrate: 2000000 # 2Mbps 复制代码
复制代码
# RTSP 配置 复制代码
rtsp: 复制代码
port: 8554 复制代码
复制代码
# ONVIF 配置 复制代码
onvif: 复制代码
port: 8080 复制代码
username: admin 复制代码
password: your-password 复制代码
复制代码
# RTMP 推流配置 复制代码
rtmp: 复制代码
enabled: true 复制代码
targets: 复制代码
- url: "rtmp://cloud.example.com/live/stream" 复制代码

key: "your-stream-key"

systemd 服务

/etc/systemd/system/rpi-cam.service:

复制代码

[Unit]
Description=rpi-cam ONVIF Camera Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=rpi-cam
ExecStart=/opt/rpi-cam/rpi-cam -config /opt/rpi-cam/config.yaml
WorkingDirectory=/opt/rpi-cam

# Security hardening
ReadWritePaths=/opt/rpi-cam
[Install]
WantedBy=multi-user.target

启用服务:

复制代码

sudo systemctl daemon-reload
sudo systemctl enable --now rpi-cam

部署完成后,NVR 就能自动发现这个相机了。WS-Discovery 会广播到局域网,NVR 收到 Probe 请求后会返回完整的设备信息。

开源地址

rpi-cam 已经开源,感兴趣的可以下载试玩:

项目文档比较全,架构设计、部署指南、API 接口都有。如果要在生产环境使用,建议先在测试环境跑一段时间,确保稳定性。

写在最后

这个项目最初就是为了给 MiBee NVR 开发 ONVIF 协议支持时有个能调试的对端。开发过程中把树莓派上原来的 MediaMTX 替换了,没想到实际跑下来资源占用更低、也更稳定------算是意外之喜。后来又发现拿来监控孵化箱特别好使,CSI 相机的小体积是普通微距摄像头比不了的。

现在 rpi-cam 在我这里同时跑着两个用途:给 NVR 做测试相机,以及 7×24 监控孵化箱。一个二进制文件,一个配置文件,systemd 托管,基本不用管。

当然,项目还有很多可以改进的地方:

  • 数字 PTZ 的实现可以更完善

  • 支持更多的相机型号

  • 添加 Web 管理界面

  • 性能优化和监控

如果你也需要一个轻量级的树莓派 ONVIF 相机服务,或者刚好需要把小体积摄像头塞进什么狭小空间里,不妨试试看。个人精力有限,前期主要围绕自己的需求迭代,有想法或者发现了 bug 欢迎提 issue 或 PR。

~ end ~

相关推荐
踏着七彩祥云的小丑3 小时前
Go学习第7天:Map集合 + 递归函数 + 类型转换
开发语言·学习·golang·go
技术硬汉17 小时前
DL 平台:DeviceTouch远程维护实战(串口)
物联网·信息与通信·远程工作·iot
踏着七彩祥云的小丑1 天前
Go 学习第6天:结构体 + 切片 + range遍历
开发语言·学习·golang·go
壮Sir不壮1 天前
GO语言——GMP调度模型
linux·开发语言·golang·go·操作系统·线程·协程
ShuiShenHuoLe1 天前
go语言time模块
go
2601_951645782 天前
Linux 编程语言全解析:C、C++、Python、Go、Rust 谁更强?
linux·python·go·c·编程语言
布子麟2 天前
NodeMcu(ESP8266)之更新固件
单片机·物联网·iot
踏着七彩祥云的小丑2 天前
Go学习第5天:变量作用域 + 数组 + 指针
开发语言·学习·golang·go