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

0 阅读8分钟

做 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 围绕几个核心需求设计,架构简洁高效:

flowchart TB
    subgraph 相机层
        CAM["CSI/USB 相机"]
    end

    subgraph rpi-cam
        CAP["相机捕获"]
        RTSP["RTSP 服务器"]
        ONVIF["ONVIF 服务"]
        RTMP["RTMP 推流"]
        CTRL["相机控制"]
    end

    subgraph 外部系统
        NVR["NVR/VMS 系统"]
        CLOUD["云服务"]
    end

    CAM --> CAP
    CAP --> RTSP
    CAP --> RTMP
    RTSP --> ONVIF
    CTRL --> ONVIF
    ONVIF --> NVR
    RTMP --> CLOUD

    classDef cam fill:#E3F2FD,stroke:#1565C0,color:#1565C0
    classDef app fill:#FFF3E0,stroke:#E65100,color:#BF360C
    classDef ext fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20
    class CAM cam
    class CAP,RTSP,ONVIF,RTMP,CTRL app
    class NVR,CLOUD ext

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

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

image.png

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 V1OV56472592×1944固定基础 5MP,当前使用
Pi Camera V2IMX2193280×2464固定低光性能更好
Pi Camera V3IMX7084608×2592自动对焦PDAF,HDR 支持
Pi HQ CameraIMX4774056×3040手动对焦可更换镜头
USB UVC各种各种各种即插即用

实际应用场景

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

sequenceDiagram
    participant CAM as rpi-cam
    participant NVR as MiBee NVR
    participant CLOUD as 云服务

    Note over CAM,NVR: ① WS-Discovery 发现
    NVR->>CAM: Probe 请求
    CAM-->>NVR: ProbeMatch (XAddr)

    Note over CAM,NVR: ② 获取设备信息
    NVR->>CAM: GetDeviceInformation
    CAM-->>NVR: 制造商/型号/固件

    Note over CAM,NVR: ③ 获取媒体配置
    NVR->>CAM: GetCapabilities + GetProfiles
    CAM-->>NVR: 编码器/分辨率

    Note over CAM,NVR: ④ 获取流地址
    NVR->>CAM: GetStreamUri
    CAM-->>NVR: rtsp://host:port/stream

    Note over CAM: ⑤ RTSP 播放
    NVR->>CAM: SETUP / PLAY
    CAM-->>NVR: H.264 视频流

    Note over CAM,CLOUD: ⑥ RTMP 推流
    CAM->>CLOUD: RTMP 推流
    CLOUD-->>CAM: 推流确认

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

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

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

13361d6196a2dfbc235371a6eb6f74d7.png

左是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/v5MediaMTX 同款库,兼容性有保证
RTMP 推流q191201771/lalGo 原生,资源占用适中,维护活跃
相机捕获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。