简介:Moonfire NVR 是一款开源的安全摄像机网络视频录像机,面向需要自建监控存储方案的开发者与运维人员。它把 IP 摄像机输出的 H.264-over-RTSP 流直接落盘,视频帧以旋转目录形式保存,其余元数据存入 SQLite3,并能按任意时间范围动态生成 .mp4 文件;由于不做解码、分析与重编码,CPU 占用极低,可在普通机器上同时处理六路 1080p/30fps 流。资源包共 116 个文件,约 5.22MB,以 48 个 Rust 源码文件为核心,配合 24 个 JavaScript 前端脚本、11 个 Markdown 文档、5 个 SQL 建表脚本,以及 Dockerfile、TOML 配置、proto 定义与示例视频等,覆盖后端、前端、数据库与容器化部署。目前已有 1409 人学习下载。借助这套代码,读者可理解 NVR 的存储分层设计、RTSP 接入流程与 Web 片段检索实现,并据此搭建或二次开发自己的录像系统。
1. 从一台总是掉线的摄像头说起:Moonfire NVR 到底解决什么问题
手里有几台 RTSP 网络摄像机的人,大概率都经历过这种场景:厂商自带的客户端只能在一台手机上看,换台电脑就得重新装一遍;想存 7×24 小时录像,要么买厂商的云存储按月付费,要么忍受 NAS 上那套笨重的套件。更麻烦的是,一旦摄像头数量上去,码流、磁盘、时间轴回放这三件事就开始互相打架。Moonfire NVR 就是冲着这个痛点来的——它是一个用 Rust 写的网络视频录像机,核心职责很纯粹:把 RTSP 摄像机的流拉进来,切成一段段可检索的录像,落到本地磁盘,再通过一个轻量 Web 界面回放和导出。
它适合谁?适合手里有 IP 摄像头、想自己掌控录像数据、又不想被厂商生态绑死的从业者。Rust 技术栈意味着它在长时间运行下的内存占用和稳定性有先天优势,不像某些脚本拼起来的方案跑几天就内存泄漏。前端是 JavaScript,浏览器直接看,不需要装插件。这篇笔记按「它是什么 → 怎么装怎么配 → 坑在哪 → 怎么用得更顺」的顺序拆一遍,新手能照着复现,熟手能直接看参数边界。
2. 拆开 Moonfire NVR 的架构:Rust 后端、RTSP 拉流与分段存储
2.1 为什么是 Rust + 分段录像这套组合
先讲清楚它的工作模型,不然后面配参数全是玄学。Moonfire NVR 的进程模型很简单:一个常驻的 Rust 二进制,内部维护若干「流(stream)」,每个流对应一路摄像机的 RTSP 地址。它不像传统 NVR 那样把视频拼成一个大文件,而是按时间切成固定时长的「录像段(recording)」,每段独立成文件,同时在 SQLite 里记一条元数据:起始时间、时长、文件路径、码流参数。
这种设计的好处是回放和清理都变成数据库查询。你要看某个时间点的录像,先查 SQLite 找到覆盖该时间点的段,再定位文件;要删旧录像,按时间条件删记录和文件即可,不用去动一个大文件。代价是文件数量会很多,磁盘的 inode 和目录遍历性能要留意,后面避坑章节会讲。
Rust 在这里的价值主要是两点:一是内存安全,长时间跑不会因为缓冲区越界崩掉;二是它用了一套基于 tokio 的异步 IO,多路 RTSP 并发拉流时线程开销可控。前端 JavaScript 只负责渲染时间轴和播放,真正的解码交给浏览器,服务端基本只做转发和存储,CPU 压力比想象中小。
2.2 目录结构与关键文件
装完之后,你会看到一个典型的数据目录,理解它才能排查问题。常见布局如下:
| 路径 | 作用 | 备注 |
|---|---|---|
db/ | SQLite 数据库 | 存流配置、录像段元数据 |
db/db | 主数据库文件 | 备份时优先拷这个 |
sample/或录像根目录 | 录像段文件 | 按流和时间组织 |
config.toml | 主配置 | 定义流、端口、存储路径 |
| 二进制本体 | moonfire-nvr | 用子命令操作 |
配置里最核心的是[[streams]]段,每一路摄像机一个块。下面是一个最小可用的配置骨架,字段名按常见版本习惯写,具体以你拿到的版本为准:
# 全局设置:Web 监听端口和数据库位置 bind_addresses = ["0.0.0.0:8080"] db_dir = "/var/lib/moonfire-nvr/db" # 每一路摄像机一个 [[streams]] [[streams]] id = 1 # RTSP 地址,用户名密码按实际填 url = "rtsp://user:pass@192.168.1.10:554/stream1" # 录像保留天数,超期自动清理 retain_days = 14 # 单段录像时长(秒),太短文件多,太长回放粒度粗 sample_file_duration = 300逻辑说明:bind_addresses决定 Web 界面从哪访问,db_dir是数据库落盘位置,retain_days是自动清理策略,sample_file_duration控制分段粒度。参数上,sample_file_duration我一般设 300 秒,也就是 5 分钟一段——设成 60 秒会让文件数暴涨,设成 3600 秒则回放时定位精度差,拖动时间轴要等整段加载。
2.3 初始化数据库与首次启动
配置写完不能直接跑,得先初始化数据库,否则进程会因为找不到表结构退出。常见做法是分两步:
# 第一步:初始化数据库,创建表结构 moonfire-nvr init --db-dir /var/lib/moonfire-nvr/db # 第二步:前台启动,方便看日志 moonfire-nvr run --db-dir /var/lib/moonfire-nvr/db --config /etc/moonfire-nvr/config.toml逻辑说明:init只做一次,重复执行一般会报错或跳过;run是常驻进程。第一次启动时重点看日志里有没有connected或stream started之类的字样,如果某路流一直重连,八成是 RTSP 地址或鉴权有问题。参数上,--db-dir必须和配置里的db_dir一致,否则会出现「配置读到了但数据库是空的」这种翻车现场。
启动成功后,浏览器打开http://<主机IP>:8080,应该能看到时间轴界面。如果页面能开但时间轴空白,说明 Web 服务正常、拉流没成功,问题在 RTSP 那一侧,不在前端。
3. 把 RTSP 摄像机接进来:地址、鉴权与码流选择
3.1 RTSP 地址的构成与常见写法
RTSP 地址是接入的第一道坎。标准格式是rtsp://用户名:密码@IP:端口/路径,但不同厂商的路径差异很大,这是最容易踩坑的地方。常见几类路径写法:
- 主码流:
/stream1、/h264/ch1/main/av_stream、/cam/realmonitor?channel=1&subtype=0 - 子码流:
/stream2、/h264/ch1/sub/av_stream、/cam/realmonitor?channel=1&subtype=1
主码流清晰但码率高,子码流省带宽但糊。Moonfire NVR 本身不做转码,拉什么存什么,所以选哪路直接决定磁盘消耗。我的习惯是:录像存主码流,如果带宽或磁盘吃紧,再考虑子码流。
3.2 用命令行先验证流能不能拉
在把地址填进配置之前,强烈建议先用工具验证一遍,别让 NVR 去当小白鼠。常见做法是用 ffmpeg 或 ffprobe 探一下:
# 探测流信息,不实际录像,几秒后自动退出 ffprobe -rtsp_transport tcp -i "rtsp://user:pass@192.168.1.10:554/stream1" -show_streams # 实际拉 10 秒存成文件,验证能否稳定解码 ffmpeg -rtsp_transport tcp -i "rtsp://user:pass@192.168.1.10:554/stream1" -t 10 -c copy test.mp4逻辑说明:-rtsp_transport tcp强制走 TCP,避免 UDP 丢包导致的画面花屏,这是 IP 摄像头场景下的血泪经验。-show_streams会打印编码格式、分辨率、帧率,确认是 H.264 还是 H.265——有些浏览器对 H.265 支持有限,回放可能黑屏。-c copy表示不重新编码,直接封装,速度快。
如果 ffprobe 都拉不到流,Moonfire NVR 一定也拉不到,先解决网络和鉴权,别去改 NVR 配置。
3.3 多路流与带宽估算
接多路之前先算笔账。假设一路主码流 4 Mbps,14 天保留:
单路每天:4 Mbps ÷ 8 = 0.5 MB/s 0.5 MB/s × 86400 s ≈ 43 GB/天 14 天 ≈ 600 GB/路四路就是 2.4 TB 左右。这个数字决定了你磁盘要留多少余量。Moonfire NVR 不做智能编码,码率是摄像机给的,所以估算只能按摄像机标称值来。实际中运动画面多的场景码率会更高,建议按标称值的 1.2 倍留余量。
配置多路时,每路一个[[streams]]块,id不能重复。启动后如果某几路正常、某几路一直重连,优先怀疑那几路的地址或该摄像机的并发连接数限制——有些摄像头同时只允许 1~2 个 RTSP 客户端,你 NVR 占了一个,手机 App 再连就挤掉了。
4. 避坑与排查:那些让录像悄悄断掉的问题
4.1 时间轴有段但点开黑屏
现象:Web 界面时间轴上明明有录像段,点进去播放却黑屏或一直转圈。
原因:多半是编码格式问题。摄像机用了 H.265,而当前浏览器不支持,或者录像段封装格式浏览器解不了。Moonfire NVR 服务端不转码,浏览器拿到什么就解什么。
解决:把摄像机切到 H.264 主码流,或者确认浏览器版本支持 H.265。用 ffprobe 看录像段文件的编码,确认和预期一致。如果必须用 H.265,回放时换支持它的浏览器,或者接受导出后用本地播放器看。
4.2 磁盘写满后录像静默停止
现象:某天发现最近几天的录像全没了,进程还在跑,日志也没明显报错。
原因:保留策略和磁盘容量没对上。retain_days设了 14 天,但磁盘只够 7 天,写满后新段写不进去,旧段又没到清理时间,就卡住了。有些版本在磁盘满时的日志级别不高,容易被忽略。
解决:先按 3.3 的公式算实际容量,把retain_days调到磁盘能承受的值,留 15% 余量。同时监控磁盘使用率,超过 85% 就告警。别指望 NVR 自己处理磁盘满,这类工具普遍是「写不进去就停」,不会帮你删。
4.3 文件数暴涨拖慢回放
现象:跑了一段时间后,时间轴加载变慢,磁盘ls一个目录要好几秒。
原因:sample_file_duration设得太小,比如 60 秒,一天就是 1440 个文件,一个月四万多个。文件系统在单目录文件数过多时性能下降明显。
解决:把sample_file_duration调到 300 秒甚至 600 秒,减少文件数。已经产生的碎文件不用手动合并,等保留期过了自动清理。如果目录已经很大,清理时用按时间批量删,别用通配符一次删几万个。
4.4 RTSP 断流后不自动重连
现象:摄像头重启或网络抖动后,那路流再也没恢复,时间轴出现一段空白。
原因:部分版本的重连逻辑有退避策略,如果摄像头短时间内反复断,退避时间会拉长;或者 TCP 连接处于半开状态,进程以为还连着。
解决:先确认进程日志里有没有重连记录。如果长时间不恢复,重启 NVR 进程是最直接的办法。长期方案是给摄像机配固定 IP、稳定的交换机端口,减少物理层抖动。也可以写个外部脚本定期检查时间轴最新段的时间,超过阈值就重启对应流。
4.5 数据库和录像文件不同步
现象:手动删了录像文件,但时间轴上还能看到那些段,点开报 404。
原因:Moonfire NVR 的元数据在 SQLite 里,文件系统是另一套。手动删文件绕过了数据库,记录还在。
解决:永远通过 NVR 自己的清理机制删录像,别手动rm。如果已经删了,需要进数据库把对应记录清掉,或者重建数据库(会丢失所有元数据,慎用)。备份时数据库和录像文件要一起备,只备一个没意义。
5. 进阶用法:用 SQLite 直接查录像元数据与自动化清理
跑顺之后,最有价值的进阶技巧是直接查它的 SQLite 数据库。Web 界面能做的事有限,但数据库里什么都有,写脚本做自动化清理、统计、告警都靠它。
先看表结构,常见的有stream、recording、sample_file几张表。查最近一小时有哪些录像段:
-- 查指定流在某个时间范围内的录像段 SELECT r.id, r.start_time_90k, -- 起始时间,单位 90kHz r.duration_90k, -- 时长,单位 90kHz sf.path -- 对应的文件路径 FROM recording r JOIN sample_file sf ON sf.recording_id = r.id WHERE r.stream_id = 1 AND r.start_time_90k > (strftime('%s','now') - 3600) * 90000 ORDER BY r.start_time_90k DESC;逻辑说明:Moonfire NVR 内部用 90kHz 时钟计数,所以时间字段要乘 90000 换算成秒。strftime('%s','now')取当前 Unix 时间戳,减 3600 秒就是最近一小时。这条查询能帮你确认某路流到底有没有在正常写录像——如果结果为空,说明那路流断了。
参数上,stream_id对应配置里的id,改这个值就能查不同摄像机。duration_90k除以 90000 就是这段录像的秒数,可以用来统计实际录像覆盖率:把一天所有段的时长加起来,除以 86400,就知道有没有漏录。
基于这个查询,可以写个巡检脚本,每小时跑一次,发现某路流最近 10 分钟没有新段就发告警。这比等用户发现录像没了要主动得多。我一般还会顺手统计磁盘增长速率,反推retain_days设得合不合理。
导出录像也是类似思路:查到目标时间段的文件路径,直接拷文件即可,Moonfire NVR 的段文件本身就是可播放的封装格式,不需要额外转码。导出后如果要合并成一个大文件,用 ffmpeg 的 concat 协议按顺序拼就行。
从那以后我每次接新摄像机,都强制先用 ffprobe 验证流、再算磁盘容量、最后才填配置,这三步少一步后面都要返工。希望帮到你。
本文还有配套的精品资源,点击获取