家里摄像头越来越多——几台小米、几个 ESP32 自制摄像头、还有树莓派挂的 CSI 模块。云存储用着总觉得不对劲:小米摄像头默认只能走米家,想拉 RTSP 流得刷固件或者买官方插件;其他品牌的云方案要么按月收费,要么断网就断录;最关键的是,视频数据躺在别人的服务器上,你不知道它被怎么处理、什么时候会被清理。
与其继续给各家云方案交月租,不如自己搭一个 NVR(Network Video Recorder),把所有路视频流收归本地。MiBeeNvr 就是作者在这种思路下写出来的轻量级家用 NVR 系统。
云存储的三个硬伤
厂商绑定。 小米摄像头默认封闭 RTSP,想拉流得走非官方路径;其他品牌各有各的 App,互不相通。你买了五个品牌的摄像头,就得装五个 App、开五个云存储账号。
网络依赖。 云录制的本质是:摄像头先把视频推到厂商服务器,你再从服务器拉回来看。家里的上行带宽有限,多路同时推流容易卡顿;一旦外网断了,录制直接中断。
持续费用。 一路摄像头一个月云存储几十块,几路加起来一年几百。硬盘买一块放家里,一次投入用几年。
自建 NVR 的思路很简单:摄像头在局域网内推 RTSP 流,NVR 在本地拉流、存盘、回放。数据不出家门,不依赖外网,不按月交钱。
让摄像头吐出 RTSP 流
自建 NVR 的前提是每路摄像头都能输出 RTSP 流。不同设备的开放程度差异很大,这里逐类说清楚。
小米摄像头。 原生固件不开放 RTSP,社区有两个主流路径:
- 刷第三方固件(风险较高,可能变砖)
- 用官方的"局域网直播"功能,部分型号支持通过米家 App 开启本地 RTSP,但稳定性参差
如果不想刷固件,一个折中方案是用小米的云直播地址做中转,但这又回到了网络依赖的老路。最干净的做法还是选支持 RTSP 的摄像头品牌。
ESP32 摄像头。 ESP32-CAM 模块天然支持 HTTP 快照和流,但原生不带 RTSP。可以用 Micro-RTSP 库给 ESP32 加上 RTSP Server:
// ESP32 Micro-RTSP 最小示例
// 依赖:https://github.com/geeksville/Micro-RTSP
#include "OV2640.h"
#include "MicroRTSPServer.h"
OV2640 cam;
RTSPServer rtspServer(8554); // RTSP 端口
void setup() {
cam.init(esp32cam_aio_config); // 根据你的板子选配置
rtspServer.begin(&cam);
}
void loop() {
rtspServer.handleClient(); // 处理 RTSP 客户端请求
}
编译上传后,用 VLC 或 FFmpeg 验证流是否可用:
ffplay rtsp://192.168.1.105:8554/mjpeg/1
ESP32 性能有限,一般只能推 640×480 @ 15fps 的 MJPEG,画质别指望太高,但作为门铃、走廊监控够用。
树莓派 CSI 摄像头。 树莓派推 RTSP 流最简单的方案是用 libcamera + v4l2relay 或直接跑 FFmpeg:
# 树莓派 Bookworm 系统,使用 libcamera 采集 + FFmpeg 推 RTSP
libcamera-vid -t 0 --codec mjpeg --width 1280 --height 720 \
--framerate 15 --output - | \
ffmpeg -re -i - -c:v copy -f rtsp \
rtsp://0.0.0.0:8554/rpi_cam
这条命令让树莓派持续采集 CSI 摄像头画面,通过 FFmpeg 以 RTSP 协议发布到本地 8554 端口。局域网内任何设备都能拉流。
用 FFmpeg 搭一个最简 NVR 核心
在正式介绍 MiBeeNvr 之前,先理解 NVR 的核心逻辑:拉流 → 分段存储 → 索引回放。用 FFmpeg 就能实现最简版本:
# 单路摄像头持续录制,每段 15 分钟,按时间命名
ffmpeg -rtsp_transport tcp -i "rtsp://192.168.1.105:8554/mjpeg/1" \
-c:v copy -f segment -segment_time 900 \
-segment_format mp4 \
-strftime 1 \
"/mnt/nvr/esp32_cam/%Y%m%d_%H%M%S.mp4"
关键参数解释:
-rtsp_transport tcp:用 TCP 拉流,比 UDP 更稳定,丢包时不会花屏-c:v copy:不重编码,直接拷贝视频流,省 CPU-segment_time 900:每 900 秒(15 分钟)切一段文件-strftime 1:文件名用时间格式化,方便按时间检索
多路摄像头就开多个 FFmpeg 进程,每路一个。用 systemd 管理可以保证进程挂掉后自动重启:
# /etc/systemd/system/nvr-esp32.service
[Unit]
Description=NVR Record - ESP32 Cam
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/ffmpeg -rtsp_transport tcp -i "rtsp://192.168.1.105:8554/mjpeg/1" -c:v copy -f segment -segment_time 900 -segment_format mp4 -strftime 1 "/mnt/nvr/esp32_cam/%Y%m%d_%H%M%S.mp4"
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
sudo systemctl enable nvr-esp32
sudo systemctl start nvr-esp32
这套方案能跑,但缺点明显:没有 Web 界面、没有实时预览、没有移动检测触发录制、存储满了没有自动清理。这就是需要 MiBeeNvr 这类专用系统的原因。
MiBeeNvr 的设计思路
根据作者的描述,MiBeeNvr 针对的就是家用场景——几路到十几路摄像头、一台 Linux 小主机(或树莓派)当服务器、不需要企业级功能但要足够省心。
一个家用 NVR 需要解决的核心问题:
| 问题 | FFmpeg 脫手方案的短板 | NVR 系统该做的事 |
|---|---|---|
| 实时预览 | 只能 ffplay 单路 | Web 端多路同看 |
| 录制管理 | 手动启停 systemd 服务 | 按配置自动启停,异常自动恢复 |
| 存储策略 | 磁盘满了就崩 | 自动清理旧文件,保留最近 N 天 |
| 回放检索 | 只能按文件名猜时间 | 时间轴拖拽、事件检索 |
| 移动检测 | 不支持 | 画面变化时触发录制或标记 |
MiBeeNvr 作为轻量级系统,大概率在架构上走的是:Go 或 Python 写后端,拉流用 FFmpeg 或 GStreamer,前端提供实时预览和回放,后台管理存储生命周期。作者没在摘要里披露具体技术栈,但这类项目的典型实现可以参考下面这个结构。
一个可改造的迷你 NVR 项目骨架
如果你想自己搭一个类似系统,或者给 MiBeeNvr 做二次开发,下面是一个最小可运行的 Python + FFmpeg NVR 骨架,包含拉流录制和存储清理:
#!/usr/bin/env python3
"""mini_nvr.py — 最简 NVR:拉流录制 + 自动清理旧文件"""
import subprocess, time, os, threading, schedule
from pathlib import Path
from datetime import datetime
# ── 配置 ──────────────────────────────────────────
CAMERAS = {
"esp32_door": "rtsp://192.168.1.105:8554/mjpeg/1",
"rpi_garden": "rtsp://192.168.1.110:8554/rpi_cam",
"xiaomi_living": "rtsp://192.168.1.120:8554/live",
}
STORE_ROOT = Path("/mnt/nvr")
SEGMENT_SEC = 900 # 15 分钟一段
KEEP_DAYS = 7 # 保留最近 7 天
FFMPEG = "ffmpeg"
# ── 单路录制进程 ────────────────────────────────────
def record_stream(name: str, url: str):
out_dir = STORE_ROOT / name
out_dir.mkdir(parents=True, exist_ok=True)
cmd = [
FFMPEG, "-rtsp_transport", "tcp",
"-i", url,
"-c:v", "copy",
"-f", "segment",
"-segment_time", str(SEGMENT_SEC),
"-segment_format", "mp4",
"-strftime", "1",
"-reset_timestamps", "1",
str(out_dir / "%Y%m%d_%H%M%S.mp4"),
]
while True:
print(f"[{name}] 启动录制: {url}")
proc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
proc.wait()
print(f"[{name}] 进程退出 (code={proc.returncode}), 10秒后重启")
time.sleep(10)
# ── 存储清理 ────────────────────────────────────────
def purge_old_files():
cutoff = datetime.now().timestamp() - KEEP_DAYS * 86400
for cam_dir in STORE_ROOT.iterdir():
if not cam_dir.is_dir():
continue
for f in cam_dir.glob("*.mp4"):
# 从文件名解析时间: 20240315_093000.mp4
try:
ts = datetime.strptime(f.stem, "%Y%m%d_%H%M%S").timestamp()
if ts < cutoff:
f.unlink()
print(f"[清理] 删除 {f}")
except ValueError:
pass
# ── 主入口 ──────────────────────────────────────────
def main():
# 为每路摄像头启动录制线程
for name, url in CAMERAS.items():
t = threading.Thread(target=record_stream, args=(name, url), daemon=True)
t.start()
# 每天凌晨 3 点清理旧文件
schedule.every().day.at("03:00").do(purge_old_files)
print("NVR 已启动,Ctrl+C 退出")
while True:
schedule.run_pending()
time.sleep(60)
if __name__ == "__main__":
main()
运行方式:
# 安装依赖
pip install schedule
# 前台运行(测试)
python3 mini_nvr.py
# 后台运行(生产)
nohup python3 mini_nvr.py >> /var/log/nvr.log 2>&1 &
修改 CAMERAS 字典加入你自己的摄像头 RTSP 地址,调整 KEEP_DAYS 控制存储保留天数。这个骨架没有 Web 界面和移动检测,但已经覆盖了 NVR 最核心的三个功能:多路并行录制、断流自动重启、存储自动清理。
自建 NVR 的硬件选择
跑 NVR 不需要很强力的硬件,关键看摄像头路数和编码格式:
- 2-4 路 MJPEG 流:树莓派 4B 就够,CPU 占用低因为
-c:v copy不重编码 - 4-8 路 H.264 流:建议用 J4125/N5105 小主机,低功耗 x86,跑 Linux 稳定
- 8 路以上:考虑 N100 或更强的小主机,或者用带硬件解码的板子
存储方面,一块 2TB 机械硬盘挂在小主机上,8 路 720p 影像存 7 天大概占 200-400GB(视编码和帧率而定),2TB 留两周绰绰有余。
上手建议
如果你也在纠结云存储的厂商绑定和持续费用,可以按这个顺序推进:
- 先让所有摄像头吐出 RTSP 流。 用
ffplay逐路验证,确保局域网内可拉流。小米摄像头是最麻烦的一环,提前调研你那个型号的 RTSP 开放路径。 - 用 FFmpeg 命令行手动录制几小时。 确认稳定性、估算存储量,再决定硬盘容量。
- 跑上面的 mini_nvr.py 骨架或直接试用 MiBeeNvr。 先接 1-2 路,跑稳后再逐步加入其余摄像头。
- 考虑网络和电力冗余。 NVR 机器接 UPS,摄像头尽量走有线以太网而不是 Wi-Fi——Wi-Fi 在多路推流时抖动明显。
自建 NVR 的投入是一次性的:一块硬盘、一台小主机、几根网线。换来的是数据完全自主、不按月交费、不受厂商政策变化影响。对于家里有多个摄像头的人来说,这笔账算下来很划算。